From 2dbaa4672bf5f3d57b89d94135cbaec7dbce038a Mon Sep 17 00:00:00 2001 From: Karlis Goba Date: Thu, 5 Aug 2021 13:56:35 +0300 Subject: [PATCH] First step of moving to C --- decode_ft8.cpp | 264 ++++++++++------- ft8/constants.cpp | 734 +++++++++++++++++++++++----------------------- ft8/constants.h | 85 +++--- ft8/decode.cpp | 166 ++++++----- ft8/decode.h | 45 ++- ft8/encode.cpp | 141 +++++---- ft8/encode.h | 44 ++- ft8/ldpc.cpp | 222 ++++++++------ ft8/ldpc.h | 25 +- ft8/pack.cpp | 208 +++++++------ ft8/pack.h | 15 +- ft8/text.cpp | 203 ++++++++----- ft8/text.h | 54 ++-- ft8/unpack.cpp | 270 +++++++++-------- ft8/unpack.h | 19 +- gen_ft8.cpp | 100 ++++--- test.cpp | 53 ++-- 17 files changed, 1457 insertions(+), 1191 deletions(-) diff --git a/decode_ft8.cpp b/decode_ft8.cpp index 1f4bc59..929eb14 100644 --- a/decode_ft8.cpp +++ b/decode_ft8.cpp @@ -13,9 +13,9 @@ #include "common/debug.h" #include "fft/kiss_fftr.h" -#define LOG_LEVEL LOG_INFO +#define LOG_LEVEL LOG_INFO -const int kMin_score = 40; // Minimum sync score threshold for candidates +const int kMin_score = 40; // Minimum sync score threshold for candidates const int kMax_candidates = 120; const int kLDPC_iterations = 25; @@ -25,29 +25,30 @@ const int kMax_message_length = 25; const int kFreq_osr = 2; const int kTime_osr = 2; -const float kFSK_dev = 6.25f; // tone deviation in Hz and symbol rate +const float kFSK_dev = 6.25f; // tone deviation in Hz and symbol rate -void usage() { +void usage() +{ fprintf(stderr, "Decode a 15-second WAV file.\n"); } - -float hann_i(int i, int N) { +float hann_i(int i, int N) +{ float x = sinf((float)M_PI * i / (N - 1)); - return x*x; + return x * x; } - -float hamming_i(int i, int N) { +float hamming_i(int i, int N) +{ const float a0 = (float)25 / 46; const float a1 = 1 - a0; float x1 = cosf(2 * (float)M_PI * i / (N - 1)); - return a0 - a1*x1; + return a0 - a1 * x1; } - -float blackman_i(int i, int N) { +float blackman_i(int i, int N) +{ const float alpha = 0.16f; // or 2860/18608 const float a0 = (1 - alpha) / 2; const float a1 = 1.0f / 2; @@ -55,28 +56,31 @@ float blackman_i(int i, int N) { float x1 = cosf(2 * (float)M_PI * i / (N - 1)); //float x2 = cosf(4 * (float)M_PI * i / (N - 1)); - float x2 = 2*x1*x1 - 1; // Use double angle formula + float x2 = 2 * x1 * x1 - 1; // Use double angle formula - return a0 - a1*x1 + a2*x2; + return a0 - a1 * x1 + a2 * x2; } -static float max2(float a, float b) { +static float max2(float a, float b) +{ return (a >= b) ? a : b; } // Compute FFT magnitudes (log power) for each timeslot in the signal -void extract_power(const float signal[], ft8::MagArray * power) { +void extract_power(const float signal[], MagArray *power) +{ const int block_size = 2 * power->num_bins; // Average over 2 bins per FSK tone const int subblock_size = block_size / power->time_osr; const int nfft = block_size * power->freq_osr; // We take FFT of two blocks, advancing by one const float fft_norm = 2.0f / nfft; - float window[nfft]; - for (int i = 0; i < nfft; ++i) { + float window[nfft]; + for (int i = 0; i < nfft; ++i) + { window[i] = hann_i(i, nfft); } - size_t fft_work_size; + size_t fft_work_size; kiss_fftr_alloc(nfft, 0, 0, &fft_work_size); LOG(LOG_INFO, "Block size = %d\n", block_size); @@ -84,34 +88,40 @@ void extract_power(const float signal[], ft8::MagArray * power) { LOG(LOG_INFO, "N_FFT = %d\n", nfft); LOG(LOG_INFO, "FFT work area = %lu\n", fft_work_size); - void *fft_work = malloc(fft_work_size); + void *fft_work = malloc(fft_work_size); kiss_fftr_cfg fft_cfg = kiss_fftr_alloc(nfft, 0, fft_work, &fft_work_size); int offset = 0; float max_mag = -100.0f; - for (int i = 0; i < power->num_blocks; ++i) { + for (int i = 0; i < power->num_blocks; ++i) + { // Loop over two possible time offsets (0 and block_size/2) - for (int time_sub = 0; time_sub < power->time_osr; ++time_sub) { + for (int time_sub = 0; time_sub < power->time_osr; ++time_sub) + { kiss_fft_scalar timedata[nfft]; - kiss_fft_cpx freqdata[nfft/2 + 1]; - float mag_db[nfft/2 + 1]; + kiss_fft_cpx freqdata[nfft / 2 + 1]; + float mag_db[nfft / 2 + 1]; // Extract windowed signal block - for (int j = 0; j < nfft; ++j) { + for (int j = 0; j < nfft; ++j) + { timedata[j] = window[j] * signal[(i * block_size) + (j + time_sub * subblock_size)]; } kiss_fftr(fft_cfg, timedata, freqdata); // Compute log magnitude in decibels - for (int j = 0; j < nfft/2 + 1; ++j) { + for (int j = 0; j < nfft / 2 + 1; ++j) + { float mag2 = (freqdata[j].i * freqdata[j].i + freqdata[j].r * freqdata[j].r); mag_db[j] = 10.0f * log10f(1E-10f + mag2 * fft_norm * fft_norm); } // Loop over two possible frequency bin offsets (for averaging) - for (int freq_sub = 0; freq_sub < power->freq_osr; ++freq_sub) { - for (int j = 0; j < power->num_bins; ++j) { + for (int freq_sub = 0; freq_sub < power->freq_osr; ++freq_sub) + { + for (int j = 0; j < power->num_bins; ++j) + { float db1 = mag_db[j * power->freq_osr + freq_sub]; //float db2 = mag_db[j * 2 + freq_sub + 1]; //float db = (db1 + db2) / 2; @@ -123,7 +133,8 @@ void extract_power(const float signal[], ft8::MagArray * power) { power->mag[offset] = (scaled < 0) ? 0 : ((scaled > 255) ? 255 : scaled); ++offset; - if (db > max_mag) max_mag = db; + if (db > max_mag) + max_mag = db; } } } @@ -133,36 +144,44 @@ void extract_power(const float signal[], ft8::MagArray * power) { free(fft_work); } - -void normalize_signal(float *signal, int num_samples) { +void normalize_signal(float *signal, int num_samples) +{ float max_amp = 1E-5f; - for (int i = 0; i < num_samples; ++i) { + for (int i = 0; i < num_samples; ++i) + { float amp = fabsf(signal[i]); - if (amp > max_amp) { + if (amp > max_amp) + { max_amp = amp; } } - for (int i = 0; i < num_samples; ++i) { + for (int i = 0; i < num_samples; ++i) + { signal[i] /= max_amp; - } + } } - -void print_tones(const uint8_t *code_map, const float *log174) { - for (int k = 0; k < ft8::N; k += 3) { +void print_tones(const uint8_t *code_map, const float *log174) +{ + for (int k = 0; k < FT8_N; k += 3) + { uint8_t max = 0; - if (log174[k + 0] > 0) max |= 4; - if (log174[k + 1] > 0) max |= 2; - if (log174[k + 2] > 0) max |= 1; + if (log174[k + 0] > 0) + max |= 4; + if (log174[k + 1] > 0) + max |= 2; + if (log174[k + 2] > 0) + max |= 1; LOG(LOG_DEBUG, "%d", code_map[max]); } LOG(LOG_DEBUG, "\n"); } - -int main(int argc, char **argv) { +int main(int argc, char **argv) +{ // Expect one command-line argument - if (argc < 2) { + if (argc < 2) + { usage(); return -1; } @@ -174,7 +193,8 @@ int main(int argc, char **argv) { float signal[num_samples]; int rc = load_wav(signal, num_samples, sample_rate, wav_path); - if (rc < 0) { + if (rc < 0) + { return -1; } normalize_signal(signal, num_samples); @@ -190,90 +210,112 @@ int main(int argc, char **argv) { // Compute FFT over the whole signal and store it uint8_t mag_power[num_blocks * kFreq_osr * kTime_osr * num_bins]; - ft8::MagArray power = { - .num_blocks = num_blocks, + MagArray power = { + .num_blocks = num_blocks, .num_bins = num_bins, .time_osr = kTime_osr, .freq_osr = kFreq_osr, - .mag = mag_power - }; + .mag = mag_power}; extract_power(signal, &power); // Find top candidates by Costas sync score and localize them in time and frequency - ft8::Candidate candidate_list[kMax_candidates]; - int num_candidates = ft8::find_sync(&power, ft8::kCostas_map, kMax_candidates, candidate_list, kMin_score); + Candidate candidate_list[kMax_candidates]; + int num_candidates = find_sync(&power, kCostas_map, kMax_candidates, candidate_list, kMin_score); // TODO: sort the candidates by strongest sync first? // Go over candidates and attempt to decode messages - char decoded[kMax_decoded_messages][kMax_message_length]; - int num_decoded = 0; - for (int idx = 0; idx < num_candidates; ++idx) { - ft8::Candidate &cand = candidate_list[idx]; - if (cand.score < kMin_score) continue; + char decoded[kMax_decoded_messages][kMax_message_length]; + int num_decoded = 0; + for (int idx = 0; idx < num_candidates; ++idx) + { + Candidate &cand = candidate_list[idx]; + if (cand.score < kMin_score) + continue; - float freq_hz = (cand.freq_offset + (float)cand.freq_sub / kFreq_osr) * kFSK_dev; + float freq_hz = (cand.freq_offset + (float)cand.freq_sub / kFreq_osr) * kFSK_dev; float time_sec = (cand.time_offset + (float)cand.time_sub / kTime_osr) / kFSK_dev; - float log174[ft8::N]; - ft8::extract_likelihood(&power, cand, ft8::kGray_map, log174); + float log174[FT8_N]; + extract_likelihood(&power, cand, kGray_map, log174); - // bp_decode() produces better decodes, uses way less memory - uint8_t plain[ft8::N]; - int n_errors = 0; - ft8::bp_decode(log174, kLDPC_iterations, plain, &n_errors); - //ft8::ldpc_decode(log174, kLDPC_iterations, plain, &n_errors); + for (int k = 100; k < 174; ++k) + { + // bp_decode() produces better decodes, uses way less memory + uint8_t plain[FT8_N]; + float log174_zeroed[FT8_N]; + int n_errors = 0; - if (n_errors > 0) { - LOG(LOG_DEBUG, "ldpc_decode() = %d (%.0f Hz)\n", n_errors, freq_hz); - continue; - } - - int sum_plain = 0; - for (int i = 0; i < ft8::N; ++i) { - sum_plain += plain[i]; - } - if (sum_plain == 0) { - // All zeroes message - continue; - } - - // Extract payload + CRC (first ft8::K bits) - uint8_t a91[ft8::K_BYTES]; - ft8::pack_bits(plain, ft8::K, a91); - - // Extract CRC and check it - uint16_t chksum = ((a91[9] & 0x07) << 11) | (a91[10] << 3) | (a91[11] >> 5); - a91[9] &= 0xF8; - a91[10] = 0; - a91[11] = 0; - uint16_t chksum2 = ft8::crc(a91, 96 - 14); - if (chksum != chksum2) { - LOG(LOG_DEBUG, "Checksum: message = %04x, CRC = %04x\n", chksum, chksum2); - continue; - } - - char message[kMax_message_length]; - if (ft8::unpack77(a91, message) < 0) { - continue; - } - - // Check for duplicate messages (TODO: use hashing) - bool found = false; - for (int i = 0; i < num_decoded; ++i) { - if (0 == strcmp(decoded[i], message)) { - found = true; - break; + for (int m = 0; m < 174; ++m) + { + if (m < k) + log174_zeroed[m] = log174[m]; + else + log174_zeroed[m] = 0; } - } + bp_decode(log174_zeroed, kLDPC_iterations, plain, &n_errors); + // ldpc_decode(log174, kLDPC_iterations, plain, &n_errors); - if (!found && num_decoded < kMax_decoded_messages) { - strcpy(decoded[num_decoded], message); - ++num_decoded; + if (n_errors > 0) + { + LOG(LOG_DEBUG, "ldpc_decode() = %d (%.0f Hz)\n", n_errors, freq_hz); + continue; + } - // Fake WSJT-X-like output for now - int snr = 0; // TODO: compute SNR - printf("000000 %3d %4.1f %4d ~ %s\n", cand.score, time_sec, (int)(freq_hz + 0.5f), message); + int sum_plain = 0; + for (int i = 0; i < FT8_N; ++i) + { + sum_plain += plain[i]; + } + if (sum_plain == 0) + { + // All zeroes message + continue; + } + + // Extract payload + CRC (first FT8_K bits) + uint8_t a91[FT8_K_BYTES]; + pack_bits(plain, FT8_K, a91); + + // Extract CRC and check it + uint16_t chksum = ((a91[9] & 0x07) << 11) | (a91[10] << 3) | (a91[11] >> 5); + a91[9] &= 0xF8; + a91[10] = 0; + a91[11] = 0; + uint16_t chksum2 = ft8_crc(a91, 96 - 14); + if (chksum != chksum2) + { + LOG(LOG_DEBUG, "Checksum: message = %04x, CRC = %04x\n", chksum, chksum2); + continue; + } + + char message[kMax_message_length]; + if (unpack77(a91, message) < 0) + { + continue; + } + + // Check for duplicate messages (TODO: use hashing) + bool found = false; + for (int i = 0; i < num_decoded; ++i) + { + if (0 == strcmp(decoded[i], message)) + { + found = true; + break; + } + } + + if (!found && num_decoded < kMax_decoded_messages) + { + strcpy(decoded[num_decoded], message); + ++num_decoded; + + // Fake WSJT-X-like output for now + int snr = 0; // TODO: compute SNR + printf("000000 %3d [%2d] %4.1f %4d ~ %s\n", cand.score, k, time_sec, (int)(freq_hz + 0.5f), message); + continue; + } } } LOG(LOG_INFO, "Decoded %d messages\n", num_decoded); diff --git a/ft8/constants.cpp b/ft8/constants.cpp index 562c7f8..94bf494 100644 --- a/ft8/constants.cpp +++ b/ft8/constants.cpp @@ -1,114 +1,109 @@ #include "constants.h" -namespace ft8 { - // Costas 7x7 tone pattern -const uint8_t kCostas_map[7] = { 3,1,4,0,6,5,2 }; +const uint8_t kCostas_map[7] = {3, 1, 4, 0, 6, 5, 2}; // Gray code map -const uint8_t kGray_map[8] = { 0,1,3,2,5,6,4,7 }; +const uint8_t kGray_map[8] = {0, 1, 3, 2, 5, 6, 4, 7}; // Parity generator matrix for (174,91) LDPC code, stored in bitpacked format (MSB first) -const uint8_t kGenerator[M][K_BYTES] = { - { 0x83, 0x29, 0xce, 0x11, 0xbf, 0x31, 0xea, 0xf5, 0x09, 0xf2, 0x7f, 0xc0 }, - { 0x76, 0x1c, 0x26, 0x4e, 0x25, 0xc2, 0x59, 0x33, 0x54, 0x93, 0x13, 0x20 }, - { 0xdc, 0x26, 0x59, 0x02, 0xfb, 0x27, 0x7c, 0x64, 0x10, 0xa1, 0xbd, 0xc0 }, - { 0x1b, 0x3f, 0x41, 0x78, 0x58, 0xcd, 0x2d, 0xd3, 0x3e, 0xc7, 0xf6, 0x20 }, - { 0x09, 0xfd, 0xa4, 0xfe, 0xe0, 0x41, 0x95, 0xfd, 0x03, 0x47, 0x83, 0xa0 }, - { 0x07, 0x7c, 0xcc, 0xc1, 0x1b, 0x88, 0x73, 0xed, 0x5c, 0x3d, 0x48, 0xa0 }, - { 0x29, 0xb6, 0x2a, 0xfe, 0x3c, 0xa0, 0x36, 0xf4, 0xfe, 0x1a, 0x9d, 0xa0 }, - { 0x60, 0x54, 0xfa, 0xf5, 0xf3, 0x5d, 0x96, 0xd3, 0xb0, 0xc8, 0xc3, 0xe0 }, - { 0xe2, 0x07, 0x98, 0xe4, 0x31, 0x0e, 0xed, 0x27, 0x88, 0x4a, 0xe9, 0x00 }, - { 0x77, 0x5c, 0x9c, 0x08, 0xe8, 0x0e, 0x26, 0xdd, 0xae, 0x56, 0x31, 0x80 }, - { 0xb0, 0xb8, 0x11, 0x02, 0x8c, 0x2b, 0xf9, 0x97, 0x21, 0x34, 0x87, 0xc0 }, - { 0x18, 0xa0, 0xc9, 0x23, 0x1f, 0xc6, 0x0a, 0xdf, 0x5c, 0x5e, 0xa3, 0x20 }, - { 0x76, 0x47, 0x1e, 0x83, 0x02, 0xa0, 0x72, 0x1e, 0x01, 0xb1, 0x2b, 0x80 }, - { 0xff, 0xbc, 0xcb, 0x80, 0xca, 0x83, 0x41, 0xfa, 0xfb, 0x47, 0xb2, 0xe0 }, - { 0x66, 0xa7, 0x2a, 0x15, 0x8f, 0x93, 0x25, 0xa2, 0xbf, 0x67, 0x17, 0x00 }, - { 0xc4, 0x24, 0x36, 0x89, 0xfe, 0x85, 0xb1, 0xc5, 0x13, 0x63, 0xa1, 0x80 }, - { 0x0d, 0xff, 0x73, 0x94, 0x14, 0xd1, 0xa1, 0xb3, 0x4b, 0x1c, 0x27, 0x00 }, - { 0x15, 0xb4, 0x88, 0x30, 0x63, 0x6c, 0x8b, 0x99, 0x89, 0x49, 0x72, 0xe0 }, - { 0x29, 0xa8, 0x9c, 0x0d, 0x3d, 0xe8, 0x1d, 0x66, 0x54, 0x89, 0xb0, 0xe0 }, - { 0x4f, 0x12, 0x6f, 0x37, 0xfa, 0x51, 0xcb, 0xe6, 0x1b, 0xd6, 0xb9, 0x40 }, - { 0x99, 0xc4, 0x72, 0x39, 0xd0, 0xd9, 0x7d, 0x3c, 0x84, 0xe0, 0x94, 0x00 }, - { 0x19, 0x19, 0xb7, 0x51, 0x19, 0x76, 0x56, 0x21, 0xbb, 0x4f, 0x1e, 0x80 }, - { 0x09, 0xdb, 0x12, 0xd7, 0x31, 0xfa, 0xee, 0x0b, 0x86, 0xdf, 0x6b, 0x80 }, - { 0x48, 0x8f, 0xc3, 0x3d, 0xf4, 0x3f, 0xbd, 0xee, 0xa4, 0xea, 0xfb, 0x40 }, - { 0x82, 0x74, 0x23, 0xee, 0x40, 0xb6, 0x75, 0xf7, 0x56, 0xeb, 0x5f, 0xe0 }, - { 0xab, 0xe1, 0x97, 0xc4, 0x84, 0xcb, 0x74, 0x75, 0x71, 0x44, 0xa9, 0xa0 }, - { 0x2b, 0x50, 0x0e, 0x4b, 0xc0, 0xec, 0x5a, 0x6d, 0x2b, 0xdb, 0xdd, 0x00 }, - { 0xc4, 0x74, 0xaa, 0x53, 0xd7, 0x02, 0x18, 0x76, 0x16, 0x69, 0x36, 0x00 }, - { 0x8e, 0xba, 0x1a, 0x13, 0xdb, 0x33, 0x90, 0xbd, 0x67, 0x18, 0xce, 0xc0 }, - { 0x75, 0x38, 0x44, 0x67, 0x3a, 0x27, 0x78, 0x2c, 0xc4, 0x20, 0x12, 0xe0 }, - { 0x06, 0xff, 0x83, 0xa1, 0x45, 0xc3, 0x70, 0x35, 0xa5, 0xc1, 0x26, 0x80 }, - { 0x3b, 0x37, 0x41, 0x78, 0x58, 0xcc, 0x2d, 0xd3, 0x3e, 0xc3, 0xf6, 0x20 }, - { 0x9a, 0x4a, 0x5a, 0x28, 0xee, 0x17, 0xca, 0x9c, 0x32, 0x48, 0x42, 0xc0 }, - { 0xbc, 0x29, 0xf4, 0x65, 0x30, 0x9c, 0x97, 0x7e, 0x89, 0x61, 0x0a, 0x40 }, - { 0x26, 0x63, 0xae, 0x6d, 0xdf, 0x8b, 0x5c, 0xe2, 0xbb, 0x29, 0x48, 0x80 }, - { 0x46, 0xf2, 0x31, 0xef, 0xe4, 0x57, 0x03, 0x4c, 0x18, 0x14, 0x41, 0x80 }, - { 0x3f, 0xb2, 0xce, 0x85, 0xab, 0xe9, 0xb0, 0xc7, 0x2e, 0x06, 0xfb, 0xe0 }, - { 0xde, 0x87, 0x48, 0x1f, 0x28, 0x2c, 0x15, 0x39, 0x71, 0xa0, 0xa2, 0xe0 }, - { 0xfc, 0xd7, 0xcc, 0xf2, 0x3c, 0x69, 0xfa, 0x99, 0xbb, 0xa1, 0x41, 0x20 }, - { 0xf0, 0x26, 0x14, 0x47, 0xe9, 0x49, 0x0c, 0xa8, 0xe4, 0x74, 0xce, 0xc0 }, - { 0x44, 0x10, 0x11, 0x58, 0x18, 0x19, 0x6f, 0x95, 0xcd, 0xd7, 0x01, 0x20 }, - { 0x08, 0x8f, 0xc3, 0x1d, 0xf4, 0xbf, 0xbd, 0xe2, 0xa4, 0xea, 0xfb, 0x40 }, - { 0xb8, 0xfe, 0xf1, 0xb6, 0x30, 0x77, 0x29, 0xfb, 0x0a, 0x07, 0x8c, 0x00 }, - { 0x5a, 0xfe, 0xa7, 0xac, 0xcc, 0xb7, 0x7b, 0xbc, 0x9d, 0x99, 0xa9, 0x00 }, - { 0x49, 0xa7, 0x01, 0x6a, 0xc6, 0x53, 0xf6, 0x5e, 0xcd, 0xc9, 0x07, 0x60 }, - { 0x19, 0x44, 0xd0, 0x85, 0xbe, 0x4e, 0x7d, 0xa8, 0xd6, 0xcc, 0x7d, 0x00 }, - { 0x25, 0x1f, 0x62, 0xad, 0xc4, 0x03, 0x2f, 0x0e, 0xe7, 0x14, 0x00, 0x20 }, - { 0x56, 0x47, 0x1f, 0x87, 0x02, 0xa0, 0x72, 0x1e, 0x00, 0xb1, 0x2b, 0x80 }, - { 0x2b, 0x8e, 0x49, 0x23, 0xf2, 0xdd, 0x51, 0xe2, 0xd5, 0x37, 0xfa, 0x00 }, - { 0x6b, 0x55, 0x0a, 0x40, 0xa6, 0x6f, 0x47, 0x55, 0xde, 0x95, 0xc2, 0x60 }, - { 0xa1, 0x8a, 0xd2, 0x8d, 0x4e, 0x27, 0xfe, 0x92, 0xa4, 0xf6, 0xc8, 0x40 }, - { 0x10, 0xc2, 0xe5, 0x86, 0x38, 0x8c, 0xb8, 0x2a, 0x3d, 0x80, 0x75, 0x80 }, - { 0xef, 0x34, 0xa4, 0x18, 0x17, 0xee, 0x02, 0x13, 0x3d, 0xb2, 0xeb, 0x00 }, - { 0x7e, 0x9c, 0x0c, 0x54, 0x32, 0x5a, 0x9c, 0x15, 0x83, 0x6e, 0x00, 0x00 }, - { 0x36, 0x93, 0xe5, 0x72, 0xd1, 0xfd, 0xe4, 0xcd, 0xf0, 0x79, 0xe8, 0x60 }, - { 0xbf, 0xb2, 0xce, 0xc5, 0xab, 0xe1, 0xb0, 0xc7, 0x2e, 0x07, 0xfb, 0xe0 }, - { 0x7e, 0xe1, 0x82, 0x30, 0xc5, 0x83, 0xcc, 0xcc, 0x57, 0xd4, 0xb0, 0x80 }, - { 0xa0, 0x66, 0xcb, 0x2f, 0xed, 0xaf, 0xc9, 0xf5, 0x26, 0x64, 0x12, 0x60 }, - { 0xbb, 0x23, 0x72, 0x5a, 0xbc, 0x47, 0xcc, 0x5f, 0x4c, 0xc4, 0xcd, 0x20 }, - { 0xde, 0xd9, 0xdb, 0xa3, 0xbe, 0xe4, 0x0c, 0x59, 0xb5, 0x60, 0x9b, 0x40 }, - { 0xd9, 0xa7, 0x01, 0x6a, 0xc6, 0x53, 0xe6, 0xde, 0xcd, 0xc9, 0x03, 0x60 }, - { 0x9a, 0xd4, 0x6a, 0xed, 0x5f, 0x70, 0x7f, 0x28, 0x0a, 0xb5, 0xfc, 0x40 }, - { 0xe5, 0x92, 0x1c, 0x77, 0x82, 0x25, 0x87, 0x31, 0x6d, 0x7d, 0x3c, 0x20 }, - { 0x4f, 0x14, 0xda, 0x82, 0x42, 0xa8, 0xb8, 0x6d, 0xca, 0x73, 0x35, 0x20 }, - { 0x8b, 0x8b, 0x50, 0x7a, 0xd4, 0x67, 0xd4, 0x44, 0x1d, 0xf7, 0x70, 0xe0 }, - { 0x22, 0x83, 0x1c, 0x9c, 0xf1, 0x16, 0x94, 0x67, 0xad, 0x04, 0xb6, 0x80 }, - { 0x21, 0x3b, 0x83, 0x8f, 0xe2, 0xae, 0x54, 0xc3, 0x8e, 0xe7, 0x18, 0x00 }, - { 0x5d, 0x92, 0x6b, 0x6d, 0xd7, 0x1f, 0x08, 0x51, 0x81, 0xa4, 0xe1, 0x20 }, - { 0x66, 0xab, 0x79, 0xd4, 0xb2, 0x9e, 0xe6, 0xe6, 0x95, 0x09, 0xe5, 0x60 }, - { 0x95, 0x81, 0x48, 0x68, 0x2d, 0x74, 0x8a, 0x38, 0xdd, 0x68, 0xba, 0xa0 }, - { 0xb8, 0xce, 0x02, 0x0c, 0xf0, 0x69, 0xc3, 0x2a, 0x72, 0x3a, 0xb1, 0x40 }, - { 0xf4, 0x33, 0x1d, 0x6d, 0x46, 0x16, 0x07, 0xe9, 0x57, 0x52, 0x74, 0x60 }, - { 0x6d, 0xa2, 0x3b, 0xa4, 0x24, 0xb9, 0x59, 0x61, 0x33, 0xcf, 0x9c, 0x80 }, - { 0xa6, 0x36, 0xbc, 0xbc, 0x7b, 0x30, 0xc5, 0xfb, 0xea, 0xe6, 0x7f, 0xe0 }, - { 0x5c, 0xb0, 0xd8, 0x6a, 0x07, 0xdf, 0x65, 0x4a, 0x90, 0x89, 0xa2, 0x00 }, - { 0xf1, 0x1f, 0x10, 0x68, 0x48, 0x78, 0x0f, 0xc9, 0xec, 0xdd, 0x80, 0xa0 }, - { 0x1f, 0xbb, 0x53, 0x64, 0xfb, 0x8d, 0x2c, 0x9d, 0x73, 0x0d, 0x5b, 0xa0 }, - { 0xfc, 0xb8, 0x6b, 0xc7, 0x0a, 0x50, 0xc9, 0xd0, 0x2a, 0x5d, 0x03, 0x40 }, - { 0xa5, 0x34, 0x43, 0x30, 0x29, 0xea, 0xc1, 0x5f, 0x32, 0x2e, 0x34, 0xc0 }, - { 0xc9, 0x89, 0xd9, 0xc7, 0xc3, 0xd3, 0xb8, 0xc5, 0x5d, 0x75, 0x13, 0x00 }, - { 0x7b, 0xb3, 0x8b, 0x2f, 0x01, 0x86, 0xd4, 0x66, 0x43, 0xae, 0x96, 0x20 }, - { 0x26, 0x44, 0xeb, 0xad, 0xeb, 0x44, 0xb9, 0x46, 0x7d, 0x1f, 0x42, 0xc0 }, - { 0x60, 0x8c, 0xc8, 0x57, 0x59, 0x4b, 0xfb, 0xb5, 0x5d, 0x69, 0x60, 0x00 } -}; +const uint8_t kGenerator[FT8_M][FT8_K_BYTES] = { + {0x83, 0x29, 0xce, 0x11, 0xbf, 0x31, 0xea, 0xf5, 0x09, 0xf2, 0x7f, 0xc0}, + {0x76, 0x1c, 0x26, 0x4e, 0x25, 0xc2, 0x59, 0x33, 0x54, 0x93, 0x13, 0x20}, + {0xdc, 0x26, 0x59, 0x02, 0xfb, 0x27, 0x7c, 0x64, 0x10, 0xa1, 0xbd, 0xc0}, + {0x1b, 0x3f, 0x41, 0x78, 0x58, 0xcd, 0x2d, 0xd3, 0x3e, 0xc7, 0xf6, 0x20}, + {0x09, 0xfd, 0xa4, 0xfe, 0xe0, 0x41, 0x95, 0xfd, 0x03, 0x47, 0x83, 0xa0}, + {0x07, 0x7c, 0xcc, 0xc1, 0x1b, 0x88, 0x73, 0xed, 0x5c, 0x3d, 0x48, 0xa0}, + {0x29, 0xb6, 0x2a, 0xfe, 0x3c, 0xa0, 0x36, 0xf4, 0xfe, 0x1a, 0x9d, 0xa0}, + {0x60, 0x54, 0xfa, 0xf5, 0xf3, 0x5d, 0x96, 0xd3, 0xb0, 0xc8, 0xc3, 0xe0}, + {0xe2, 0x07, 0x98, 0xe4, 0x31, 0x0e, 0xed, 0x27, 0x88, 0x4a, 0xe9, 0x00}, + {0x77, 0x5c, 0x9c, 0x08, 0xe8, 0x0e, 0x26, 0xdd, 0xae, 0x56, 0x31, 0x80}, + {0xb0, 0xb8, 0x11, 0x02, 0x8c, 0x2b, 0xf9, 0x97, 0x21, 0x34, 0x87, 0xc0}, + {0x18, 0xa0, 0xc9, 0x23, 0x1f, 0xc6, 0x0a, 0xdf, 0x5c, 0x5e, 0xa3, 0x20}, + {0x76, 0x47, 0x1e, 0x83, 0x02, 0xa0, 0x72, 0x1e, 0x01, 0xb1, 0x2b, 0x80}, + {0xff, 0xbc, 0xcb, 0x80, 0xca, 0x83, 0x41, 0xfa, 0xfb, 0x47, 0xb2, 0xe0}, + {0x66, 0xa7, 0x2a, 0x15, 0x8f, 0x93, 0x25, 0xa2, 0xbf, 0x67, 0x17, 0x00}, + {0xc4, 0x24, 0x36, 0x89, 0xfe, 0x85, 0xb1, 0xc5, 0x13, 0x63, 0xa1, 0x80}, + {0x0d, 0xff, 0x73, 0x94, 0x14, 0xd1, 0xa1, 0xb3, 0x4b, 0x1c, 0x27, 0x00}, + {0x15, 0xb4, 0x88, 0x30, 0x63, 0x6c, 0x8b, 0x99, 0x89, 0x49, 0x72, 0xe0}, + {0x29, 0xa8, 0x9c, 0x0d, 0x3d, 0xe8, 0x1d, 0x66, 0x54, 0x89, 0xb0, 0xe0}, + {0x4f, 0x12, 0x6f, 0x37, 0xfa, 0x51, 0xcb, 0xe6, 0x1b, 0xd6, 0xb9, 0x40}, + {0x99, 0xc4, 0x72, 0x39, 0xd0, 0xd9, 0x7d, 0x3c, 0x84, 0xe0, 0x94, 0x00}, + {0x19, 0x19, 0xb7, 0x51, 0x19, 0x76, 0x56, 0x21, 0xbb, 0x4f, 0x1e, 0x80}, + {0x09, 0xdb, 0x12, 0xd7, 0x31, 0xfa, 0xee, 0x0b, 0x86, 0xdf, 0x6b, 0x80}, + {0x48, 0x8f, 0xc3, 0x3d, 0xf4, 0x3f, 0xbd, 0xee, 0xa4, 0xea, 0xfb, 0x40}, + {0x82, 0x74, 0x23, 0xee, 0x40, 0xb6, 0x75, 0xf7, 0x56, 0xeb, 0x5f, 0xe0}, + {0xab, 0xe1, 0x97, 0xc4, 0x84, 0xcb, 0x74, 0x75, 0x71, 0x44, 0xa9, 0xa0}, + {0x2b, 0x50, 0x0e, 0x4b, 0xc0, 0xec, 0x5a, 0x6d, 0x2b, 0xdb, 0xdd, 0x00}, + {0xc4, 0x74, 0xaa, 0x53, 0xd7, 0x02, 0x18, 0x76, 0x16, 0x69, 0x36, 0x00}, + {0x8e, 0xba, 0x1a, 0x13, 0xdb, 0x33, 0x90, 0xbd, 0x67, 0x18, 0xce, 0xc0}, + {0x75, 0x38, 0x44, 0x67, 0x3a, 0x27, 0x78, 0x2c, 0xc4, 0x20, 0x12, 0xe0}, + {0x06, 0xff, 0x83, 0xa1, 0x45, 0xc3, 0x70, 0x35, 0xa5, 0xc1, 0x26, 0x80}, + {0x3b, 0x37, 0x41, 0x78, 0x58, 0xcc, 0x2d, 0xd3, 0x3e, 0xc3, 0xf6, 0x20}, + {0x9a, 0x4a, 0x5a, 0x28, 0xee, 0x17, 0xca, 0x9c, 0x32, 0x48, 0x42, 0xc0}, + {0xbc, 0x29, 0xf4, 0x65, 0x30, 0x9c, 0x97, 0x7e, 0x89, 0x61, 0x0a, 0x40}, + {0x26, 0x63, 0xae, 0x6d, 0xdf, 0x8b, 0x5c, 0xe2, 0xbb, 0x29, 0x48, 0x80}, + {0x46, 0xf2, 0x31, 0xef, 0xe4, 0x57, 0x03, 0x4c, 0x18, 0x14, 0x41, 0x80}, + {0x3f, 0xb2, 0xce, 0x85, 0xab, 0xe9, 0xb0, 0xc7, 0x2e, 0x06, 0xfb, 0xe0}, + {0xde, 0x87, 0x48, 0x1f, 0x28, 0x2c, 0x15, 0x39, 0x71, 0xa0, 0xa2, 0xe0}, + {0xfc, 0xd7, 0xcc, 0xf2, 0x3c, 0x69, 0xfa, 0x99, 0xbb, 0xa1, 0x41, 0x20}, + {0xf0, 0x26, 0x14, 0x47, 0xe9, 0x49, 0x0c, 0xa8, 0xe4, 0x74, 0xce, 0xc0}, + {0x44, 0x10, 0x11, 0x58, 0x18, 0x19, 0x6f, 0x95, 0xcd, 0xd7, 0x01, 0x20}, + {0x08, 0x8f, 0xc3, 0x1d, 0xf4, 0xbf, 0xbd, 0xe2, 0xa4, 0xea, 0xfb, 0x40}, + {0xb8, 0xfe, 0xf1, 0xb6, 0x30, 0x77, 0x29, 0xfb, 0x0a, 0x07, 0x8c, 0x00}, + {0x5a, 0xfe, 0xa7, 0xac, 0xcc, 0xb7, 0x7b, 0xbc, 0x9d, 0x99, 0xa9, 0x00}, + {0x49, 0xa7, 0x01, 0x6a, 0xc6, 0x53, 0xf6, 0x5e, 0xcd, 0xc9, 0x07, 0x60}, + {0x19, 0x44, 0xd0, 0x85, 0xbe, 0x4e, 0x7d, 0xa8, 0xd6, 0xcc, 0x7d, 0x00}, + {0x25, 0x1f, 0x62, 0xad, 0xc4, 0x03, 0x2f, 0x0e, 0xe7, 0x14, 0x00, 0x20}, + {0x56, 0x47, 0x1f, 0x87, 0x02, 0xa0, 0x72, 0x1e, 0x00, 0xb1, 0x2b, 0x80}, + {0x2b, 0x8e, 0x49, 0x23, 0xf2, 0xdd, 0x51, 0xe2, 0xd5, 0x37, 0xfa, 0x00}, + {0x6b, 0x55, 0x0a, 0x40, 0xa6, 0x6f, 0x47, 0x55, 0xde, 0x95, 0xc2, 0x60}, + {0xa1, 0x8a, 0xd2, 0x8d, 0x4e, 0x27, 0xfe, 0x92, 0xa4, 0xf6, 0xc8, 0x40}, + {0x10, 0xc2, 0xe5, 0x86, 0x38, 0x8c, 0xb8, 0x2a, 0x3d, 0x80, 0x75, 0x80}, + {0xef, 0x34, 0xa4, 0x18, 0x17, 0xee, 0x02, 0x13, 0x3d, 0xb2, 0xeb, 0x00}, + {0x7e, 0x9c, 0x0c, 0x54, 0x32, 0x5a, 0x9c, 0x15, 0x83, 0x6e, 0x00, 0x00}, + {0x36, 0x93, 0xe5, 0x72, 0xd1, 0xfd, 0xe4, 0xcd, 0xf0, 0x79, 0xe8, 0x60}, + {0xbf, 0xb2, 0xce, 0xc5, 0xab, 0xe1, 0xb0, 0xc7, 0x2e, 0x07, 0xfb, 0xe0}, + {0x7e, 0xe1, 0x82, 0x30, 0xc5, 0x83, 0xcc, 0xcc, 0x57, 0xd4, 0xb0, 0x80}, + {0xa0, 0x66, 0xcb, 0x2f, 0xed, 0xaf, 0xc9, 0xf5, 0x26, 0x64, 0x12, 0x60}, + {0xbb, 0x23, 0x72, 0x5a, 0xbc, 0x47, 0xcc, 0x5f, 0x4c, 0xc4, 0xcd, 0x20}, + {0xde, 0xd9, 0xdb, 0xa3, 0xbe, 0xe4, 0x0c, 0x59, 0xb5, 0x60, 0x9b, 0x40}, + {0xd9, 0xa7, 0x01, 0x6a, 0xc6, 0x53, 0xe6, 0xde, 0xcd, 0xc9, 0x03, 0x60}, + {0x9a, 0xd4, 0x6a, 0xed, 0x5f, 0x70, 0x7f, 0x28, 0x0a, 0xb5, 0xfc, 0x40}, + {0xe5, 0x92, 0x1c, 0x77, 0x82, 0x25, 0x87, 0x31, 0x6d, 0x7d, 0x3c, 0x20}, + {0x4f, 0x14, 0xda, 0x82, 0x42, 0xa8, 0xb8, 0x6d, 0xca, 0x73, 0x35, 0x20}, + {0x8b, 0x8b, 0x50, 0x7a, 0xd4, 0x67, 0xd4, 0x44, 0x1d, 0xf7, 0x70, 0xe0}, + {0x22, 0x83, 0x1c, 0x9c, 0xf1, 0x16, 0x94, 0x67, 0xad, 0x04, 0xb6, 0x80}, + {0x21, 0x3b, 0x83, 0x8f, 0xe2, 0xae, 0x54, 0xc3, 0x8e, 0xe7, 0x18, 0x00}, + {0x5d, 0x92, 0x6b, 0x6d, 0xd7, 0x1f, 0x08, 0x51, 0x81, 0xa4, 0xe1, 0x20}, + {0x66, 0xab, 0x79, 0xd4, 0xb2, 0x9e, 0xe6, 0xe6, 0x95, 0x09, 0xe5, 0x60}, + {0x95, 0x81, 0x48, 0x68, 0x2d, 0x74, 0x8a, 0x38, 0xdd, 0x68, 0xba, 0xa0}, + {0xb8, 0xce, 0x02, 0x0c, 0xf0, 0x69, 0xc3, 0x2a, 0x72, 0x3a, 0xb1, 0x40}, + {0xf4, 0x33, 0x1d, 0x6d, 0x46, 0x16, 0x07, 0xe9, 0x57, 0x52, 0x74, 0x60}, + {0x6d, 0xa2, 0x3b, 0xa4, 0x24, 0xb9, 0x59, 0x61, 0x33, 0xcf, 0x9c, 0x80}, + {0xa6, 0x36, 0xbc, 0xbc, 0x7b, 0x30, 0xc5, 0xfb, 0xea, 0xe6, 0x7f, 0xe0}, + {0x5c, 0xb0, 0xd8, 0x6a, 0x07, 0xdf, 0x65, 0x4a, 0x90, 0x89, 0xa2, 0x00}, + {0xf1, 0x1f, 0x10, 0x68, 0x48, 0x78, 0x0f, 0xc9, 0xec, 0xdd, 0x80, 0xa0}, + {0x1f, 0xbb, 0x53, 0x64, 0xfb, 0x8d, 0x2c, 0x9d, 0x73, 0x0d, 0x5b, 0xa0}, + {0xfc, 0xb8, 0x6b, 0xc7, 0x0a, 0x50, 0xc9, 0xd0, 0x2a, 0x5d, 0x03, 0x40}, + {0xa5, 0x34, 0x43, 0x30, 0x29, 0xea, 0xc1, 0x5f, 0x32, 0x2e, 0x34, 0xc0}, + {0xc9, 0x89, 0xd9, 0xc7, 0xc3, 0xd3, 0xb8, 0xc5, 0x5d, 0x75, 0x13, 0x00}, + {0x7b, 0xb3, 0x8b, 0x2f, 0x01, 0x86, 0xd4, 0x66, 0x43, 0xae, 0x96, 0x20}, + {0x26, 0x44, 0xeb, 0xad, 0xeb, 0x44, 0xb9, 0x46, 0x7d, 0x1f, 0x42, 0xc0}, + {0x60, 0x8c, 0xc8, 0x57, 0x59, 0x4b, 0xfb, 0xb5, 0x5d, 0x69, 0x60, 0x00}}; // Column order (permutation) in which the bits in codeword are stored // (Not really used in FT8 v2 - instead the Nm, Mn and generator matrices are already permuted) -const uint8_t kColumn_order[N] = { - 0, 1, 2, 3, 28, 4, 5, 6, 7, 8, 9, 10, 11, 34, 12, 32, 13, 14, 15, 16, - 17, 18, 36, 29, 43, 19, 20, 42, 21, 40, 30, 37, 22, 47, 61, 45, 44, 23, 41, 39, - 49, 24, 46, 50, 48, 26, 31, 33, 51, 38, 52, 59, 55, 66, 57, 27, 60, 35, 54, 58, - 25, 56, 62, 64, 67, 69, 63, 68, 70, 72, 65, 73, 75, 74, 71, 77, 78, 76, 79, 80, - 53, 81, 83, 82, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, - 100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119, - 120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139, - 140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, - 160,161,162,163,164,165,166,167,168,169,170,171,172,173 -}; - +const uint8_t kColumn_order[FT8_N] = { + 0, 1, 2, 3, 28, 4, 5, 6, 7, 8, 9, 10, 11, 34, 12, 32, 13, 14, 15, 16, + 17, 18, 36, 29, 43, 19, 20, 42, 21, 40, 30, 37, 22, 47, 61, 45, 44, 23, 41, 39, + 49, 24, 46, 50, 48, 26, 31, 33, 51, 38, 52, 59, 55, 66, 57, 27, 60, 35, 54, 58, + 25, 56, 62, 64, 67, 69, 63, 68, 70, 72, 65, 73, 75, 74, 71, 77, 78, 76, 79, 80, + 53, 81, 83, 82, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, + 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, + 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, + 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173}; // this is the LDPC(174,91) parity check matrix. // 83 rows. @@ -116,281 +111,276 @@ const uint8_t kColumn_order[N] = { // each number is an index into the codeword (1-origin). // the codeword bits mentioned in each row must xor to zero. // From WSJT-X's ldpc_174_91_c_reordered_parity.f90. -const uint8_t kNm[M][7] = { - { 4, 31, 59, 91, 92, 96, 153 }, - { 5, 32, 60, 93, 115, 146, 0 }, - { 6, 24, 61, 94, 122, 151, 0 }, - { 7, 33, 62, 95, 96, 143, 0 }, - { 8, 25, 63, 83, 93, 96, 148 }, - { 6, 32, 64, 97, 126, 138, 0 }, - { 5, 34, 65, 78, 98, 107, 154 }, - { 9, 35, 66, 99, 139, 146, 0 }, - { 10, 36, 67, 100, 107, 126, 0 }, - { 11, 37, 67, 87, 101, 139, 158 }, - { 12, 38, 68, 102, 105, 155, 0 }, - { 13, 39, 69, 103, 149, 162, 0 }, - { 8, 40, 70, 82, 104, 114, 145 }, - { 14, 41, 71, 88, 102, 123, 156 }, - { 15, 42, 59, 106, 123, 159, 0 }, - { 1, 33, 72, 106, 107, 157, 0 }, - { 16, 43, 73, 108, 141, 160, 0 }, - { 17, 37, 74, 81, 109, 131, 154 }, - { 11, 44, 75, 110, 121, 166, 0 }, - { 45, 55, 64, 111, 130, 161, 173 }, - { 8, 46, 71, 112, 119, 166, 0 }, - { 18, 36, 76, 89, 113, 114, 143 }, - { 19, 38, 77, 104, 116, 163, 0 }, - { 20, 47, 70, 92, 138, 165, 0 }, - { 2, 48, 74, 113, 128, 160, 0 }, - { 21, 45, 78, 83, 117, 121, 151 }, - { 22, 47, 58, 118, 127, 164, 0 }, - { 16, 39, 62, 112, 134, 158, 0 }, - { 23, 43, 79, 120, 131, 145, 0 }, - { 19, 35, 59, 73, 110, 125, 161 }, - { 20, 36, 63, 94, 136, 161, 0 }, - { 14, 31, 79, 98, 132, 164, 0 }, - { 3, 44, 80, 124, 127, 169, 0 }, - { 19, 46, 81, 117, 135, 167, 0 }, - { 7, 49, 58, 90, 100, 105, 168 }, - { 12, 50, 61, 118, 119, 144, 0 }, - { 13, 51, 64, 114, 118, 157, 0 }, - { 24, 52, 76, 129, 148, 149, 0 }, - { 25, 53, 69, 90, 101, 130, 156 }, - { 20, 46, 65, 80, 120, 140, 170 }, - { 21, 54, 77, 100, 140, 171, 0 }, - { 35, 82, 133, 142, 171, 174, 0 }, - { 14, 30, 83, 113, 125, 170, 0 }, - { 4, 29, 68, 120, 134, 173, 0 }, - { 1, 4, 52, 57, 86, 136, 152 }, - { 26, 51, 56, 91, 122, 137, 168 }, - { 52, 84, 110, 115, 145, 168, 0 }, - { 7, 50, 81, 99, 132, 173, 0 }, - { 23, 55, 67, 95, 172, 174, 0 }, - { 26, 41, 77, 109, 141, 148, 0 }, - { 2, 27, 41, 61, 62, 115, 133 }, - { 27, 40, 56, 124, 125, 126, 0 }, - { 18, 49, 55, 124, 141, 167, 0 }, - { 6, 33, 85, 108, 116, 156, 0 }, - { 28, 48, 70, 85, 105, 129, 158 }, - { 9, 54, 63, 131, 147, 155, 0 }, - { 22, 53, 68, 109, 121, 174, 0 }, - { 3, 13, 48, 78, 95, 123, 0 }, - { 31, 69, 133, 150, 155, 169, 0 }, - { 12, 43, 66, 89, 97, 135, 159 }, - { 5, 39, 75, 102, 136, 167, 0 }, - { 2, 54, 86, 101, 135, 164, 0 }, - { 15, 56, 87, 108, 119, 171, 0 }, - { 10, 44, 82, 91, 111, 144, 149 }, - { 23, 34, 71, 94, 127, 153, 0 }, - { 11, 49, 88, 92, 142, 157, 0 }, - { 29, 34, 87, 97, 147, 162, 0 }, - { 30, 50, 60, 86, 137, 142, 162 }, - { 10, 53, 66, 84, 112, 128, 165 }, - { 22, 57, 85, 93, 140, 159, 0 }, - { 28, 32, 72, 103, 132, 166, 0 }, - { 28, 29, 84, 88, 117, 143, 150 }, - { 1, 26, 45, 80, 128, 147, 0 }, - { 17, 27, 89, 103, 116, 153, 0 }, - { 51, 57, 98, 163, 165, 172, 0 }, - { 21, 37, 73, 138, 152, 169, 0 }, - { 16, 47, 76, 130, 137, 154, 0 }, - { 3, 24, 30, 72, 104, 139, 0 }, - { 9, 40, 90, 106, 134, 151, 0 }, - { 15, 58, 60, 74, 111, 150, 163 }, - { 18, 42, 79, 144, 146, 152, 0 }, - { 25, 38, 65, 99, 122, 160, 0 }, - { 17, 42, 75, 129, 170, 172, 0 } -}; +const uint8_t kNm[FT8_M][7] = { + {4, 31, 59, 91, 92, 96, 153}, + {5, 32, 60, 93, 115, 146, 0}, + {6, 24, 61, 94, 122, 151, 0}, + {7, 33, 62, 95, 96, 143, 0}, + {8, 25, 63, 83, 93, 96, 148}, + {6, 32, 64, 97, 126, 138, 0}, + {5, 34, 65, 78, 98, 107, 154}, + {9, 35, 66, 99, 139, 146, 0}, + {10, 36, 67, 100, 107, 126, 0}, + {11, 37, 67, 87, 101, 139, 158}, + {12, 38, 68, 102, 105, 155, 0}, + {13, 39, 69, 103, 149, 162, 0}, + {8, 40, 70, 82, 104, 114, 145}, + {14, 41, 71, 88, 102, 123, 156}, + {15, 42, 59, 106, 123, 159, 0}, + {1, 33, 72, 106, 107, 157, 0}, + {16, 43, 73, 108, 141, 160, 0}, + {17, 37, 74, 81, 109, 131, 154}, + {11, 44, 75, 110, 121, 166, 0}, + {45, 55, 64, 111, 130, 161, 173}, + {8, 46, 71, 112, 119, 166, 0}, + {18, 36, 76, 89, 113, 114, 143}, + {19, 38, 77, 104, 116, 163, 0}, + {20, 47, 70, 92, 138, 165, 0}, + {2, 48, 74, 113, 128, 160, 0}, + {21, 45, 78, 83, 117, 121, 151}, + {22, 47, 58, 118, 127, 164, 0}, + {16, 39, 62, 112, 134, 158, 0}, + {23, 43, 79, 120, 131, 145, 0}, + {19, 35, 59, 73, 110, 125, 161}, + {20, 36, 63, 94, 136, 161, 0}, + {14, 31, 79, 98, 132, 164, 0}, + {3, 44, 80, 124, 127, 169, 0}, + {19, 46, 81, 117, 135, 167, 0}, + {7, 49, 58, 90, 100, 105, 168}, + {12, 50, 61, 118, 119, 144, 0}, + {13, 51, 64, 114, 118, 157, 0}, + {24, 52, 76, 129, 148, 149, 0}, + {25, 53, 69, 90, 101, 130, 156}, + {20, 46, 65, 80, 120, 140, 170}, + {21, 54, 77, 100, 140, 171, 0}, + {35, 82, 133, 142, 171, 174, 0}, + {14, 30, 83, 113, 125, 170, 0}, + {4, 29, 68, 120, 134, 173, 0}, + {1, 4, 52, 57, 86, 136, 152}, + {26, 51, 56, 91, 122, 137, 168}, + {52, 84, 110, 115, 145, 168, 0}, + {7, 50, 81, 99, 132, 173, 0}, + {23, 55, 67, 95, 172, 174, 0}, + {26, 41, 77, 109, 141, 148, 0}, + {2, 27, 41, 61, 62, 115, 133}, + {27, 40, 56, 124, 125, 126, 0}, + {18, 49, 55, 124, 141, 167, 0}, + {6, 33, 85, 108, 116, 156, 0}, + {28, 48, 70, 85, 105, 129, 158}, + {9, 54, 63, 131, 147, 155, 0}, + {22, 53, 68, 109, 121, 174, 0}, + {3, 13, 48, 78, 95, 123, 0}, + {31, 69, 133, 150, 155, 169, 0}, + {12, 43, 66, 89, 97, 135, 159}, + {5, 39, 75, 102, 136, 167, 0}, + {2, 54, 86, 101, 135, 164, 0}, + {15, 56, 87, 108, 119, 171, 0}, + {10, 44, 82, 91, 111, 144, 149}, + {23, 34, 71, 94, 127, 153, 0}, + {11, 49, 88, 92, 142, 157, 0}, + {29, 34, 87, 97, 147, 162, 0}, + {30, 50, 60, 86, 137, 142, 162}, + {10, 53, 66, 84, 112, 128, 165}, + {22, 57, 85, 93, 140, 159, 0}, + {28, 32, 72, 103, 132, 166, 0}, + {28, 29, 84, 88, 117, 143, 150}, + {1, 26, 45, 80, 128, 147, 0}, + {17, 27, 89, 103, 116, 153, 0}, + {51, 57, 98, 163, 165, 172, 0}, + {21, 37, 73, 138, 152, 169, 0}, + {16, 47, 76, 130, 137, 154, 0}, + {3, 24, 30, 72, 104, 139, 0}, + {9, 40, 90, 106, 134, 151, 0}, + {15, 58, 60, 74, 111, 150, 163}, + {18, 42, 79, 144, 146, 152, 0}, + {25, 38, 65, 99, 122, 160, 0}, + {17, 42, 75, 129, 170, 172, 0}}; // Mn from WSJT-X's bpdecode174.f90. // each row corresponds to a codeword bit. // the numbers indicate which three parity // checks (rows in Nm) refer to the codeword bit. // 1-origin. -const uint8_t kMn[N][3] = { - { 16, 45, 73 }, - { 25, 51, 62 }, - { 33, 58, 78 }, - { 1, 44, 45 }, - { 2, 7, 61 }, - { 3, 6, 54 }, - { 4, 35, 48 }, - { 5, 13, 21 }, - { 8, 56, 79 }, - { 9, 64, 69 }, - { 10, 19, 66 }, - { 11, 36, 60 }, - { 12, 37, 58 }, - { 14, 32, 43 }, - { 15, 63, 80 }, - { 17, 28, 77 }, - { 18, 74, 83 }, - { 22, 53, 81 }, - { 23, 30, 34 }, - { 24, 31, 40 }, - { 26, 41, 76 }, - { 27, 57, 70 }, - { 29, 49, 65 }, - { 3, 38, 78 }, - { 5, 39, 82 }, - { 46, 50, 73 }, - { 51, 52, 74 }, - { 55, 71, 72 }, - { 44, 67, 72 }, - { 43, 68, 78 }, - { 1, 32, 59 }, - { 2, 6, 71 }, - { 4, 16, 54 }, - { 7, 65, 67 }, - { 8, 30, 42 }, - { 9, 22, 31 }, - { 10, 18, 76 }, - { 11, 23, 82 }, - { 12, 28, 61 }, - { 13, 52, 79 }, - { 14, 50, 51 }, - { 15, 81, 83 }, - { 17, 29, 60 }, - { 19, 33, 64 }, - { 20, 26, 73 }, - { 21, 34, 40 }, - { 24, 27, 77 }, - { 25, 55, 58 }, - { 35, 53, 66 }, - { 36, 48, 68 }, - { 37, 46, 75 }, - { 38, 45, 47 }, - { 39, 57, 69 }, - { 41, 56, 62 }, - { 20, 49, 53 }, - { 46, 52, 63 }, - { 45, 70, 75 }, - { 27, 35, 80 }, - { 1, 15, 30 }, - { 2, 68, 80 }, - { 3, 36, 51 }, - { 4, 28, 51 }, - { 5, 31, 56 }, - { 6, 20, 37 }, - { 7, 40, 82 }, - { 8, 60, 69 }, - { 9, 10, 49 }, - { 11, 44, 57 }, - { 12, 39, 59 }, - { 13, 24, 55 }, - { 14, 21, 65 }, - { 16, 71, 78 }, - { 17, 30, 76 }, - { 18, 25, 80 }, - { 19, 61, 83 }, - { 22, 38, 77 }, - { 23, 41, 50 }, - { 7, 26, 58 }, - { 29, 32, 81 }, - { 33, 40, 73 }, - { 18, 34, 48 }, - { 13, 42, 64 }, - { 5, 26, 43 }, - { 47, 69, 72 }, - { 54, 55, 70 }, - { 45, 62, 68 }, - { 10, 63, 67 }, - { 14, 66, 72 }, - { 22, 60, 74 }, - { 35, 39, 79 }, - { 1, 46, 64 }, - { 1, 24, 66 }, - { 2, 5, 70 }, - { 3, 31, 65 }, - { 4, 49, 58 }, - { 1, 4, 5 }, - { 6, 60, 67 }, - { 7, 32, 75 }, - { 8, 48, 82 }, - { 9, 35, 41 }, - { 10, 39, 62 }, - { 11, 14, 61 }, - { 12, 71, 74 }, - { 13, 23, 78 }, - { 11, 35, 55 }, - { 15, 16, 79 }, - { 7, 9, 16 }, - { 17, 54, 63 }, - { 18, 50, 57 }, - { 19, 30, 47 }, - { 20, 64, 80 }, - { 21, 28, 69 }, - { 22, 25, 43 }, - { 13, 22, 37 }, - { 2, 47, 51 }, - { 23, 54, 74 }, - { 26, 34, 72 }, - { 27, 36, 37 }, - { 21, 36, 63 }, - { 29, 40, 44 }, - { 19, 26, 57 }, - { 3, 46, 82 }, - { 14, 15, 58 }, - { 33, 52, 53 }, - { 30, 43, 52 }, - { 6, 9, 52 }, - { 27, 33, 65 }, - { 25, 69, 73 }, - { 38, 55, 83 }, - { 20, 39, 77 }, - { 18, 29, 56 }, - { 32, 48, 71 }, - { 42, 51, 59 }, - { 28, 44, 79 }, - { 34, 60, 62 }, - { 31, 45, 61 }, - { 46, 68, 77 }, - { 6, 24, 76 }, - { 8, 10, 78 }, - { 40, 41, 70 }, - { 17, 50, 53 }, - { 42, 66, 68 }, - { 4, 22, 72 }, - { 36, 64, 81 }, - { 13, 29, 47 }, - { 2, 8, 81 }, - { 56, 67, 73 }, - { 5, 38, 50 }, - { 12, 38, 64 }, - { 59, 72, 80 }, - { 3, 26, 79 }, - { 45, 76, 81 }, - { 1, 65, 74 }, - { 7, 18, 77 }, - { 11, 56, 59 }, - { 14, 39, 54 }, - { 16, 37, 66 }, - { 10, 28, 55 }, - { 15, 60, 70 }, - { 17, 25, 82 }, - { 20, 30, 31 }, - { 12, 67, 68 }, - { 23, 75, 80 }, - { 27, 32, 62 }, - { 24, 69, 75 }, - { 19, 21, 71 }, - { 34, 53, 61 }, - { 35, 46, 47 }, - { 33, 59, 76 }, - { 40, 43, 83 }, - { 41, 42, 63 }, - { 49, 75, 83 }, - { 20, 44, 48 }, - { 42, 49, 57 } -}; +const uint8_t kMn[FT8_N][3] = { + {16, 45, 73}, + {25, 51, 62}, + {33, 58, 78}, + {1, 44, 45}, + {2, 7, 61}, + {3, 6, 54}, + {4, 35, 48}, + {5, 13, 21}, + {8, 56, 79}, + {9, 64, 69}, + {10, 19, 66}, + {11, 36, 60}, + {12, 37, 58}, + {14, 32, 43}, + {15, 63, 80}, + {17, 28, 77}, + {18, 74, 83}, + {22, 53, 81}, + {23, 30, 34}, + {24, 31, 40}, + {26, 41, 76}, + {27, 57, 70}, + {29, 49, 65}, + {3, 38, 78}, + {5, 39, 82}, + {46, 50, 73}, + {51, 52, 74}, + {55, 71, 72}, + {44, 67, 72}, + {43, 68, 78}, + {1, 32, 59}, + {2, 6, 71}, + {4, 16, 54}, + {7, 65, 67}, + {8, 30, 42}, + {9, 22, 31}, + {10, 18, 76}, + {11, 23, 82}, + {12, 28, 61}, + {13, 52, 79}, + {14, 50, 51}, + {15, 81, 83}, + {17, 29, 60}, + {19, 33, 64}, + {20, 26, 73}, + {21, 34, 40}, + {24, 27, 77}, + {25, 55, 58}, + {35, 53, 66}, + {36, 48, 68}, + {37, 46, 75}, + {38, 45, 47}, + {39, 57, 69}, + {41, 56, 62}, + {20, 49, 53}, + {46, 52, 63}, + {45, 70, 75}, + {27, 35, 80}, + {1, 15, 30}, + {2, 68, 80}, + {3, 36, 51}, + {4, 28, 51}, + {5, 31, 56}, + {6, 20, 37}, + {7, 40, 82}, + {8, 60, 69}, + {9, 10, 49}, + {11, 44, 57}, + {12, 39, 59}, + {13, 24, 55}, + {14, 21, 65}, + {16, 71, 78}, + {17, 30, 76}, + {18, 25, 80}, + {19, 61, 83}, + {22, 38, 77}, + {23, 41, 50}, + {7, 26, 58}, + {29, 32, 81}, + {33, 40, 73}, + {18, 34, 48}, + {13, 42, 64}, + {5, 26, 43}, + {47, 69, 72}, + {54, 55, 70}, + {45, 62, 68}, + {10, 63, 67}, + {14, 66, 72}, + {22, 60, 74}, + {35, 39, 79}, + {1, 46, 64}, + {1, 24, 66}, + {2, 5, 70}, + {3, 31, 65}, + {4, 49, 58}, + {1, 4, 5}, + {6, 60, 67}, + {7, 32, 75}, + {8, 48, 82}, + {9, 35, 41}, + {10, 39, 62}, + {11, 14, 61}, + {12, 71, 74}, + {13, 23, 78}, + {11, 35, 55}, + {15, 16, 79}, + {7, 9, 16}, + {17, 54, 63}, + {18, 50, 57}, + {19, 30, 47}, + {20, 64, 80}, + {21, 28, 69}, + {22, 25, 43}, + {13, 22, 37}, + {2, 47, 51}, + {23, 54, 74}, + {26, 34, 72}, + {27, 36, 37}, + {21, 36, 63}, + {29, 40, 44}, + {19, 26, 57}, + {3, 46, 82}, + {14, 15, 58}, + {33, 52, 53}, + {30, 43, 52}, + {6, 9, 52}, + {27, 33, 65}, + {25, 69, 73}, + {38, 55, 83}, + {20, 39, 77}, + {18, 29, 56}, + {32, 48, 71}, + {42, 51, 59}, + {28, 44, 79}, + {34, 60, 62}, + {31, 45, 61}, + {46, 68, 77}, + {6, 24, 76}, + {8, 10, 78}, + {40, 41, 70}, + {17, 50, 53}, + {42, 66, 68}, + {4, 22, 72}, + {36, 64, 81}, + {13, 29, 47}, + {2, 8, 81}, + {56, 67, 73}, + {5, 38, 50}, + {12, 38, 64}, + {59, 72, 80}, + {3, 26, 79}, + {45, 76, 81}, + {1, 65, 74}, + {7, 18, 77}, + {11, 56, 59}, + {14, 39, 54}, + {16, 37, 66}, + {10, 28, 55}, + {15, 60, 70}, + {17, 25, 82}, + {20, 30, 31}, + {12, 67, 68}, + {23, 75, 80}, + {27, 32, 62}, + {24, 69, 75}, + {19, 21, 71}, + {34, 53, 61}, + {35, 46, 47}, + {33, 59, 76}, + {40, 43, 83}, + {41, 42, 63}, + {49, 75, 83}, + {20, 44, 48}, + {42, 49, 57}}; -const uint8_t kNrw[M] = { - 7,6,6,6,7,6,7,6,6,7,6,6,7,7,6,6, - 6,7,6,7,6,7,6,6,6,7,6,6,6,7,6,6, - 6,6,7,6,6,6,7,7,6,6,6,6,7,7,6,6, - 6,6,7,6,6,6,7,6,6,6,6,7,6,6,6,7, - 6,6,6,7,7,6,6,7,6,6,6,6,6,6,6,7, - 6,6,6 -}; - -} // namespace \ No newline at end of file +const uint8_t kNrw[FT8_M] = { + 7, 6, 6, 6, 7, 6, 7, 6, 6, 7, 6, 6, 7, 7, 6, 6, + 6, 7, 6, 7, 6, 7, 6, 6, 6, 7, 6, 6, 6, 7, 6, 6, + 6, 6, 7, 6, 6, 6, 7, 7, 6, 6, 6, 6, 7, 7, 6, 6, + 6, 6, 7, 6, 6, 6, 7, 6, 6, 6, 6, 7, 6, 6, 6, 7, + 6, 6, 6, 7, 7, 6, 6, 7, 6, 6, 6, 6, 6, 6, 6, 7, + 6, 6, 6}; diff --git a/ft8/constants.h b/ft8/constants.h index edd6c60..80d4c41 100644 --- a/ft8/constants.h +++ b/ft8/constants.h @@ -1,59 +1,52 @@ -#pragma once +#ifndef _INCLUDE_CONSTANTS_H_ +#define _INCLUDE_CONSTANTS_H_ #include -namespace ft8 { +// Define FT8 symbol counts +#define FT8_ND (58) // Data symbols +#define FT8_NS (21) // Sync symbols (3 @ Costas 7x7) +#define FT8_NN (FT8_NS + FT8_ND) // Total channel symbols (79) - // Define FT8 symbol counts - constexpr int ND = 58; // Data symbols - constexpr int NS = 21; // Sync symbols (3 @ Costas 7x7) - constexpr int NN = NS + ND; // Total channel symbols (79) +// Define the LDPC sizes +#define FT8_N (174) // Number of bits in the encoded message (payload + checksum) +#define FT8_K (91) // Number of payload bits +#define FT8_M (FT8_N - FT8_K) // Number of checksum bits +#define FT8_K_BYTES ((FT8_K + 7) / 8) // Number of whole bytes needed to store K bits - // Define the LDPC sizes - constexpr int N = 174; // Number of bits in the encoded message - constexpr int K = 91; // Number of payload bits - constexpr int M = N - K; // Number of checksum bits - constexpr int K_BYTES = (K + 7) / 8; // Number of whole bytes needed to store K bits +// Define CRC parameters +#define CRC_POLYNOMIAL ((uint16_t)0x2757u) // CRC-14 polynomial without the leading (MSB) 1 +#define CRC_WIDTH (14) - // Define CRC parameters - constexpr uint16_t CRC_POLYNOMIAL = 0x2757; // CRC-14 polynomial without the leading (MSB) 1 - constexpr int CRC_WIDTH = 14; +// Costas 7x7 tone pattern +extern const uint8_t kCostas_map[7]; - // Costas 7x7 tone pattern - extern const uint8_t kCostas_map[7]; +// Gray code map +extern const uint8_t kGray_map[8]; +// Parity generator matrix for (174,91) LDPC code, stored in bitpacked format (MSB first) +extern const uint8_t kGenerator[FT8_M][FT8_K_BYTES]; - // Gray code map - extern const uint8_t kGray_map[8]; +// Column order (permutation) in which the bits in codeword are stored +// (Not really used in FT8 v2 - instead the Nm, Mn and generator matrices are already permuted) +extern const uint8_t kColumn_order[FT8_N]; +// this is the LDPC(174,91) parity check matrix. +// 83 rows. +// each row describes one parity check. +// each number is an index into the codeword (1-origin). +// the codeword bits mentioned in each row must xor to zero. +// From WSJT-X's ldpc_174_91_c_reordered_parity.f90. +extern const uint8_t kNm[FT8_M][7]; - // Parity generator matrix for (174,91) LDPC code, stored in bitpacked format (MSB first) - extern const uint8_t kGenerator[M][K_BYTES]; +// Mn from WSJT-X's bpdecode174.f90. +// each row corresponds to a codeword bit. +// the numbers indicate which three parity +// checks (rows in Nm) refer to the codeword bit. +// 1-origin. +extern const uint8_t kMn[FT8_N][3]; +// Number of rows (columns in C/C++) in the array Nm. +extern const uint8_t kNrw[FT8_M]; - // Column order (permutation) in which the bits in codeword are stored - // (Not really used in FT8 v2 - instead the Nm, Mn and generator matrices are already permuted) - extern const uint8_t kColumn_order[N]; - - - // this is the LDPC(174,91) parity check matrix. - // 83 rows. - // each row describes one parity check. - // each number is an index into the codeword (1-origin). - // the codeword bits mentioned in each row must xor to zero. - // From WSJT-X's ldpc_174_91_c_reordered_parity.f90. - extern const uint8_t kNm[M][7]; - - - // Mn from WSJT-X's bpdecode174.f90. - // each row corresponds to a codeword bit. - // the numbers indicate which three parity - // checks (rows in Nm) refer to the codeword bit. - // 1-origin. - extern const uint8_t kMn[N][3]; - - - // Number of rows (columns in C/C++) in the array Nm. - extern const uint8_t kNrw[M]; - -} \ No newline at end of file +#endif // _INCLUDE_CONSTANTS_H_ diff --git a/ft8/decode.cpp b/ft8/decode.cpp index ae6d490..1a2692c 100644 --- a/ft8/decode.cpp +++ b/ft8/decode.cpp @@ -1,10 +1,7 @@ #include "decode.h" - -#include - #include "constants.h" -namespace ft8 { +#include static float max2(float a, float b); static float max4(float a, float b, float c, float d); @@ -13,32 +10,42 @@ static void heapify_up(Candidate *heap, int heap_size); static void decode_symbol(const uint8_t *power, const uint8_t *code_map, int bit_idx, float *log174); static void decode_multi_symbols(const uint8_t *power, int num_bins, int n_syms, const uint8_t *code_map, int bit_idx, float *log174); -static int get_index(const MagArray *power, int block, int time_sub, int freq_sub, int bin) { - return ((((block * power->time_osr) + time_sub) * power->freq_osr + freq_sub) * power->num_bins) + bin; +static int get_index(const MagArray *power, int block, int time_sub, int freq_sub, int bin) +{ + return ((((block * power->time_osr) + time_sub) * power->freq_osr + freq_sub) * power->num_bins) + bin; } // Localize top N candidates in frequency and time according to their sync strength (looking at Costas symbols) // We treat and organize the candidate list as a min-heap (empty initially). -int find_sync(const MagArray *power, const uint8_t *sync_map, int num_candidates, Candidate *heap, int min_score) { +int find_sync(const MagArray *power, const uint8_t *sync_map, int num_candidates, Candidate *heap, int min_score) +{ int heap_size = 0; int num_alt = power->time_osr * power->freq_osr; // Here we allow time offsets that exceed signal boundaries, as long as we still have all data bits. // I.e. we can afford to skip the first 7 or the last 7 Costas symbols, as long as we track how many // sync symbols we included in the score, so the score is averaged. - for (int time_sub = 0; time_sub < power->time_osr; ++time_sub) { - for (int freq_sub = 0; freq_sub < power->freq_osr; ++freq_sub) { - for (int time_offset = -7; time_offset < power->num_blocks - ft8::NN + 7; ++time_offset) { - for (int freq_offset = 0; freq_offset < power->num_bins - 8; ++freq_offset) { + for (int time_sub = 0; time_sub < power->time_osr; ++time_sub) + { + for (int freq_sub = 0; freq_sub < power->freq_osr; ++freq_sub) + { + for (int time_offset = -7; time_offset < power->num_blocks - FT8_NN + 7; ++time_offset) + { + for (int freq_offset = 0; freq_offset < power->num_bins - 8; ++freq_offset) + { int score = 0; // Compute average score over sync symbols (m+k = 0-7, 36-43, 72-79) int num_symbols = 0; - for (int m = 0; m <= 72; m += 36) { - for (int k = 0; k < 7; ++k) { + for (int m = 0; m <= 72; m += 36) + { + for (int k = 0; k < 7; ++k) + { // Check for time boundaries - if (time_offset + k + m < 0) continue; - if (time_offset + k + m >= power->num_blocks) break; + if (time_offset + k + m < 0) + continue; + if (time_offset + k + m >= power->num_blocks) + break; // int offset = ((time_offset + k + m) * num_alt + alt) * power->num_bins + freq_offset; int offset = get_index(power, time_offset + k + m, time_sub, freq_sub, freq_offset); @@ -51,34 +58,40 @@ int find_sync(const MagArray *power, const uint8_t *sync_map, int num_candidates // p8[4] - p8[5] - p8[6] - p8[7]; // Check only the neighbors of the expected symbol frequency- and time-wise - int sm = sync_map[k]; // Index of the expected bin - if (sm > 0) { + int sm = sync_map[k]; // Index of the expected bin + if (sm > 0) + { // look at one frequency bin lower score += p8[sm] - p8[sm - 1]; } - if (sm < 7) { + if (sm < 7) + { // look at one frequency bin higher score += p8[sm] - p8[sm + 1]; } - if (k > 0) { + if (k > 0) + { // look one symbol back in time score += p8[sm] - p8[sm - num_alt * power->num_bins]; } - if (k < 6) { + if (k < 6) + { // look one symbol forward in time score += p8[sm] - p8[sm + num_alt * power->num_bins]; } - + ++num_symbols; } } score /= num_symbols; - if (score < min_score) continue; + if (score < min_score) + continue; - // If the heap is full AND the current candidate is better than + // If the heap is full AND the current candidate is better than // the worst in the heap, we remove the worst and make space - if (heap_size == num_candidates && score > heap[0].score) { + if (heap_size == num_candidates && score > heap[0].score) + { heap[0] = heap[heap_size - 1]; --heap_size; @@ -86,7 +99,8 @@ int find_sync(const MagArray *power, const uint8_t *sync_map, int num_candidates } // If there's free space in the heap, we add the current candidate - if (heap_size < num_candidates) { + if (heap_size < num_candidates) + { heap[heap_size].score = score; heap[heap_size].time_offset = time_offset; heap[heap_size].freq_offset = freq_offset; @@ -104,10 +118,10 @@ int find_sync(const MagArray *power, const uint8_t *sync_map, int num_candidates return heap_size; } - -// Compute log likelihood log(p(1) / p(0)) of 174 message bits +// Compute log likelihood log(p(1) / p(0)) of 174 message bits // for later use in soft-decision LDPC decoding -void extract_likelihood(const MagArray *power, const Candidate & cand, const uint8_t *code_map, float *log174) { +void extract_likelihood(const MagArray *power, const Candidate &cand, const uint8_t *code_map, float *log174) +{ int num_alt = power->time_osr * power->freq_osr; // int offset = (cand.time_offset * num_alt + cand.time_sub * power->freq_osr + cand.freq_sub) * power->num_bins + cand.freq_offset; int offset = get_index(power, cand.time_offset, cand.time_sub, cand.freq_sub, cand.freq_offset); @@ -116,9 +130,10 @@ void extract_likelihood(const MagArray *power, const Candidate & cand, const uin const int n_syms = 1; const int n_bits = 3 * n_syms; const int n_tones = (1 << n_bits); - for (int k = 0; k < ft8::ND; k += n_syms) { + for (int k = 0; k < FT8_ND; k += n_syms) + { // Add either 7 or 14 extra symbols to account for sync - int sym_idx = (k < ft8::ND / 2) ? (k + 7) : (k + 14); + int sym_idx = (k < FT8_ND / 2) ? (k + 7) : (k + 14); int bit_idx = 3 * k; // Pointer to 8 bins of the current symbol @@ -128,11 +143,12 @@ void extract_likelihood(const MagArray *power, const Candidate & cand, const uin } // Compute the variance of log174 - float sum = 0; - float sum2 = 0; - float inv_n = 1.0f / ft8::N; - for (int i = 0; i < ft8::N; ++i) { - sum += log174[i]; + float sum = 0; + float sum2 = 0; + float inv_n = 1.0f / FT8_N; + for (int i = 0; i < FT8_N; ++i) + { + sum += log174[i]; sum2 += log174[i] * log174[i]; } float variance = (sum2 - sum * sum * inv_n) * inv_n; @@ -140,37 +156,42 @@ void extract_likelihood(const MagArray *power, const Candidate & cand, const uin // Normalize log174 such that sigma = 2.83 (Why? It's in WSJT-X, ft8b.f90) // Seems to be 2.83 = sqrt(8). Experimentally sqrt(16) works better. float norm_factor = sqrtf(16.0f / variance); - for (int i = 0; i < ft8::N; ++i) { + for (int i = 0; i < FT8_N; ++i) + { log174[i] *= norm_factor; } } - -static float max2(float a, float b) { +static float max2(float a, float b) +{ return (a >= b) ? a : b; } - -static float max4(float a, float b, float c, float d) { +static float max4(float a, float b, float c, float d) +{ return max2(max2(a, b), max2(c, d)); } - -static void heapify_down(Candidate *heap, int heap_size) { +static void heapify_down(Candidate *heap, int heap_size) +{ // heapify from the root down int current = 0; - while (true) { + while (true) + { int largest = current; int left = 2 * current + 1; int right = left + 1; - if (left < heap_size && heap[left].score < heap[largest].score) { + if (left < heap_size && heap[left].score < heap[largest].score) + { largest = left; } - if (right < heap_size && heap[right].score < heap[largest].score) { + if (right < heap_size && heap[right].score < heap[largest].score) + { largest = right; } - if (largest == current) { + if (largest == current) + { break; } @@ -181,13 +202,15 @@ static void heapify_down(Candidate *heap, int heap_size) { } } - -static void heapify_up(Candidate *heap, int heap_size) { +static void heapify_up(Candidate *heap, int heap_size) +{ // heapify from the last node up int current = heap_size - 1; - while (current > 0) { + while (current > 0) + { int parent = (current - 1) / 2; - if (heap[current].score >= heap[parent].score) { + if (heap[current].score >= heap[parent].score) + { break; } @@ -198,13 +221,14 @@ static void heapify_up(Candidate *heap, int heap_size) { } } - // Compute unnormalized log likelihood log(p(1) / p(0)) of 3 message bits (1 FSK symbol) -static void decode_symbol(const uint8_t *power, const uint8_t *code_map, int bit_idx, float *log174) { +static void decode_symbol(const uint8_t *power, const uint8_t *code_map, int bit_idx, float *log174) +{ // Cleaned up code for the simple case of n_syms==1 float s2[8]; - for (int j = 0; j < 8; ++j) { + for (int j = 0; j < 8; ++j) + { s2[j] = (float)power[code_map[j]]; } @@ -213,26 +237,29 @@ static void decode_symbol(const uint8_t *power, const uint8_t *code_map, int bit log174[bit_idx + 2] = max4(s2[1], s2[3], s2[5], s2[7]) - max4(s2[0], s2[2], s2[4], s2[6]); } - // Compute unnormalized log likelihood log(p(1) / p(0)) of bits corresponding to several FSK symbols at once -static void decode_multi_symbols(const uint8_t *power, int num_bins, int n_syms, const uint8_t *code_map, int bit_idx, float *log174) { +static void decode_multi_symbols(const uint8_t *power, int num_bins, int n_syms, const uint8_t *code_map, int bit_idx, float *log174) +{ // The following section implements what seems to be multiple-symbol decode at one go, - // corresponding to WSJT-X's ft8b.f90. Experimentally found not to be any better than + // corresponding to WSJT-X's ft8b.f90. Experimentally found not to be any better than // 1-symbol decode. const int n_bits = 3 * n_syms; const int n_tones = (1 << n_bits); - + float s2[n_tones]; - for (int j = 0; j < n_tones; ++j) { + for (int j = 0; j < n_tones; ++j) + { int j1 = j & 0x07; - if (n_syms == 1) { + if (n_syms == 1) + { s2[j] = (float)power[code_map[j1]]; continue; } int j2 = (j >> 3) & 0x07; - if (n_syms == 2) { + if (n_syms == 2) + { s2[j] = (float)power[code_map[j2]]; s2[j] += (float)power[code_map[j1] + 4 * num_bins]; continue; @@ -249,25 +276,28 @@ static void decode_multi_symbols(const uint8_t *power, int num_bins, int n_syms, // Extract bit significance (and convert them to float) // 8 FSK tones = 3 bits - for (int i = 0; i < n_bits; ++i) { - if (bit_idx + i >= ft8::N) { + for (int i = 0; i < n_bits; ++i) + { + if (bit_idx + i >= FT8_N) + { // Respect array size break; } uint16_t mask = (n_tones >> (i + 1)); float max_zero = -1000, max_one = -1000; - for (int n = 0; n < n_tones; ++n) { - if (n & mask) { + for (int n = 0; n < n_tones; ++n) + { + if (n & mask) + { max_one = max2(max_one, s2[n]); } - else { + else + { max_zero = max2(max_zero, s2[n]); } } log174[bit_idx + i] = max_one - max_zero; - } + } } - -} // namespace diff --git a/ft8/decode.h b/ft8/decode.h index 0894ac6..e358fb4 100644 --- a/ft8/decode.h +++ b/ft8/decode.h @@ -1,33 +1,32 @@ -#pragma once +#ifndef _INCLUDE_DECODE_H_ +#define _INCLUDE_DECODE_H_ #include -namespace ft8 { - -struct MagArray { - int num_blocks; // number of total blocks (symbols) - int num_bins; // number of FFT bins - int time_osr; // number of time subdivisions - int freq_osr; // number of frequency subdivisions - uint8_t * mag; // FFT magnitudes as [blocks][time_sub][freq_sub][num_bins] -}; - -struct Candidate { - int16_t score; - int16_t time_offset; - int16_t freq_offset; - uint8_t time_sub; - uint8_t freq_sub; -}; +typedef struct +{ + int num_blocks; // number of total blocks (symbols) + int num_bins; // number of FFT bins + int time_osr; // number of time subdivisions + int freq_osr; // number of frequency subdivisions + uint8_t *mag; // FFT magnitudes as [blocks][time_sub][freq_sub][num_bins] +} MagArray; +typedef struct +{ + int16_t score; + int16_t time_offset; + int16_t freq_offset; + uint8_t time_sub; + uint8_t freq_sub; +} Candidate; // Localize top N candidates in frequency and time according to their sync strength (looking at Costas symbols) // We treat and organize the candidate list as a min-heap (empty initially). -int find_sync(const MagArray * power, const uint8_t *sync_map, int num_candidates, Candidate *heap, int min_score = 0); +int find_sync(const MagArray *power, const uint8_t *sync_map, int num_candidates, Candidate *heap, int min_score); - -// Compute log likelihood log(p(1) / p(0)) of 174 message bits +// Compute log likelihood log(p(1) / p(0)) of 174 message bits // for later use in soft-decision LDPC decoding -void extract_likelihood(const MagArray *power, const Candidate & cand, const uint8_t *code_map, float *log174); +void extract_likelihood(const MagArray *power, const Candidate &cand, const uint8_t *code_map, float *log174); -} +#endif // _INCLUDE_DECODE_H_ diff --git a/ft8/encode.cpp b/ft8/encode.cpp index 9903052..609df77 100644 --- a/ft8/encode.cpp +++ b/ft8/encode.cpp @@ -3,26 +3,26 @@ #include -namespace ft8 { - +#define TOPBIT (1 << (CRC_WIDTH - 1)) // Returns 1 if an odd number of bits are set in x, zero otherwise -uint8_t parity8(uint8_t x) { - x ^= x >> 4; // a b c d ae bf cg dh - x ^= x >> 2; // a b ac bd cae dbf aecg bfdh - x ^= x >> 1; // a ab bac acbd bdcae caedbf aecgbfdh - return (x) & 1; +uint8_t parity8(uint8_t x) +{ + x ^= x >> 4; // a b c d ae bf cg dh + x ^= x >> 2; // a b ac bd cae dbf aecg bfdh + x ^= x >> 1; // a ab bac acbd bdcae caedbf aecgbfdh + return (x)&1; } - -// Encode a 91-bit message and return a 174-bit codeword. -// The generator matrix has dimensions (87,87). +// Encode a 91-bit message and return a 174-bit codeword. +// The generator matrix has dimensions (87,87). // The code is a (174,91) regular ldpc code with column weight 3. // The code was generated using the PEG algorithm. // Arguments: // [IN] message - array of 91 bits stored as 12 bytes (MSB first) // [OUT] codeword - array of 174 bits stored as 22 bytes (MSB first) -void encode174(const uint8_t *message, uint8_t *codeword) { +void encode174(const uint8_t *message, uint8_t *codeword) +{ // Here we don't generate the generator bit matrix as in WSJT-X implementation // Instead we access the generator bits straight from the binary representation in kGenerator @@ -31,91 +31,92 @@ void encode174(const uint8_t *message, uint8_t *codeword) { // codeword(K+1:N)=pchecks // printf("Encode "); - // for (int i = 0; i < ft8::K_BYTES; ++i) { + // for (int i = 0; i < FT8_K_BYTES; ++i) { // printf("%02x ", message[i]); // } // printf("\n"); // Fill the codeword with message and zeros, as we will only update binary ones later - for (int j = 0; j < (7 + ft8::N) / 8; ++j) { - codeword[j] = (j < ft8::K_BYTES) ? message[j] : 0; + for (int j = 0; j < (7 + FT8_N) / 8; ++j) + { + codeword[j] = (j < FT8_K_BYTES) ? message[j] : 0; } - uint8_t col_mask = (0x80 >> (ft8::K % 8)); // bitmask of current byte - uint8_t col_idx = ft8::K_BYTES - 1; // index into byte array + uint8_t col_mask = (0x80 >> (FT8_K % 8)); // bitmask of current byte + uint8_t col_idx = FT8_K_BYTES - 1; // index into byte array - // Compute the first part of itmp (1:ft8::M) and store the result in codeword - for (int i = 0; i < ft8::M; ++i) { // do i=1,ft8::M + // Compute the first part of itmp (1:FT8_M) and store the result in codeword + for (int i = 0; i < FT8_M; ++i) + { // do i=1,FT8_M // Fast implementation of bitwise multiplication and parity checking - // Normally nsum would contain the result of dot product between message and kGenerator[i], + // Normally nsum would contain the result of dot product between message and kGenerator[i], // but we only compute the sum modulo 2. uint8_t nsum = 0; - for (int j = 0; j < ft8::K_BYTES; ++j) { - uint8_t bits = message[j] & kGenerator[i][j]; // bitwise AND (bitwise multiplication) - nsum ^= parity8(bits); // bitwise XOR (addition modulo 2) + for (int j = 0; j < FT8_K_BYTES; ++j) + { + uint8_t bits = message[j] & kGenerator[i][j]; // bitwise AND (bitwise multiplication) + nsum ^= parity8(bits); // bitwise XOR (addition modulo 2) } // Check if we need to set a bit in codeword - if (nsum % 2) { // pchecks(i)=mod(nsum,2) - codeword[col_idx] |= col_mask; + if (nsum % 2) + { // pchecks(i)=mod(nsum,2) + codeword[col_idx] |= col_mask; } col_mask >>= 1; - if (col_mask == 0) { - col_mask = 0x80; - ++col_idx; + if (col_mask == 0) + { + col_mask = 0x80; + ++col_idx; } } // printf("Result "); - // for (int i = 0; i < (ft8::N + 7) / 8; ++i) { + // for (int i = 0; i < (FT8_N + 7) / 8; ++i) { // printf("%02x ", codeword[i]); // } // printf("\n"); } - // Compute 14-bit CRC for a sequence of given number of bits +// Adapted from https://barrgroup.com/Embedded-Systems/How-To/CRC-Calculation-C-Code // [IN] message - byte sequence (MSB first) // [IN] num_bits - number of bits in the sequence -uint16_t crc(uint8_t *message, int num_bits) { - // Adapted from https://barrgroup.com/Embedded-Systems/How-To/CRC-Calculation-C-Code - constexpr uint16_t TOPBIT = (1 << (CRC_WIDTH - 1)); - - // printf("CRC, %d bits: ", num_bits); - // for (int i = 0; i < (num_bits + 7) / 8; ++i) { - // printf("%02x ", message[i]); - // } - // printf("\n"); - +uint16_t ft8_crc(uint8_t *message, int num_bits) +{ uint16_t remainder = 0; int idx_byte = 0; // Perform modulo-2 division, a bit at a time. - for (int idx_bit = 0; idx_bit < num_bits; ++idx_bit) { - if (idx_bit % 8 == 0) { + for (int idx_bit = 0; idx_bit < num_bits; ++idx_bit) + { + if (idx_bit % 8 == 0) + { // Bring the next byte into the remainder. remainder ^= (message[idx_byte] << (CRC_WIDTH - 8)); ++idx_byte; } // Try to divide the current data bit. - if (remainder & TOPBIT) { + if (remainder & TOPBIT) + { remainder = (remainder << 1) ^ CRC_POLYNOMIAL; } - else { + else + { remainder = (remainder << 1); } } - // printf("CRC = %04xh\n", remainder & ((1 << CRC_WIDTH) - 1)); + return remainder & ((1 << CRC_WIDTH) - 1); } - // Generate FT8 tone sequence from payload data // [IN] payload - 10 byte array consisting of 77 bit payload (MSB first) // [OUT] itone - array of NN (79) bytes to store the generated tones (encoded as 0..7) -void genft8(const uint8_t *payload, uint8_t *itone) { - uint8_t a91[12]; // Store 77 bits of payload + 14 bits CRC +void genft8(const uint8_t *payload, uint8_t *itone) +{ + uint8_t a91[12]; // Store 77 bits of payload + 14 bits CRC // Copy 77 bits of payload data for (int i = 0; i < 10; i++) @@ -127,7 +128,7 @@ void genft8(const uint8_t *payload, uint8_t *itone) { a91[11] = 0; // Calculate CRC of 12 bytes = 96 bits, see WSJT-X code - uint16_t checksum = ft8::crc(a91, 96 - 14); + uint16_t checksum = ft8_crc(a91, 96 - 14); // Store the CRC at the end of 77 bit message a91[9] |= (uint8_t)(checksum >> 11); @@ -139,34 +140,50 @@ void genft8(const uint8_t *payload, uint8_t *itone) { encode174(a91, codeword); // Message structure: S7 D29 S7 D29 S7 - for (int i = 0; i < 7; ++i) { - itone[i] = kCostas_map[i]; + for (int i = 0; i < 7; ++i) + { + itone[i] = kCostas_map[i]; itone[36 + i] = kCostas_map[i]; itone[72 + i] = kCostas_map[i]; } - int k = 7; // Skip over the first set of Costas symbols + int k = 7; // Skip over the first set of Costas symbols uint8_t mask = 0x80; int i_byte = 0; - for (int j = 0; j < ft8::ND; ++j) { // do j=1,ft8::ND - if (j == 29) { - k += 7; // Skip over the second set of Costas symbols + for (int j = 0; j < FT8_ND; ++j) + { // do j=1,FT8_ND + if (j == 29) + { + k += 7; // Skip over the second set of Costas symbols } // Extract 3 bits from codeword at i-th position uint8_t bits3 = 0; - if (codeword[i_byte] & mask) bits3 |= 4; - if (0 == (mask >>= 1)) { mask = 0x80; i_byte++; } - if (codeword[i_byte] & mask) bits3 |= 2; - if (0 == (mask >>= 1)) { mask = 0x80; i_byte++; } - if (codeword[i_byte] & mask) bits3 |= 1; - if (0 == (mask >>= 1)) { mask = 0x80; i_byte++; } + if (codeword[i_byte] & mask) + bits3 |= 4; + if (0 == (mask >>= 1)) + { + mask = 0x80; + i_byte++; + } + if (codeword[i_byte] & mask) + bits3 |= 2; + if (0 == (mask >>= 1)) + { + mask = 0x80; + i_byte++; + } + if (codeword[i_byte] & mask) + bits3 |= 1; + if (0 == (mask >>= 1)) + { + mask = 0x80; + i_byte++; + } itone[k] = kGray_map[bits3]; ++k; } } - -} // namespace \ No newline at end of file diff --git a/ft8/encode.h b/ft8/encode.h index 1377607..f8d4d6d 100644 --- a/ft8/encode.h +++ b/ft8/encode.h @@ -1,29 +1,27 @@ -#pragma once +#ifndef _INCLUDE_ENCODE_H_ +#define _INCLUDE_ENCODE_H_ #include -namespace ft8 { +// Generate FT8 tone sequence from payload data +// [IN] payload - 9 byte array consisting of 72 bit payload +// [OUT] itone - array of NN (79) bytes to store the generated tones (encoded as 0..7) +void genft8(const uint8_t *payload, uint8_t *itone); - // Generate FT8 tone sequence from payload data - // [IN] payload - 9 byte array consisting of 72 bit payload - // [OUT] itone - array of NN (79) bytes to store the generated tones (encoded as 0..7) - void genft8(const uint8_t *payload, uint8_t *itone); +// Encode an 87-bit message and return a 174-bit codeword. +// The generator matrix has dimensions (87,87). +// The code is a (174,87) regular ldpc code with column weight 3. +// The code was generated using the PEG algorithm. +// After creating the codeword, the columns are re-ordered according to +// "colorder" to make the codeword compatible with the parity-check matrix +// Arguments: +// * message - array of 87 bits stored as 11 bytes (MSB first) +// * codeword - array of 174 bits stored as 22 bytes (MSB first) +void encode174(const uint8_t *message, uint8_t *codeword); +// Compute 14-bit CRC for a sequence of given number of bits +// [IN] message - byte sequence (MSB first) +// [IN] num_bits - number of bits in the sequence +uint16_t ft8_crc(uint8_t *message, int num_bits); - // Encode an 87-bit message and return a 174-bit codeword. - // The generator matrix has dimensions (87,87). - // The code is a (174,87) regular ldpc code with column weight 3. - // The code was generated using the PEG algorithm. - // After creating the codeword, the columns are re-ordered according to - // "colorder" to make the codeword compatible with the parity-check matrix - // Arguments: - // * message - array of 87 bits stored as 11 bytes (MSB first) - // * codeword - array of 174 bits stored as 22 bytes (MSB first) - void encode174(const uint8_t *message, uint8_t *codeword); - - - // Compute 14-bit CRC for a sequence of given number of bits - // [IN] message - byte sequence (MSB first) - // [IN] num_bits - number of bits in the sequence - uint16_t crc(uint8_t *message, int num_bits); -}; +#endif // _INCLUDE_ENCODE_H_ diff --git a/ft8/ldpc.cpp b/ft8/ldpc.cpp index fd1eafb..0c22bf5 100644 --- a/ft8/ldpc.cpp +++ b/ft8/ldpc.cpp @@ -9,65 +9,76 @@ // codeword[i] = log ( P(x=0) / P(x=1) ) // +#include "ldpc.h" +#include "constants.h" + #include #include #include -#include "constants.h" - -namespace ft8 { static int ldpc_check(uint8_t codeword[]); static float fast_tanh(float x); static float fast_atanh(float x); - -// Packs a string of bits each represented as a zero/non-zero byte in plain[], +// Packs a string of bits each represented as a zero/non-zero byte in plain[], // as a string of packed bits starting from the MSB of the first byte of packed[] -void pack_bits(const uint8_t plain[], int num_bits, uint8_t packed[]) { +void pack_bits(const uint8_t plain[], int num_bits, uint8_t packed[]) +{ int num_bytes = (num_bits + 7) / 8; - for (int i = 0; i < num_bytes; ++i) { + for (int i = 0; i < num_bytes; ++i) + { packed[i] = 0; } uint8_t mask = 0x80; - int byte_idx = 0; - for (int i = 0; i < num_bits; ++i) { - if (plain[i]) { + int byte_idx = 0; + for (int i = 0; i < num_bits; ++i) + { + if (plain[i]) + { packed[byte_idx] |= mask; } mask >>= 1; - if (!mask) { + if (!mask) + { mask = 0x80; ++byte_idx; } } } - // codeword is 174 log-likelihoods. // plain is a return value, 174 ints, to be 0 or 1. // max_iters is how hard to try. // ok == 87 means success. -void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok) { - float m[ft8::M][ft8::N]; // ~60 kB - float e[ft8::M][ft8::N]; // ~60 kB - int min_errors = ft8::M; +void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok) +{ + float m[FT8_M][FT8_N]; // ~60 kB + float e[FT8_M][FT8_N]; // ~60 kB + int min_errors = FT8_M; - for (int j = 0; j < ft8::M; j++) { - for (int i = 0; i < ft8::N; i++) { + for (int j = 0; j < FT8_M; j++) + { + for (int i = 0; i < FT8_N; i++) + { m[j][i] = codeword[i]; e[j][i] = 0.0f; } } - for (int iter = 0; iter < max_iters; iter++) { - for (int j = 0; j < ft8::M; j++) { - for (int ii1 = 0; ii1 < kNrw[j]; ii1++) { + for (int iter = 0; iter < max_iters; iter++) + { + for (int j = 0; j < FT8_M; j++) + { + for (int ii1 = 0; ii1 < kNrw[j]; ii1++) + { int i1 = kNm[j][ii1] - 1; float a = 1.0f; - for (int ii2 = 0; ii2 < kNrw[j]; ii2++) { + for (int ii2 = 0; ii2 < kNrw[j]; ii2++) + { int i2 = kNm[j][ii2] - 1; - if (i2 != i1) { + if (i2 != i1) + { a *= fast_tanh(-m[j][i2] / 2.0f); } } @@ -75,7 +86,8 @@ void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok) { } } - for (int i = 0; i < ft8::N; i++) { + for (int i = 0; i < FT8_N; i++) + { float l = codeword[i]; for (int j = 0; j < 3; j++) l += e[kMn[i][j] - 1][i]; @@ -84,21 +96,27 @@ void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok) { int errors = ldpc_check(plain); - if (errors < min_errors) { + if (errors < min_errors) + { // Update the current best result min_errors = errors; - if (errors == 0) { - break; // Found a perfect answer + if (errors == 0) + { + break; // Found a perfect answer } } - for (int i = 0; i < ft8::N; i++) { - for (int ji1 = 0; ji1 < 3; ji1++) { + for (int i = 0; i < FT8_N; i++) + { + for (int ji1 = 0; ji1 < 3; ji1++) + { int j1 = kMn[i][ji1] - 1; float l = codeword[i]; - for (int ji2 = 0; ji2 < 3; ji2++) { - if (ji1 != ji2) { + for (int ji2 = 0; ji2 < 3; ji2++) + { + if (ji1 != ji2) + { int j2 = kMn[i][ji2] - 1; l += e[j2][i]; } @@ -111,55 +129,64 @@ void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok) { *ok = min_errors; } - // // does a 174-bit codeword pass the FT8's LDPC parity checks? // returns the number of parity errors. // 0 means total success. // -static int ldpc_check(uint8_t codeword[]) { +static int ldpc_check(uint8_t codeword[]) +{ int errors = 0; - for (int j = 0; j < ft8::M; ++j) { + for (int j = 0; j < FT8_M; ++j) + { uint8_t x = 0; - for (int i = 0; i < kNrw[j]; ++i) { + for (int i = 0; i < kNrw[j]; ++i) + { x ^= codeword[kNm[j][i] - 1]; } - if (x != 0) { + if (x != 0) + { ++errors; } } return errors; } +void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok) +{ + float tov[FT8_N][3]; + float toc[FT8_M][7]; -void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok) { - float tov[ft8::N][3]; - float toc[ft8::M][7]; - - int min_errors = ft8::M; + int min_errors = FT8_M; int nclast = 0; int ncnt = 0; // initialize messages to checks - for (int i = 0; i < ft8::M; ++i) { - for (int j = 0; j < kNrw[i]; ++j) { + for (int i = 0; i < FT8_M; ++i) + { + for (int j = 0; j < kNrw[i]; ++j) + { toc[i][j] = codeword[kNm[i][j] - 1]; } } - for (int i = 0; i < ft8::N; ++i) { - for (int j = 0; j < 3; ++j) { + for (int i = 0; i < FT8_N; ++i) + { + for (int j = 0; j < 3; ++j) + { tov[i][j] = 0; } } - for (int iter = 0; iter < max_iters; ++iter) { - float zn[ft8::N]; + for (int iter = 0; iter < max_iters; ++iter) + { + float zn[FT8_N]; // Update bit log likelihood ratios (tov=0 in iter 0) - for (int i = 0; i < ft8::N; ++i) { + for (int i = 0; i < FT8_N; ++i) + { zn[i] = codeword[i] + tov[i][0] + tov[i][1] + tov[i][2]; plain[i] = (zn[i] > 0) ? 1 : 0; } @@ -167,42 +194,55 @@ void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok) { // Check to see if we have a codeword (check before we do any iter) int errors = ldpc_check(plain); - if (errors < min_errors) { + if (errors < min_errors) + { // we have a better guess - update the result min_errors = errors; - if (errors == 0) { - break; // Found a perfect answer + if (errors == 0) + { + break; // Found a perfect answer } } - // Send messages from bits to check nodes - for (int i = 0; i < ft8::M; ++i) { - for (int j = 0; j < kNrw[i]; ++j) { + // Send messages from bits to check nodes + for (int i = 0; i < FT8_M; ++i) + { + for (int j = 0; j < kNrw[i]; ++j) + { int ibj = kNm[i][j] - 1; toc[i][j] = zn[ibj]; - for (int kk = 0; kk < 3; ++kk) { + for (int kk = 0; kk < 3; ++kk) + { // subtract off what the bit had received from the check - if (kMn[ibj][kk] - 1 == i) { + if (kMn[ibj][kk] - 1 == i) + { toc[i][j] -= tov[ibj][kk]; + break; } } } } - + // send messages from check nodes to variable nodes - for (int i = 0; i < ft8::M; ++i) { - for (int j = 0; j < kNrw[i]; ++j) { + for (int i = 0; i < FT8_M; ++i) + { + for (int j = 0; j < kNrw[i]; ++j) + { toc[i][j] = fast_tanh(-toc[i][j] / 2); } } - for (int i = 0; i < ft8::N; ++i) { - for (int j = 0; j < 3; ++j) { + for (int i = 0; i < FT8_N; ++i) + { + for (int j = 0; j < 3; ++j) + { int ichk = kMn[i][j] - 1; // kMn(:,j) are the checks that include bit j float Tmn = 1.0f; - for (int k = 0; k < kNrw[ichk]; ++k) { - if (kNm[ichk][k] - 1 != i) { + for (int k = 0; k < kNrw[ichk]; ++k) + { + if (kNm[ichk][k] - 1 != i) + { Tmn *= toc[ichk][k]; } } @@ -218,14 +258,16 @@ void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok) { // http://functions.wolfram.com/ElementaryFunctions/ArcTanh/10/0001/ // https://mathr.co.uk/blog/2017-09-06_approximating_hyperbolic_tangent.html - // thank you Douglas Bagnall // https://math.stackexchange.com/a/446411 -static float fast_tanh(float x) { - if (x < -4.97f) { +static float fast_tanh(float x) +{ + if (x < -4.97f) + { return -1.0f; } - if (x > 4.97f) { + if (x > 4.97f) + { return 1.0f; } float x2 = x * x; @@ -238,8 +280,8 @@ static float fast_tanh(float x) { return a / b; } - -static float fast_atanh(float x) { +static float fast_atanh(float x) +{ float x2 = x * x; //float a = x * (-15015.0f + x2 * (19250.0f + x2 * (-5943.0f + x2 * 256.0f))); //float b = (-15015.0f + x2 * (24255.0f + x2 * (-11025.0f + x2 * 1225.0f))); @@ -250,48 +292,56 @@ static float fast_atanh(float x) { return a / b; } - -static float pltanh(float x) { +static float pltanh(float x) +{ float isign = +1; - if (x < 0) { + if (x < 0) + { isign = -1; x = -x; } - if (x < 0.8f) { + if (x < 0.8f) + { return isign * 0.83 * x; } - if (x < 1.6f) { + if (x < 1.6f) + { return isign * (0.322f * x + 0.4064f); } - if (x < 3.0f) { + if (x < 3.0f) + { return isign * (0.0524f * x + 0.8378f); } - if (x < 7.0f) { + if (x < 7.0f) + { return isign * (0.0012f * x + 0.9914f); } - return isign*0.9998f; + return isign * 0.9998f; } - -static float platanh(float x) { +static float platanh(float x) +{ float isign = +1; - if (x < 0) { + if (x < 0) + { isign = -1; x = -x; } - if (x < 0.664f) { + if (x < 0.664f) + { return isign * x / 0.83f; } - if (x < 0.9217f) { + if (x < 0.9217f) + { return isign * (x - 0.4064f) / 0.322f; } - if (x < 0.9951f) { + if (x < 0.9951f) + { return isign * (x - 0.8378f) / 0.0524f; } - if (x < 0.9998f) { + if (x < 0.9998f) + { return isign * (x - 0.9914f) / 0.0012f; } return isign * 7.0f; } - -} // namespace \ No newline at end of file diff --git a/ft8/ldpc.h b/ft8/ldpc.h index 75290f5..e726589 100644 --- a/ft8/ldpc.h +++ b/ft8/ldpc.h @@ -1,17 +1,18 @@ -#pragma once +#ifndef _INCLUDE_LDPC_H_ +#define _INCLUDE_LDPC_H_ -namespace ft8 { +#include - // codeword is 174 log-likelihoods. - // plain is a return value, 174 ints, to be 0 or 1. - // iters is how hard to try. - // ok == 87 means success. - void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok); +// codeword is 174 log-likelihoods. +// plain is a return value, 174 ints, to be 0 or 1. +// iters is how hard to try. +// ok == 87 means success. +void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok); - void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok); +void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok); - // Packs a string of bits each represented as a zero/non-zero byte in plain[], - // as a string of packed bits starting from the MSB of the first byte of packed[] - void pack_bits(const uint8_t plain[], int num_bits, uint8_t packed[]); +// Packs a string of bits each represented as a zero/non-zero byte in plain[], +// as a string of packed bits starting from the MSB of the first byte of packed[] +void pack_bits(const uint8_t plain[], int num_bits, uint8_t packed[]); -} \ No newline at end of file +#endif // _INCLUDE_LDPC_H_ diff --git a/ft8/pack.cpp b/ft8/pack.cpp index afb5caf..42a95cc 100644 --- a/ft8/pack.cpp +++ b/ft8/pack.cpp @@ -1,12 +1,14 @@ #include "pack.h" - #include "text.h" +#include #include #include #include -namespace ft8 { +#define NTOKENS ((uint32_t)2063592L) +#define MAX22 ((uint32_t)4194304L) +#define MAXGRID4 ((uint16_t)32400) // TODO: This is wasteful, should figure out something more elegant const char A0[] = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+-./?"; @@ -15,19 +17,20 @@ const char A2[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; const char A3[] = "0123456789"; const char A4[] = " ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - -// Pack a special token, a 22-bit hash code, or a valid base call +// Pack a special token, a 22-bit hash code, or a valid base call // into a 28-bit integer. -int32_t pack28(const char *callsign) { - constexpr int32_t NTOKENS = 2063592L; - constexpr int32_t MAX22 = 4194304L; - +int32_t pack28(const char *callsign) +{ // Check for special tokens first - if (starts_with(callsign, "DE ")) return 0; - if (starts_with(callsign, "QRZ ")) return 1; - if (starts_with(callsign, "CQ ")) return 2; + if (starts_with(callsign, "DE ")) + return 0; + if (starts_with(callsign, "QRZ ")) + return 1; + if (starts_with(callsign, "CQ ")) + return 2; - if (starts_with(callsign, "CQ_")) { + if (starts_with(callsign, "CQ_")) + { int nnum = 0, nlet = 0; // TODO: @@ -43,27 +46,33 @@ int32_t pack28(const char *callsign) { char c6[6] = {' ', ' ', ' ', ' ', ' ', ' '}; int length = 0; // strlen(callsign); // We will need it later - while (callsign[length] != ' ' && callsign[length] != 0) { + while (callsign[length] != ' ' && callsign[length] != 0) + { length++; } // Copy callsign to 6 character buffer - if (starts_with(callsign, "3DA0") && length <= 7) { + if (starts_with(callsign, "3DA0") && length <= 7) + { // Work-around for Swaziland prefix: 3DA0XYZ -> 3D0XYZ memcpy(c6, "3D0", 3); memcpy(c6 + 3, callsign + 4, length - 4); } - else if (starts_with(callsign, "3X") && is_letter(callsign[2]) && length <= 7) { + else if (starts_with(callsign, "3X") && is_letter(callsign[2]) && length <= 7) + { // Work-around for Guinea prefixes: 3XA0XYZ -> QA0XYZ memcpy(c6, "Q", 1); memcpy(c6 + 1, callsign + 2, length - 2); } - else { - if (is_digit(callsign[2]) && length <= 6) { + else + { + if (is_digit(callsign[2]) && length <= 6) + { // AB0XYZ memcpy(c6, callsign, length); } - else if (is_digit(callsign[1]) && length <= 5) { + else if (is_digit(callsign[1]) && length <= 5) + { // A0XYZ -> " A0XYZ" memcpy(c6 + 1, callsign, length); } @@ -73,7 +82,7 @@ int32_t pack28(const char *callsign) { int i0, i1, i2, i3, i4, i5; if ((i0 = char_index(A1, c6[0])) >= 0 && (i1 = char_index(A2, c6[1])) >= 0 && (i2 = char_index(A3, c6[2])) >= 0 && (i3 = char_index(A4, c6[3])) >= 0 && - (i4 = char_index(A4, c6[4])) >= 0 && (i5 = char_index(A4, c6[5])) >= 0) + (i4 = char_index(A4, c6[4])) >= 0 && (i5 = char_index(A4, c6[5])) >= 0) { //printf("Pack28: idx=[%d, %d, %d, %d, %d, %d]\n", i0, i1, i2, i3, i4, i5); // This is a standard callsign @@ -100,48 +109,56 @@ int32_t pack28(const char *callsign) { return -1; } - // Check if a string could be a valid standard callsign or a valid // compound callsign. // Return base call "bc" and a logical "cok" indicator. -bool chkcall(const char *call, char *bc) { - int length = strlen(call); // n1=len_trim(w) - if (length > 11) return false; - if (0 != strchr(call, '.')) return false; - if (0 != strchr(call, '+')) return false; - if (0 != strchr(call, '-')) return false; - if (0 != strchr(call, '?')) return false; - if (length > 6 && 0 != strchr(call, '/')) return false; +bool chkcall(const char *call, char *bc) +{ + int length = strlen(call); // n1=len_trim(w) + if (length > 11) + return false; + if (0 != strchr(call, '.')) + return false; + if (0 != strchr(call, '+')) + return false; + if (0 != strchr(call, '-')) + return false; + if (0 != strchr(call, '?')) + return false; + if (length > 6 && 0 != strchr(call, '/')) + return false; // TODO: implement suffix parsing (or rework?) - //bc=w(1:6) - //i0=char_index(w,'/') - //if(max(i0-1,n1-i0).gt.6) go to 100 !Base call must be < 7 characters - //if(i0.ge.2 .and. i0.le.n1-1) then !Extract base call from compound call - // if(i0-1.le.n1-i0) bc=w(i0+1:n1)//' ' - // if(i0-1.gt.n1-i0) bc=w(1:i0-1)//' ' + //bc=w(1:6) + //i0=char_index(w,'/') + //if(max(i0-1,n1-i0).gt.6) go to 100 !Base call must be < 7 characters + //if(i0.ge.2 .and. i0.le.n1-1) then !Extract base call from compound call + // if(i0-1.le.n1-i0) bc=w(i0+1:n1)//' ' + // if(i0-1.gt.n1-i0) bc=w(1:i0-1)//' ' return true; } - -uint16_t packgrid(const char *grid4) { - constexpr uint16_t MAXGRID4 = 32400; - - if (grid4 == 0) { +uint16_t packgrid(const char *grid4) +{ + if (grid4 == 0) + { // Two callsigns only, no report/grid return MAXGRID4 + 1; } // Take care of special cases - if (equals(grid4, "RRR")) return MAXGRID4 + 2; - if (equals(grid4, "RR73")) return MAXGRID4 + 3; - if (equals(grid4, "73")) return MAXGRID4 + 4; + if (equals(grid4, "RRR")) + return MAXGRID4 + 2; + if (equals(grid4, "RR73")) + return MAXGRID4 + 3; + if (equals(grid4, "73")) + return MAXGRID4 + 4; // Check for standard 4 letter grid - if (in_range(grid4[0], 'A', 'R') && + if (in_range(grid4[0], 'A', 'R') && in_range(grid4[1], 'A', 'R') && - is_digit(grid4[2]) && is_digit(grid4[3])) + is_digit(grid4[2]) && is_digit(grid4[3])) { //if (w(3).eq.'R ') ir=1 uint16_t igrid4 = (grid4[0] - 'A'); @@ -153,48 +170,55 @@ uint16_t packgrid(const char *grid4) { // Parse report: +dd / -dd / R+dd / R-dd // TODO: check the range of dd - if (grid4[0] == 'R') { + if (grid4[0] == 'R') + { int dd = dd_to_int(grid4 + 1, 3); uint16_t irpt = 35 + dd; - return (MAXGRID4 + irpt) | 0x8000; // ir = 1 + return (MAXGRID4 + irpt) | 0x8000; // ir = 1 } - else { + else + { int dd = dd_to_int(grid4, 3); uint16_t irpt = 35 + dd; - return (MAXGRID4 + irpt); // ir = 0 + return (MAXGRID4 + irpt); // ir = 0 } return MAXGRID4 + 1; } // Pack Type 1 (Standard 77-bit message) and Type 2 (ditto, with a "/P" call) -int pack77_1(const char *msg, uint8_t *b77) { +int pack77_1(const char *msg, uint8_t *b77) +{ // Locate the first delimiter const char *s1 = strchr(msg, ' '); - if (s1 == 0) return -1; + if (s1 == 0) + return -1; - const char *call1 = msg; // 1st call - const char *call2 = s1 + 1; // 2nd call + const char *call1 = msg; // 1st call + const char *call2 = s1 + 1; // 2nd call int32_t n28a = pack28(call1); int32_t n28b = pack28(call2); - - if (n28a < 0 || n28b < 0) return -1; + + if (n28a < 0 || n28b < 0) + return -1; uint16_t igrid4; // Locate the second delimiter const char *s2 = strchr(s1 + 1, ' '); - if (s2 != 0) { + if (s2 != 0) + { igrid4 = packgrid(s2 + 1); } - else { + else + { // Two callsigns, no grid/report igrid4 = packgrid(0); } uint8_t i3 = 1; // No suffix or /R - + // TODO: check for suffixes // if(char_index(w(1),'/P').ge.4 .or. char_index(w(2),'/P').ge.4) i3=2 !Type 2, with "/P" // if(char_index(w(1),'/P').ge.4 .or. char_index(w(1),'/R').ge.4) ipa=1 @@ -206,7 +230,7 @@ int pack77_1(const char *msg, uint8_t *b77) { // Pack into (28 + 1) + (28 + 1) + (1 + 15) + 3 bits // write(c77,1000) n28a,ipa,n28b,ipb,ir,igrid4,i3 - // 1000 format(2(b28.28,b1),b1,b15.15,b3.3) + // 1000 format(2(b28.28,b1),b1,b15.15,b3.3) b77[0] = (n28a >> 21); b77[1] = (n28a >> 13); @@ -222,41 +246,48 @@ int pack77_1(const char *msg, uint8_t *b77) { return 0; } - -void packtext77(const char *text, uint8_t *b77) { +void packtext77(const char *text, uint8_t *b77) +{ int length = strlen(text); // Skip leading and trailing spaces - while (*text == ' ' && *text != 0) { + while (*text == ' ' && *text != 0) + { ++text; --length; } - while (length > 0 && text[length - 1] == ' ') { + while (length > 0 && text[length - 1] == ' ') + { --length; } // Clear the first 72 bits representing a long number - for (int i = 0; i < 9; ++i) { + for (int i = 0; i < 9; ++i) + { b77[i] = 0; } - // Now express the text as base-42 number stored + // Now express the text as base-42 number stored // in the first 72 bits of b77 - for (int j = 0; j < 13; ++j) { + for (int j = 0; j < 13; ++j) + { // Multiply the long integer in b77 by 42 uint16_t x = 0; - for (int i = 8; i >= 0; --i) { + for (int i = 8; i >= 0; --i) + { x += b77[i] * (uint16_t)42; b77[i] = (x & 0xFF); x >>= 8; } // Get the index of the current char - if (j < length) { + if (j < length) + { int q = char_index(A0, text[j]); x = (q > 0) ? q : 0; } - else { + else + { x = 0; } // Here we double each added number in order to have the result multiplied @@ -264,8 +295,10 @@ void packtext77(const char *text, uint8_t *b77) { x <<= 1; // Now add the number to our long number - for (int i = 8; i >= 0; --i) { - if (x == 0) break; + for (int i = 8; i >= 0; --i) + { + if (x == 0) + break; x += b77[i]; b77[i] = (x & 0xFF); x >>= 8; @@ -277,10 +310,11 @@ void packtext77(const char *text, uint8_t *b77) { b77[9] &= 0x00; } - -int pack77(const char *msg, uint8_t *c77) { +int pack77(const char *msg, uint8_t *c77) +{ // Check Type 1 (Standard 77-bit message) or Type 2, with optional "/P" - if (0 == pack77_1(msg, c77)) { + if (0 == pack77_1(msg, c77)) + { return 0; } @@ -297,15 +331,12 @@ int pack77(const char *msg, uint8_t *c77) { return 0; } -}; // namespace - #ifdef UNIT_TEST #include -using namespace std; - -bool test1() { +bool test1() +{ const char *inputs[] = { "", " ", @@ -317,10 +348,10 @@ bool test1() { "LL3JG", "LL3AJG", "CQ ", - 0 - }; + 0}; - for (int i = 0; inputs[i]; ++i) { + for (int i = 0; inputs[i]; ++i) + { int32_t result = ft8_v2::pack28(inputs[i]); printf("pack28(\"%s\") = %d\n", inputs[i], result); } @@ -328,7 +359,8 @@ bool test1() { return true; } -bool test2() { +bool test2() +{ const char *inputs[] = { "CQ LL3JG", "CQ LL3JG KO26", @@ -336,14 +368,15 @@ bool test2() { "L0UAA LL3JG +02", "L0UAA LL3JG RRR", "L0UAA LL3JG 73", - 0 - }; + 0}; - for (int i = 0; inputs[i]; ++i) { + for (int i = 0; inputs[i]; ++i) + { uint8_t result[10]; int rc = ft8_v2::pack77_1(inputs[i], result); printf("pack77_1(\"%s\") = %d\t[", inputs[i], rc); - for (int j = 0; j < 10; ++j) { + for (int j = 0; j < 10; ++j) + { printf("%02x ", result[j]); } printf("]\n"); @@ -352,7 +385,8 @@ bool test2() { return true; } -int main() { +int main() +{ test1(); test2(); return 0; diff --git a/ft8/pack.h b/ft8/pack.h index 1b2ced4..baa42f8 100644 --- a/ft8/pack.h +++ b/ft8/pack.h @@ -1,12 +1,11 @@ -#pragma once +#ifndef _INCLUDE_PACK_H_ +#define _INCLUDE_PACK_H_ #include -namespace ft8 { +// Pack FT8 text message into 72 bits +// [IN] msg - FT8 message (e.g. "CQ TE5T KN01") +// [OUT] c77 - 10 byte array to store the 77 bit payload (MSB first) +int pack77(const char *msg, uint8_t *c77); - // Pack FT8 text message into 72 bits - // [IN] msg - FT8 message (e.g. "CQ TE5T KN01") - // [OUT] c77 - 10 byte array to store the 77 bit payload (MSB first) - int pack77(const char *msg, uint8_t *c77); - -} \ No newline at end of file +#endif // _INCLUDE_PACK_H_ diff --git a/ft8/text.cpp b/ft8/text.cpp index c700a93..aae5705 100644 --- a/ft8/text.cpp +++ b/ft8/text.cpp @@ -2,80 +2,94 @@ #include -namespace ft8 { - -const char * trim_front(const char *str) { +const char *trim_front(const char *str) +{ // Skip leading whitespace - while (*str == ' ') { + while (*str == ' ') + { str++; - } + } return str; } -void trim_back(char *str) { +void trim_back(char *str) +{ // Skip trailing whitespace by replacing it with '\0' characters int idx = strlen(str) - 1; - while (idx >= 0 && str[idx] == ' ') { + while (idx >= 0 && str[idx] == ' ') + { str[idx--] = '\0'; } } // 1) trims a string from the back by changing whitespaces to '\0' // 2) trims a string from the front by skipping whitespaces -char * trim(char *str) { +char *trim(char *str) +{ str = (char *)trim_front(str); trim_back(str); // return a pointer to the first non-whitespace character - return str; + return str; } -char to_upper(char c) { +char to_upper(char c) +{ return (c >= 'a' && c <= 'z') ? (c - 'a' + 'A') : c; } -bool is_digit(char c) { +bool is_digit(char c) +{ return (c >= '0') && (c <= '9'); } -bool is_letter(char c) { +bool is_letter(char c) +{ return ((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')); } -bool is_space(char c) { +bool is_space(char c) +{ return (c == ' '); } -bool in_range(char c, char min, char max) { +bool in_range(char c, char min, char max) +{ return (c >= min) && (c <= max); } -bool starts_with(const char *string, const char *prefix) { +bool starts_with(const char *string, const char *prefix) +{ return 0 == memcmp(string, prefix, strlen(prefix)); } -bool equals(const char *string1, const char *string2) { +bool equals(const char *string1, const char *string2) +{ return 0 == strcmp(string1, string2); } - -int char_index(const char *string, char c) { - for (int i = 0; *string; ++i, ++string) { - if (c == *string) { +int char_index(const char *string, char c) +{ + for (int i = 0; *string; ++i, ++string) + { + if (c == *string) + { return i; } } - return -1; // Not found + return -1; // Not found } - -// Text message formatting: +// Text message formatting: // - replaces lowercase letters with uppercase // - merges consecutive spaces into single space -void fmtmsg(char *msg_out, const char *msg_in) { +void fmtmsg(char *msg_out, const char *msg_in) +{ char c; char last_out = 0; - while ( (c = *msg_in) ) { - if (c != ' ' || last_out != ' ') { + while ((c = *msg_in)) + { + if (c != ' ' || last_out != ' ') + { last_out = to_upper(c); *msg_out = last_out; ++msg_out; @@ -85,24 +99,29 @@ void fmtmsg(char *msg_out, const char *msg_in) { *msg_out = 0; // Add zero termination } - // Parse a 2 digit integer from string -int dd_to_int(const char *str, int length) { +int dd_to_int(const char *str, int length) +{ int result = 0; bool negative; int i; - if (str[0] == '-') { + if (str[0] == '-') + { negative = true; - i = 1; // Consume the - sign + i = 1; // Consume the - sign } - else { + else + { negative = false; - i = (str[0] == '+') ? 1 : 0; // Consume a + sign if found + i = (str[0] == '+') ? 1 : 0; // Consume a + sign if found } - - while (i < length) { - if (str[i] == 0) break; - if (!is_digit(str[i])) break; + + while (i < length) + { + if (str[i] == 0) + break; + if (!is_digit(str[i])) + break; result *= 10; result += (str[i] - '0'); ++i; @@ -111,25 +130,29 @@ int dd_to_int(const char *str, int length) { return negative ? -result : result; } - // Convert a 2 digit integer to string -void int_to_dd(char *str, int value, int width, bool full_sign) { - if (value < 0) { +void int_to_dd(char *str, int value, int width, bool full_sign) +{ + if (value < 0) + { *str = '-'; ++str; value = -value; } - else if (full_sign) { + else if (full_sign) + { *str = '+'; ++str; } int divisor = 1; - for (int i = 0; i < width - 1; ++i) { + for (int i = 0; i < width - 1; ++i) + { divisor *= 10; } - while (divisor >= 1) { + while (divisor >= 1) + { int digit = value / divisor; *str = '0' + digit; @@ -138,7 +161,7 @@ void int_to_dd(char *str, int value, int width, bool full_sign) { value -= digit * divisor; divisor /= 10; } - *str = 0; // Add zero terminator + *str = 0; // Add zero terminator } // convert integer index to ASCII character according to one of 6 tables: @@ -148,59 +171,83 @@ void int_to_dd(char *str, int value, int width, bool full_sign) { // table 3: "0123456789" // table 4: " ABCDEFGHIJKLMNOPQRSTUVWXYZ" // table 5: " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ/" -char charn(int c, int table_idx) { - if (table_idx != 2 && table_idx != 3) { - if (c == 0) return ' '; +char charn(int c, int table_idx) +{ + if (table_idx != 2 && table_idx != 3) + { + if (c == 0) + return ' '; c -= 1; } - if (table_idx != 4) { - if (c < 10) return '0' + c; + if (table_idx != 4) + { + if (c < 10) + return '0' + c; c -= 10; } - if (table_idx != 3) { - if (c < 26) return 'A' + c; + if (table_idx != 3) + { + if (c < 26) + return 'A' + c; c -= 26; } - - if (table_idx == 0) { - if (c < 5) return "+-./?" [c]; + + if (table_idx == 0) + { + if (c < 5) + return "+-./?"[c]; } - else if (table_idx == 5) { - if (c == 0) return '/'; + else if (table_idx == 5) + { + if (c == 0) + return '/'; } return '_'; // unknown character, should never get here } // Convert character to its index (charn in reverse) according to a table -int nchar(char c, int table_idx) { +int nchar(char c, int table_idx) +{ int n = 0; - if (table_idx != 2 && table_idx != 3) { - if (c == ' ') return n + 0; + if (table_idx != 2 && table_idx != 3) + { + if (c == ' ') + return n + 0; n += 1; } - if (table_idx != 4) { - if (c >= '0' && c <= '9') return n + (c - '0'); + if (table_idx != 4) + { + if (c >= '0' && c <= '9') + return n + (c - '0'); n += 10; } - if (table_idx != 3) { - if (c >= 'A' && c <= 'Z') return n + (c - 'A'); + if (table_idx != 3) + { + if (c >= 'A' && c <= 'Z') + return n + (c - 'A'); n += 26; } - - if (table_idx == 0) { - if (c == '+') return n + 0; - if (c == '-') return n + 1; - if (c == '.') return n + 2; - if (c == '/') return n + 3; - if (c == '?') return n + 4; - } - else if (table_idx == 5) { - if (c == '/') return n + 0; - } - - // Character not found - return -1; -} -} // namespace \ No newline at end of file + if (table_idx == 0) + { + if (c == '+') + return n + 0; + if (c == '-') + return n + 1; + if (c == '.') + return n + 2; + if (c == '/') + return n + 3; + if (c == '?') + return n + 4; + } + else if (table_idx == 5) + { + if (c == '/') + return n + 0; + } + + // Character not found + return -1; +} diff --git a/ft8/text.h b/ft8/text.h index 32ec747..9421e3c 100644 --- a/ft8/text.h +++ b/ft8/text.h @@ -1,33 +1,37 @@ -#pragma once +#ifndef _INCLUDE_TEXT_H_ +#define _INCLUDE_TEXT_H_ -namespace ft8 { - // Utility functions for characters and strings +#include +#include - const char * trim_front(const char *str); - void trim_back(char *str); - char * trim(char *str); +// Utility functions for characters and strings - char to_upper(char c); - bool is_digit(char c); - bool is_letter(char c); - bool is_space(char c); - bool in_range(char c, char min, char max); - bool starts_with(const char *string, const char *prefix); - bool equals(const char *string1, const char *string2); +const char *trim_front(const char *str); +void trim_back(char *str); +char *trim(char *str); - int char_index(const char *string, char c); +char to_upper(char c); +bool is_digit(char c); +bool is_letter(char c); +bool is_space(char c); +bool in_range(char c, char min, char max); +bool starts_with(const char *string, const char *prefix); +bool equals(const char *string1, const char *string2); - // Text message formatting: - // - replaces lowercase letters with uppercase - // - merges consecutive spaces into single space - void fmtmsg(char *msg_out, const char *msg_in); +int char_index(const char *string, char c); - // Parse a 2 digit integer from string - int dd_to_int(const char *str, int length); +// Text message formatting: +// - replaces lowercase letters with uppercase +// - merges consecutive spaces into single space +void fmtmsg(char *msg_out, const char *msg_in); - // Convert a 2 digit integer to string - void int_to_dd(char *str, int value, int width, bool full_sign = false); +// Parse a 2 digit integer from string +int dd_to_int(const char *str, int length); - char charn(int c, int table_idx); - int nchar(char c, int table_idx); -} \ No newline at end of file +// Convert a 2 digit integer to string +void int_to_dd(char *str, int value, int width, bool full_sign = false); + +char charn(int c, int table_idx); +int nchar(char c, int table_idx); + +#endif // _INCLUDE_TEXT_H_ diff --git a/ft8/unpack.cpp b/ft8/unpack.cpp index b509de5..7bfc6c0 100644 --- a/ft8/unpack.cpp +++ b/ft8/unpack.cpp @@ -3,53 +3,61 @@ #include -namespace ft8 { - -//const uint32_t NBASE = 37L*36L*10L*27L*27L*27L; -const uint32_t MAX22 = 4194304L; -const uint32_t NTOKENS = 2063592L; -const uint16_t MAXGRID4 = 32400L; - +//#define NBASE (uint32_t)(37L*36L*10L*27L*27L*27L) +#define MAX22 ((uint32_t)4194304L) +#define NTOKENS ((uint32_t)2063592L) +#define MAXGRID4 ((uint16_t)32400L) // n28 is a 28-bit integer, e.g. n28a or n28b, containing all the // call sign bits from a packed message. -int unpack28(uint32_t n28, uint8_t ip, uint8_t i3, char *result) { +int unpack28(uint32_t n28, uint8_t ip, uint8_t i3, char *result) +{ // Check for special tokens DE, QRZ, CQ, CQ_nnn, CQ_aaaa - if (n28 < NTOKENS) { - if (n28 <= 2) { - if (n28 == 0) strcpy(result, "DE"); - if (n28 == 1) strcpy(result, "QRZ"); - if (n28 == 2) strcpy(result, "CQ"); - return 0; // Success + if (n28 < NTOKENS) + { + if (n28 <= 2) + { + if (n28 == 0) + strcpy(result, "DE"); + if (n28 == 1) + strcpy(result, "QRZ"); + if (n28 == 2) + strcpy(result, "CQ"); + return 0; // Success } - if (n28 <= 1002) { + if (n28 <= 1002) + { // CQ_nnn with 3 digits strcpy(result, "CQ "); int_to_dd(result + 3, n28 - 3, 3); - return 0; // Success + return 0; // Success } - if (n28 <= 532443L) { + if (n28 <= 532443L) + { // CQ_aaaa with 4 alphanumeric symbols uint32_t n = n28 - 1003; char aaaa[5]; aaaa[4] = '\0'; - for (int i = 3; /* */; --i) { + for (int i = 3; /* */; --i) + { aaaa[i] = charn(n % 27, 4); - if (i == 0) break; + if (i == 0) + break; n /= 27; } strcpy(result, "CQ "); strcat(result, trim_front(aaaa)); - return 0; // Success + return 0; // Success } // ? TODO: unspecified in the WSJT-X code return -1; } n28 = n28 - NTOKENS; - if (n28 < MAX22) { + if (n28 < MAX22) + { // This is a 22-bit hash of a result //call hash22(n22,c13) !Retrieve result from hash table // TODO: implement @@ -80,49 +88,55 @@ int unpack28(uint32_t n28, uint8_t ip, uint8_t i3, char *result) { // Skip trailing and leading whitespace in case of a short callsign strcpy(result, trim(callsign)); - if (strlen(result) == 0) return -1; + if (strlen(result) == 0) + return -1; // Check if we should append /R or /P suffix - if (ip) { - if (i3 == 1) { + if (ip) + { + if (i3 == 1) + { strcat(result, "/R"); } - else if (i3 == 2) { + else if (i3 == 2) + { strcat(result, "/P"); } } - - return 0; // Success + + return 0; // Success } - -int unpack_type1(const uint8_t *a77, uint8_t i3, char *field1, char *field2, char *field3) { +int unpack_type1(const uint8_t *a77, uint8_t i3, char *field1, char *field2, char *field3) +{ uint32_t n28a, n28b; uint16_t igrid4; - uint8_t ir; - + uint8_t ir; + // Extract packed fields // read(c77,1000) n28a,ipa,n28b,ipb,ir,igrid4,i3 // 1000 format(2(b28,b1),b1,b15,b3) - n28a = (a77[0] << 21); + n28a = (a77[0] << 21); n28a |= (a77[1] << 13); n28a |= (a77[2] << 5); n28a |= (a77[3] >> 3); - n28b = ((a77[3] & 0x07) << 26); + n28b = ((a77[3] & 0x07) << 26); n28b |= (a77[4] << 18); n28b |= (a77[5] << 10); n28b |= (a77[6] << 2); n28b |= (a77[7] >> 6); - ir = ((a77[7] & 0x20) >> 5); - igrid4 = ((a77[7] & 0x1F) << 10); + ir = ((a77[7] & 0x20) >> 5); + igrid4 = ((a77[7] & 0x1F) << 10); igrid4 |= (a77[8] << 2); igrid4 |= (a77[9] >> 6); // Unpack both callsigns - if (unpack28(n28a >> 1, n28a & 0x01, i3, field1) < 0) { + if (unpack28(n28a >> 1, n28a & 0x01, i3, field1) < 0) + { return -1; } - if (unpack28(n28b >> 1, n28b & 0x01, i3, field2) < 0) { + if (unpack28(n28b >> 1, n28b & 0x01, i3, field2) < 0) + { return -2; } // Fix "CQ_" to "CQ " -> already done in unpack28() @@ -135,11 +149,13 @@ int unpack_type1(const uint8_t *a77, uint8_t i3, char *field1, char *field2, cha // save_hash_call(field2) // } - if (igrid4 <= MAXGRID4) { + if (igrid4 <= MAXGRID4) + { // Extract 4 symbol grid locator char *dst = field3; uint16_t n = igrid4; - if (ir > 0) { + if (ir > 0) + { // In case of ir=1 add an "R" before grid dst = stpcpy(dst, "R "); } @@ -155,19 +171,26 @@ int unpack_type1(const uint8_t *a77, uint8_t i3, char *field1, char *field2, cha // if(msg(1:3).eq.'CQ ' .and. ir.eq.1) unpk77_success=.false. // if (ir > 0 && strncmp(field1, "CQ", 2) == 0) return -1; } - else { + else + { // Extract report int irpt = igrid4 - MAXGRID4; // Check special cases first - if (irpt == 1) field3[0] = '\0'; - else if (irpt == 2) strcpy(field3, "RRR"); - else if (irpt == 3) strcpy(field3, "RR73"); - else if (irpt == 4) strcpy(field3, "73"); - else if (irpt >= 5) { + if (irpt == 1) + field3[0] = '\0'; + else if (irpt == 2) + strcpy(field3, "RRR"); + else if (irpt == 3) + strcpy(field3, "RR73"); + else if (irpt == 4) + strcpy(field3, "73"); + else if (irpt >= 5) + { char *dst = field3; // Extract signal report as a two digit number with a + or - sign - if (ir > 0) { + if (ir > 0) + { *dst++ = 'R'; // Add "R" before report } int_to_dd(dst, irpt - 35, 2, true); @@ -176,50 +199,55 @@ int unpack_type1(const uint8_t *a77, uint8_t i3, char *field1, char *field2, cha // if (irpt >= 2 && strncmp(field1, "CQ", 2) == 0) return -1; } - return 0; // Success + return 0; // Success } - -int unpack_text(const uint8_t *a71, char *text) { +int unpack_text(const uint8_t *a71, char *text) +{ // TODO: test uint8_t b71[9]; uint8_t carry = 0; - for (int i = 0; i < 9; ++i) { + for (int i = 0; i < 9; ++i) + { b71[i] = carry | (a71[i] >> 1); carry = (a71[i] & 1) ? 0x80 : 0; } - char c14[14]; - c14[13] = 0; - for (int idx = 12; idx >= 0; --idx) { + char c14[14]; + c14[13] = 0; + for (int idx = 12; idx >= 0; --idx) + { // Divide the long integer in b71 by 42 uint16_t rem = 0; - for (int i = 0; i < 9; ++i) { + for (int i = 0; i < 9; ++i) + { rem = (rem << 8) | b71[i]; b71[i] = rem / 42; - rem = rem % 42; + rem = rem % 42; } c14[idx] = charn(rem, 0); } - strcpy(text, trim(c14)); - return 0; // Success + strcpy(text, trim(c14)); + return 0; // Success } - -int unpack_telemetry(const uint8_t *a71, char *telemetry) { +int unpack_telemetry(const uint8_t *a71, char *telemetry) +{ uint8_t b71[9]; // Shift bits in a71 right by 1 uint8_t carry = 0; - for (int i = 0; i < 9; ++i) { + for (int i = 0; i < 9; ++i) + { b71[i] = (carry << 7) | (a71[i] >> 1); carry = (a71[i] & 0x01); } // Convert b71 to hexadecimal string - for (int i = 0; i < 9; ++i) { + for (int i = 0; i < 9; ++i) + { uint8_t nibble1 = (b71[i] >> 4); uint8_t nibble2 = (b71[i] & 0x0F); char c1 = (nibble1 > 9) ? (nibble1 - 10 + 'A') : nibble1 + '0'; @@ -232,45 +260,46 @@ int unpack_telemetry(const uint8_t *a71, char *telemetry) { return 0; } - //none standard for wsjt-x 2.0 //by KD8CEC -int unpack_nonstandard(const uint8_t *a77, char *field1, char *field2, char *field3) +int unpack_nonstandard(const uint8_t *a77, char *field1, char *field2, char *field3) { -/* + /* wsjt-x 2.1.0 rc5 read(c77,1050) n12,n58,iflip,nrpt,icq 1050 format(b12,b58,b1,b2,b1) */ - uint32_t n12, iflip, nrpt, icq; - uint64_t n58; - n12 = (a77[0] << 4); //11 ~4 : 8 - n12 |= (a77[1] >> 4); //3~0 : 12 + uint32_t n12, iflip, nrpt, icq; + uint64_t n58; + n12 = (a77[0] << 4); //11 ~4 : 8 + n12 |= (a77[1] >> 4); //3~0 : 12 - n58 = ((uint64_t)(a77[1] & 0x0F) << 54); //57 ~ 54 : 4 - n58 |= ((uint64_t)a77[2] << 46); //53 ~ 46 : 12 - n58 |= ((uint64_t)a77[3] << 38); //45 ~ 38 : 12 - n58 |= ((uint64_t)a77[4] << 30); //37 ~ 30 : 12 - n58 |= ((uint64_t)a77[5] << 22); //29 ~ 22 : 12 - n58 |= ((uint64_t)a77[6] << 14); //21 ~ 14 : 12 - n58 |= ((uint64_t)a77[7] << 6); //13 ~ 6 : 12 - n58 |= ((uint64_t)a77[8] >> 2); //5 ~ 0 : 765432 10 + n58 = ((uint64_t)(a77[1] & 0x0F) << 54); //57 ~ 54 : 4 + n58 |= ((uint64_t)a77[2] << 46); //53 ~ 46 : 12 + n58 |= ((uint64_t)a77[3] << 38); //45 ~ 38 : 12 + n58 |= ((uint64_t)a77[4] << 30); //37 ~ 30 : 12 + n58 |= ((uint64_t)a77[5] << 22); //29 ~ 22 : 12 + n58 |= ((uint64_t)a77[6] << 14); //21 ~ 14 : 12 + n58 |= ((uint64_t)a77[7] << 6); //13 ~ 6 : 12 + n58 |= ((uint64_t)a77[8] >> 2); //5 ~ 0 : 765432 10 - iflip = (a77[8] >> 1) & 0x01; //76543210 - nrpt = ((a77[8] & 0x01) << 1); - nrpt |= (a77[9] >> 7); //76543210 - icq = ((a77[9] >> 6) & 0x01); + iflip = (a77[8] >> 1) & 0x01; //76543210 + nrpt = ((a77[8] & 0x01) << 1); + nrpt |= (a77[9] >> 7); //76543210 + icq = ((a77[9] >> 6) & 0x01); - char c11[12]; - c11[11] = '\0'; + char c11[12]; + c11[11] = '\0'; - for (int i = 10; /* no condition */ ; --i) { - c11[i] = charn(n58 % 38, 5); - if (i == 0) break; - n58 /= 38; + for (int i = 10; /* no condition */; --i) + { + c11[i] = charn(n58 % 38, 5); + if (i == 0) + break; + n58 /= 38; } - char call_3[15]; + char call_3[15]; // should replace with hash12(n12, call_3); // strcpy(call_3, "<...>"); call_3[0] = '<'; @@ -278,32 +307,37 @@ int unpack_nonstandard(const uint8_t *a77, char *field1, char *field2, char *fie call_3[5] = '>'; call_3[6] = '\0'; - char * call_1 = (iflip) ? c11 : call_3; - char * call_2 = (iflip) ? call_3 : c11; - //save_hash_call(c11_trimmed); + char *call_1 = (iflip) ? c11 : call_3; + char *call_2 = (iflip) ? call_3 : c11; + //save_hash_call(c11_trimmed); - if (icq == 0) { - strcpy(field1, trim(call_1)); - if (nrpt == 1) - strcpy(field3, "RRR"); - else if (nrpt == 2) - strcpy(field3, "RR73"); - else if (nrpt == 3) - strcpy(field3, "73"); - else { + if (icq == 0) + { + strcpy(field1, trim(call_1)); + if (nrpt == 1) + strcpy(field3, "RRR"); + else if (nrpt == 2) + strcpy(field3, "RR73"); + else if (nrpt == 3) + strcpy(field3, "73"); + else + { field3[0] = '\0'; } - } else { - strcpy(field1, "CQ"); + } + else + { + strcpy(field1, "CQ"); field3[0] = '\0'; - } + } strcpy(field2, trim(call_2)); return 0; } -int unpack77_fields(const uint8_t *a77, char *field1, char *field2, char *field3) { - uint8_t n3, i3; +int unpack77_fields(const uint8_t *a77, char *field1, char *field2, char *field3) +{ + uint8_t n3, i3; // Extract n3 (bits 71..73) and i3 (bits 74..76) n3 = ((a77[8] << 2) & 0x04) | ((a77[9] >> 6) & 0x03); @@ -311,7 +345,8 @@ int unpack77_fields(const uint8_t *a77, char *field1, char *field2, char *field3 field1[0] = field2[0] = field3[0] = '\0'; - if (i3 == 0 && n3 == 0) { + if (i3 == 0 && n3 == 0) + { // 0.0 Free text return unpack_text(a77, field1); } @@ -325,22 +360,25 @@ int unpack77_fields(const uint8_t *a77, char *field1, char *field2, char *field3 // // 0.3 WA9XYZ KA1ABC R 16A EMA 28 28 1 4 3 7 71 ARRL Field Day // // 0.4 WA9XYZ KA1ABC R 32A EMA 28 28 1 4 3 7 71 ARRL Field Day // } - else if (i3 == 0 && n3 == 5) { + else if (i3 == 0 && n3 == 5) + { // 0.5 0123456789abcdef01 71 71 Telemetry (18 hex) return unpack_telemetry(a77, field1); } - else if (i3 == 1 || i3 == 2) { + else if (i3 == 1 || i3 == 2) + { // Type 1 (standard message) or Type 2 ("/P" form for EU VHF contest) return unpack_type1(a77, i3, field1, field2, field3); } // else if (i3 == 3) { // // Type 3: ARRL RTTY Contest // } - else if (i3 == 4) { - // // Type 4: Nonstandard calls, e.g. PJ4/KA1ABC RR73 - // // One hashed call or "CQ"; one compound or nonstandard call with up - // // to 11 characters; and (if not "CQ") an optional RRR, RR73, or 73. - return unpack_nonstandard(a77, field1, field2, field3); + else if (i3 == 4) + { + // // Type 4: Nonstandard calls, e.g. PJ4/KA1ABC RR73 + // // One hashed call or "CQ"; one compound or nonstandard call with up + // // to 11 characters; and (if not "CQ") an optional RRR, RR73, or 73. + return unpack_nonstandard(a77, field1, field2, field3); } // else if (i3 == 5) { // // Type 5: TU; W9XYZ K1ABC R-09 FN 1 28 28 1 7 9 74 WWROF contest @@ -350,12 +388,14 @@ int unpack77_fields(const uint8_t *a77, char *field1, char *field2, char *field3 return -1; } -int unpack77(const uint8_t *a77, char *message) { +int unpack77(const uint8_t *a77, char *message) +{ char field1[14]; char field2[14]; char field3[7]; int rc = unpack77_fields(a77, field1, field2, field3); - if (rc < 0) return rc; + if (rc < 0) + return rc; char *dst = message; // int msg_sz = strlen(field1) + strlen(field2) + strlen(field3) + 2; @@ -369,5 +409,3 @@ int unpack77(const uint8_t *a77, char *message) { return 0; } - -} // namespace diff --git a/ft8/unpack.h b/ft8/unpack.h index 89a706d..2575083 100644 --- a/ft8/unpack.h +++ b/ft8/unpack.h @@ -1,15 +1,14 @@ -#pragma once +#ifndef _INCLUDE_UNPACK_H_ +#define _INCLUDE_UNPACK_H_ #include -namespace ft8 { +// field1 - at least 14 bytes +// field2 - at least 14 bytes +// field3 - at least 7 bytes +int unpack77_fields(const uint8_t *a77, char *field1, char *field2, char *field3); - // field1 - at least 14 bytes - // field2 - at least 14 bytes - // field3 - at least 7 bytes - int unpack77_fields(const uint8_t *a77, char *field1, char *field2, char *field3); - - // message should have at least 35 bytes allocated (34 characters + zero terminator) - int unpack77(const uint8_t *a77, char *message); +// message should have at least 35 bytes allocated (34 characters + zero terminator) +int unpack77(const uint8_t *a77, char *message); -} +#endif // _INCLUDE_UNPACK_H_ diff --git a/gen_ft8.cpp b/gen_ft8.cpp index 9c7fb46..f0fb5d2 100644 --- a/gen_ft8.cpp +++ b/gen_ft8.cpp @@ -11,84 +11,94 @@ #include "ft8/encode.h" #include "ft8/constants.h" -#define LOG_LEVEL LOG_INFO +#define LOG_LEVEL LOG_INFO -void gfsk_pulse(int n_spsym, float b, float *pulse) { +void gfsk_pulse(int n_spsym, float b, float *pulse) +{ const float c = M_PI * sqrtf(2 / logf(2)); - for (int i = 0; i < 3*n_spsym; ++i) { - float t = i/(float)n_spsym - 1.5f; + for (int i = 0; i < 3 * n_spsym; ++i) + { + float t = i / (float)n_spsym - 1.5f; pulse[i] = (erff(c * b * (t + 0.5f)) - erff(c * b * (t - 0.5f))) / 2; } } // Same as synth_fsk, but uses GFSK phase shaping -void synth_gfsk(const uint8_t *symbols, int n_sym, float f0, int n_spsym, int signal_rate, float *signal) +void synth_gfsk(const uint8_t *symbols, int n_sym, float f0, int n_spsym, int signal_rate, float *signal) { LOG(LOG_DEBUG, "n_spsym = %d\n", n_spsym); int n_wave = n_sym * n_spsym; float hmod = 1.0f; // Compute the smoothed frequency waveform. - // Length = (nsym+2)*nsps samples, first and last symbols extended + // Length = (nsym+2)*nsps samples, first and last symbols extended float dphi_peak = 2 * M_PI * hmod / n_spsym; - float dphi[n_wave + 2*n_spsym]; + float dphi[n_wave + 2 * n_spsym]; // Shift frequency up by f0 - for (int i = 0; i < n_wave + 2*n_spsym; ++i) { + for (int i = 0; i < n_wave + 2 * n_spsym; ++i) + { dphi[i] = 2 * M_PI * f0 / signal_rate; } float pulse[3 * n_spsym]; gfsk_pulse(n_spsym, 2.0f, pulse); - for (int i = 0; i < n_sym; ++i) { + for (int i = 0; i < n_sym; ++i) + { int ib = i * n_spsym; - for (int j = 0; j < 3*n_spsym; ++j) { - dphi[j + ib] += dphi_peak*symbols[i]*pulse[j]; + for (int j = 0; j < 3 * n_spsym; ++j) + { + dphi[j + ib] += dphi_peak * symbols[i] * pulse[j]; } } // Add dummy symbols at beginning and end with tone values equal to 1st and last symbol, respectively - for (int j = 0; j < 2*n_spsym; ++j) { - dphi[j] += dphi_peak*pulse[j + n_spsym]*symbols[0]; - dphi[j + n_sym * n_spsym] += dphi_peak*pulse[j]*symbols[n_sym - 1]; + for (int j = 0; j < 2 * n_spsym; ++j) + { + dphi[j] += dphi_peak * pulse[j + n_spsym] * symbols[0]; + dphi[j + n_sym * n_spsym] += dphi_peak * pulse[j] * symbols[n_sym - 1]; } // Calculate and insert the audio waveform float phi = 0; - for (int k = 0; k < n_wave; ++k) { // Don't include dummy symbols + for (int k = 0; k < n_wave; ++k) + { // Don't include dummy symbols signal[k] = sinf(phi); - phi = fmodf(phi + dphi[k + n_spsym], 2*M_PI); + phi = fmodf(phi + dphi[k + n_spsym], 2 * M_PI); } // Apply envelope shaping to the first and last symbols int n_ramp = n_spsym / 8; - for (int i = 0; i < n_ramp; ++i) { + for (int i = 0; i < n_ramp; ++i) + { float env = (1 - cosf(2 * M_PI * i / (2 * n_ramp))) / 2; signal[i] *= env; signal[n_wave - 1 - i] *= env; } } - // Convert a sequence of symbols (tones) into a sinewave of continuous phase (FSK). // Symbol 0 gets encoded as a sine of frequency f0, the others are spaced in increasing // fashion. -void synth_fsk(const uint8_t *symbols, int num_symbols, float f0, float spacing, - float symbol_rate, float signal_rate, float *signal) { +void synth_fsk(const uint8_t *symbols, int num_symbols, float f0, float spacing, + float symbol_rate, float signal_rate, float *signal) +{ float phase = 0; - float dt = 1/signal_rate; - float dt_sym = 1/symbol_rate; + float dt = 1 / signal_rate; + float dt_sym = 1 / symbol_rate; float t = 0; int j = 0; int i = 0; - while (j < num_symbols) { + while (j < num_symbols) + { float f = f0 + symbols[j] * spacing; phase = fmodf(phase + 2 * M_PI * f / signal_rate, 2 * M_PI); signal[i] = sinf(phase); t += dt; - if (t >= dt_sym) { + if (t >= dt_sym) + { // Move to the next symbol t -= dt_sym; ++j; @@ -97,8 +107,8 @@ void synth_fsk(const uint8_t *symbols, int num_symbols, float f0, float spacing, } } - -void usage() { +void usage() +{ printf("Generate a 15-second WAV file encoding a given message.\n"); printf("Usage:\n"); printf("\n"); @@ -107,10 +117,11 @@ void usage() { printf("(Note that you might have to enclose your message in quote marks if it contains spaces)\n"); } - -int main(int argc, char **argv) { +int main(int argc, char **argv) +{ // Expect two command-line arguments - if (argc < 3) { + if (argc < 3) + { usage(); return -1; } @@ -118,33 +129,37 @@ int main(int argc, char **argv) { const char *message = argv[1]; const char *wav_path = argv[2]; float frequency = 1000.0; - if (argc > 3) { - frequency = atof(argv[3]); + if (argc > 3) + { + frequency = atof(argv[3]); } // First, pack the text data into binary message - uint8_t packed[ft8::K_BYTES]; + uint8_t packed[FT8_K_BYTES]; //int rc = packmsg(message, packed); - int rc = ft8::pack77(message, packed); - if (rc < 0) { + int rc = pack77(message, packed); + if (rc < 0) + { printf("Cannot parse message!\n"); printf("RC = %d\n", rc); return -2; } printf("Packed data: "); - for (int j = 0; j < 10; ++j) { + for (int j = 0; j < 10; ++j) + { printf("%02x ", packed[j]); } printf("\n"); // Second, encode the binary message as a sequence of FSK tones - uint8_t tones[ft8::NN]; // FT8_NN = 79, lack of better name at the moment + uint8_t tones[FT8_NN]; // FT8_NN = 79, lack of better name at the moment //genft8(packed, 0, tones); - ft8::genft8(packed, tones); + genft8(packed, tones); printf("FSK tones: "); - for (int j = 0; j < ft8::NN; ++j) { + for (int j = 0; j < FT8_NN; ++j) + { printf("%d", tones[j]); } printf("\n"); @@ -152,15 +167,16 @@ int main(int argc, char **argv) { // Third, convert the FSK tones into an audio signal const int sample_rate = 12000; const float symbol_rate = 6.25f; - const int num_samples = (int)(0.5f + ft8::NN / symbol_rate * sample_rate); + const int num_samples = (int)(0.5f + FT8_NN / symbol_rate * sample_rate); const int num_silence = (15 * sample_rate - num_samples) / 2; float signal[num_silence + num_samples + num_silence]; - for (int i = 0; i < num_silence + num_samples + num_silence; i++) { + for (int i = 0; i < num_silence + num_samples + num_silence; i++) + { signal[i] = 0; } - // synth_fsk(tones, ft8::NN, frequency, symbol_rate, symbol_rate, sample_rate, signal + num_silence); - synth_gfsk(tones, ft8::NN, frequency, sample_rate / symbol_rate, sample_rate, signal + num_silence); + // synth_fsk(tones, FT8_NN, frequency, symbol_rate, symbol_rate, sample_rate, signal + num_silence); + synth_gfsk(tones, FT8_NN, frequency, sample_rate / symbol_rate, sample_rate, signal + num_silence); save_wav(signal, num_silence + num_samples + num_silence, sample_rate, wav_path); return 0; diff --git a/test.cpp b/test.cpp index 09f0a38..c68b08b 100644 --- a/test.cpp +++ b/test.cpp @@ -14,29 +14,34 @@ #include "fft/kiss_fftr.h" #include "common/debug.h" -#define LOG_LEVEL LOG_INFO +#define LOG_LEVEL LOG_INFO - -void convert_8bit_to_6bit(uint8_t *dst, const uint8_t *src, int nBits) { +void convert_8bit_to_6bit(uint8_t *dst, const uint8_t *src, int nBits) +{ // Zero-fill the destination array as we will only be setting bits later - for (int j = 0; j < (nBits + 5) / 6; ++j) { + for (int j = 0; j < (nBits + 5) / 6; ++j) + { dst[j] = 0; } // Set the relevant bits uint8_t mask_src = (1 << 7); uint8_t mask_dst = (1 << 5); - for (int i = 0, j = 0; nBits > 0; --nBits) { - if (src[i] & mask_src) { + for (int i = 0, j = 0; nBits > 0; --nBits) + { + if (src[i] & mask_src) + { dst[j] |= mask_dst; } mask_src >>= 1; - if (mask_src == 0) { + if (mask_src == 0) + { mask_src = (1 << 7); ++i; } mask_dst >>= 1; - if (mask_dst == 0) { + if (mask_dst == 0) + { mask_dst = (1 << 5); ++j; } @@ -97,53 +102,57 @@ void test3() { } */ -void test_tones(float *log174) { +void test_tones(float *log174) +{ // Just a test case - for (int i = 0; i < ft8::ND; ++i) { + for (int i = 0; i < FT8_ND; ++i) + { const uint8_t inv_map[8] = {0, 1, 3, 2, 6, 4, 5, 7}; uint8_t tone = ("0000000011721762454112705354533170166234757420515470163426"[i]) - '0'; uint8_t b3 = inv_map[tone]; - log174[3 * i] = (b3 & 4) ? +1.0 : -1.0; + log174[3 * i] = (b3 & 4) ? +1.0 : -1.0; log174[3 * i + 1] = (b3 & 2) ? +1.0 : -1.0; log174[3 * i + 2] = (b3 & 1) ? +1.0 : -1.0; } } - -void test4() { +void test4() +{ const int nfft = 128; const float fft_norm = 2.0 / nfft; - size_t fft_work_size; + size_t fft_work_size; kiss_fftr_alloc(nfft, 0, 0, &fft_work_size); printf("N_FFT = %d\n", nfft); printf("FFT work area = %lu\n", fft_work_size); - void *fft_work = malloc(fft_work_size); + void *fft_work = malloc(fft_work_size); kiss_fftr_cfg fft_cfg = kiss_fftr_alloc(nfft, 0, fft_work, &fft_work_size); - kiss_fft_scalar window[nfft]; - for (int i = 0; i < nfft; ++i) { + kiss_fft_scalar window[nfft]; + for (int i = 0; i < nfft; ++i) + { window[i] = sinf(i * 2 * (float)M_PI / nfft); } - kiss_fft_cpx freqdata[nfft/2 + 1]; + kiss_fft_cpx freqdata[nfft / 2 + 1]; kiss_fftr(fft_cfg, window, freqdata); float mag_db[nfft]; // Compute log magnitude in decibels - for (int j = 0; j < nfft/2 + 1; ++j) { + for (int j = 0; j < nfft / 2 + 1; ++j) + { float mag2 = (freqdata[j].i * freqdata[j].i + freqdata[j].r * freqdata[j].r); mag_db[j] = 10.0f * log10f(1E-10f + mag2 * fft_norm * fft_norm); - } + } printf("F[0] = %.1f dB\n", mag_db[0]); printf("F[1] = %.3f dB\n", mag_db[1]); } - -int main() { +int main() +{ //test1(); test4();