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