diff --git a/include/encoder/ModemController.h b/include/encoder/ModemController.h index d602575..48bfcca 100644 --- a/include/encoder/ModemController.h +++ b/include/encoder/ModemController.h @@ -40,7 +40,7 @@ public: * @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) + ModemController(const size_t _baud_rate, const bool _is_voice, const bool _is_frequency_hopping, const size_t _interleave_setting) : baud_rate(_baud_rate), is_voice(_is_voice), is_frequency_hopping(_is_frequency_hopping), @@ -49,7 +49,6 @@ public: 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(48000, _is_frequency_hopping, 48) {} @@ -58,7 +57,7 @@ public: * @return The scrambled data ready for modulation. * @note The modulated signal is generated internally but is intended to be handled externally. */ - std::vector transmit() { + std::vector transmit(BitStream input_data) { // Step 1: Append EOM Symbols BitStream eom_appended_data = appendEOMSymbols(input_data); @@ -84,11 +83,17 @@ public: return modulated_signal; } + BitStream receive(const std::vector& passband_signal) { + // Step one: Demodulate the passband signal and retrieve decoded symbols + std::vector demodulated_symbols = modulator.demodulate(passband_signal, baud_rate, interleave_setting, is_voice); + + return BitStream(); + } + 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. size_t sample_rate; diff --git a/include/encoder/Scrambler.h b/include/encoder/Scrambler.h index d73bb70..42c841f 100644 --- a/include/encoder/Scrambler.h +++ b/include/encoder/Scrambler.h @@ -15,14 +15,14 @@ public: /** * @brief Constructor initializes the scrambler with a predefined register value. */ - Scrambler() : data_sequence_register(0x0BAD), symbol_count(0) {} + Scrambler() : data_sequence_register(0x0BAD), symbol_count(0), preamble_table_index(0) {} /** * @brief Scrambles a synchronization preamble using a fixed randomizer sequence. * @param preamble The synchronization preamble to scramble. * @return The scrambled synchronization preamble. */ - std::vector scrambleSyncPreamble(const std::vector& preamble) const { + std::vector scrambleSyncPreamble(const std::vector& preamble) { static const std::array sync_randomizer_sequence = { 7, 4, 3, 0, 5, 1, 5, 0, 2, 2, 1, 1, 5, 7, 4, 3, 5, 0, 2, 6, 2, 1, 6, 2, @@ -33,8 +33,9 @@ public: 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; + uint8_t scrambled_value = (preamble[i] + sync_randomizer_sequence[preamble_table_index]) % 8; scrambled_preamble.push_back(scrambled_value); + preamble_table_index = (preamble_table_index + 1) % sync_randomizer_sequence.size(); } return scrambled_preamble; @@ -61,6 +62,7 @@ public: private: uint16_t data_sequence_register; size_t symbol_count; + size_t preamble_table_index; /** * @brief Generates the next value from the data sequence randomizing generator. diff --git a/include/modulation/PSKModulator.h b/include/modulation/PSKModulator.h index f8e6697..4ed7b0c 100644 --- a/include/modulation/PSKModulator.h +++ b/include/modulation/PSKModulator.h @@ -3,6 +3,7 @@ #include +#include #include #include #include @@ -10,9 +11,12 @@ #include #include #include +#include +#include #include "costasloop.h" #include "filters.h" +#include "Scrambler.h" static constexpr double CARRIER_FREQ = 1800.0; static constexpr size_t SYMBOL_RATE = 2400; @@ -77,9 +81,9 @@ public: return final_signal; } - std::vector demodulate(const std::vector passband_signal) { + std::vector demodulate(const std::vector passband_signal, size_t& baud_rate, size_t& interleave_setting, bool& is_voice) { // Carrier recovery. initialize the Costas loop. - CostasLoop costas_loop(CARRIER_FREQ, sample_rate, symbolMap, 5.0); + CostasLoop costas_loop(CARRIER_FREQ, sample_rate, symbolMap, 5.0, 0.05, 0.01); // Convert passband signal to doubles. std::vector normalized_passband(passband_signal.size()); @@ -89,23 +93,30 @@ public: // Downmix passband to baseband std::vector> baseband_IQ = costas_loop.process(normalized_passband); + std::vector detected_symbols; // Phase detection and symbol formation - std::vector baseband_symbols; size_t samples_per_symbol = sample_rate / SYMBOL_RATE; + bool sync_found = false; + size_t sync_segments_detected; + size_t window_size = 32*15; + for (size_t i = 0; i < baseband_IQ.size(); i += samples_per_symbol) { std::complex symbol_avg(0.0, 0.0); - for (size_t j = 0; j < samples_per_symbol; ++j) { + for (size_t j = 0; j < samples_per_symbol; j++) { symbol_avg += baseband_IQ[i + j]; } symbol_avg /= static_cast(samples_per_symbol); - // Detect symbol from averaged signal - baseband_symbols.emplace_back(phase_detector.getSymbol(symbol_avg)); + uint8_t detected_symbol = phase_detector.getSymbol(symbol_avg); + detected_symbols.push_back(detected_symbol); + } + + if (processSyncSegments(detected_symbols, baud_rate, interleave_setting, is_voice)) { + return processDataSymbols(detected_symbols); } - return baseband_symbols; } private: @@ -137,6 +148,220 @@ private: {gain * std::cos(2.0*M_PI*(7.0/8.0)), gain * std::sin(2.0*M_PI*(7.0/8.0))} // 7 (111) corresponds to I = cos(315), Q = sin(315) }; } + + uint8_t extractBestTribit(const std::vector& stream, const size_t start, const size_t window_size) const { + if (start + window_size > stream.size()) { + throw std::out_of_range("Window size exceeds symbol stream size."); + } + + Scrambler scrambler; + std::vector symbol(stream.begin() + start, stream.begin() + start + window_size); + std::vector descrambled_symbol = scrambler.scrambleSyncPreamble(symbol); + + const size_t split_len = window_size / 4; + std::array tribit_counts = {0}; // Counts for each channel symbol (000 to 111) + + // Loop through each split segment (4 segments) + for (size_t i = 0; i < 4; ++i) { + // Extract the range for this split + size_t segment_start = start + i * split_len; + size_t segment_end = segment_start + split_len; + + // Compare this segment to the predefined patterns from the table and map to a channel symbol + uint8_t tribit_value = mapSegmentToChannelSymbol(descrambled_symbol, segment_start, segment_end); + + // Increment the corresponding channel symbol count + tribit_counts[tribit_value]++; + } + + // Find the channel symbol with the highest count (majority vote) + uint8_t best_symbol = std::distance(tribit_counts.begin(), std::max_element(tribit_counts.begin(), tribit_counts.end())); + + return best_symbol; + } + + // Function to map a segment of the stream back to a channel symbol based on the repeating patterns + uint8_t mapSegmentToChannelSymbol(const std::vector& segment, size_t start, size_t end) const { + std::vector extracted_pattern(segment.begin() + start, segment.begin() + end); + + // Compare the extracted pattern with known patterns from the table + if (matchesPattern(extracted_pattern, {0, 0, 0, 0, 0, 0, 0, 0})) return 0b000; + if (matchesPattern(extracted_pattern, {0, 4, 0, 4, 0, 4, 0, 4})) return 0b001; + if (matchesPattern(extracted_pattern, {0, 0, 4, 4, 0, 0, 4, 4})) return 0b010; + if (matchesPattern(extracted_pattern, {0, 4, 4, 0, 0, 4, 4, 0})) return 0b011; + if (matchesPattern(extracted_pattern, {0, 0, 0, 0, 4, 4, 4, 4})) return 0b100; + if (matchesPattern(extracted_pattern, {0, 4, 0, 4, 4, 0, 4, 0})) return 0b101; + if (matchesPattern(extracted_pattern, {0, 0, 4, 4, 4, 4, 0, 0})) return 0b110; + if (matchesPattern(extracted_pattern, {0, 4, 4, 0, 4, 0, 0, 4})) return 0b111; + + throw std::invalid_argument("Invalid segment pattern"); + } + + // Helper function to compare two patterns + bool matchesPattern(const std::vector& segment, const std::vector& pattern) const { + return std::equal(segment.begin(), segment.end(), pattern.begin()); + } + + bool configureModem(uint8_t D1, uint8_t D2, size_t& baud_rate, size_t& interleave_setting, bool& is_voice) { + // Predefine all the valid combinations in a lookup map + static const std::map, std::tuple> modemConfig = { + {{7, 6}, {4800, 1, false}}, // 4800 bps + {{7, 7}, {2400, 1, true}}, // 2400 bps, voice + {{6, 4}, {2400, 1, false}}, // 2400 bps, data + {{6, 5}, {1200, 1, false}}, // 1200 bps + {{6, 6}, {600, 1, false}}, // 600 bps + {{6, 7}, {300, 1, false}}, // 300 bps + {{7, 4}, {150, 1, false}}, // 150 bps + {{7, 5}, {75, 1, false}}, // 75 bps + {{4, 4}, {2400, 2, false}}, // 2400 bps, long interleave + {{4, 5}, {1200, 2, false}}, // 1200 bps, long interleave + {{4, 6}, {600, 2, false}}, // 600 bps, long interleave + {{4, 7}, {300, 2, false}}, // 300 bps, long interleave + {{5, 4}, {150, 2, false}}, // 150 bps, long interleave + {{5, 5}, {75, 2, false}}, // 75 bps, long interleave + }; + + // Use D1 and D2 to look up the correct configuration + auto it = modemConfig.find({D1, D2}); + if (it != modemConfig.end()) { + // Set the parameters if found + std::tie(baud_rate, interleave_setting, is_voice) = it->second; + return true; + } else { + return false; + } + } + + uint8_t calculateSegmentCount(const uint8_t C1, const uint8_t C2, const uint8_t C3) { + uint8_t extracted_C1 = C1 & 0b11; + uint8_t extracted_C2 = C2 & 0b11; + uint8_t extracted_C3 = C3 & 0b11; + + uint8_t segment_count = (extracted_C1 << 4) | (extracted_C2 << 2) | extracted_C3; + return segment_count; + } + + bool processSegment(const std::vector& detected_symbols, size_t& start, size_t symbol_size, size_t& segment_count, uint8_t& D1, uint8_t& D2) { + size_t sync_pattern_length = 9; + + if (start + symbol_size * sync_pattern_length > detected_symbols.size()) { + start = detected_symbols.size(); + return false; + } + + std::vector window(detected_symbols.begin() + start, detected_symbols.begin() + start + sync_pattern_length * symbol_size); + std::vector extracted_window; + for (size_t i = 0; i < sync_pattern_length; i++) { + extracted_window.push_back(extractBestTribit(window, i * symbol_size, symbol_size)); + } + + if (!matchesPattern(extracted_window, {0, 1, 3, 0, 1, 3, 1, 2, 0})) { + start += symbol_size; + return false; + } + + start += sync_pattern_length * symbol_size; + size_t D1_index = start + symbol_size; + size_t D2_index = D1_index + symbol_size; + + if (D2_index + symbol_size > detected_symbols.size()) { + start = detected_symbols.size(); + return false; + } + + D1 = extractBestTribit(detected_symbols, D1_index, symbol_size); + D2 = extractBestTribit(detected_symbols, D2_index, symbol_size); + + // Process the count symbols (C1, C2, C3) + size_t C1_index = D2_index + symbol_size; + size_t C2_index = C1_index + symbol_size; + size_t C3_index = C2_index + symbol_size; + + if (C3_index + symbol_size > detected_symbols.size()) { + start = detected_symbols.size(); + return false; + } + + uint8_t C1 = extractBestTribit(detected_symbols, C1_index, symbol_size); + uint8_t C2 = extractBestTribit(detected_symbols, C2_index, symbol_size); + uint8_t C3 = extractBestTribit(detected_symbols, C3_index, symbol_size); + + segment_count = calculateSegmentCount(C1, C2, C3); + + // Check for the constant zero pattern + size_t constant_zero_index = C3_index + symbol_size; + + if (constant_zero_index + symbol_size > detected_symbols.size()) { + start = detected_symbols.size(); + return false; + } + uint8_t constant_zero = extractBestTribit(detected_symbols, constant_zero_index, symbol_size); + + if (constant_zero != 0) { + start = constant_zero_index + symbol_size; + return false; // Failed zero check, resync + } + + // Successfully processed the segment + start = constant_zero_index + symbol_size; // Move start to next segment + return true; + } + + bool processSyncSegments(const std::vector& detected_symbols, size_t& baud_rate, size_t& interleave_setting, bool& is_voice) { + size_t symbol_size = 32; + size_t start = 0; + size_t segment_count = 0; + std::map, int> vote_map; + const int short_interleave_threshold = 2; + const int long_interleave_threshold = 5; + + // Attempt to detect interleave setting dynamically + bool interleave_detected = false; + int current_threshold = short_interleave_threshold; // Start by assuming short interleave + + while (start + symbol_size * 15 < detected_symbols.size()) { + uint8_t D1 = 0, D2 = 0; + if (processSegment(detected_symbols, start, symbol_size, segment_count, D1, D2)) { + vote_map[{D1, D2}]++; + + // Check if we have enough votes to make a decision based on current interleave assumption + if (vote_map.size() >= current_threshold) { + auto majority_vote = std::max_element(vote_map.begin(), vote_map.end(), [](const auto& a, const auto& b) { return a.second < b.second; }); + + if (configureModem(majority_vote->first.first, majority_vote->first.second, baud_rate, interleave_setting, is_voice)) { + interleave_detected = true; + break; // Successfully configured modem, exit loop + } else { + // If configuration fails, retry with the other interleave type + if (current_threshold == short_interleave_threshold) { + current_threshold = long_interleave_threshold; // Switch to long interleave + vote_map.clear(); // Clear the vote map and start fresh + start = 0; // Restart segment processing + } else { + continue; // Both short and long interleave attempts failed, signal is not usable + } + } + } + + if (segment_count > 0) { + while (segment_count > 0 && start < detected_symbols.size()) { + uint8_t dummy_D1, dummy_D2; + if (!processSegment(detected_symbols, start, symbol_size, segment_count, dummy_D1, dummy_D2)) { + continue; + } + } + } + } else { + start += symbol_size; // Move to the next segment + } + } + + return interleave_detected; + } + + std::vector processDataSymbols(const std::vector& detected_symbols) { + return std::vector(); + } }; #endif \ No newline at end of file diff --git a/include/utils/bitstream.h b/include/utils/bitstream.h index d0d70e8..81ce276 100644 --- a/include/utils/bitstream.h +++ b/include/utils/bitstream.h @@ -19,7 +19,7 @@ public: /** * @brief Default constructor. */ - BitStream() : bit_index(0), max_bit_idx(0) {} + BitStream() : std::vector(), bit_index(0), max_bit_idx(0) {} /** * @brief Constructs a BitStream from an existing vector of bytes. diff --git a/include/utils/costasloop.h b/include/utils/costasloop.h index aeff5b7..52d01be 100644 --- a/include/utils/costasloop.h +++ b/include/utils/costasloop.h @@ -39,8 +39,8 @@ private: class CostasLoop { public: - CostasLoop(const double _carrier_freq, const double _sample_rate, const std::vector>& _symbolMap, const double _vco_gain) - : carrier_freq(_carrier_freq), sample_rate(_sample_rate), vco_gain(_vco_gain), k_factor(-1 / (_sample_rate * _vco_gain)), + CostasLoop(const double _carrier_freq, const double _sample_rate, const std::vector>& _symbolMap, const double _vco_gain, const double _alpha, const double _beta) + : carrier_freq(_carrier_freq), sample_rate(_sample_rate), vco_gain(_vco_gain), alpha(_alpha), beta(_beta), freq_error(0.0), k_factor(-1 / (_sample_rate * _vco_gain)), prev_in_iir(0), prev_out_iir(0), prev_in_vco(0), feedback(1.0, 0.0), error_total(0), out_iir_total(0), in_vco_total(0), srrc_filter(SRRCFilter(48, _sample_rate, 2400, 0.35)) {} @@ -67,26 +67,22 @@ public: std::complex limited = limiter(filtered); // IIR Filter - double in_iir = std::asin(std::clamp(multiplied.imag() * limited.real() - multiplied.real() * limited.imag(), -1.0, 1.0)); - error_total += in_iir; + double error_real = (limited.real() > 0 ? 1.0 : -1.0) * limited.imag(); + double error_imag = (limited.imag() > 0 ? 1.0 : -1.0) * limited.real(); + double phase_error = error_real - error_imag; + phase_error = 0.5 * (std::abs(phase_error + 1) - std::abs(phase_error - 1)); - double out_iir = 1.0001 * in_iir - prev_in_iir + prev_out_iir; - prev_in_iir = in_iir; - prev_out_iir = out_iir; - out_iir_total += out_iir; + freq_error += beta * phase_error; + double phase_adjust = alpha * phase_error + freq_error; - // VCO Block - double in_vco = out_iir + prev_in_vco; - in_vco_total += in_vco; - prev_in_vco = in_vco; + current_phase += 2 * M_PI * carrier_freq / sample_rate + k_factor * phase_adjust; + if (current_phase > M_PI) current_phase -= 2 * M_PI; + else if (current_phase < -M_PI) current_phase += 2 * M_PI; // Generate feedback signal for next iteration double feedback_real = std::cos(current_phase); double feedback_imag = -std::sin(current_phase); feedback = std::complex(feedback_real, feedback_imag); - - current_phase += 2 * M_PI * carrier_freq / sample_rate + k_factor * in_vco; - if (current_phase > 2 * M_PI) current_phase -= 2 * M_PI; } return output_signal; @@ -105,6 +101,9 @@ private: double in_vco_total; SRRCFilter srrc_filter; double vco_gain; + double alpha; + double beta; + double freq_error; std::complex limiter(const std::complex& sample) const { double limited_I = std::clamp(sample.real(), -1.0, 1.0); diff --git a/main.cpp b/main.cpp index f9ba598..d579b83 100644 --- a/main.cpp +++ b/main.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -8,49 +9,54 @@ #include "ModemController.h" #include "PSKModulator.h" -int main() { - // Sample test data - std::string sample_string = "The quick brown fox jumps over the lazy dog 1234567890"; - std::vector sample_data(sample_string.begin(), sample_string.end()); +BitStream generateBernoulliData(size_t length, double p = 0.5) { + BitStream random_data; + std::random_device rd; + std::mt19937 gen(rd()); + std::bernoulli_distribution dist(p); + for (size_t i = 0; i < length * 8; ++i) { + random_data.putBit(dist(gen)); // Generates 0 or 1 with probability p + } + return random_data; +} + +int main() { // Convert sample data to a BitStream object - BitStream input_data(sample_data, sample_data.size() * 8); + BitStream input_data = generateBernoulliData(28800); // Configuration for modem - size_t baud_rate = 150; + size_t baud_rate = 75; bool is_voice = false; // False indicates data mode bool is_frequency_hopping = false; // Fixed frequency operation - size_t interleave_setting = 1; // Short interleave + size_t interleave_setting = 2; // Short interleave // Create ModemController instance - ModemController modem(baud_rate, is_voice, is_frequency_hopping, interleave_setting, input_data); - PSKModulator modulator(48000, is_frequency_hopping, 48); - - const char* file_name = "modulated_signal_75bps_shortinterleave.wav"; + ModemController modem(baud_rate, is_voice, is_frequency_hopping, interleave_setting); + + const char* file_name = "modulated_signal_75bps_longinterleave.wav"; // Perform transmit operation to generate modulated signal - std::vector modulated_signal = modem.transmit(); + std::vector modulated_signal = modem.transmit(input_data); - std::vector demodulated_symbols = modulator.demodulate(modulated_signal); + // 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; - //// 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"; + SNDFILE* sndfile = sf_open(file_name, SFM_WRITE, &sfinfo); + if (sndfile == nullptr) { + std::cerr << "Unable to open WAV file for writing modulated signal: " << sf_strerror(sndfile) << "\n"; + return 1; + } + + sf_write_short(sndfile, modulated_signal.data(), modulated_signal.size()); + sf_close(sndfile); + std::cout << "Modulated signal written to " << file_name << '\n'; + + // Success message + std::cout << "Modem test completed successfully.\n"; return 0; } \ No newline at end of file