Initial upload

This commit is contained in:
RecklessAndFeckless 2024-10-08 23:49:40 -04:00
parent 37acd1fea9
commit 48f7f5176d
17 changed files with 1698 additions and 0 deletions

6
.gitignore vendored
View File

@ -45,3 +45,9 @@ _deps
*.out *.out
*.app *.app
# Output files
*.wav
# VSCode
.vscode
build

43
CMakeLists.txt Normal file
View 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
View 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()

View 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

View 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

View 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

View 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

View 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

View 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

View 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 */

View 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 */

View File

View 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
View 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
View 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
View File

View 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);
}