From 3f84b984fedafe4bb5b6c7a8e3e3659ee42cfed7 Mon Sep 17 00:00:00 2001 From: Karlis Goba Date: Wed, 2 Jan 2019 20:54:18 +0200 Subject: [PATCH] Dropped _v2 suffix and moved everything to ft8:: namespace --- Makefile | 6 +-- decode_ft8.cpp | 30 ++++++------ ft8/constants.cpp | 14 ++++-- ft8/constants.h | 78 +++++++++++++++++-------------- ft8/decode.cpp | 21 +++++---- ft8/decode.h | 4 ++ ft8/{encode_v2.cpp => encode.cpp} | 30 ++++++------ ft8/{encode_v2.h => encode.h} | 4 +- ft8/ldpc.cpp | 60 +++++++++++++----------- ft8/ldpc.h | 22 +++++---- ft8/{pack_v2.cpp => pack.cpp} | 4 +- ft8/{pack_v2.h => pack.h} | 4 +- ft8/text.cpp | 6 ++- ft8/text.h | 36 +++++++------- ft8/{unpack_v2.cpp => unpack.cpp} | 6 ++- ft8/unpack.h | 10 ++++ ft8/unpack_v2.h | 7 --- gen_ft8.cpp | 18 +++---- test.cpp | 53 +++++++++++++++++---- 19 files changed, 244 insertions(+), 169 deletions(-) rename ft8/{encode_v2.cpp => encode.cpp} (86%) rename ft8/{encode_v2.h => encode.h} (93%) rename ft8/{pack_v2.cpp => pack.cpp} (99%) rename ft8/{pack_v2.h => pack.h} (92%) rename ft8/{unpack_v2.cpp => unpack.cpp} (99%) create mode 100644 ft8/unpack.h delete mode 100644 ft8/unpack_v2.h diff --git a/Makefile b/Makefile index bf0e4cb..c27a20a 100644 --- a/Makefile +++ b/Makefile @@ -10,13 +10,13 @@ all: $(TARGETS) run_tests: test @./test -gen_ft8: gen_ft8.o ft8/constants.o ft8/text.o ft8/pack_v2.o ft8/encode_v2.o common/wave.o +gen_ft8: gen_ft8.o ft8/constants.o ft8/text.o ft8/pack.o ft8/encode.o common/wave.o $(CXX) $(LDFLAGS) -o $@ $^ -test: test.o ft8/v1/encode.o ft8/v1/pack.o ft8/v1/unpack.o ft8/pack_v2.o ft8/encode_v2.o ft8/text.o ft8/constants.o fft/kiss_fftr.o fft/kiss_fft.o +test: test.o ft8/pack.o ft8/encode.o ft8/text.o ft8/constants.o fft/kiss_fftr.o fft/kiss_fft.o $(CXX) $(LDFLAGS) -o $@ $^ -decode_ft8: decode_ft8.o fft/kiss_fftr.o fft/kiss_fft.o ft8/decode.o ft8/encode_v2.o ft8/ldpc.o ft8/unpack_v2.o ft8/text.o ft8/constants.o common/wave.o +decode_ft8: decode_ft8.o fft/kiss_fftr.o fft/kiss_fft.o ft8/decode.o ft8/encode.o ft8/ldpc.o ft8/unpack.o ft8/text.o ft8/constants.o common/wave.o $(CXX) $(LDFLAGS) -o $@ $^ clean: diff --git a/decode_ft8.cpp b/decode_ft8.cpp index a89adb4..724805f 100644 --- a/decode_ft8.cpp +++ b/decode_ft8.cpp @@ -3,11 +3,11 @@ #include #include -#include "ft8/unpack_v2.h" +#include "ft8/unpack.h" #include "ft8/ldpc.h" #include "ft8/decode.h" #include "ft8/constants.h" -#include "ft8/encode_v2.h" +#include "ft8/encode.h" #include "common/wave.h" #include "common/debug.h" @@ -136,7 +136,7 @@ void normalize_signal(float *signal, int num_samples) { void print_tones(const uint8_t *code_map, const float *log174) { - for (int k = 0; k < 3 * FT8_ND; k += 3) { + 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; @@ -180,8 +180,8 @@ int main(int argc, char **argv) { extract_power(signal, num_blocks, num_bins, power); // Find top candidates by Costas sync score and localize them in time and frequency - Candidate candidate_list[kMax_candidates]; - int num_candidates = find_sync(power, num_blocks, num_bins, kCostas_map, kMax_candidates, candidate_list); + ft8::Candidate candidate_list[kMax_candidates]; + int num_candidates = ft8::find_sync(power, num_blocks, num_bins, ft8::kCostas_map, kMax_candidates, candidate_list); // TODO: sort the candidates by strongest sync first? @@ -189,17 +189,17 @@ int main(int argc, char **argv) { 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]; + ft8::Candidate &cand = candidate_list[idx]; float freq_hz = (cand.freq_offset + cand.freq_sub / 2.0f) * fsk_dev; float time_sec = (cand.time_offset + cand.time_sub / 2.0f) / fsk_dev; - float log174[FT8_N]; - extract_likelihood(power, num_bins, cand, kGray_map, log174); + float log174[ft8::N]; + ft8::extract_likelihood(power, num_bins, cand, ft8::kGray_map, log174); // bp_decode() produces better decodes, uses way less memory - uint8_t plain[FT8_N]; + uint8_t plain[ft8::N]; int n_errors = 0; - bp_decode(log174, kLDPC_iterations, plain, &n_errors); + ft8::bp_decode(log174, kLDPC_iterations, plain, &n_errors); //ldpc_decode(log174, kLDPC_iterations, plain, &n_errors); if (n_errors > 0) { @@ -207,23 +207,23 @@ int main(int argc, char **argv) { continue; } - // Extract payload + CRC (first FT8_K bits) - uint8_t a91[FT8_K_BYTES]; - pack_bits(plain, FT8_K, a91); + // 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_v2::ft8_crc(a91, 96 - 14); + 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]; - unpack77(a91, message); + ft8::unpack77(a91, message); // Check for duplicate messages (TODO: use hashing) bool found = false; diff --git a/ft8/constants.cpp b/ft8/constants.cpp index a58af5e..562c7f8 100644 --- a/ft8/constants.cpp +++ b/ft8/constants.cpp @@ -1,5 +1,7 @@ #include "constants.h" +namespace ft8 { + // Costas 7x7 tone pattern const uint8_t kCostas_map[7] = { 3,1,4,0,6,5,2 }; @@ -7,7 +9,7 @@ const uint8_t kCostas_map[7] = { 3,1,4,0,6,5,2 }; 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[FT8_M][FT8_K_BYTES] = { +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 }, @@ -95,7 +97,7 @@ const uint8_t kGenerator[FT8_M][FT8_K_BYTES] = { // 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[FT8_N] = { +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, @@ -114,7 +116,7 @@ const uint8_t kColumn_order[FT8_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[FT8_M][7] = { +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 }, @@ -205,7 +207,7 @@ const uint8_t kNm[FT8_M][7] = { // the numbers indicate which three parity // checks (rows in Nm) refer to the codeword bit. // 1-origin. -const uint8_t kMn[FT8_N][3] = { +const uint8_t kMn[N][3] = { { 16, 45, 73 }, { 25, 51, 62 }, { 33, 58, 78 }, @@ -382,7 +384,7 @@ const uint8_t kMn[FT8_N][3] = { { 42, 49, 57 } }; -const uint8_t kNrw[FT8_M] = { +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, @@ -390,3 +392,5 @@ const uint8_t kNrw[FT8_M] = { 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 diff --git a/ft8/constants.h b/ft8/constants.h index 31fbca1..edd6c60 100644 --- a/ft8/constants.h +++ b/ft8/constants.h @@ -1,53 +1,59 @@ +#pragma once + #include -// Define FT8 symbol counts -constexpr int FT8_ND = 58; // Data symbols -constexpr int FT8_NS = 21; // Sync symbols (3 @ Costas 7x7) -constexpr int FT8_NN = FT8_NS + FT8_ND; // Total channel symbols (79) +namespace ft8 { -// Define the LDPC sizes -constexpr int FT8_N = 174; // Number of bits in the encoded message -constexpr int FT8_K = 91; // Number of payload bits -constexpr int FT8_M = FT8_N - FT8_K; // Number of checksum bits -constexpr int FT8_K_BYTES = (FT8_K + 7) / 8; // Number of whole bytes needed to store K bits + // 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 CRC parameters -constexpr uint16_t CRC_POLYNOMIAL = 0x2757; // CRC-14 polynomial without the leading (MSB) 1 -constexpr int CRC_WIDTH = 14; + // 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 -// Costas 7x7 tone pattern -extern const uint8_t kCostas_map[7]; + // 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]; -// Gray code map -extern const uint8_t kGray_map[8]; + // 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]; + // Parity generator matrix for (174,91) LDPC code, stored in bitpacked format (MSB first) + extern const uint8_t kGenerator[M][K_BYTES]; -// 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]; + // 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[FT8_M][7]; + // 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[FT8_N][3]; + // 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[FT8_M]; + // Number of rows (columns in C/C++) in the array Nm. + extern const uint8_t kNrw[M]; + +} \ No newline at end of file diff --git a/ft8/decode.cpp b/ft8/decode.cpp index 13f0b2b..02ae2c5 100644 --- a/ft8/decode.cpp +++ b/ft8/decode.cpp @@ -4,6 +4,8 @@ #include "constants.h" +namespace ft8 { + static float max2(float a, float b); static float max4(float a, float b, float c, float d); static void heapify_down(Candidate *heap, int heap_size); @@ -11,7 +13,6 @@ 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); - // 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 uint8_t *power, int num_blocks, int num_bins, const uint8_t *sync_map, int num_candidates, Candidate *heap) { @@ -21,7 +22,7 @@ int find_sync(const uint8_t *power, int num_blocks, int num_bins, const uint8_t // 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 alt = 0; alt < 4; ++alt) { - for (int time_offset = -7; time_offset < num_blocks - FT8_NN + 7; ++time_offset) { + for (int time_offset = -7; time_offset < num_blocks - ft8::NN + 7; ++time_offset) { for (int freq_offset = 0; freq_offset < num_bins - 8; ++freq_offset) { int score = 0; @@ -89,8 +90,8 @@ void extract_likelihood(const uint8_t *power, int num_bins, const Candidate & ca 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) { - int sym_idx = (k < FT8_ND / 2) ? (k + 7) : (k + 14); + for (int k = 0; k < ft8::ND; k += n_syms) { + int sym_idx = (k < ft8::ND / 2) ? (k + 7) : (k + 14); int bit_idx = 3 * k; // Pointer to 8 bins of the current symbol @@ -102,8 +103,8 @@ void extract_likelihood(const uint8_t *power, int num_bins, const Candidate & ca // 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) { + float inv_n = 1.0f / ft8::N; + for (int i = 0; i < ft8::N; ++i) { sum += log174[i]; sum2 += log174[i] * log174[i]; } @@ -112,7 +113,7 @@ void extract_likelihood(const uint8_t *power, int num_bins, const Candidate & ca // 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; } } @@ -222,7 +223,7 @@ 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) { + if (bit_idx + i >= ft8::N) { // Respect array size break; } @@ -240,4 +241,6 @@ static void decode_multi_symbols(const uint8_t *power, int num_bins, int n_syms, log174[bit_idx + i] = max_one - max_zero; } -} \ No newline at end of file +} + +} // namespace \ No newline at end of file diff --git a/ft8/decode.h b/ft8/decode.h index 03ee73c..52211d9 100644 --- a/ft8/decode.h +++ b/ft8/decode.h @@ -2,6 +2,8 @@ #include +namespace ft8 { + struct Candidate { int16_t score; int16_t time_offset; @@ -19,3 +21,5 @@ int find_sync(const uint8_t *power, int num_blocks, int num_bins, const uint8_t // Compute log likelihood log(p(1) / p(0)) of 174 message bits // for later use in soft-decision LDPC decoding void extract_likelihood(const uint8_t *power, int num_bins, const Candidate & cand, const uint8_t *code_map, float *log174); + +} \ No newline at end of file diff --git a/ft8/encode_v2.cpp b/ft8/encode.cpp similarity index 86% rename from ft8/encode_v2.cpp rename to ft8/encode.cpp index 48acc44..9903052 100644 --- a/ft8/encode_v2.cpp +++ b/ft8/encode.cpp @@ -1,9 +1,9 @@ -#include "encode_v2.h" +#include "encode.h" #include "constants.h" #include -namespace ft8_v2 { +namespace ft8 { // Returns 1 if an odd number of bits are set in x, zero otherwise @@ -31,26 +31,26 @@ 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], // but we only compute the sum modulo 2. uint8_t nsum = 0; - for (int j = 0; j < FT8_K_BYTES; ++j) { + 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) } @@ -67,7 +67,7 @@ void encode174(const uint8_t *message, uint8_t *codeword) { } // 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"); @@ -77,7 +77,7 @@ 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) { +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)); @@ -127,7 +127,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); @@ -149,7 +149,7 @@ void genft8(const uint8_t *payload, uint8_t *itone) { uint8_t mask = 0x80; int i_byte = 0; - for (int j = 0; j < FT8_ND; ++j) { // do j=1,FT8_ND + 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 } @@ -169,4 +169,4 @@ void genft8(const uint8_t *payload, uint8_t *itone) { } } -}; // ft8_v2 +} // namespace \ No newline at end of file diff --git a/ft8/encode_v2.h b/ft8/encode.h similarity index 93% rename from ft8/encode_v2.h rename to ft8/encode.h index 5426c53..1377607 100644 --- a/ft8/encode_v2.h +++ b/ft8/encode.h @@ -2,7 +2,7 @@ #include -namespace ft8_v2 { +namespace ft8 { // Generate FT8 tone sequence from payload data // [IN] payload - 9 byte array consisting of 72 bit payload @@ -25,5 +25,5 @@ namespace ft8_v2 { // 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); + uint16_t crc(uint8_t *message, int num_bits); }; diff --git a/ft8/ldpc.cpp b/ft8/ldpc.cpp index 205f508..fd1eafb 100644 --- a/ft8/ldpc.cpp +++ b/ft8/ldpc.cpp @@ -14,9 +14,11 @@ #include #include "constants.h" -int ldpc_check(uint8_t codeword[]); -float fast_tanh(float x); -float fast_atanh(float x); +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[], @@ -47,19 +49,19 @@ void pack_bits(const uint8_t plain[], int num_bits, uint8_t packed[]) { // 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; + 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 j = 0; j < ft8::M; j++) { for (int ii1 = 0; ii1 < kNrw[j]; ii1++) { int i1 = kNm[j][ii1] - 1; float a = 1.0f; @@ -73,7 +75,7 @@ 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]; @@ -91,7 +93,7 @@ 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++) { for (int ji1 = 0; ji1 < 3; ji1++) { int j1 = kMn[i][ji1] - 1; float l = codeword[i]; @@ -115,10 +117,10 @@ void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok) { // returns the number of parity errors. // 0 means total success. // -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) { x ^= codeword[kNm[j][i] - 1]; @@ -132,32 +134,32 @@ int ldpc_check(uint8_t codeword[]) { void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok) { - float tov[FT8_N][3]; - float toc[FT8_M][7]; + 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 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 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]; + 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; } @@ -175,7 +177,7 @@ void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok) { } // Send messages from bits to check nodes - for (int i = 0; i < FT8_M; ++i) { + 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]; @@ -189,13 +191,13 @@ void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok) { } // send messages from check nodes to variable nodes - for (int i = 0; i < FT8_M; ++i) { + 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 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; @@ -219,7 +221,7 @@ void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok) { // thank you Douglas Bagnall // https://math.stackexchange.com/a/446411 -float fast_tanh(float x) { +static float fast_tanh(float x) { if (x < -4.97f) { return -1.0f; } @@ -237,7 +239,7 @@ float fast_tanh(float x) { } -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))); @@ -249,7 +251,7 @@ float fast_atanh(float x) { } -float pltanh(float x) { +static float pltanh(float x) { float isign = +1; if (x < 0) { isign = -1; @@ -271,7 +273,7 @@ float pltanh(float x) { } -float platanh(float x) { +static float platanh(float x) { float isign = +1; if (x < 0) { isign = -1; @@ -290,4 +292,6 @@ float platanh(float x) { return isign * (x - 0.9914f) / 0.0012f; } return isign * 7.0f; -} \ No newline at end of file +} + +} // namespace \ No newline at end of file diff --git a/ft8/ldpc.h b/ft8/ldpc.h index 56866da..75290f5 100644 --- a/ft8/ldpc.h +++ b/ft8/ldpc.h @@ -1,13 +1,17 @@ #pragma once -// 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); +namespace ft8 { -void bp_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); -// 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 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[]); + +} \ No newline at end of file diff --git a/ft8/pack_v2.cpp b/ft8/pack.cpp similarity index 99% rename from ft8/pack_v2.cpp rename to ft8/pack.cpp index 41e9dad..fb990ce 100644 --- a/ft8/pack_v2.cpp +++ b/ft8/pack.cpp @@ -1,4 +1,4 @@ -#include "pack_v2.h" +#include "pack.h" #include "text.h" @@ -6,7 +6,7 @@ #include #include -namespace ft8_v2 { +namespace ft8 { // TODO: This is wasteful, should figure out something more elegant const char A0[] = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+-./?"; diff --git a/ft8/pack_v2.h b/ft8/pack.h similarity index 92% rename from ft8/pack_v2.h rename to ft8/pack.h index 29d4e97..1b2ced4 100644 --- a/ft8/pack_v2.h +++ b/ft8/pack.h @@ -2,11 +2,11 @@ #include -namespace ft8_v2 { +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); -}; \ No newline at end of file +} \ No newline at end of file diff --git a/ft8/text.cpp b/ft8/text.cpp index 7f52184..340c1e8 100644 --- a/ft8/text.cpp +++ b/ft8/text.cpp @@ -2,6 +2,8 @@ #include +namespace ft8 { + // Utility functions for characters and strings char to_upper(char c) { @@ -114,4 +116,6 @@ void int_to_dd(char *str, int value, int width, bool full_sign) { divisor /= 10; } *str = 0; // Add zero terminator -} \ No newline at end of file +} + +} // namespace \ No newline at end of file diff --git a/ft8/text.h b/ft8/text.h index 7218050..97f3930 100644 --- a/ft8/text.h +++ b/ft8/text.h @@ -1,22 +1,26 @@ #pragma once -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); +namespace ft8 { -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); + + // Convert a 2 digit integer to string + void int_to_dd(char *str, int value, int width, bool full_sign = false); + +} \ No newline at end of file diff --git a/ft8/unpack_v2.cpp b/ft8/unpack.cpp similarity index 99% rename from ft8/unpack_v2.cpp rename to ft8/unpack.cpp index 653043f..65472b7 100644 --- a/ft8/unpack_v2.cpp +++ b/ft8/unpack.cpp @@ -1,8 +1,10 @@ -#include "unpack_v2.h" +#include "unpack.h" #include "text.h" #include +namespace ft8 { + //const uint32_t NBASE = 37L*36L*10L*27L*27L*27L; const uint32_t MAX22 = 4194304L; const uint32_t NTOKENS = 2063592L; @@ -327,3 +329,5 @@ int unpack77(const uint8_t *a77, char *message) { return 0; } + +} // namespace \ No newline at end of file diff --git a/ft8/unpack.h b/ft8/unpack.h new file mode 100644 index 0000000..5d88539 --- /dev/null +++ b/ft8/unpack.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace ft8 { + + // message should have at least 19 bytes allocated (18 characters + zero terminator) + int unpack77(const uint8_t *a77, char *message); + +} \ No newline at end of file diff --git a/ft8/unpack_v2.h b/ft8/unpack_v2.h deleted file mode 100644 index 2771d4c..0000000 --- a/ft8/unpack_v2.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include - - -// message should have at least 19 bytes allocated (18 characters + zero terminator) -int unpack77(const uint8_t *a77, char *message); diff --git a/gen_ft8.cpp b/gen_ft8.cpp index 3be8621..8eb4922 100644 --- a/gen_ft8.cpp +++ b/gen_ft8.cpp @@ -6,8 +6,8 @@ #include "common/wave.h" //#include "ft8/v1/pack.h" //#include "ft8/v1/encode.h" -#include "ft8/pack_v2.h" -#include "ft8/encode_v2.h" +#include "ft8/pack.h" +#include "ft8/encode.h" #include "ft8/constants.h" // Convert a sequence of symbols (tones) into a sinewave of continuous phase (FSK). @@ -57,9 +57,9 @@ int main(int argc, char **argv) { const char *wav_path = argv[2]; // First, pack the text data into binary message - uint8_t packed[10]; + uint8_t packed[ft8::K_BYTES]; //int rc = packmsg(message, packed); - int rc = ft8_v2::pack77(message, packed); + int rc = ft8::pack77(message, packed); if (rc < 0) { printf("Cannot parse message!\n"); printf("RC = %d\n", rc); @@ -73,12 +73,12 @@ int main(int argc, char **argv) { 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_v2::genft8(packed, tones); + ft8::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"); @@ -86,14 +86,14 @@ 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++) { signal[i] = 0; } - synth_fsk(tones, FT8_NN, 1000, symbol_rate, symbol_rate, sample_rate, signal + num_silence); + synth_fsk(tones, ft8::NN, 1000, symbol_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 07b8afb..09f0a38 100644 --- a/test.cpp +++ b/test.cpp @@ -4,13 +4,14 @@ #include #include "ft8/text.h" -#include "ft8/v1/pack.h" -#include "ft8/v1/unpack.h" -#include "ft8/v1/encode.h" -#include "ft8/pack_v2.h" -#include "ft8/encode_v2.h" +//#include "ft8/v1/pack.h" +//#include "ft8/v1/unpack.h" +//#include "ft8/v1/encode.h" +#include "ft8/pack.h" +#include "ft8/encode.h" #include "ft8/constants.h" +#include "fft/kiss_fftr.h" #include "common/debug.h" #define LOG_LEVEL LOG_INFO @@ -42,7 +43,7 @@ void convert_8bit_to_6bit(uint8_t *dst, const uint8_t *src, int nBits) { } } - +/* bool test1() { //const char *msg = "CQ DL7ACA JO40"; // 62, 32, 32, 49, 37, 27, 59, 2, 30, 19, 49, 16 const char *msg = "VA3UG F1HMR 73"; // 52, 54, 60, 12, 55, 54, 7, 19, 2, 23, 59, 16 @@ -94,11 +95,11 @@ void test3() { uint16_t crc1 = ft8_crc(test_in2, 76); // Calculate CRC of 76 bits only LOG(LOG_INFO, "CRC: %04x\n", crc1); // should be 0x0708 } - +*/ 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]; @@ -109,8 +110,42 @@ void test_tones(float *log174) { } +void test4() { + const int nfft = 128; + const float fft_norm = 2.0 / nfft; + + 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); + 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) { + window[i] = sinf(i * 2 * (float)M_PI / nfft); + } + + 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) { + 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() { - test1(); + //test1(); + test4(); return 0; } \ No newline at end of file