Initial upload
This commit is contained in:
parent
37acd1fea9
commit
48f7f5176d
6
.gitignore
vendored
6
.gitignore
vendored
@ -45,3 +45,9 @@ _deps
|
|||||||
*.out
|
*.out
|
||||||
*.app
|
*.app
|
||||||
|
|
||||||
|
# Output files
|
||||||
|
*.wav
|
||||||
|
|
||||||
|
# VSCode
|
||||||
|
.vscode
|
||||||
|
build
|
43
CMakeLists.txt
Normal file
43
CMakeLists.txt
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
project(MILSTD110C)
|
||||||
|
|
||||||
|
# Set C++17 standard
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
|
||||||
|
# Include directories
|
||||||
|
include_directories(include)
|
||||||
|
include_directories(include/encoder)
|
||||||
|
include_directories(include/modulation)
|
||||||
|
include_directories(include/utils)
|
||||||
|
|
||||||
|
# Add subdirectories for organization
|
||||||
|
add_subdirectory(tests)
|
||||||
|
|
||||||
|
# Set source files
|
||||||
|
set(SOURCES main.cpp)
|
||||||
|
|
||||||
|
# Link with libsndfile
|
||||||
|
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
|
||||||
|
find_package(SndFile REQUIRED)
|
||||||
|
|
||||||
|
# Add executable
|
||||||
|
add_executable(MILSTD110C ${SOURCES})
|
||||||
|
|
||||||
|
# Link executable with libsndfile library
|
||||||
|
target_link_libraries(MILSTD110C SndFile::sndfile)
|
||||||
|
|
||||||
|
# Debug and Release Build Types
|
||||||
|
set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "" FORCE)
|
||||||
|
|
||||||
|
# Add options for a Debug build
|
||||||
|
if(CMAKE_BUILD_TYPE MATCHES Debug)
|
||||||
|
message(STATUS "Building with Debug flags")
|
||||||
|
target_compile_definitions(MILSTD110C PRIVATE DEBUG)
|
||||||
|
target_compile_options(MILSTD110C PRIVATE -g -O0) # Add debugging symbols, no optimization
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Add options for a Release build
|
||||||
|
if(CMAKE_BUILD_TYPE MATCHES Release)
|
||||||
|
message(STATUS "Building with Release flags")
|
||||||
|
target_compile_options(MILSTD110C PRIVATE -O3) # Optimize for performance
|
||||||
|
endif()
|
18
cmake/FindSndFile.cmake
Normal file
18
cmake/FindSndFile.cmake
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
find_path(SNDFILE_INCLUDE_DIR sndfile.h)
|
||||||
|
find_library(SNDFILE_LIBRARY NAMES sndfile libsndfile)
|
||||||
|
|
||||||
|
include(FindPackageHandleStandardArgs)
|
||||||
|
find_package_handle_standard_args(SndFile DEFAULT_MSG SNDFILE_LIBRARY SNDFILE_INCLUDE_DIR)
|
||||||
|
|
||||||
|
if(SNDFILE_FOUND)
|
||||||
|
set(SNDFILE_LIBRARIES ${SNDFILE_LIBRARY})
|
||||||
|
set(SNDFILE_INCLUDE_DIRS ${SNDFILE_INCLUDE_DIR})
|
||||||
|
|
||||||
|
mark_as_advanced(SNDFILE_LIBRARY SNDFILE_INCLUDE_DIR)
|
||||||
|
|
||||||
|
add_library(SndFile::sndfile UNKNOWN IMPORTED)
|
||||||
|
set_target_properties(SndFile::sndfile PROPERTIES
|
||||||
|
IMPORTED_LOCATION "${SNDFILE_LIBRARY}"
|
||||||
|
INTERFACE_INCLUDE_DIRECTORIES "${SNDFILE_INCLUDE_DIR}"
|
||||||
|
)
|
||||||
|
endif()
|
110
include/encoder/FECEncoder.h
Normal file
110
include/encoder/FECEncoder.h
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
#ifndef FEC_ENCODER_H
|
||||||
|
#define FEC_ENCODER_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "bitstream.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class FECEncoder
|
||||||
|
* @brief A class to perform Forward Error Correction (FEC) encoding on input data.
|
||||||
|
*
|
||||||
|
* The FECEncoder class implements convolutional coding with different rates based on the
|
||||||
|
* baud rate and whether frequency hopping is used. It uses a constraint length 7 convolutional
|
||||||
|
* code with repeat coding or puncturing as required.
|
||||||
|
*/
|
||||||
|
class FECEncoder {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Constructs an FECEncoder.
|
||||||
|
* @param baud_rate The baud rate for the encoding process.
|
||||||
|
* @param is_frequency_hopping True if frequency hopping is used, otherwise false.
|
||||||
|
*/
|
||||||
|
FECEncoder(size_t baud_rate, bool is_frequency_hopping)
|
||||||
|
: baud_rate(baud_rate), is_frequency_hopping(is_frequency_hopping), shift_register(0) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Encodes the input data using convolutional coding.
|
||||||
|
* @param data The input BitStream to be encoded.
|
||||||
|
* @return The encoded BitStream.
|
||||||
|
*/
|
||||||
|
BitStream encode(const BitStream& data) {
|
||||||
|
BitStream input_data(data);
|
||||||
|
BitStream output_data;
|
||||||
|
|
||||||
|
while (input_data.hasNext()) {
|
||||||
|
uint8_t bit = input_data.getNextBit();
|
||||||
|
// Shift the input bit into the shift register
|
||||||
|
shift_register = ((shift_register << 1) | bit) & 0x7F;
|
||||||
|
|
||||||
|
// Calculate T1 and T2 using the generator polynomials
|
||||||
|
uint8_t t1 = calculateT1();
|
||||||
|
uint8_t t2 = calculateT2();
|
||||||
|
|
||||||
|
// Append T1 and T2 to the encoded data
|
||||||
|
output_data.putBit(t1);
|
||||||
|
output_data.putBit(t2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply repetition or puncturing based on baud rate and operation mode
|
||||||
|
return adjustRate(output_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t baud_rate; ///< The baud rate used for encoding.
|
||||||
|
bool is_frequency_hopping; ///< Indicates if frequency hopping is being used.
|
||||||
|
uint8_t shift_register; ///< The shift register used for convolutional coding.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates the T1 output bit using the generator polynomial T1(x) = x^6 + x^4 + x^3 + x + 1.
|
||||||
|
* @return The calculated T1 bit.
|
||||||
|
*/
|
||||||
|
uint8_t calculateT1() {
|
||||||
|
return (shift_register >> 6) ^ ((shift_register >> 4) & 0x01) ^ ((shift_register >> 3) & 0x01) ^ ((shift_register >> 1) & 0x01) ^ (shift_register & 0x01);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculates the T2 output bit using the generator polynomial T2(x) = x^6 + x^5 + x^4 + x + 1.
|
||||||
|
* @return The calculated T2 bit.
|
||||||
|
*/
|
||||||
|
uint8_t calculateT2() {
|
||||||
|
return (shift_register >> 6) ^ ((shift_register >> 5) & 0x01) ^ ((shift_register >> 4) & 0x01) ^ ((shift_register >> 1) & 0x01) ^ (shift_register & 0x01);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Adjusts the rate of the encoded data by applying repetition or puncturing.
|
||||||
|
* @param encoded_data The encoded BitStream to be adjusted.
|
||||||
|
* @return The adjusted BitStream.
|
||||||
|
*/
|
||||||
|
BitStream adjustRate(const BitStream& encoded_data) {
|
||||||
|
BitStream adjusted_data;
|
||||||
|
|
||||||
|
if ((baud_rate == 300 || baud_rate == 150 || baud_rate == 75) && is_frequency_hopping) {
|
||||||
|
// Repetition for frequency-hopping operation at lower baud rates
|
||||||
|
size_t repetition_factor = (baud_rate == 300) ? 2 : (baud_rate == 150) ? 4 : 8;
|
||||||
|
for (size_t i = 0; i < encoded_data.getMaxBitIndex(); i += 2) {
|
||||||
|
for (size_t j = 0; j < repetition_factor; j++) {
|
||||||
|
adjusted_data.putBit(encoded_data.getBitVal(i));
|
||||||
|
adjusted_data.putBit(encoded_data.getBitVal(i + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ((baud_rate == 300 || baud_rate == 150) && !is_frequency_hopping) {
|
||||||
|
// Repetition for fixed-frequency operation at lower baud rates
|
||||||
|
size_t repetition_factor = (baud_rate == 300) ? 2 : 4;
|
||||||
|
for (size_t i = 0; i < encoded_data.getMaxBitIndex(); i += 2) {
|
||||||
|
for (size_t j = 0; j < repetition_factor; j++) {
|
||||||
|
adjusted_data.putBit(encoded_data.getBitVal(i));
|
||||||
|
adjusted_data.putBit(encoded_data.getBitVal(i + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
adjusted_data = encoded_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return adjusted_data;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
224
include/encoder/Interleaver.h
Normal file
224
include/encoder/Interleaver.h
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
#ifndef INTERLEAVER_H
|
||||||
|
#define INTERLEAVER_H
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "bitstream.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class Interleaver
|
||||||
|
* @brief A class to interleave input bitstreams based on the specified baud rate and interleave settings.
|
||||||
|
*
|
||||||
|
* The Interleaver processes input data in chunks, rearranging the bit order as required
|
||||||
|
* by the MIL-STD-188-110 standard for different baud rates and interleave settings.
|
||||||
|
*/
|
||||||
|
class Interleaver {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Constructs an Interleaver with specific baud rate, interleave settings, and operation mode.
|
||||||
|
* @param baud_rate The baud rate of the transmission.
|
||||||
|
* @param interleave_setting The interleave setting (0, short, or long).
|
||||||
|
* @param is_frequency_hopping Boolean flag indicating if frequency-hopping mode is enabled.
|
||||||
|
*/
|
||||||
|
Interleaver(size_t baud_rate, size_t interleave_setting, bool is_frequency_hopping)
|
||||||
|
: baud_rate(baud_rate), interleave_setting(interleave_setting), is_frequency_hopping(is_frequency_hopping) {
|
||||||
|
setMatrixDimensions();
|
||||||
|
matrix.resize(rows * columns, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Interleaves the entire input BitStream and returns the interleaved result.
|
||||||
|
* @param input_data The input BitStream to be interleaved.
|
||||||
|
* @return A new BitStream containing the interleaved data.
|
||||||
|
*/
|
||||||
|
std::vector<uint8_t> interleaveStream(const BitStream& input_data) {
|
||||||
|
BitStream data = input_data;
|
||||||
|
BitStream interleaved_data;
|
||||||
|
size_t chunk_size = rows * columns;
|
||||||
|
size_t input_index = 0;
|
||||||
|
|
||||||
|
while (input_index < data.getMaxBitIndex()) {
|
||||||
|
size_t end_index = std::min(input_index + chunk_size, data.getMaxBitIndex());
|
||||||
|
BitStream chunk = data.getSubStream(input_index, end_index);
|
||||||
|
if (chunk.getMaxBitIndex() > rows * columns) {
|
||||||
|
throw std::invalid_argument("Input data exceeds interleaver matrix size in loadChunk()");
|
||||||
|
}
|
||||||
|
BitStream interleaved_chunk = interleaveChunk(chunk);
|
||||||
|
interleaved_data += interleaved_chunk;
|
||||||
|
input_index = end_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply puncturing for 2400 bps in frequency-hopping mode (Rate 2/3)
|
||||||
|
if (baud_rate == 2400 && is_frequency_hopping) {
|
||||||
|
return applyPuncturing(interleaved_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> final_interleaved_data = groupSymbols(interleaved_data);
|
||||||
|
return final_interleaved_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Retrieves the number of bits required to fully flush the interleaver matrix.
|
||||||
|
* @return The number of bits needed for a complete flush.
|
||||||
|
*/
|
||||||
|
size_t getFlushBits() const {
|
||||||
|
return rows * columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t baud_rate;
|
||||||
|
size_t interleave_setting;
|
||||||
|
bool is_frequency_hopping;
|
||||||
|
size_t rows;
|
||||||
|
size_t columns;
|
||||||
|
std::vector<uint8_t> matrix; ///< 1D vector representing the interleaver matrix.
|
||||||
|
|
||||||
|
static constexpr size_t ROW_INCREMENT_DEFAULT = 9;
|
||||||
|
static constexpr size_t COLUMN_DECREMENT_DEFAULT = 17;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Groups the bits into symbols based on the baud rate (e.g., 2 bits per symbol at 1200 bps).
|
||||||
|
* @param input_data The input BitStream to be grouped into symbols.
|
||||||
|
* @return A vector of grouped symbols.
|
||||||
|
*/
|
||||||
|
std::vector<uint8_t> groupSymbols(BitStream& input_data) {
|
||||||
|
std::vector<uint8_t> grouped_data;
|
||||||
|
size_t max_index = input_data.getMaxBitIndex();
|
||||||
|
size_t bits_per_symbol = (baud_rate == 2400) ? 3 : (baud_rate == 1200 || (baud_rate == 75 && !is_frequency_hopping)) ? 2 : 1;
|
||||||
|
size_t current_index = 0;
|
||||||
|
|
||||||
|
while ((current_index + bits_per_symbol) < max_index) {
|
||||||
|
uint8_t symbol = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < bits_per_symbol; i++) {
|
||||||
|
symbol = (symbol << 1) | input_data.getBitVal(current_index + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
grouped_data.push_back(symbol);
|
||||||
|
current_index += bits_per_symbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
return grouped_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Interleaves a chunk of the input BitStream and returns the result.
|
||||||
|
* @param input_data The input BitStream chunk.
|
||||||
|
* @return A BitStream representing the interleaved chunk.
|
||||||
|
*/
|
||||||
|
BitStream interleaveChunk(const BitStream& input_data) {
|
||||||
|
loadChunk(input_data);
|
||||||
|
return fetchChunk();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Loads bits from the input BitStream into the interleaver matrix.
|
||||||
|
* @param data The input BitStream to load.
|
||||||
|
*/
|
||||||
|
void loadChunk(const BitStream& data) {
|
||||||
|
size_t row = 0;
|
||||||
|
size_t col = 0;
|
||||||
|
size_t index = 0;
|
||||||
|
|
||||||
|
size_t row_increment = (baud_rate == 75 && interleave_setting == 2) ? 7 : 9;
|
||||||
|
|
||||||
|
// Load bits into the matrix
|
||||||
|
std::fill(matrix.begin(), matrix.end(), 0); // Clear previous data
|
||||||
|
while (index < data.getMaxBitIndex() && col < columns) {
|
||||||
|
size_t matrix_idx = row * columns + col;
|
||||||
|
if (matrix_idx >= matrix.size()) {
|
||||||
|
throw std::out_of_range("Matrix index out of bounds in loadChunk()");
|
||||||
|
}
|
||||||
|
|
||||||
|
matrix[matrix_idx] = data.getBitVal(index++);
|
||||||
|
row = (row + row_increment) % rows;
|
||||||
|
|
||||||
|
if (row == 0) {
|
||||||
|
col++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Fetches bits from the interleaver matrix in the interleaved order.
|
||||||
|
* @return A BitStream containing the fetched interleaved data.
|
||||||
|
*/
|
||||||
|
BitStream fetchChunk() {
|
||||||
|
BitStream fetched_data;
|
||||||
|
size_t row = 0;
|
||||||
|
size_t col = 0;
|
||||||
|
|
||||||
|
size_t column_decrement = (baud_rate == 75 && interleave_setting == 2) ? 7 : 17;
|
||||||
|
|
||||||
|
// Fetch bits from the matrix
|
||||||
|
while (fetched_data.getMaxBitIndex() < rows * columns) {
|
||||||
|
size_t matrix_idx = row * columns + col;
|
||||||
|
if (matrix_idx >= matrix.size()) {
|
||||||
|
throw std::out_of_range("Matrix index out of bounds in fetchChunk()");
|
||||||
|
}
|
||||||
|
|
||||||
|
fetched_data.putBit(matrix[matrix_idx]);
|
||||||
|
row++;
|
||||||
|
|
||||||
|
if (row == rows) {
|
||||||
|
row = 0;
|
||||||
|
col = (col + 1) % columns;
|
||||||
|
} else {
|
||||||
|
col = (col + columns - column_decrement) % columns;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetched_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the matrix dimensions based on baud rate and interleave setting.
|
||||||
|
*/
|
||||||
|
void setMatrixDimensions() {
|
||||||
|
if (baud_rate == 2400) {
|
||||||
|
rows = 40;
|
||||||
|
columns = (interleave_setting == 2) ? 576 : 72;
|
||||||
|
} else if (baud_rate == 1200) {
|
||||||
|
rows = 40;
|
||||||
|
columns = (interleave_setting == 2) ? 288 : 36;
|
||||||
|
} else if (baud_rate == 600) {
|
||||||
|
rows = 40;
|
||||||
|
columns = (interleave_setting == 2) ? 144 : 18;
|
||||||
|
} else if (baud_rate == 300 || baud_rate == 150) {
|
||||||
|
rows = 40;
|
||||||
|
columns = (interleave_setting == 2) ? 144 : 18;
|
||||||
|
} else if (baud_rate == 75) {
|
||||||
|
if (is_frequency_hopping) {
|
||||||
|
rows = 40;
|
||||||
|
columns = 18;
|
||||||
|
} else {
|
||||||
|
rows = (interleave_setting == 2) ? 20 : 10;
|
||||||
|
columns = (interleave_setting == 2) ? 36 : 9;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw std::invalid_argument("Invalid baud rate for setMatrixDimensions");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Applies puncturing to the interleaved data (used for 2400 bps in frequency-hopping mode).
|
||||||
|
* @param interleaved_data The interleaved data to be punctured.
|
||||||
|
* @return A BitStream containing punctured data.
|
||||||
|
*/
|
||||||
|
BitStream applyPuncturing(const BitStream& interleaved_data) {
|
||||||
|
BitStream punctured_data;
|
||||||
|
for (size_t i = 0; i < interleaved_data.getMaxBitIndex(); i++) {
|
||||||
|
if ((i % 4) != 1) { // Skip every fourth bit (the second value of T2)
|
||||||
|
punctured_data.putBit(interleaved_data.getBitVal(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return punctured_data;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
49
include/encoder/MGDDecoder.h
Normal file
49
include/encoder/MGDDecoder.h
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#ifndef MGD_DECODER_H
|
||||||
|
#define MGD_DECODER_H
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cmath>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class MGDDecoder {
|
||||||
|
public:
|
||||||
|
MGDDecoder(size_t baud_rate, bool is_frequency_hopping) : baud_rate(baud_rate), is_frequency_hopping(is_frequency_hopping) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Decodes the grouped symbols using Modified Gray Decoding (MGD).
|
||||||
|
* @param symbols The input symbols to be decoded.
|
||||||
|
* @return A vector of decoded symbols.
|
||||||
|
*/
|
||||||
|
std::vector<uint8_t> mgdDecode(const std::vector<uint8_t>& symbols) {
|
||||||
|
std::vector<uint8_t> decodedSymbols;
|
||||||
|
|
||||||
|
for (uint8_t symbol : symbols) {
|
||||||
|
if (baud_rate == 2400 || baud_rate == 4800) {
|
||||||
|
decodedSymbols.push_back(mgd8psk(symbol));
|
||||||
|
} else if (baud_rate == 1200 || (baud_rate == 75 && !is_frequency_hopping)) {
|
||||||
|
decodedSymbols.push_back(mgd4psk(symbol));
|
||||||
|
} else {
|
||||||
|
decodedSymbols.push_back(symbol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return decodedSymbols;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t baud_rate;
|
||||||
|
bool is_frequency_hopping;
|
||||||
|
|
||||||
|
int mgd8psk(int input) {
|
||||||
|
static std::array<int, 8> lookupTable = {0, 1, 3, 2, 7, 6, 4, 5};
|
||||||
|
return lookupTable[input];
|
||||||
|
}
|
||||||
|
|
||||||
|
int mgd4psk(int input) {
|
||||||
|
static std::array<int, 4> lookupTable = {0, 1, 3, 2};
|
||||||
|
return lookupTable[input];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
177
include/encoder/ModemController.h
Normal file
177
include/encoder/ModemController.h
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
#ifndef MODEM_CONTROLLER_H
|
||||||
|
#define MODEM_CONTROLLER_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <limits>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "bitstream.h"
|
||||||
|
#include "FECEncoder.h"
|
||||||
|
#include "Interleaver.h"
|
||||||
|
#include "MGDDecoder.h"
|
||||||
|
#include "PSKModulator.h"
|
||||||
|
#include "Scrambler.h"
|
||||||
|
#include "SymbolFormation.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Clamps an integer value to the range of int16_t.
|
||||||
|
* @param x The value to be clamped.
|
||||||
|
* @return The clamped value.
|
||||||
|
*/
|
||||||
|
constexpr int16_t clamp(int16_t x) {
|
||||||
|
constexpr int16_t max_val = std::numeric_limits<int16_t>::max();
|
||||||
|
constexpr int16_t min_val = std::numeric_limits<int16_t>::min();
|
||||||
|
return (x > max_val) ? max_val : (x < min_val) ? min_val : x;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class ModemController
|
||||||
|
* @brief Controls the modulation process for transmitting data using FEC encoding, interleaving, scrambling, and PSK modulation.
|
||||||
|
*/
|
||||||
|
class ModemController {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Constructs a ModemController object.
|
||||||
|
* @param baud_rate The baud rate for the modem.
|
||||||
|
* @param is_voice Indicates if the data being transmitted is voice.
|
||||||
|
* @param is_frequency_hopping Indicates if frequency hopping is used.
|
||||||
|
* @param interleave_setting The interleave setting to be used.
|
||||||
|
* @param data The input data stream to be transmitted. The `is_voice` parameter controls whether the modem treats it as binary file data,
|
||||||
|
* or a binary stream from the MELPe (or other) voice codec.
|
||||||
|
*/
|
||||||
|
ModemController(const size_t _baud_rate, const bool _is_voice, const bool _is_frequency_hopping, const size_t _interleave_setting, BitStream _data)
|
||||||
|
: baud_rate(_baud_rate),
|
||||||
|
is_voice(_is_voice),
|
||||||
|
is_frequency_hopping(_is_frequency_hopping),
|
||||||
|
interleave_setting(_interleave_setting),
|
||||||
|
symbol_formation(baud_rate, interleave_setting, is_voice, is_frequency_hopping),
|
||||||
|
scrambler(),
|
||||||
|
fec_encoder(baud_rate, is_frequency_hopping),
|
||||||
|
interleaver(baud_rate, interleave_setting, is_frequency_hopping),
|
||||||
|
input_data(std::move(_data)),
|
||||||
|
mgd_decoder(baud_rate, is_frequency_hopping),
|
||||||
|
modulator(PSKModulatorConfig(1500, 48000, baud_rate)) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Transmits the input data by processing it through different phases like FEC encoding, interleaving, symbol formation, scrambling, and modulation.
|
||||||
|
* @return The scrambled data ready for modulation.
|
||||||
|
* @note The modulated signal is generated internally but is intended to be handled externally.
|
||||||
|
*/
|
||||||
|
std::vector<int16_t> transmit() {
|
||||||
|
// Step 1: Append EOM Symbols
|
||||||
|
BitStream eom_appended_data = appendEOMSymbols(input_data);
|
||||||
|
|
||||||
|
std::vector<uint8_t> processed_data;
|
||||||
|
if (baud_rate == 4800) {
|
||||||
|
processed_data = splitTribitSymbols(eom_appended_data);
|
||||||
|
} else {
|
||||||
|
// Step 2: FEC Encoding
|
||||||
|
BitStream fec_encoded_data = fec_encoder.encode(eom_appended_data);
|
||||||
|
|
||||||
|
// Step 3: Interleaving
|
||||||
|
processed_data = interleaver.interleaveStream(fec_encoded_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> mgd_decoded_data = mgd_decoder.mgdDecode(processed_data);
|
||||||
|
|
||||||
|
// Step 4: Symbol Formation. This function injects the sync preamble symbols.
|
||||||
|
std::vector<uint8_t> symbol_stream = symbol_formation.formSymbols(mgd_decoded_data);
|
||||||
|
|
||||||
|
// Step 5: Scrambling
|
||||||
|
std::vector<uint8_t> scrambled_data = scrambler.scrambleData(symbol_stream);
|
||||||
|
|
||||||
|
// Step 6: PSK Modulation. This is the final baseband output of the class.
|
||||||
|
std::vector<int16_t> modulated_signal = modulator.modulate(scrambled_data);
|
||||||
|
|
||||||
|
// Step 7: Apply Sqrt Half Cosine Filter with a rolloff factor of 0.3 (30%)
|
||||||
|
std::vector<int16_t> filtered_signal = SqrtHalfCosine(modulated_signal, 0.3);
|
||||||
|
|
||||||
|
return filtered_signal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t baud_rate; ///< The baud rate for the modem.
|
||||||
|
bool is_voice; ///< Indicates if the data being transmitted is voice.
|
||||||
|
bool is_frequency_hopping; ///< Indicates if frequency hopping is used.
|
||||||
|
BitStream input_data; ///< The input data stream.
|
||||||
|
size_t interleave_setting; ///< The interleave setting to be used.
|
||||||
|
|
||||||
|
SymbolFormation symbol_formation; ///< Symbol formation instance to form symbols from data.
|
||||||
|
Scrambler scrambler; ///< Scrambler instance for scrambling the data.
|
||||||
|
FECEncoder fec_encoder; ///< FEC encoder instance for encoding the data.
|
||||||
|
Interleaver interleaver; ///< Interleaver instance for interleaving the data.
|
||||||
|
milstd::PSKModulator modulator; ///< PSK modulator instance for modulating the data.
|
||||||
|
MGDDecoder mgd_decoder; ///< MGD decoder
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Appends the EOM symbols to the input data and flushes the FEC encoder and interleaver.
|
||||||
|
* @param input_data The input data to which the EOM symbols are appended.
|
||||||
|
* @return The input data with EOM symbols and flush bits appended.
|
||||||
|
* @details The EOM sequence (4B65A5B2 in hexadecimal) is appended to the data, followed by enough zero bits to flush
|
||||||
|
* the FEC encoder and interleaver matrices. The function calculates the number of flush bits required
|
||||||
|
* based on the FEC and interleaver settings.
|
||||||
|
*/
|
||||||
|
BitStream appendEOMSymbols(const BitStream& input_data) const {
|
||||||
|
BitStream eom_data = input_data;
|
||||||
|
// Append the EOM sequence (4B65A5B2 in hexadecimal)
|
||||||
|
BitStream eom_sequence({0x4B, 0x65, 0xA5, 0xB2}, 32);
|
||||||
|
eom_data += eom_sequence;
|
||||||
|
|
||||||
|
// Append additional zeros to flush the FEC encoder and interleaver
|
||||||
|
size_t fec_flush_bits = 144; // FEC encoder flush bits
|
||||||
|
size_t interleave_flush_bits = interleaver.getFlushBits();
|
||||||
|
size_t total_flush_bits = fec_flush_bits + ((interleave_setting == 0) ? 0 : interleave_flush_bits);
|
||||||
|
while ((eom_data.getMaxBitIndex() + total_flush_bits) % interleave_flush_bits)
|
||||||
|
total_flush_bits++;
|
||||||
|
|
||||||
|
size_t total_bytes = (total_flush_bits + 7) / 8; // Round up to ensure we have enough bytes to handle all bits.
|
||||||
|
BitStream flush_bits(std::vector<uint8_t>(total_bytes, 0), total_flush_bits);
|
||||||
|
eom_data += flush_bits;
|
||||||
|
|
||||||
|
return eom_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Applies a square-root raised cosine filter to the input signal.
|
||||||
|
* @param input_signal The input modulated signal.
|
||||||
|
* @param rolloff_factor The rolloff factor of the filter.
|
||||||
|
* @return The filtered signal.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] std::vector<int16_t> SqrtHalfCosine(const std::vector<int16_t>& input_signal, double rolloff_factor) const {
|
||||||
|
std::vector<int16_t> filtered_signal(input_signal.size());
|
||||||
|
const double pi = M_PI;
|
||||||
|
const double normalization_factor = 1.0 / (1.0 + rolloff_factor);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < input_signal.size(); ++i) {
|
||||||
|
double t = static_cast<double>(i) / input_signal.size();
|
||||||
|
double cosine_term = std::cos(pi * t * rolloff_factor);
|
||||||
|
double filtered_value = input_signal[i] * normalization_factor * (0.5 + 0.5 * cosine_term);
|
||||||
|
|
||||||
|
filtered_signal[i] = clamp(static_cast<int16_t>(filtered_value));
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered_signal;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> splitTribitSymbols(const BitStream& input_data) {
|
||||||
|
std::vector<uint8_t> return_vector;
|
||||||
|
size_t max_index = input_data.getMaxBitIndex();
|
||||||
|
size_t current_index = 0;
|
||||||
|
|
||||||
|
while (current_index + 2 < max_index) {
|
||||||
|
uint8_t symbol = 0;
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
symbol = (symbol << 1) | input_data.getBitVal(current_index + i);
|
||||||
|
}
|
||||||
|
return_vector.push_back(symbol);
|
||||||
|
current_index += 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
return return_vector;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
96
include/encoder/Scrambler.h
Normal file
96
include/encoder/Scrambler.h
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
#ifndef SCRAMBLER_H
|
||||||
|
#define SCRAMBLER_H
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class Scrambler
|
||||||
|
* @brief A class that performs scrambling operations for data sequences.
|
||||||
|
*/
|
||||||
|
class Scrambler {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Constructor initializes the scrambler with a predefined register value.
|
||||||
|
*/
|
||||||
|
Scrambler() : data_sequence_register(0x0BAD) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Scrambles a synchronization preamble using a fixed randomizer sequence.
|
||||||
|
* @param preamble The synchronization preamble to scramble.
|
||||||
|
* @return The scrambled synchronization preamble.
|
||||||
|
*/
|
||||||
|
std::vector<uint8_t> scrambleSyncPreamble(const std::vector<uint8_t>& preamble) const {
|
||||||
|
static const std::array<uint8_t, 32> sync_randomizer_sequence = {
|
||||||
|
7, 4, 3, 0, 5, 1, 5, 0, 2, 2, 1, 1,
|
||||||
|
5, 7, 4, 3, 5, 0, 2, 6, 2, 1, 6, 2,
|
||||||
|
0, 0, 5, 0, 5, 2, 6, 6
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<uint8_t> scrambled_preamble;
|
||||||
|
scrambled_preamble.reserve(preamble.size()); // Preallocate to improve efficiency
|
||||||
|
|
||||||
|
for (size_t i = 0; i < preamble.size(); ++i) {
|
||||||
|
uint8_t scrambled_value = (preamble[i] + sync_randomizer_sequence[i % sync_randomizer_sequence.size()]) % 8;
|
||||||
|
scrambled_preamble.push_back(scrambled_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return scrambled_preamble;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Scrambles data using a pseudo-random sequence generated from an LFSR.
|
||||||
|
* @param data The data to scramble.
|
||||||
|
* @return The scrambled data.
|
||||||
|
*/
|
||||||
|
std::vector<uint8_t> scrambleData(const std::vector<uint8_t>& data) {
|
||||||
|
std::vector<uint8_t> scrambled_data;
|
||||||
|
scrambled_data.reserve(data.size()); // Preallocate to improve efficiency
|
||||||
|
|
||||||
|
for (size_t i = 0; i < data.size(); ++i) {
|
||||||
|
uint8_t random_value = getNextRandomValue();
|
||||||
|
uint8_t scrambled_value = (data[i] + random_value) % 8;
|
||||||
|
scrambled_data.push_back(scrambled_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return scrambled_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint16_t data_sequence_register;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generates the next value from the data sequence randomizing generator.
|
||||||
|
* @return A 3-bit random value (0-7) from the current state of the LFSR.
|
||||||
|
*/
|
||||||
|
uint8_t getNextRandomValue() {
|
||||||
|
uint8_t output = data_sequence_register & 0x07;
|
||||||
|
|
||||||
|
for (int i = 0; i < 8; ++i) {
|
||||||
|
// Get the most significant bit
|
||||||
|
uint16_t msb = (data_sequence_register >> 11) & 0x01;
|
||||||
|
|
||||||
|
// XOR taps and shift into the LFSR
|
||||||
|
uint16_t bit1 = (data_sequence_register & 0x01) ^ msb;
|
||||||
|
uint16_t bit4 = ((data_sequence_register >> 3) & 0x01) ^ msb;
|
||||||
|
uint16_t bit6 = ((data_sequence_register >> 5) & 0x01) ^ msb;
|
||||||
|
|
||||||
|
// Update specific bits in the shift register
|
||||||
|
data_sequence_register = (data_sequence_register & ~(1 << 5)) | (bit6 << 5);
|
||||||
|
data_sequence_register = (data_sequence_register & ~(1 << 3)) | (bit4 << 3);
|
||||||
|
data_sequence_register = (data_sequence_register & ~0x01) | bit1;
|
||||||
|
|
||||||
|
// Shift left and insert the MSB
|
||||||
|
data_sequence_register = (data_sequence_register << 1) | msb;
|
||||||
|
|
||||||
|
// Keep the LFSR in 12 bits
|
||||||
|
data_sequence_register &= 0x0FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
388
include/encoder/SymbolFormation.h
Normal file
388
include/encoder/SymbolFormation.h
Normal file
@ -0,0 +1,388 @@
|
|||||||
|
#ifndef SYMBOLFORMATION_H
|
||||||
|
#define SYMBOLFORMATION_H
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "Scrambler.h"
|
||||||
|
|
||||||
|
enum class SymbolType { SYNC_PREAMBLE, UNKNOWN_DATA, PROBE_DATA };
|
||||||
|
|
||||||
|
static constexpr size_t LONG_PROBE_DATA_BLOCK_SIZE = 20;
|
||||||
|
static constexpr size_t SHORT_PROBE_DATA_BLOCK_SIZE = 16;
|
||||||
|
static constexpr size_t LONG_UNKNOWN_DATA_BLOCK_SIZE = 32;
|
||||||
|
static constexpr size_t SHORT_UNKNOWN_DATA_BLOCK_SIZE = 20;
|
||||||
|
static constexpr size_t SET_SIZE_LONG = 360;
|
||||||
|
static constexpr size_t SET_SIZE_SHORT = 32;
|
||||||
|
|
||||||
|
std::vector<uint8_t> Sync_Preamble_0 = {0, 0, 0, 0, 0, 0, 0, 0};
|
||||||
|
std::vector<uint8_t> Sync_Preamble_1 = {0, 4, 0, 4, 0, 4, 0, 4};
|
||||||
|
std::vector<uint8_t> Sync_Preamble_2 = {0, 0, 4, 4, 0, 0, 4, 4};
|
||||||
|
std::vector<uint8_t> Sync_Preamble_3 = {0, 4, 4, 0, 0, 4, 4, 0};
|
||||||
|
std::vector<uint8_t> Sync_Preamble_4 = {0, 0, 0, 0, 4, 4, 4, 4};
|
||||||
|
std::vector<uint8_t> Sync_Preamble_5 = {0, 4, 0, 4, 4, 0, 4, 0};
|
||||||
|
std::vector<uint8_t> Sync_Preamble_6 = {0, 0, 4, 4, 4, 4, 0, 0};
|
||||||
|
std::vector<uint8_t> Sync_Preamble_7 = {0, 4, 4, 0, 4, 0, 0, 4};
|
||||||
|
|
||||||
|
std::vector<uint8_t> baud75_exceptional_0 = {0, 0, 0, 0, 4, 4, 4, 4};
|
||||||
|
std::vector<uint8_t> baud75_exceptional_1 = {0, 4, 0, 4, 4, 0, 4, 0};
|
||||||
|
std::vector<uint8_t> baud75_exceptional_2 = {0, 0, 4, 4, 4, 4, 0, 0};
|
||||||
|
std::vector<uint8_t> baud75_exceptional_3 = {0, 4, 4, 0, 4, 0, 0, 4};
|
||||||
|
std::vector<uint8_t> baud75_normal_0 = {0, 0, 0, 0};
|
||||||
|
std::vector<uint8_t> baud75_normal_1 = {0, 4, 0, 4};
|
||||||
|
std::vector<uint8_t> baud75_normal_2 = {0, 0, 4, 4};
|
||||||
|
std::vector<uint8_t> baud75_normal_3 = {0, 4, 4, 0};
|
||||||
|
|
||||||
|
class SymbolFormation {
|
||||||
|
public:
|
||||||
|
SymbolFormation(size_t baud_rate, size_t interleave_setting, bool is_voice, bool is_frequency_hopping) : interleave_setting(interleave_setting), baud_rate(baud_rate), is_voice(is_voice), is_frequency_hopping(is_frequency_hopping) {}
|
||||||
|
|
||||||
|
std::vector<uint8_t> formSymbols(std::vector<uint8_t>& symbol_data) {
|
||||||
|
// Generate and scramble the sync preamble
|
||||||
|
std::vector<uint8_t> sync_preamble = generateSyncPreamble();
|
||||||
|
sync_preamble = scrambler.scrambleSyncPreamble(sync_preamble);
|
||||||
|
|
||||||
|
size_t unknown_data_block_size = (baud_rate >= 2400) ? LONG_UNKNOWN_DATA_BLOCK_SIZE : SHORT_UNKNOWN_DATA_BLOCK_SIZE;
|
||||||
|
size_t interleaver_block_size;
|
||||||
|
if (baud_rate == 2400) {
|
||||||
|
interleaver_block_size = (interleave_setting == 2) ? (40 * 576) : (40 * 72);
|
||||||
|
} else if (baud_rate == 1200) {
|
||||||
|
interleaver_block_size = (interleave_setting == 2) ? (40 * 288) : (40 * 36);
|
||||||
|
} else if ((baud_rate >= 150) || (baud_rate == 75 && is_frequency_hopping)) {
|
||||||
|
interleaver_block_size = (interleave_setting == 2) ? (40 * 144) : (40 * 18);
|
||||||
|
} else {
|
||||||
|
interleaver_block_size = (interleave_setting == 2) ? (20 * 36) : (10 * 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t set_count = 0;
|
||||||
|
size_t symbol_count = 0;
|
||||||
|
std::vector<uint8_t> data_stream;
|
||||||
|
|
||||||
|
size_t current_index = 0;
|
||||||
|
while (current_index < symbol_data.size()) {
|
||||||
|
// Process unknown data block
|
||||||
|
size_t block_size = std::min(unknown_data_block_size, symbol_data.size() - current_index);
|
||||||
|
std::vector<uint8_t> unknown_data_block(symbol_data.begin() + current_index, symbol_data.begin() + current_index + block_size);
|
||||||
|
current_index += block_size;
|
||||||
|
|
||||||
|
if (baud_rate == 75) {
|
||||||
|
size_t set_size = (interleave_setting == 2) ? SET_SIZE_LONG : SET_SIZE_SHORT;
|
||||||
|
for (size_t i = 0; i < unknown_data_block.size(); i += set_size) {
|
||||||
|
bool is_exceptional_set = (set_count % ((interleave_setting == 1) ? 45 : 360)) == 0;
|
||||||
|
std::vector<uint8_t> mapped_set = map75bpsSet(unknown_data_block, i, set_size, is_exceptional_set);
|
||||||
|
data_stream.insert(data_stream.end(), mapped_set.begin(), mapped_set.end());
|
||||||
|
set_count++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::vector<uint8_t> mapped_unknown_data = mapUnknownData(unknown_data_block);
|
||||||
|
symbol_count += mapped_unknown_data.size();
|
||||||
|
data_stream.insert(data_stream.end(), mapped_unknown_data.begin(), mapped_unknown_data.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (baud_rate > 75) {
|
||||||
|
bool inside_block = (symbol_count % interleaver_block_size) != 0;
|
||||||
|
std::vector<uint8_t> probe_data = generateProbeData(inside_block);
|
||||||
|
data_stream.insert(data_stream.end(), probe_data.begin(), probe_data.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scramble the entire data stream
|
||||||
|
data_stream = scrambler.scrambleData(data_stream);
|
||||||
|
|
||||||
|
// Combine sync preamble and scrambled data stream
|
||||||
|
std::vector<uint8_t> symbol_stream;
|
||||||
|
symbol_stream.insert(symbol_stream.end(), sync_preamble.begin(), sync_preamble.end());
|
||||||
|
symbol_stream.insert(symbol_stream.end(), data_stream.begin(), data_stream.end());
|
||||||
|
|
||||||
|
return symbol_stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
int baud_rate;
|
||||||
|
int interleave_setting;
|
||||||
|
bool is_voice;
|
||||||
|
bool is_frequency_hopping;
|
||||||
|
Scrambler scrambler = Scrambler();
|
||||||
|
|
||||||
|
void appendProbeMapping(std::vector<uint8_t>& symbol_stream, uint8_t symbol, size_t repeat_count) {
|
||||||
|
switch (symbol) {
|
||||||
|
case 0:
|
||||||
|
for (int i = 0; i < repeat_count; ++i) {
|
||||||
|
symbol_stream.insert(symbol_stream.end(), Sync_Preamble_0.begin(), Sync_Preamble_0.end());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
for (int i = 0; i < repeat_count; ++i) {
|
||||||
|
symbol_stream.insert(symbol_stream.end(), Sync_Preamble_1.begin(), Sync_Preamble_1.end());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
for (int i = 0; i < repeat_count; ++i) {
|
||||||
|
symbol_stream.insert(symbol_stream.end(), Sync_Preamble_2.begin(), Sync_Preamble_2.end());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
for (int i = 0; i < repeat_count; ++i) {
|
||||||
|
symbol_stream.insert(symbol_stream.end(), Sync_Preamble_3.begin(), Sync_Preamble_3.end());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
for (int i = 0; i < repeat_count; ++i) {
|
||||||
|
symbol_stream.insert(symbol_stream.end(), Sync_Preamble_4.begin(), Sync_Preamble_4.end());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
for (int i = 0; i < repeat_count; ++i) {
|
||||||
|
symbol_stream.insert(symbol_stream.end(), Sync_Preamble_5.begin(), Sync_Preamble_5.end());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
for (int i = 0; i < repeat_count; ++i) {
|
||||||
|
symbol_stream.insert(symbol_stream.end(), Sync_Preamble_6.begin(), Sync_Preamble_6.end());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
for (int i = 0; i < repeat_count; ++i) {
|
||||||
|
symbol_stream.insert(symbol_stream.end(), Sync_Preamble_7.begin(), Sync_Preamble_7.end());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw std::invalid_argument("Invalid channel symbol");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void append75bpsMapping(std::vector<uint8_t>& symbol_stream, uint8_t symbol, bool is_exceptional_set) {
|
||||||
|
if (is_exceptional_set) {
|
||||||
|
switch (symbol) {
|
||||||
|
case 0:
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
symbol_stream.insert(symbol_stream.end(), baud75_exceptional_0.begin(), baud75_exceptional_0.end());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
symbol_stream.insert(symbol_stream.end(), baud75_exceptional_1.begin(), baud75_exceptional_1.end());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
symbol_stream.insert(symbol_stream.end(), baud75_exceptional_2.begin(), baud75_exceptional_2.end());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
symbol_stream.insert(symbol_stream.end(), baud75_exceptional_3.begin(), baud75_exceptional_3.end());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw std::invalid_argument("Invalid channel symbol");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (symbol) {
|
||||||
|
case 0:
|
||||||
|
for (int i = 0; i < 8; ++i) {
|
||||||
|
symbol_stream.insert(symbol_stream.end(), baud75_normal_0.begin(), baud75_normal_0.end());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
for (int i = 0; i < 8; ++i) {
|
||||||
|
symbol_stream.insert(symbol_stream.end(), baud75_normal_1.begin(), baud75_normal_1.end());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
for (int i = 0; i < 8; ++i) {
|
||||||
|
symbol_stream.insert(symbol_stream.end(), baud75_normal_2.begin(), baud75_normal_2.end());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
for (int i = 0; i < 8; ++i) {
|
||||||
|
symbol_stream.insert(symbol_stream.end(), baud75_normal_3.begin(), baud75_normal_3.end());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw std::invalid_argument("Invalid channel symbol");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> generateSyncPreamble() {
|
||||||
|
std::vector<uint8_t> preamble;
|
||||||
|
|
||||||
|
size_t num_segments = (interleave_setting == 0) ? 3 : 24;
|
||||||
|
|
||||||
|
std::vector<uint8_t> segment_sequence = {0,1,3,0,1,3,1,2,0};
|
||||||
|
|
||||||
|
uint8_t D1, D2;
|
||||||
|
if (baud_rate == 4800) {
|
||||||
|
D1 = 7; D2 = 6;
|
||||||
|
} else if (baud_rate == 2400 && is_voice) {
|
||||||
|
D1 = 7; D2 = 7;
|
||||||
|
} else if (baud_rate == 2400) {
|
||||||
|
D1 = (interleave_setting <= 1) ? 6 : 4;
|
||||||
|
D2 = 4;
|
||||||
|
} else if (baud_rate == 1200) {
|
||||||
|
D1 = (interleave_setting <= 1) ? 6 : 4;
|
||||||
|
D2 = 5;
|
||||||
|
} else if (baud_rate == 600) {
|
||||||
|
D1 = (interleave_setting <= 1) ? 6 : 4;
|
||||||
|
D2 = 6;
|
||||||
|
} else if (baud_rate == 300) {
|
||||||
|
D1 = (interleave_setting <= 1) ? 6 : 4;
|
||||||
|
D2 = 7;
|
||||||
|
} else if (baud_rate == 150) {
|
||||||
|
D1 = (interleave_setting <= 1) ? 7 : 5;
|
||||||
|
D2 = 4;
|
||||||
|
} else if (baud_rate == 75) {
|
||||||
|
D1 = (interleave_setting <= 1) ? 7 : 5;
|
||||||
|
D2 = 5;
|
||||||
|
} else {
|
||||||
|
throw std::invalid_argument("Invalid baud rate for Generate Sync Preamble");
|
||||||
|
}
|
||||||
|
|
||||||
|
segment_sequence.push_back(D1);
|
||||||
|
segment_sequence.push_back(D2);
|
||||||
|
|
||||||
|
uint8_t C1, C2, C3;
|
||||||
|
if (interleave_setting == 2) {
|
||||||
|
C1 = 5; C2 = 5; C3 = 7;
|
||||||
|
} else {
|
||||||
|
C1 = 1; C2 = 1; C3 = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < num_segments; i++) {
|
||||||
|
std::vector<uint8_t> full_segment_sequence = segment_sequence;
|
||||||
|
|
||||||
|
C1 = (1 << 2) | C1;
|
||||||
|
C2 = (1 << 2) | C2;
|
||||||
|
C3 = (1 << 2) | C3;
|
||||||
|
|
||||||
|
full_segment_sequence.push_back(C1);
|
||||||
|
full_segment_sequence.push_back(C2);
|
||||||
|
full_segment_sequence.push_back(C3);
|
||||||
|
full_segment_sequence.push_back(0);
|
||||||
|
|
||||||
|
preamble.insert(preamble.end(), full_segment_sequence.begin(), full_segment_sequence.end());
|
||||||
|
|
||||||
|
if (C3 > 0) {
|
||||||
|
C3--;
|
||||||
|
} else if (C2 > 0) {
|
||||||
|
C2--;
|
||||||
|
C3 = 3;
|
||||||
|
} else if (C1 > 0) {
|
||||||
|
C1--;
|
||||||
|
C2 = 3;
|
||||||
|
C3 = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> final_preamble;
|
||||||
|
for (auto& symbol : preamble) {
|
||||||
|
appendProbeMapping(final_preamble, symbol, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
return final_preamble;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> generateProbeData(bool is_inside_block) {
|
||||||
|
std::vector<uint8_t> probe_data;
|
||||||
|
size_t interleaver_block_size;
|
||||||
|
if (baud_rate == 2400) {
|
||||||
|
interleaver_block_size = (interleave_setting == 2) ? (40 * 576) : (40 * 72);
|
||||||
|
} else if (baud_rate == 1200) {
|
||||||
|
interleaver_block_size = (interleave_setting == 2) ? (40 * 288) : (40 * 36);
|
||||||
|
} else if ((baud_rate >= 150) || (baud_rate == 75 && is_frequency_hopping)) {
|
||||||
|
interleaver_block_size = (interleave_setting == 2) ? (40 * 144) : (40 * 18);
|
||||||
|
} else {
|
||||||
|
interleaver_block_size = (interleave_setting == 2) ? (20 * 36) : (10 * 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
probe_data.resize(interleaver_block_size, 0x00);
|
||||||
|
|
||||||
|
if (!is_inside_block) {
|
||||||
|
// Set the known symbol patterns for D1 and D2 based on Table XI and Table XIII
|
||||||
|
uint8_t D1, D2;
|
||||||
|
if (baud_rate == 4800) {
|
||||||
|
D1 = 7; D2 = 6;
|
||||||
|
} else if (baud_rate == 2400 && is_voice) {
|
||||||
|
D1 = 7; D2 = 7;
|
||||||
|
} else if (baud_rate == 2400) {
|
||||||
|
D1 = (interleave_setting <= 1) ? 6 : 4;
|
||||||
|
D2 = 4;
|
||||||
|
} else if (baud_rate == 1200) {
|
||||||
|
D1 = (interleave_setting <= 1) ? 6 : 4;
|
||||||
|
D2 = 5;
|
||||||
|
} else if (baud_rate == 600) {
|
||||||
|
D1 = (interleave_setting <= 1) ? 6 : 4;
|
||||||
|
D2 = 6;
|
||||||
|
} else if (baud_rate == 300) {
|
||||||
|
D1 = (interleave_setting <= 1) ? 6 : 4;
|
||||||
|
D2 = 7;
|
||||||
|
} else if (baud_rate == 150) {
|
||||||
|
D1 = (interleave_setting <= 1) ? 7 : 5;
|
||||||
|
D2 = 4;
|
||||||
|
} else {
|
||||||
|
throw std::invalid_argument("Invalid baud rate for generateProbeData");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> final_probe_data;
|
||||||
|
for (auto symbol : probe_data) {
|
||||||
|
appendProbeMapping(final_probe_data, symbol, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return probe_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> map75bpsSet(const std::vector<uint8_t>& data, size_t start_index, size_t set_size, bool is_exceptional_set) {
|
||||||
|
std::vector<uint8_t> mapped_set;
|
||||||
|
|
||||||
|
for (auto symbol : data) {
|
||||||
|
append75bpsMapping(mapped_set, symbol, is_exceptional_set);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapped_set;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> mapUnknownData(const std::vector<uint8_t>& data) {
|
||||||
|
std::vector<uint8_t> mapped_data;
|
||||||
|
|
||||||
|
for (auto symbol : data) {
|
||||||
|
|
||||||
|
if (baud_rate >= 2400) {
|
||||||
|
// Pass tribit symbols as-is
|
||||||
|
mapped_data.push_back(symbol);
|
||||||
|
} else if (baud_rate == 1200) {
|
||||||
|
// Map dibit symbols to tribit symbols 0, 2, 4, 6
|
||||||
|
switch (symbol) {
|
||||||
|
case 0:
|
||||||
|
mapped_data.push_back(0);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
mapped_data.push_back(2);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
mapped_data.push_back(4);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
mapped_data.push_back(6);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw std::invalid_argument("Invalid dibit symbol for 1200 bps");
|
||||||
|
}
|
||||||
|
} else if (baud_rate >= 150 && baud_rate <= 600) {
|
||||||
|
// Map binary symbols to tribit symbols 0 and 4
|
||||||
|
mapped_data.push_back(symbol == 0 ? 0 : 4);
|
||||||
|
} else {
|
||||||
|
throw std::invalid_argument("Invalid baud rate for mapUnknownData");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapped_data;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
131
include/modulation/FSKDemodulator.h
Normal file
131
include/modulation/FSKDemodulator.h
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
#ifndef FSK_DEMODULATOR_H
|
||||||
|
#define FSK_DEMODULATOR_H
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "BitStreamWriter.h"
|
||||||
|
|
||||||
|
class FSKDemodulatorConfig {
|
||||||
|
public:
|
||||||
|
int freq_lo;
|
||||||
|
int freq_hi;
|
||||||
|
int sample_rate;
|
||||||
|
int baud_rate;
|
||||||
|
std::shared_ptr<BitStreamWriter> bitstreamwriter;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace milstd {
|
||||||
|
class FSKDemodulator {
|
||||||
|
public:
|
||||||
|
FSKDemodulator(const FSKDemodulatorConfig& s) : freq_lo(s.freq_lo), freq_hi(s.freq_hi), sample_rate(s.sample_rate), baud_rate(s.baud_rate), bit_writer(s.bitstreamwriter) {
|
||||||
|
initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
void demodulate(const std::vector<int16_t>& samples) {
|
||||||
|
size_t nb = samples.size();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < nb; i++) {
|
||||||
|
filter_buf[buf_ptr++] = samples[i];
|
||||||
|
|
||||||
|
if (buf_ptr == filter_buf.size()) {
|
||||||
|
std::copy(filter_buf.begin() + filter_buf.size() - filter_size, filter_buf.end(), filter_buf.begin());
|
||||||
|
buf_ptr = filter_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
int corr;
|
||||||
|
int sum = 0;
|
||||||
|
|
||||||
|
corr = dotProduct(&filter_buf[buf_ptr - filter_size], filter_hi_i.data(), filter_size);
|
||||||
|
sum += corr * corr;
|
||||||
|
|
||||||
|
corr = dotProduct(&filter_buf[buf_ptr - filter_size], filter_hi_q.data(), filter_size);
|
||||||
|
sum += corr * corr;
|
||||||
|
|
||||||
|
corr = dotProduct(&filter_buf[buf_ptr - filter_size], filter_lo_i.data(), filter_size);
|
||||||
|
sum -= corr * corr;
|
||||||
|
|
||||||
|
corr = dotProduct(&filter_buf[buf_ptr - filter_size], filter_lo_q.data(), filter_size);
|
||||||
|
sum -= corr * corr;
|
||||||
|
|
||||||
|
int new_sample = (sum > 0) ? 1 : 0;
|
||||||
|
|
||||||
|
if (last_sample != new_sample) {
|
||||||
|
last_sample = new_sample;
|
||||||
|
if (baud_pll < 0.5)
|
||||||
|
baud_pll += baud_pll_adj;
|
||||||
|
else
|
||||||
|
baud_pll -= baud_pll_adj;
|
||||||
|
}
|
||||||
|
|
||||||
|
baud_pll += baud_incr;
|
||||||
|
|
||||||
|
if (baud_pll >= 1.0) {
|
||||||
|
baud_pll -= 1.0;
|
||||||
|
bit_writer->putBit(last_sample);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
int freq_lo;
|
||||||
|
int freq_hi;
|
||||||
|
int sample_rate;
|
||||||
|
int baud_rate;
|
||||||
|
std::shared_ptr<BitStreamWriter> bit_writer;
|
||||||
|
|
||||||
|
int filter_size;
|
||||||
|
std::vector<double> filter_lo_i;
|
||||||
|
std::vector<double> filter_lo_q;
|
||||||
|
std::vector<double> filter_hi_i;
|
||||||
|
std::vector<double> filter_hi_q;
|
||||||
|
std::vector<double> filter_buf;
|
||||||
|
size_t buf_ptr;
|
||||||
|
|
||||||
|
double baud_incr;
|
||||||
|
double baud_pll;
|
||||||
|
double baud_pll_adj;
|
||||||
|
int last_sample;
|
||||||
|
|
||||||
|
void initialize() {
|
||||||
|
baud_incr = static_cast<double>(baud_rate) / sample_rate;
|
||||||
|
baud_pll = 0.0;
|
||||||
|
baud_pll_adj = baud_incr / 4;
|
||||||
|
|
||||||
|
filter_size = sample_rate / baud_rate;
|
||||||
|
|
||||||
|
filter_buf.resize(filter_size * 2, 0.0);
|
||||||
|
buf_ptr = filter_size;
|
||||||
|
|
||||||
|
last_sample = 0;
|
||||||
|
|
||||||
|
filter_lo_i.resize(filter_size);
|
||||||
|
filter_lo_q.resize(filter_size);
|
||||||
|
filter_hi_i.resize(filter_size);
|
||||||
|
filter_hi_q.resize(filter_size);
|
||||||
|
|
||||||
|
for (int i = 0; i < filter_size; i++) {
|
||||||
|
double phase_lo = 2.0 * M_PI * freq_lo * i / sample_rate;
|
||||||
|
filter_lo_i[i] = std::cos(phase_lo);
|
||||||
|
filter_lo_q[i] = std::sin(phase_lo);
|
||||||
|
|
||||||
|
double phase_hi = 2.0 * M_PI * freq_hi * i / sample_rate;
|
||||||
|
filter_hi_i[i] = std::cos(phase_hi);
|
||||||
|
filter_hi_q[i] = std::sin(phase_hi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double dotProduct(const double* x, const double* y, size_t size) {
|
||||||
|
double sum = 0.0;
|
||||||
|
for (size_t i = 0; i < size; i++) {
|
||||||
|
sum += x[i] * y[i];
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace milstd
|
||||||
|
|
||||||
|
#endif /* FSK_DEMODULATOR_H */
|
87
include/modulation/FSKModulator.h
Normal file
87
include/modulation/FSKModulator.h
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
#ifndef FSK_MODULATOR_H
|
||||||
|
#define FSK_MODULATOR_H
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "BitStreamReader.h"
|
||||||
|
|
||||||
|
class FSKModulatorConfig {
|
||||||
|
public:
|
||||||
|
int freq_lo;
|
||||||
|
int freq_hi;
|
||||||
|
int sample_rate;
|
||||||
|
int baud_rate;
|
||||||
|
std::shared_ptr<BitStreamReader> bitstreamreader;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace milstd {
|
||||||
|
class FSKModulator {
|
||||||
|
public:
|
||||||
|
FSKModulator(const FSKModulatorConfig& s) : freq_lo(s.freq_lo), freq_hi(s.freq_hi), sample_rate(s.sample_rate), baud_rate(s.baud_rate), bit_reader(s.bitstreamreader) {
|
||||||
|
omega[0] = (2.0 * M_PI * freq_lo) / sample_rate;
|
||||||
|
omega[1] = (2.0 * M_PI * freq_hi) / sample_rate;
|
||||||
|
baud_incr = static_cast<double>(baud_rate) / sample_rate;
|
||||||
|
phase = 0.0;
|
||||||
|
baud_frac = 0.0;
|
||||||
|
current_bit = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<int16_t> modulate(unsigned int num_samples) {
|
||||||
|
std::vector<int16_t> samples;
|
||||||
|
samples.reserve(num_samples);
|
||||||
|
|
||||||
|
int bit = current_bit;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < num_samples; i++) {
|
||||||
|
baud_frac += baud_incr;
|
||||||
|
if (baud_frac >= 1.0) {
|
||||||
|
baud_frac -= 1.0;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bit = bit_reader->getNextBit();
|
||||||
|
}
|
||||||
|
catch(const std::out_of_range&)
|
||||||
|
{
|
||||||
|
bit = 0;
|
||||||
|
}
|
||||||
|
if (bit != 0 && bit != 1)
|
||||||
|
bit = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double sample = std::cos(phase);
|
||||||
|
int16_t sample_int = static_cast<int16_t>(sample * 32767);
|
||||||
|
samples.push_back(sample_int);
|
||||||
|
|
||||||
|
phase += omega[bit];
|
||||||
|
if (phase >= 2.0 * M_PI) {
|
||||||
|
phase -= 2.0 * M_PI;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
current_bit = bit;
|
||||||
|
|
||||||
|
return samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// parameters
|
||||||
|
int freq_lo, freq_hi;
|
||||||
|
int sample_rate;
|
||||||
|
int baud_rate;
|
||||||
|
std::shared_ptr<BitStreamReader> bit_reader;
|
||||||
|
|
||||||
|
// state variables
|
||||||
|
double phase;
|
||||||
|
double baud_frac;
|
||||||
|
double baud_incr;
|
||||||
|
std::array<double, 2> omega;
|
||||||
|
int current_bit;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace milstd
|
||||||
|
|
||||||
|
#endif /* FSK_MODULATOR_H */
|
0
include/modulation/PSKDemodulator.h
Normal file
0
include/modulation/PSKDemodulator.h
Normal file
74
include/modulation/PSKModulator.h
Normal file
74
include/modulation/PSKModulator.h
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
#ifndef PSK_MODULATOR_H
|
||||||
|
#define PSK_MODULATOR_H
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
|
||||||
|
class PSKModulatorConfig {
|
||||||
|
public:
|
||||||
|
int carrier_frequency;
|
||||||
|
int sample_rate;
|
||||||
|
int baud_rate;
|
||||||
|
int bits_per_symbol;
|
||||||
|
|
||||||
|
PSKModulatorConfig(int cf, int sr, int br) : carrier_frequency(cf), sample_rate(sr), baud_rate(br) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace milstd {
|
||||||
|
class PSKModulator {
|
||||||
|
public:
|
||||||
|
PSKModulator(const PSKModulatorConfig& config) : carrier_frequency(config.carrier_frequency), sample_rate(config.sample_rate), baud_rate(config.baud_rate) {
|
||||||
|
omega = 2.0 * M_PI * carrier_frequency / sample_rate;
|
||||||
|
baud_incr = static_cast<double>(baud_rate) / sample_rate;
|
||||||
|
phase = 0.0;
|
||||||
|
baud_frac = 0.0;
|
||||||
|
initializeSymbolMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<int16_t> modulate(const std::vector<uint8_t>& symbol_stream) {
|
||||||
|
std::vector<int16_t> modulated_signal;
|
||||||
|
modulated_signal.reserve(static_cast<size_t>(symbol_stream.size() / baud_incr));
|
||||||
|
|
||||||
|
for (uint8_t symbol : symbol_stream) {
|
||||||
|
double symbol_phase = symbolMap[symbol];
|
||||||
|
|
||||||
|
while (baud_frac < 1.0) {
|
||||||
|
double sample = std::sin(omega * phase + symbol_phase);
|
||||||
|
int16_t output_sample = static_cast<int16_t>(sample * 32767);
|
||||||
|
modulated_signal.push_back(output_sample);
|
||||||
|
|
||||||
|
phase = std::fmod(phase + omega, 2.0 * M_PI);
|
||||||
|
baud_frac += baud_incr;
|
||||||
|
}
|
||||||
|
|
||||||
|
baud_frac -= 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return modulated_signal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
int carrier_frequency;
|
||||||
|
int sample_rate;
|
||||||
|
int baud_rate;
|
||||||
|
|
||||||
|
double phase;
|
||||||
|
double baud_frac;
|
||||||
|
double baud_incr;
|
||||||
|
double omega;
|
||||||
|
|
||||||
|
std::array<double, 8> symbolMap;
|
||||||
|
|
||||||
|
void initializeSymbolMap() {
|
||||||
|
double phase_increment = 2.0 * M_PI / 8.0;
|
||||||
|
for (int symbol = 0; symbol < 8; ++symbol) {
|
||||||
|
symbolMap[symbol] = symbol * phase_increment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* PSK_MODULATOR_H */
|
220
include/utils/bitstream.h
Normal file
220
include/utils/bitstream.h
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
#ifndef BITSTREAM_H
|
||||||
|
#define BITSTREAM_H
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class BitStream
|
||||||
|
* @brief A class to represent a stream of bits with bit-level read and write access.
|
||||||
|
*
|
||||||
|
* The BitStream class provides functionality to manipulate a byte stream at the bit level.
|
||||||
|
* It derives from std::vector<uint8_t> to utilize the benefits of a byte vector while providing
|
||||||
|
* additional methods for bit manipulation.
|
||||||
|
*/
|
||||||
|
class BitStream : public std::vector<uint8_t> {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Default constructor.
|
||||||
|
*/
|
||||||
|
BitStream() : bit_index(0), max_bit_idx(0) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs a BitStream from an existing vector of bytes.
|
||||||
|
* @param data The byte stream to be used for initializing the BitStream.
|
||||||
|
*/
|
||||||
|
BitStream(const std::vector<uint8_t>& data) : std::vector<uint8_t>(data), bit_index(0), max_bit_idx(data.size() * 8) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs a BitStream from an existing vector of bytes with a specified bit size.
|
||||||
|
* @param data The byte stream to be used for initializing the BitStream.
|
||||||
|
* @param size_in_bits The number of bits to consider in the stream.
|
||||||
|
*/
|
||||||
|
BitStream(const std::vector<uint8_t>& data, size_t size_in_bits) : std::vector<uint8_t>(data), bit_index(0), max_bit_idx(size_in_bits) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copy constructor from another BitStream.
|
||||||
|
* @param data The BitStream to copy from.
|
||||||
|
*/
|
||||||
|
BitStream(const BitStream& data) : std::vector<uint8_t>(data), bit_index(0), max_bit_idx(data.max_bit_idx) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs a BitStream from a substream of another BitStream.
|
||||||
|
* @param other The original BitStream.
|
||||||
|
* @param start_bit The starting bit index of the substream.
|
||||||
|
* @param end_bit The ending bit index of the substream (exclusive).
|
||||||
|
* @throws std::out_of_range if start or end indices are out of bounds.
|
||||||
|
*/
|
||||||
|
BitStream(const BitStream& other, size_t start_bit, size_t end_bit) : bit_index(0) {
|
||||||
|
if (start_bit >= other.max_bit_idx || end_bit > other.max_bit_idx || start_bit > end_bit) {
|
||||||
|
throw std::out_of_range("BitStream substream indices are out of range.");
|
||||||
|
}
|
||||||
|
max_bit_idx = end_bit - start_bit;
|
||||||
|
for (size_t i = start_bit; i < end_bit; i++) {
|
||||||
|
putBit(other.getBitVal(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reads the next bit from the stream.
|
||||||
|
* @return The next bit (0 or 1).
|
||||||
|
* @throws std::out_of_range if no more bits are available in the stream.
|
||||||
|
*/
|
||||||
|
int getNextBit() {
|
||||||
|
if (bit_index >= max_bit_idx) {
|
||||||
|
throw std::out_of_range("No more bits available in the stream.");
|
||||||
|
}
|
||||||
|
|
||||||
|
int bit = getBitVal(bit_index++);
|
||||||
|
return bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets the value of a bit at a specific index.
|
||||||
|
* @param idx The index of the bit to be retrieved.
|
||||||
|
* @return The value of the bit (0 or 1).
|
||||||
|
* @throws std::out_of_range if the bit index is out of range.
|
||||||
|
*/
|
||||||
|
int getBitVal(const size_t idx) const {
|
||||||
|
if (idx >= max_bit_idx) {
|
||||||
|
throw std::out_of_range("Bit index out of range in getBitVal.");
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t byte_idx = idx / 8;
|
||||||
|
size_t bit_idx = idx % 8;
|
||||||
|
|
||||||
|
uint8_t tmp = this->at(byte_idx);
|
||||||
|
uint8_t mask = 0x80 >> bit_idx;
|
||||||
|
uint8_t result = tmp & mask;
|
||||||
|
|
||||||
|
return result ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks if there are more bits available in the stream.
|
||||||
|
* @return True if there are more bits available, otherwise false.
|
||||||
|
*/
|
||||||
|
bool hasNext() const {
|
||||||
|
return bit_index < max_bit_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets a specific bit value in the stream.
|
||||||
|
* @param idx The index of the bit to set.
|
||||||
|
* @param val The value to set the bit to (0 or 1).
|
||||||
|
*
|
||||||
|
* This function ensures that the stream has enough bytes to accommodate
|
||||||
|
* the given bit index. If the bit index is out of bounds, the stream is
|
||||||
|
* resized accordingly.
|
||||||
|
*/
|
||||||
|
void setBitVal(const size_t idx, uint8_t val) {
|
||||||
|
size_t byte_idx = idx / 8;
|
||||||
|
size_t bit_idx = idx % 8;
|
||||||
|
uint8_t mask = 0x80 >> bit_idx;
|
||||||
|
|
||||||
|
if (byte_idx >= this->size()) {
|
||||||
|
this->resize(byte_idx + 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (val == 0) {
|
||||||
|
this->at(byte_idx) &= ~mask;
|
||||||
|
} else {
|
||||||
|
this->at(byte_idx) |= mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Appends a bit to the end of the stream.
|
||||||
|
* @param bit The value of the bit to append (0 or 1).
|
||||||
|
*
|
||||||
|
* This function keeps track of the current bit index and appends bits
|
||||||
|
* sequentially. If necessary, the stream is resized to accommodate the new bit.
|
||||||
|
*/
|
||||||
|
void putBit(uint8_t bit) {
|
||||||
|
size_t byte_idx = max_bit_idx / 8;
|
||||||
|
if (byte_idx >= this->size()) {
|
||||||
|
this->push_back(0);
|
||||||
|
}
|
||||||
|
size_t bit_idx = max_bit_idx % 8;
|
||||||
|
setBitVal(max_bit_idx, bit);
|
||||||
|
max_bit_idx += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Resets the bit index to the beginning of the stream.
|
||||||
|
*/
|
||||||
|
void resetBitIndex() {
|
||||||
|
bit_index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the maximum bit index value (total number of bits in the stream).
|
||||||
|
* @return The total number of bits in the stream.
|
||||||
|
*/
|
||||||
|
size_t getMaxBitIndex() const {
|
||||||
|
return max_bit_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
BitStream& operator=(const BitStream& other) {
|
||||||
|
this->clear();
|
||||||
|
this->resize(other.size());
|
||||||
|
std::copy(other.begin(), other.end(), this->begin());
|
||||||
|
this->bit_index = other.bit_index;
|
||||||
|
this->max_bit_idx = other.max_bit_idx;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Adds the contents of another BitStream to the current BitStream.
|
||||||
|
* @param other The BitStream to be added.
|
||||||
|
* @return Reference to the current BitStream after adding.
|
||||||
|
*/
|
||||||
|
BitStream& operator+=(const BitStream& other) {
|
||||||
|
size_t other_max_bit_idx = other.getMaxBitIndex();
|
||||||
|
for (size_t i = 0; i < other_max_bit_idx; i++) {
|
||||||
|
this->putBit(other.getBitVal(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Gets a substream from the current BitStream.
|
||||||
|
* @param start_bit The starting bit index of the substream.
|
||||||
|
* @param end_bit The ending bit index of the substream (exclusive).
|
||||||
|
* @return A new BitStream containing the specified substream.
|
||||||
|
* @throws std::out_of_range if start or end indices are out of bounds.
|
||||||
|
*/
|
||||||
|
BitStream getSubStream(size_t start_bit, size_t end_bit) const {
|
||||||
|
if (start_bit >= max_bit_idx || end_bit > max_bit_idx || start_bit > end_bit) {
|
||||||
|
throw std::out_of_range("BitStream substream indices are out of range.");
|
||||||
|
}
|
||||||
|
BitStream substream;
|
||||||
|
for (size_t i = start_bit; i < end_bit; i++) {
|
||||||
|
substream.putBit(getBitVal(i));
|
||||||
|
}
|
||||||
|
return substream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the current bit index in the stream.
|
||||||
|
* @return The current bit index.
|
||||||
|
*/
|
||||||
|
size_t getCurrentBitIndex() const {
|
||||||
|
return bit_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t bit_index; ///< The current bit index in the stream.
|
||||||
|
size_t max_bit_idx; ///< The total number of bits in the stream.
|
||||||
|
};
|
||||||
|
|
||||||
|
BitStream operator+(const BitStream& lhs, const BitStream& rhs) {
|
||||||
|
BitStream result = lhs;
|
||||||
|
result += rhs;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
52
main.cpp
Normal file
52
main.cpp
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
#include <bitset>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <sndfile.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "ModemController.h"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// Sample test data
|
||||||
|
std::string sample_string = "The quick brown fox jumps over the lazy dog 1234567890";
|
||||||
|
std::vector<uint8_t> sample_data(sample_string.begin(), sample_string.end());
|
||||||
|
|
||||||
|
// Convert sample data to a BitStream object
|
||||||
|
BitStream bitstream(sample_data, sample_data.size() * 8);
|
||||||
|
|
||||||
|
// Configuration for modem
|
||||||
|
size_t baud_rate = 150;
|
||||||
|
bool is_voice = false; // False indicates data mode
|
||||||
|
bool is_frequency_hopping = false; // Fixed frequency operation
|
||||||
|
size_t interleave_setting = 1; // Short interleave
|
||||||
|
|
||||||
|
// Create ModemController instance
|
||||||
|
ModemController modem(baud_rate, is_voice, is_frequency_hopping, interleave_setting, bitstream);
|
||||||
|
|
||||||
|
const char* file_name = "modulated_signal_150bps_shortinterleave.wav";
|
||||||
|
|
||||||
|
// Perform transmit operation to generate modulated signal
|
||||||
|
std::vector<int16_t> modulated_signal = modem.transmit();
|
||||||
|
|
||||||
|
// Output modulated signal to a WAV file using libsndfile
|
||||||
|
SF_INFO sfinfo;
|
||||||
|
sfinfo.channels = 1;
|
||||||
|
sfinfo.samplerate = 48000;
|
||||||
|
sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
|
||||||
|
|
||||||
|
SNDFILE* sndfile = sf_open(file_name, SFM_WRITE, &sfinfo);
|
||||||
|
if (sndfile == nullptr) {
|
||||||
|
std::cerr << "Unable to open WAV file for writing modulated signal: " << sf_strerror(sndfile) << "\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sf_write_short(sndfile, modulated_signal.data(), modulated_signal.size());
|
||||||
|
sf_close(sndfile);
|
||||||
|
std::cout << "Modulated signal written to " << file_name << '\n';
|
||||||
|
|
||||||
|
// Success message
|
||||||
|
std::cout << "Modem test completed successfully.\n";
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
0
tests/CMakeLists.txt
Normal file
0
tests/CMakeLists.txt
Normal file
23
tests/FSKModulatorTests.cpp
Normal file
23
tests/FSKModulatorTests.cpp
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#include "gtest/gtest.h"
|
||||||
|
#include "FSKModulator.h"
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
TEST(FSKModulatorTest, SignalLength) {
|
||||||
|
using namespace milstd;
|
||||||
|
|
||||||
|
// Parameters
|
||||||
|
FSKModulator modulator(FSKModulator::ShiftType::NarrowShift, 75.0, 8000.0);
|
||||||
|
|
||||||
|
// Input data bits
|
||||||
|
std::vector<uint8_t> dataBits = {1, 0, 1, 1, 0};
|
||||||
|
|
||||||
|
// Modulate the data
|
||||||
|
std::vector<double> signal = modulator.modulate(dataBits);
|
||||||
|
|
||||||
|
// Calculate expected number of samples
|
||||||
|
size_t samplesPerSymbol = static_cast<size_t>(modulator.getSampleRate() * modulator.getSymbolDuration());
|
||||||
|
size_t expectedSamples = dataBits.size() * samplesPerSymbol;
|
||||||
|
|
||||||
|
// Verify signal length
|
||||||
|
EXPECT_EQ(signal.size(), expectedSamples);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user