#pragma once #include #include #include #include #include #include "phy/common.hh" #include "schmidl_cox.hh" #include "bip_buffer.hh" #include "theil_sen.hh" #include "blockdc.hh" #include "hilbert.hh" #include "phasor.hh" #include "delay.hh" #include "polar_encoder.hh" #include "polar_list_decoder.hh" #include "hadamard_decoder.hh" template class BufferWritePCM { public: BufferWritePCM(int rate, int bits, int channels) : rate_(rate), bits_(bits), channels_(channels) {} void write(const T* buffer, int count, int ch = 1) { for (int i = 0; i < count; ++i) { // 2 channels, only take real part for mono output if (ch == 2) { samples_.push_back(buffer[i * 2]); // real } else { samples_.push_back(buffer[i]); } } } void silence(int count) { for (int i = 0; i < count; ++i) { samples_.push_back(T(0)); } } const std::vector& samples() const { return samples_; } std::vector& samples() { return samples_; } void clear() { samples_.clear(); } int rate() const { return rate_; } int bits() const { return bits_; } int channels() const { return channels_; } private: std::vector samples_; int rate_, bits_, channels_; }; // Modem configuration struct ModemConfig { int sample_rate = 48000; int center_freq = 1500; int64_t call_sign = 0; int oper_mode = 0; static int64_t encode_callsign(const char* str) { int64_t acc = 0; for (char c = *str++; c; c = *str++) { acc *= 40; if (c == '/') acc += 3; else if (c >= '0' && c <= '9') acc += c - '0' + 4; else if (c >= 'a' && c <= 'z') acc += c - 'a' + 14; else if (c >= 'A' && c <= 'Z') acc += c - 'A' + 14; else if (c != ' ') return -1; } return acc; } static int encode_mode(const char* modulation, const char* code_rate, bool short_frame) { int mode = 0; if (!strcmp(modulation, "BPSK")) mode |= 0 << 4; else if (!strcmp(modulation, "QPSK")) mode |= 1 << 4; else if (!strcmp(modulation, "8PSK")) mode |= 2 << 4; else if (!strcmp(modulation, "QAM16")) mode |= 3 << 4; else if (!strcmp(modulation, "QAM64")) mode |= 4 << 4; else if (!strcmp(modulation, "QAM256")) mode |= 5 << 4; else if (!strcmp(modulation, "QAM1024")) mode |= 6 << 4; else if (!strcmp(modulation, "QAM4096")) mode |= 7 << 4; else return -1; if (!strcmp(code_rate, "1/2")) mode |= 0 << 1; else if (!strcmp(code_rate, "2/3")) mode |= 1 << 1; else if (!strcmp(code_rate, "3/4")) mode |= 2 << 1; else if (!strcmp(code_rate, "5/6")) mode |= 3 << 1; else if (!strcmp(code_rate, "1/4")) mode |= 4 << 1; else return -1; if (!short_frame) mode |= 1; return mode; } }; // Encoder template class ModemEncoder : public Common { public: typedef int8_t code_type; static const int guard_len = rate / 300; static const int symbol_len = guard_len * 40; ModemEncoder() {} // encode our data to audio samples std::vector encode(const uint8_t* input_data, size_t input_len, int freq_off, int64_t call_sign, int oper_mode) { BufferWritePCM pcm(rate, 32, 1); if (!setup(oper_mode)) { std::cerr << "Encoder: invalid mode" << std::endl; return {}; } int offset = (freq_off * symbol_len) / rate; tone_off = offset - tone_count / 2; guard_interval_weights(); meta_data((call_sign << 8) | oper_mode); // leading noise CODE::MLS noise(mls2_poly); for (int j = 0; j < 1; ++j) { for (int i = 0; i < tone_count; ++i) tone[i] = nrz(noise()); symbol(&pcm, -3); } // Copy input data (pad if necessary) std::memset(data, 0, data_max); std::memcpy(data, input_data, std::min(input_len, (size_t)data_bytes)); // Scramble CODE::Xorshift32 scrambler; for (int i = 0; i < data_bytes; ++i) data[i] ^= scrambler(); // Schmidl-Cox preamble CODE::MLS seq0(mls0_poly, mls0_seed); for (int i = 0; i < tone_count; ++i) tone[i] = nrz(seq0()); symbol(&pcm, -2); symbol(&pcm, -1); // Encode payload for (int i = 0; i < data_bits; ++i) mesg[i] = nrz(CODE::get_le_bit(data, i)); crc1.reset(); for (int i = 0; i < data_bytes; ++i) crc1(data[i]); for (int i = 0; i < 32; ++i) mesg[i + data_bits] = nrz((crc1() >> i) & 1); polar_encoder(code, mesg, frozen_bits, code_order); shuffle(perm, code, code_order); // Generate symbols CODE::MLS seq1(mls1_poly); for (int j = 0, k = 0, m = 0; j < symbol_count + 1; ++j) { seed_off = (block_skew * j + first_seed) % block_length; for (int i = 0; i < tone_count; ++i) { if (i % block_length == seed_off) { tone[i] = nrz(seq1()); } else if (j) { int bits = mod_bits; if (mod_bits == 3 && k % 32 == 30) bits = 2; if (mod_bits == 6 && k % 64 == 60) bits = 4; if (mod_bits == 10 && k % 128 == 120) bits = 8; if (mod_bits == 12 && k % 128 == 120) bits = 8; tone[i] = map_bits(perm + k, bits); k += bits; } else { tone[i] = map_bits(meta + m++, 1); } } symbol(&pcm, j); } for (int i = 0; i < guard_len; ++i) guard[i] *= 1 - weight[i]; pcm.write(reinterpret_cast(guard), guard_len, 2); return std::move(pcm.samples()); } int get_payload_size(int oper_mode) { if (!setup(oper_mode)) return 0; return data_bytes; } private: DSP::FastFourierTransform fwd; DSP::FastFourierTransform bwd; CODE::PolarEncoder polar_encoder; code_type code[bits_max], perm[bits_max], mesg[bits_max], meta[data_tones]; cmplx fdom[symbol_len]; cmplx tdom[symbol_len]; cmplx test[symbol_len]; cmplx kern[symbol_len]; cmplx guard[guard_len]; cmplx tone[tone_count]; cmplx temp[tone_count]; value weight[guard_len]; value papr[symbols_max]; static int bin(int carrier) { return (carrier + symbol_len) % symbol_len; } static int nrz(bool bit) { return 1 - 2 * bit; } cmplx map_bits(code_type* b, int bits) { switch (bits) { case 1: return PhaseShiftKeying<2, cmplx, code_type>::map(b); case 2: return PhaseShiftKeying<4, cmplx, code_type>::map(b); case 3: return PhaseShiftKeying<8, cmplx, code_type>::map(b); case 4: return QuadratureAmplitudeModulation<16, cmplx, code_type>::map(b); case 6: return QuadratureAmplitudeModulation<64, cmplx, code_type>::map(b); case 8: return QuadratureAmplitudeModulation<256, cmplx, code_type>::map(b); case 10: return QuadratureAmplitudeModulation<1024, cmplx, code_type>::map(b); case 12: return QuadratureAmplitudeModulation<4096, cmplx, code_type>::map(b); } return 0; } void shuffle(code_type* dest, const code_type* src, int order) { if (order == 8) { CODE::XorShiftMask seq; dest[0] = src[0]; for (int i = 1; i < 256; ++i) dest[i] = src[seq()]; } else if (order == 11) { CODE::XorShiftMask seq; dest[0] = src[0]; for (int i = 1; i < 2048; ++i) dest[i] = src[seq()]; } else if (order == 12) { CODE::XorShiftMask seq; dest[0] = src[0]; for (int i = 1; i < 4096; ++i) dest[i] = src[seq()]; } else if (order == 13) { CODE::XorShiftMask seq; dest[0] = src[0]; for (int i = 1; i < 8192; ++i) dest[i] = src[seq()]; } else if (order == 14) { CODE::XorShiftMask seq; dest[0] = src[0]; for (int i = 1; i < 16384; ++i) dest[i] = src[seq()]; } else if (order == 15) { CODE::XorShiftMask seq; dest[0] = src[0]; for (int i = 1; i < 32768; ++i) dest[i] = src[seq()]; } else if (order == 16) { CODE::XorShiftMask seq; dest[0] = src[0]; for (int i = 1; i < 65536; ++i) dest[i] = src[seq()]; } } void guard_interval_weights() { for (int i = 0; i < guard_len / 4; ++i) weight[i] = 0; for (int i = guard_len / 4; i < guard_len / 4 + guard_len / 2; ++i) { value x = value(i - guard_len / 4) / value(guard_len / 2 - 1); weight[i] = value(0.5) * (value(1) - std::cos(DSP::Const::Pi() * x)); } for (int i = guard_len / 4 + guard_len / 2; i < guard_len; ++i) weight[i] = 1; } void clipping_and_filtering(value scale) { for (int i = 0; i < symbol_len; ++i) { value pwr = norm(tdom[i]); if (pwr > value(1)) tdom[i] /= sqrt(pwr); } fwd(fdom, tdom); for (int i = 0; i < symbol_len; ++i) { int j = bin(i + tone_off); if (i >= tone_count) fdom[j] = 0; else fdom[j] *= 1 / (scale * symbol_len); } bwd(tdom, fdom); for (int i = 0; i < symbol_len; ++i) tdom[i] *= scale; auto clamp = [](value v) { return v < value(-1) ? value(-1) : v > value(1) ? value(1) : v; }; for (int i = 0; i < symbol_len; ++i) tdom[i] = cmplx(clamp(tdom[i].real()), clamp(tdom[i].imag())); } void symbol(BufferWritePCM* pcm, int symbol_number) { value scale = value(0.5) / std::sqrt(value(tone_count)); if (symbol_number < 0) { for (int i = 0; i < symbol_len; ++i) fdom[i] = 0; for (int i = 0; i < tone_count; ++i) fdom[bin(i + tone_off)] = tone[i]; bwd(tdom, fdom); for (int i = 0; i < symbol_len; ++i) tdom[i] *= scale; } else { value best_papr = 1000; for (int seed_value = 0; seed_value < 128; ++seed_value) { for (int i = 0; i < tone_count; ++i) temp[i] = tone[i]; hadamard_encoder(seed, seed_value); for (int i = 0; i < seed_tones; ++i) temp[i * block_length + seed_off] *= seed[i]; if (seed_value) { CODE::MLS seq(mls2_poly, seed_value); for (int i = 0; i < tone_count; ++i) if (i % block_length != seed_off) temp[i] *= nrz(seq()); } for (int i = 0; i < symbol_len; ++i) fdom[i] = 0; for (int i = 0; i < tone_count; ++i) fdom[bin(i + tone_off)] = temp[i]; bwd(test, fdom); for (int i = 0; i < symbol_len; ++i) test[i] *= scale; value peak = 0, mean = 0; for (int i = 0; i < symbol_len; ++i) { value power(norm(test[i])); peak = std::max(peak, power); mean += power; } mean /= symbol_len; value test_papr(peak / mean); if (test_papr < best_papr) { best_papr = test_papr; papr[symbol_number] = test_papr; for (int i = 0; i < symbol_len; ++i) tdom[i] = test[i]; if (test_papr < 5) break; } } } clipping_and_filtering(scale); if (symbol_number != -1) { for (int i = 0; i < guard_len; ++i) guard[i] = DSP::lerp(guard[i], tdom[i + symbol_len - guard_len], weight[i]); pcm->write(reinterpret_cast(guard), guard_len, 2); } for (int i = 0; i < guard_len; ++i) guard[i] = tdom[i]; pcm->write(reinterpret_cast(tdom), symbol_len, 2); } void meta_data(uint64_t md) { for (int i = 0; i < 56; ++i) mesg[i] = nrz((md >> i) & 1); crc0.reset(); crc0(md << 8); for (int i = 0; i < 16; ++i) mesg[i + 56] = nrz((crc0() >> i) & 1); polar_encoder(code, mesg, frozen_256_72, 8); shuffle(meta, code, 8); } }; // Decoder template class ModemDecoder : public Common { public: typedef int16_t code_type; typedef SIMD mesg_type; typedef DSP::Const Const; static const int guard_len = rate / 300; static const int symbol_len = guard_len * 40; static const int filter_len = 129; static const int extended_len = symbol_len + guard_len; static const int buffer_len = 5 * extended_len; static const int search_pos = extended_len; static const int tone_off_const = -tone_count / 2; using FrameCallback = std::function; // Constellation callback - called after each symbol is demodulated // Parameters: pointer to demodulated symbols, count, modulation bits std::function constellation_callback; ModemDecoder() { // init fdom_mls before correlator uses it init_mls0_seq(); correlator_ptr = new SchmidlCox(fdom_mls); blockdc.samples(filter_len); } ~ModemDecoder() { delete correlator_ptr; delete seq1_ptr; } void process(const value* samples, size_t count, FrameCallback callback) { for (size_t i = 0; i < count; ++i) { process_sample(samples[i], callback); } } // Reset decoder state void reset() { state_ = State::SEARCHING; sample_count_ = 0; symbol_index_ = 0; samples_needed_ = 0; k_ = 0; } // Get average SNR from last successful decode value get_last_snr() const { return last_avg_snr_; } // Get current modulation bits int get_mod_bits() const { return mod_bits; } // Get last per-frame BER value get_last_ber() const { return last_ber_; } // Get smoothed BER via EMA value get_ber_ema() const { return ber_ema_; } void reset_ber() { last_ber_ = -1; ber_ema_ = -1; } // decode statistics int stats_sync_count = 0; // corelator int stats_preamble_errors = 0; // preamble decoding failed int stats_symbol_errors = 0; // seed damage int stats_crc_errors = 0; // polar CRC failed private: enum class State { SEARCHING, // looking for preamble COLLECTING_SYMBOLS, // Collecting data symbols }; // Arrays used by correlator cmplx fdom_mls[symbol_len]; cmplx fdom[symbol_len], tdom[symbol_len]; DSP::FastFourierTransform fwd; DSP::BlockDC blockdc; DSP::Hilbert hilbert; DSP::BipBuffer input_hist; DSP::TheilSenEstimator tse; SchmidlCox* correlator_ptr = nullptr; CODE::HadamardDecoder<7> hadamard_decoder; CODE::PolarListDecoder polar_decoder; CODE::PolarEncoder ber_encoder; int8_t ber_mesg[bits_max], ber_code[bits_max]; DSP::Phasor osc; mesg_type mesg[bits_max]; code_type code[bits_max], perm[bits_max]; cmplx demod[tone_count], chan[tone_count], tone[tone_count]; cmplx saved_demod[symbols_max * tone_count]; int saved_seed_off[symbols_max]; int fwd_perm_table[bits_max]; value index[tone_count], phase[tone_count]; value snr[symbols_max]; value cfo_rad; int symbol_pos; value last_avg_snr_ = 0; value last_ber_ = -1; value ber_ema_ = -1; State state_ = State::SEARCHING; size_t sample_count_ = 0; int symbol_index_ = 0; int samples_needed_ = 0; int k_ = 0; const cmplx* buf_ = nullptr; CODE::MLS* seq1_ptr = nullptr; static int bin(int carrier) { return (carrier + symbol_len) % symbol_len; } static value nrz(bool bit) { return 1 - 2 * bit; } static cmplx demod_or_erase(cmplx curr, cmplx prev) { if (norm(prev) > 0) { cmplx d = curr / prev; if (norm(d) < 4) return d; } return 0; } void init_mls0_seq() { CODE::MLS seq0(mls0_poly, mls0_seed); value cur = 0, prv = 0; for (int i = 0; i < tone_count; ++i, prv = cur) fdom_mls[bin(i + tone_off_const)] = prv * (cur = nrz(seq0())); } cmplx map_bits(code_type* b, int bits) { switch (bits) { case 1: return PhaseShiftKeying<2, cmplx, code_type>::map(b); case 2: return PhaseShiftKeying<4, cmplx, code_type>::map(b); case 3: return PhaseShiftKeying<8, cmplx, code_type>::map(b); case 4: return QuadratureAmplitudeModulation<16, cmplx, code_type>::map(b); case 6: return QuadratureAmplitudeModulation<64, cmplx, code_type>::map(b); case 8: return QuadratureAmplitudeModulation<256, cmplx, code_type>::map(b); case 10: return QuadratureAmplitudeModulation<1024, cmplx, code_type>::map(b); case 12: return QuadratureAmplitudeModulation<4096, cmplx, code_type>::map(b); } return 0; } void demap_soft(code_type* b, cmplx c, value precision, int bits) { switch (bits) { case 1: return PhaseShiftKeying<2, cmplx, code_type>::soft(b, c, precision); case 2: return PhaseShiftKeying<4, cmplx, code_type>::soft(b, c, precision); case 3: return PhaseShiftKeying<8, cmplx, code_type>::soft(b, c, precision); case 4: return QuadratureAmplitudeModulation<16, cmplx, code_type>::soft(b, c, precision); case 6: return QuadratureAmplitudeModulation<64, cmplx, code_type>::soft(b, c, precision); case 8: return QuadratureAmplitudeModulation<256, cmplx, code_type>::soft(b, c, precision); case 10: return QuadratureAmplitudeModulation<1024, cmplx, code_type>::soft(b, c, precision); case 12: return QuadratureAmplitudeModulation<4096, cmplx, code_type>::soft(b, c, precision); } } void demap_hard(code_type* b, cmplx c, int bits) { switch (bits) { case 1: return PhaseShiftKeying<2, cmplx, code_type>::hard(b, c); case 2: return PhaseShiftKeying<4, cmplx, code_type>::hard(b, c); case 3: return PhaseShiftKeying<8, cmplx, code_type>::hard(b, c); case 4: return QuadratureAmplitudeModulation<16, cmplx, code_type>::hard(b, c); case 6: return QuadratureAmplitudeModulation<64, cmplx, code_type>::hard(b, c); case 8: return QuadratureAmplitudeModulation<256, cmplx, code_type>::hard(b, c); case 10: return QuadratureAmplitudeModulation<1024, cmplx, code_type>::hard(b, c); case 12: return QuadratureAmplitudeModulation<4096, cmplx, code_type>::hard(b, c); } } void shuffle(code_type* dest, const code_type* src, int order) { if (order == 8) { CODE::XorShiftMask seq; dest[0] = src[0]; for (int i = 1; i < 256; ++i) dest[seq()] = src[i]; } else if (order == 11) { CODE::XorShiftMask seq; dest[0] = src[0]; for (int i = 1; i < 2048; ++i) dest[seq()] = src[i]; } else if (order == 12) { CODE::XorShiftMask seq; dest[0] = src[0]; for (int i = 1; i < 4096; ++i) dest[seq()] = src[i]; } else if (order == 13) { CODE::XorShiftMask seq; dest[0] = src[0]; for (int i = 1; i < 8192; ++i) dest[seq()] = src[i]; } else if (order == 14) { CODE::XorShiftMask seq; dest[0] = src[0]; for (int i = 1; i < 16384; ++i) dest[seq()] = src[i]; } else if (order == 15) { CODE::XorShiftMask seq; dest[0] = src[0]; for (int i = 1; i < 32768; ++i) dest[seq()] = src[i]; } else if (order == 16) { CODE::XorShiftMask seq; dest[0] = src[0]; for (int i = 1; i < 65536; ++i) dest[seq()] = src[i]; } } static void base40_decoder(char* str, int64_t val, int len) { for (int i = len - 1; i >= 0; --i, val /= 40) str[i] = " /0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[val % 40]; } int64_t meta_data() { shuffle(code, perm, 8); polar_decoder(nullptr, mesg, code, frozen_256_72, 8); int best = -1; for (int k = 0; k < mesg_type::SIZE; ++k) { crc0.reset(); for (int i = 0; i < 72; ++i) crc0(mesg[i].v[k] < 0); if (crc0() == 0) { best = k; break; } } if (best < 0) return -1; uint64_t md = 0; for (int i = 0; i < 56; ++i) md |= uint64_t(mesg[i].v[best] < 0) << i; return md; } void process_sample(value sample, FrameCallback callback) { // Convert to complex via Hilbert transform cmplx tmp = hilbert(blockdc(sample)); buf_ = input_hist(tmp); ++sample_count_; switch (state_) { case State::SEARCHING: if ((*correlator_ptr)(buf_)) { // Sync found ++stats_sync_count; symbol_pos = correlator_ptr->symbol_pos; cfo_rad = correlator_ptr->cfo_rad; std::cerr << "Decoder: Sync found at sample " << sample_count_ << std::endl; std::cerr << "Decoder: CFO = " << cfo_rad * (rate / Const::TwoPi()) << " Hz" << std::endl; // Initialize seq1 for the whole frame delete seq1_ptr; seq1_ptr = new CODE::MLS(mls1_poly); // Process preamble and start collecting symbols if (process_preamble()) { state_ = State::COLLECTING_SYMBOLS; symbol_index_ = 1; // Symbol 0 (meta) already processed // Need to advance past preamble: symbol_pos + symbol_len + extended_len // Plus extended_len for the first data symbol samples_needed_ = symbol_pos + symbol_len + 2 * extended_len; } else { ++stats_preamble_errors; reset(); } } break; case State::COLLECTING_SYMBOLS: // Keep feeding correlator to maintain buffer (*correlator_ptr)(buf_); samples_needed_--; if (samples_needed_ <= 0) { // Process this symbol if (!process_symbol(symbol_index_)) { // Error, go back to searching ++stats_symbol_errors; reset(); break; } symbol_index_++; if (symbol_index_ > symbol_count) { // All symbols collected decode_frame(callback); reset(); } else { samples_needed_ = extended_len; } } break; } } bool process_preamble() { // Process Schmidl-Cox preamble symbols osc.omega(-cfo_rad); // First preamble symbol for (int i = 0; i < symbol_len; ++i) tdom[i] = buf_[i + symbol_pos] * osc(); fwd(fdom, tdom); for (int i = 0; i < tone_count; ++i) tone[i] = fdom[bin(i + tone_off_const)]; // Second preamble symbol for (int i = 0; i < symbol_len; ++i) tdom[i] = buf_[i + symbol_pos + symbol_len] * osc(); for (int i = 0; i < guard_len; ++i) osc(); fwd(fdom, tdom); for (int i = 0; i < tone_count; ++i) chan[i] = fdom[bin(i + tone_off_const)]; // Estimate SFO for (int i = 0; i < tone_count; ++i) { index[i] = tone_off_const + i; phase[i] = arg(demod_or_erase(chan[i], tone[i])); } tse.compute(index, phase, tone_count); std::cerr << "Decoder: SFO = " << -1000000 * tse.slope() / Const::TwoPi() << " ppm" << std::endl; // Correct channel estimate for (int i = 0; i < tone_count; ++i) tone[i] *= DSP::polar(1, tse(i + tone_off_const)); for (int i = 0; i < tone_count; ++i) chan[i] = DSP::lerp(chan[i], tone[i], value(0.5)); // Remove preamble sequence CODE::MLS seq0(mls0_poly, mls0_seed); for (int i = 0; i < tone_count; ++i) chan[i] *= nrz(seq0()); // Process meta symbol (symbol 0) for (int i = 0; i < symbol_len; ++i) tdom[i] = buf_[i + symbol_pos + symbol_len + extended_len] * osc(); for (int i = 0; i < guard_len; ++i) osc(); fwd(fdom, tdom); // Decode meta symbol seed_off = first_seed; auto clamp = [](int v) { return v < -127 ? -127 : v > 127 ? 127 : v; }; for (int i = 0; i < tone_count; ++i) tone[i] = fdom[bin(i + tone_off_const)]; for (int i = seed_off; i < tone_count; i += block_length) tone[i] *= nrz((*seq1_ptr)()); for (int i = 0; i < tone_count; ++i) demod[i] = demod_or_erase(tone[i], chan[i]); // Decode seed for meta symbol for (int i = 0; i < seed_tones; ++i) seed[i] = clamp(std::nearbyint(127 * demod[i * block_length + seed_off].real())); int seed_value = hadamard_decoder(seed); if (seed_value < 0) { std::cerr << "Decoder: Seed value damaged in meta" << std::endl; return false; } hadamard_encoder(seed, seed_value); for (int i = 0; i < seed_tones; ++i) { tone[block_length * i + seed_off] *= seed[i]; demod[block_length * i + seed_off] *= seed[i]; } // Phase correction for (int i = 0; i < seed_tones; ++i) { index[i] = tone_off_const + block_length * i + seed_off; phase[i] = arg(demod[block_length * i + seed_off]); } tse.compute(index, phase, seed_tones); for (int i = 0; i < tone_count; ++i) demod[i] *= DSP::polar(1, -tse(i + tone_off_const)); for (int i = 0; i < tone_count; ++i) chan[i] *= DSP::polar(1, tse(i + tone_off_const)); if (seed_value) { CODE::MLS seq(mls2_poly, seed_value); for (int i = 0; i < tone_count; ++i) if (i % block_length != seed_off) demod[i] *= nrz(seq()); } // SNR estimation and demapping for meta symbol (mod_bits = 1 for meta) value sp = 0, np = 0; for (int i = 0, l = 0; i < tone_count; ++i) { cmplx hard(1, 0); if (i % block_length != seed_off) { demap_hard(perm + l, demod[i], 1); hard = map_bits(perm + l, 1); l += 1; } cmplx error = demod[i] - hard; sp += norm(hard); np += norm(error); } value precision = sp / np; precision = std::min(precision, value(1023)); // std::cerr << "Decoder: Meta symbol SNR = " << 10 * std::log10(precision) << " dB" << std::endl; // Soft demap meta symbol int k = 0; for (int i = 0; i < tone_count; ++i) { if (i % block_length != seed_off) { demap_soft(perm + k, demod[i], precision, 1); k += 1; } } // Update channel for meta symbol pilots for (int i = seed_off; i < tone_count; i += block_length) chan[i] = DSP::lerp(chan[i], tone[i], value(0.5)); // Decode meta data int64_t meta_info = meta_data(); if (meta_info < 0) { std::cerr << "Decoder: Preamble decoding error" << std::endl; return false; } int64_t call = meta_info >> 8; if (call == 0 || call >= 262144000000000L) { std::cerr << "Decoder: Invalid call sign" << std::endl; return false; } char call_sign[10]; base40_decoder(call_sign, call, 9); call_sign[9] = 0; std::cerr << "Decoder: Call sign: " << call_sign << std::endl; int mode = meta_info & 255; if (!setup(mode)) { std::cerr << "Decoder: Invalid mode" << std::endl; return false; } std::cerr << "Decoder: Mode " << oper_mode << ", " << symbol_count << " data symbols, mod_bits=" << mod_bits << ", code_order=" << code_order << ", data_bytes=" << data_bytes << std::endl; // Reset for data collection k_ = 0; snr[0] = 100; return true; } void build_fwd_perm(int* table, int order) { int len = 1 << order; table[0] = 0; if (order == 11) { CODE::XorShiftMask seq; for (int i = 1; i < len; ++i) table[i] = seq(); } else if (order == 12) { CODE::XorShiftMask seq; for (int i = 1; i < len; ++i) table[i] = seq(); } else if (order == 13) { CODE::XorShiftMask seq; for (int i = 1; i < len; ++i) table[i] = seq(); } else if (order == 14) { CODE::XorShiftMask seq; for (int i = 1; i < len; ++i) table[i] = seq(); } else if (order == 15) { CODE::XorShiftMask seq; for (int i = 1; i < len; ++i) table[i] = seq(); } else if (order == 16) { CODE::XorShiftMask seq; for (int i = 1; i < len; ++i) table[i] = seq(); } } bool process_symbol(int j) { seed_off = (block_skew * j + first_seed) % block_length; auto clamp = [](int v) { return v < -127 ? -127 : v > 127 ? 127 : v; }; // FFT the current symbol for (int i = 0; i < symbol_len; ++i) tdom[i] = buf_[i] * osc(); for (int i = 0; i < guard_len; ++i) osc(); fwd(fdom, tdom); for (int i = 0; i < tone_count; ++i) tone[i] = fdom[bin(i + tone_off_const)]; // Remove pilot sequence for (int i = seed_off; i < tone_count; i += block_length) tone[i] *= nrz((*seq1_ptr)()); for (int i = 0; i < tone_count; ++i) demod[i] = demod_or_erase(tone[i], chan[i]); // Decode seed for (int i = 0; i < seed_tones; ++i) seed[i] = clamp(std::nearbyint(127 * demod[i * block_length + seed_off].real())); int seed_value = hadamard_decoder(seed); if (seed_value < 0) { std::cerr << "Decoder: Seed damaged at symbol " << j << std::endl; return false; } hadamard_encoder(seed, seed_value); for (int i = 0; i < seed_tones; ++i) { tone[block_length * i + seed_off] *= seed[i]; demod[block_length * i + seed_off] *= seed[i]; } // Phase correction for (int i = 0; i < seed_tones; ++i) { index[i] = tone_off_const + block_length * i + seed_off; phase[i] = arg(demod[block_length * i + seed_off]); } tse.compute(index, phase, seed_tones); for (int i = 0; i < tone_count; ++i) demod[i] *= DSP::polar(1, -tse(i + tone_off_const)); for (int i = 0; i < tone_count; ++i) chan[i] *= DSP::polar(1, tse(i + tone_off_const)); if (seed_value) { CODE::MLS seq(mls2_poly, seed_value); for (int i = 0; i < tone_count; ++i) if (i % block_length != seed_off) demod[i] *= nrz(seq()); } // Save demod for post-decode corrected SNR std::memcpy(&saved_demod[j * tone_count], demod, tone_count * sizeof(cmplx)); saved_seed_off[j] = seed_off; // Notify constellation callback with fully-corrected demodulated symbols if (constellation_callback) { constellation_callback(demod, tone_count, mod_bits); } // SNR estimation from data tones only excluding seed/pilot tones value sp = 0, np = 0; for (int i = 0, l = k_; i < tone_count; ++i) { if (i % block_length != seed_off) { int bits = mod_bits; if (mod_bits == 3 && l % 32 == 30) bits = 2; if (mod_bits == 6 && l % 64 == 60) bits = 4; if (mod_bits == 10 && l % 128 == 120) bits = 8; if (mod_bits == 12 && l % 128 == 120) bits = 8; demap_hard(perm + l, demod[i], bits); cmplx hard = map_bits(perm + l, bits); cmplx error = demod[i] - hard; sp += norm(hard); np += norm(error); l += bits; } } value precision = sp / np; snr[j] = precision; precision = std::min(precision, value(1023)); std::cerr << "Decoder: Symbol " << j << " SNR = " << 10 * std::log10(snr[j]) << " dB, k=" << k_ << std::endl; for (int i = 0; i < tone_count; ++i) { if (i % block_length != seed_off) { int bits = mod_bits; if (mod_bits == 3 && k_ % 32 == 30) bits = 2; if (mod_bits == 6 && k_ % 64 == 60) bits = 4; if (mod_bits == 10 && k_ % 128 == 120) bits = 8; if (mod_bits == 12 && k_ % 128 == 120) bits = 8; demap_soft(perm + k_, demod[i], precision, bits); k_ += bits; } } for (int i = seed_off; i < tone_count; i += block_length) chan[i] = DSP::lerp(chan[i], tone[i], value(0.5)); return true; } void decode_frame(FrameCallback callback) { std::cerr << "Decoder: Decoding frame, k_=" << k_ << " bits collected" << std::endl; std::cerr << "Decoder: Expected code_order=" << code_order << " (code length=" << (1 << code_order) << ")" << std::endl; int crc_bits = data_bits + 32; shuffle(code, perm, code_order); polar_decoder(nullptr, mesg, code, frozen_bits, code_order); int best = -1; for (int k = 0; k < mesg_type::SIZE; ++k) { crc1.reset(); for (int i = 0; i < crc_bits; ++i) crc1(mesg[i].v[k] < 0); if (crc1() == 0) { best = k; break; } } if (best < 0) { std::cerr << "Decoder: CRC failed" << std::endl; ++stats_crc_errors; return; } // Fallback: average per-symbol SNR value total_snr = 0; int snr_count = 0; for (int i = 1; i < symbol_index_; ++i) { if (snr[i] > 0) { total_snr += snr[i]; snr_count++; } } if (snr_count > 0) { last_avg_snr_ = 10 * std::log10(total_snr / snr_count); } // Extract data for (int i = 0; i < data_bits; ++i) CODE::set_le_bit(data, i, mesg[i].v[best] < 0); // Descramble CODE::Xorshift32 scrambler; for (int i = 0; i < data_bytes; ++i) data[i] ^= scrambler(); // BER: re-encode decoded message and compare against received hard decisions int code_len = 1 << code_order; for (int i = 0; i < data_bits + 32; ++i) ber_mesg[i] = (mesg[i].v[best] < 0) ? -1 : 1; ber_encoder(ber_code, ber_mesg, frozen_bits, code_order); int bit_errors = 0; for (int i = 0; i < code_len; ++i) { if ((code[i] < 0) != (ber_code[i] < 0)) bit_errors++; } last_ber_ = value(bit_errors) / value(code_len); if (ber_ema_ < 0) ber_ema_ = last_ber_; else ber_ema_ = value(0.3) * last_ber_ + value(0.7) * ber_ema_; // use known-correct codeword as referenc build_fwd_perm(fwd_perm_table, code_order); value corr_sp = 0, corr_np = 0; int bk = 0; for (int sj = 1; sj <= symbol_count; ++sj) { int soff = saved_seed_off[sj]; for (int i = 0; i < tone_count; ++i) { if (i % block_length != soff) { int bits = mod_bits; if (mod_bits == 3 && bk % 32 == 30) bits = 2; if (mod_bits == 6 && bk % 64 == 60) bits = 4; if (mod_bits == 10 && bk % 128 == 120) bits = 8; if (mod_bits == 12 && bk % 128 == 120) bits = 8; code_type ideal_bits[mod_max]; for (int b = 0; b < bits; ++b) ideal_bits[b] = ber_code[fwd_perm_table[bk + b]]; cmplx ideal = map_bits(ideal_bits, bits); cmplx error = saved_demod[sj * tone_count + i] - ideal; corr_sp += norm(ideal); corr_np += norm(error); bk += bits; } } } if (corr_np > 0) last_avg_snr_ = 10 * std::log10(corr_sp / corr_np); std::cerr << "Decoder: Frame decoded " << data_bytes << " bytes, SNR=" << last_avg_snr_ << " dB, BER=" << (last_ber_ * 100) << "%" << std::endl; callback(data, data_bytes); } }; using Encoder48k = ModemEncoder, 48000>; using Decoder48k = ModemDecoder, 48000>;