From 48f7f5176d78f2dc45615cfc5c9ac31180bb5304 Mon Sep 17 00:00:00 2001 From: RecklessAndFeckless Date: Tue, 8 Oct 2024 23:49:40 -0400 Subject: [PATCH] Initial upload --- .gitignore | 6 + CMakeLists.txt | 43 +++ cmake/FindSndFile.cmake | 18 ++ include/encoder/FECEncoder.h | 110 ++++++++ include/encoder/Interleaver.h | 224 ++++++++++++++++ include/encoder/MGDDecoder.h | 49 ++++ include/encoder/ModemController.h | 177 +++++++++++++ include/encoder/Scrambler.h | 96 +++++++ include/encoder/SymbolFormation.h | 388 ++++++++++++++++++++++++++++ include/modulation/FSKDemodulator.h | 131 ++++++++++ include/modulation/FSKModulator.h | 87 +++++++ include/modulation/PSKDemodulator.h | 0 include/modulation/PSKModulator.h | 74 ++++++ include/utils/bitstream.h | 220 ++++++++++++++++ main.cpp | 52 ++++ tests/CMakeLists.txt | 0 tests/FSKModulatorTests.cpp | 23 ++ 17 files changed, 1698 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 cmake/FindSndFile.cmake create mode 100644 include/encoder/FECEncoder.h create mode 100644 include/encoder/Interleaver.h create mode 100644 include/encoder/MGDDecoder.h create mode 100644 include/encoder/ModemController.h create mode 100644 include/encoder/Scrambler.h create mode 100644 include/encoder/SymbolFormation.h create mode 100644 include/modulation/FSKDemodulator.h create mode 100644 include/modulation/FSKModulator.h create mode 100644 include/modulation/PSKDemodulator.h create mode 100644 include/modulation/PSKModulator.h create mode 100644 include/utils/bitstream.h create mode 100644 main.cpp create mode 100644 tests/CMakeLists.txt create mode 100644 tests/FSKModulatorTests.cpp diff --git a/.gitignore b/.gitignore index d93251f..4961ba5 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,9 @@ _deps *.out *.app +# Output files +*.wav + +# VSCode +.vscode +build \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..fde67e4 --- /dev/null +++ b/CMakeLists.txt @@ -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() diff --git a/cmake/FindSndFile.cmake b/cmake/FindSndFile.cmake new file mode 100644 index 0000000..9360686 --- /dev/null +++ b/cmake/FindSndFile.cmake @@ -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() \ No newline at end of file diff --git a/include/encoder/FECEncoder.h b/include/encoder/FECEncoder.h new file mode 100644 index 0000000..e7aaa94 --- /dev/null +++ b/include/encoder/FECEncoder.h @@ -0,0 +1,110 @@ +#ifndef FEC_ENCODER_H +#define FEC_ENCODER_H + +#include +#include +#include + +#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 \ No newline at end of file diff --git a/include/encoder/Interleaver.h b/include/encoder/Interleaver.h new file mode 100644 index 0000000..40d8296 --- /dev/null +++ b/include/encoder/Interleaver.h @@ -0,0 +1,224 @@ +#ifndef INTERLEAVER_H +#define INTERLEAVER_H + +#include +#include +#include +#include + +#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 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 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 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 groupSymbols(BitStream& input_data) { + std::vector 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 diff --git a/include/encoder/MGDDecoder.h b/include/encoder/MGDDecoder.h new file mode 100644 index 0000000..208f924 --- /dev/null +++ b/include/encoder/MGDDecoder.h @@ -0,0 +1,49 @@ +#ifndef MGD_DECODER_H +#define MGD_DECODER_H + +#include +#include +#include +#include + +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 mgdDecode(const std::vector& symbols) { + std::vector 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 lookupTable = {0, 1, 3, 2, 7, 6, 4, 5}; + return lookupTable[input]; + } + + int mgd4psk(int input) { + static std::array lookupTable = {0, 1, 3, 2}; + return lookupTable[input]; + } +}; + +#endif \ No newline at end of file diff --git a/include/encoder/ModemController.h b/include/encoder/ModemController.h new file mode 100644 index 0000000..a57974a --- /dev/null +++ b/include/encoder/ModemController.h @@ -0,0 +1,177 @@ +#ifndef MODEM_CONTROLLER_H +#define MODEM_CONTROLLER_H + +#include +#include +#include +#include + +#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::max(); + constexpr int16_t min_val = std::numeric_limits::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 transmit() { + // Step 1: Append EOM Symbols + BitStream eom_appended_data = appendEOMSymbols(input_data); + + std::vector 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 mgd_decoded_data = mgd_decoder.mgdDecode(processed_data); + + // Step 4: Symbol Formation. This function injects the sync preamble symbols. + std::vector symbol_stream = symbol_formation.formSymbols(mgd_decoded_data); + + // Step 5: Scrambling + std::vector scrambled_data = scrambler.scrambleData(symbol_stream); + + // Step 6: PSK Modulation. This is the final baseband output of the class. + std::vector modulated_signal = modulator.modulate(scrambled_data); + + // Step 7: Apply Sqrt Half Cosine Filter with a rolloff factor of 0.3 (30%) + std::vector 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(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 SqrtHalfCosine(const std::vector& input_signal, double rolloff_factor) const { + std::vector 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(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(filtered_value)); + } + + return filtered_signal; + } + + std::vector splitTribitSymbols(const BitStream& input_data) { + std::vector 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 diff --git a/include/encoder/Scrambler.h b/include/encoder/Scrambler.h new file mode 100644 index 0000000..48ffa76 --- /dev/null +++ b/include/encoder/Scrambler.h @@ -0,0 +1,96 @@ +#ifndef SCRAMBLER_H +#define SCRAMBLER_H + +#include +#include +#include +#include + +/** + * @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 scrambleSyncPreamble(const std::vector& preamble) const { + static const std::array 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 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 scrambleData(const std::vector& data) { + std::vector 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 diff --git a/include/encoder/SymbolFormation.h b/include/encoder/SymbolFormation.h new file mode 100644 index 0000000..ba02bfb --- /dev/null +++ b/include/encoder/SymbolFormation.h @@ -0,0 +1,388 @@ +#ifndef SYMBOLFORMATION_H +#define SYMBOLFORMATION_H + +#include +#include +#include +#include +#include +#include + +#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 Sync_Preamble_0 = {0, 0, 0, 0, 0, 0, 0, 0}; +std::vector Sync_Preamble_1 = {0, 4, 0, 4, 0, 4, 0, 4}; +std::vector Sync_Preamble_2 = {0, 0, 4, 4, 0, 0, 4, 4}; +std::vector Sync_Preamble_3 = {0, 4, 4, 0, 0, 4, 4, 0}; +std::vector Sync_Preamble_4 = {0, 0, 0, 0, 4, 4, 4, 4}; +std::vector Sync_Preamble_5 = {0, 4, 0, 4, 4, 0, 4, 0}; +std::vector Sync_Preamble_6 = {0, 0, 4, 4, 4, 4, 0, 0}; +std::vector Sync_Preamble_7 = {0, 4, 4, 0, 4, 0, 0, 4}; + +std::vector baud75_exceptional_0 = {0, 0, 0, 0, 4, 4, 4, 4}; +std::vector baud75_exceptional_1 = {0, 4, 0, 4, 4, 0, 4, 0}; +std::vector baud75_exceptional_2 = {0, 0, 4, 4, 4, 4, 0, 0}; +std::vector baud75_exceptional_3 = {0, 4, 4, 0, 4, 0, 0, 4}; +std::vector baud75_normal_0 = {0, 0, 0, 0}; +std::vector baud75_normal_1 = {0, 4, 0, 4}; +std::vector baud75_normal_2 = {0, 0, 4, 4}; +std::vector 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 formSymbols(std::vector& symbol_data) { + // Generate and scramble the sync preamble + std::vector 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 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 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 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 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 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 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& 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& 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 generateSyncPreamble() { + std::vector preamble; + + size_t num_segments = (interleave_setting == 0) ? 3 : 24; + + std::vector 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 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 final_preamble; + for (auto& symbol : preamble) { + appendProbeMapping(final_preamble, symbol, 4); + } + + return final_preamble; + } + + std::vector generateProbeData(bool is_inside_block) { + std::vector 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 final_probe_data; + for (auto symbol : probe_data) { + appendProbeMapping(final_probe_data, symbol, 2); + } + } + + return probe_data; + } + + std::vector map75bpsSet(const std::vector& data, size_t start_index, size_t set_size, bool is_exceptional_set) { + std::vector mapped_set; + + for (auto symbol : data) { + append75bpsMapping(mapped_set, symbol, is_exceptional_set); + } + + return mapped_set; + } + + std::vector mapUnknownData(const std::vector& data) { + std::vector 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 \ No newline at end of file diff --git a/include/modulation/FSKDemodulator.h b/include/modulation/FSKDemodulator.h new file mode 100644 index 0000000..3611003 --- /dev/null +++ b/include/modulation/FSKDemodulator.h @@ -0,0 +1,131 @@ +#ifndef FSK_DEMODULATOR_H +#define FSK_DEMODULATOR_H + +#include +#include +#include +#include +#include + +#include "BitStreamWriter.h" + +class FSKDemodulatorConfig { + public: + int freq_lo; + int freq_hi; + int sample_rate; + int baud_rate; + std::shared_ptr 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& 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 bit_writer; + + int filter_size; + std::vector filter_lo_i; + std::vector filter_lo_q; + std::vector filter_hi_i; + std::vector filter_hi_q; + std::vector 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(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 */ \ No newline at end of file diff --git a/include/modulation/FSKModulator.h b/include/modulation/FSKModulator.h new file mode 100644 index 0000000..6ed2648 --- /dev/null +++ b/include/modulation/FSKModulator.h @@ -0,0 +1,87 @@ +#ifndef FSK_MODULATOR_H +#define FSK_MODULATOR_H + +#include +#include +#include +#include +#include + +#include "BitStreamReader.h" + +class FSKModulatorConfig { + public: + int freq_lo; + int freq_hi; + int sample_rate; + int baud_rate; + std::shared_ptr 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(baud_rate) / sample_rate; + phase = 0.0; + baud_frac = 0.0; + current_bit = 0; + } + + std::vector modulate(unsigned int num_samples) { + std::vector 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(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 bit_reader; + + // state variables + double phase; + double baud_frac; + double baud_incr; + std::array omega; + int current_bit; + }; + +} // namespace milstd + +#endif /* FSK_MODULATOR_H */ \ No newline at end of file diff --git a/include/modulation/PSKDemodulator.h b/include/modulation/PSKDemodulator.h new file mode 100644 index 0000000..e69de29 diff --git a/include/modulation/PSKModulator.h b/include/modulation/PSKModulator.h new file mode 100644 index 0000000..6e80b04 --- /dev/null +++ b/include/modulation/PSKModulator.h @@ -0,0 +1,74 @@ +#ifndef PSK_MODULATOR_H +#define PSK_MODULATOR_H + +#include +#include +#include +#include + + +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(baud_rate) / sample_rate; + phase = 0.0; + baud_frac = 0.0; + initializeSymbolMap(); + } + + std::vector modulate(const std::vector& symbol_stream) { + std::vector modulated_signal; + modulated_signal.reserve(static_cast(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(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 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 */ \ No newline at end of file diff --git a/include/utils/bitstream.h b/include/utils/bitstream.h new file mode 100644 index 0000000..d0d70e8 --- /dev/null +++ b/include/utils/bitstream.h @@ -0,0 +1,220 @@ +#ifndef BITSTREAM_H +#define BITSTREAM_H + +#include +#include +#include +#include + +/** + * @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 to utilize the benefits of a byte vector while providing + * additional methods for bit manipulation. + */ +class BitStream : public std::vector { +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& data) : std::vector(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& data, size_t size_in_bits) : std::vector(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(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 diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..321daca --- /dev/null +++ b/main.cpp @@ -0,0 +1,52 @@ +#include +#include +#include +#include +#include +#include + +#include "ModemController.h" + +int main() { + // Sample test data + std::string sample_string = "The quick brown fox jumps over the lazy dog 1234567890"; + std::vector 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 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; +} \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/FSKModulatorTests.cpp b/tests/FSKModulatorTests.cpp new file mode 100644 index 0000000..75a7e94 --- /dev/null +++ b/tests/FSKModulatorTests.cpp @@ -0,0 +1,23 @@ +#include "gtest/gtest.h" +#include "FSKModulator.h" +#include + +TEST(FSKModulatorTest, SignalLength) { + using namespace milstd; + + // Parameters + FSKModulator modulator(FSKModulator::ShiftType::NarrowShift, 75.0, 8000.0); + + // Input data bits + std::vector dataBits = {1, 0, 1, 1, 0}; + + // Modulate the data + std::vector signal = modulator.modulate(dataBits); + + // Calculate expected number of samples + size_t samplesPerSymbol = static_cast(modulator.getSampleRate() * modulator.getSymbolDuration()); + size_t expectedSamples = dataBits.size() * samplesPerSymbol; + + // Verify signal length + EXPECT_EQ(signal.size(), expectedSamples); +} \ No newline at end of file