diff --git a/decode_ft8.cpp b/decode_ft8.cpp index a9518bc..84b8bc7 100644 --- a/decode_ft8.cpp +++ b/decode_ft8.cpp @@ -79,10 +79,7 @@ void heapify_up(Candidate * heap, int heap_size) { // Find 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). -void find_sync(const uint8_t * power, int num_blocks, int num_bins, int num_candidates, Candidate * heap) { - // Costas 7x7 tone pattern - const uint8_t ICOS7[] = { 2,5,6,0,4,1,3 }; - +void find_sync(const uint8_t *power, int num_blocks, int num_bins, const uint8_t *sync_map, int num_candidates, Candidate *heap) { int heap_size = 0; for (int alt = 0; alt < 4; ++alt) { @@ -94,7 +91,7 @@ void find_sync(const uint8_t * power, int num_blocks, int num_bins, int num_cand for (int m = 0; m <= 72; m += 36) { for (int k = 0; k < 7; ++k) { int offset = ((time_offset + k + m) * 4 + alt) * num_bins + freq_offset; - score += 8 * (int)power[offset + ICOS7[k]] - + score += 8 * (int)power[offset + sync_map[k]] - power[offset + 0] - power[offset + 1] - power[offset + 2] - power[offset + 3] - power[offset + 4] - power[offset + 5] - @@ -201,7 +198,7 @@ uint8_t max4(uint8_t a, uint8_t b, uint8_t cand, uint8_t d) { // 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, float * log174) { +void extract_likelihood(const uint8_t *power, int num_bins, const Candidate & cand, const uint8_t *code_map, float *log174) { int offset = (cand.time_offset * 4 + cand.time_sub * 2 + cand.freq_sub) * num_bins + cand.freq_offset; int k = 0; @@ -211,12 +208,17 @@ void extract_likelihood(const uint8_t * power, int num_bins, const Candidate & c // Pointer to 8 bins of the current symbol const uint8_t * ps = power + (offset + i * 4 * num_bins); + uint8_t s2[8]; + + for (int i = 0; i < 8; ++i) { + s2[i] = ps[code_map[i]]; + } // Extract bit significance (and convert them to float) // 8 FSK tones = 3 bits - log174[k + 0] = (int)max4(ps[4], ps[5], ps[6], ps[7]) - (int)max4(ps[0], ps[1], ps[2], ps[3]); - log174[k + 1] = (int)max4(ps[2], ps[3], ps[6], ps[7]) - (int)max4(ps[0], ps[1], ps[4], ps[5]); - log174[k + 2] = (int)max4(ps[1], ps[3], ps[5], ps[7]) - (int)max4(ps[0], ps[2], ps[4], ps[6]); + log174[k + 0] = (int)max4(s2[4], s2[5], s2[6], s2[7]) - (int)max4(s2[0], s2[1], s2[2], s2[3]); + log174[k + 1] = (int)max4(s2[2], s2[3], s2[6], s2[7]) - (int)max4(s2[0], s2[1], s2[4], s2[5]); + log174[k + 2] = (int)max4(s2[1], s2[3], s2[5], s2[7]) - (int)max4(s2[0], s2[2], s2[4], s2[6]); // printf("%d %d %d %d %d %d %d %d : %.0f %.0f %.0f\n", // ps[0], ps[1], ps[2], ps[3], ps[4], ps[5], ps[6], ps[7], // log174[k + 0], log174[k + 1], log174[k + 2]); @@ -243,6 +245,7 @@ void extract_likelihood(const uint8_t * power, int num_bins, const Candidate & c //printf("\n"); } + int main(int argc, char ** argv) { // Expect one command-line argument if (argc < 2) { @@ -261,6 +264,13 @@ int main(int argc, char ** argv) { return -1; } + // Costas 7x7 tone pattern + const uint8_t kCostas_map_v1[] = { 2,5,6,0,4,1,3 }; + const uint8_t kCostas_map_v2[] = { 3,1,4,0,6,5,2 }; + // Gray maps (used only in v2) + const uint8_t kGray_map_v1[8] = { 0,1,2,3,4,5,6,7 }; // identity map + const uint8_t kGray_map_v2[8] = { 0,1,3,2,5,6,4,7 }; + const float fsk_dev = 6.25f; const int num_bins = (int)(sample_rate / (2 * fsk_dev)); @@ -275,13 +285,13 @@ int main(int argc, char ** argv) { int num_candidates = 250; Candidate heap[num_candidates]; - find_sync(power, num_blocks, num_bins, num_candidates, heap); + find_sync(power, num_blocks, num_bins, kCostas_map_v1, num_candidates, heap); for (int idx = 0; idx < num_candidates; ++idx) { Candidate &cand = heap[idx]; float log174[3 * ND]; - extract_likelihood(power, num_bins, cand, log174); + extract_likelihood(power, num_bins, cand, kGray_map_v1, log174); const int num_iters = 20; int plain[3 * ND]; diff --git a/ft8/encode.cpp b/ft8/encode.cpp index 7081897..e74237b 100644 --- a/ft8/encode.cpp +++ b/ft8/encode.cpp @@ -1,11 +1,16 @@ #include "encode.h" -constexpr int N = 174, K = 87, M = N-K; // Define the LDPC sizes +// Define the LDPC sizes +constexpr int N = 174; // Number of bits in the encoded message +constexpr int K = 87; // Number of payload bits +constexpr int M = N - K; // Number of checksum bits constexpr uint16_t POLYNOMIAL = 0xC06; // CRC-12 polynomial without the leading (MSB) 1 +constexpr int K_BYTES = (K + 7) / 8; // Number of whole bytes needed to store K bits + // Parity generator matrix for (174,87) LDPC code, stored in bitpacked format (MSB first) -const uint8_t G[87][11] = { +const uint8_t kGenerator[M][K_BYTES] = { { 0x23, 0xbb, 0xa8, 0x30, 0xe2, 0x3b, 0x6b, 0x6f, 0x50, 0x98, 0x2e }, { 0x1f, 0x8e, 0x55, 0xda, 0x21, 0x8c, 0x5d, 0xf3, 0x30, 0x90, 0x52 }, { 0xca, 0x7b, 0x32, 0x17, 0xcd, 0x92, 0xbd, 0x59, 0xa5, 0xae, 0x20 }, @@ -96,7 +101,7 @@ const uint8_t G[87][11] = { }; // Column order (permutation) in which the bits in codeword are stored -const uint8_t colorder[174] = { +const uint8_t kColumn_order[174] = { 0, 1, 2, 3, 30, 4, 5, 6, 7, 8, 9, 10, 11, 32, 12, 40, 13, 14, 15, 16, 17, 18, 37, 45, 29, 19, 20, 21, 41, 22, 42, 31, 33, 34, 44, 35, 47, 51, 50, 43, 36, 52, 63, 46, 25, 55, 27, 24, 23, 53, 39, 49, 59, 38, 48, 61, 60, 57, 28, 62, @@ -109,7 +114,7 @@ const uint8_t colorder[174] = { }; // Costas 7x7 tone pattern -const uint8_t ICOS7[] = { 2,5,6,0,4,1,3 }; +const uint8_t kCostas_map[] = { 2,5,6,0,4,1,3 }; // Returns 1 if an odd number of bits are set in x, zero otherwise @@ -126,13 +131,13 @@ uint8_t parity8(uint8_t x) { // 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 +// "kColumn_order" to make the codeword compatible with the parity-check matrix // Arguments: // [IN] message - array of 87 bits stored as 11 bytes (MSB first) // [OUT] codeword - array of 174 bits stored as 22 bytes (MSB first) 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 G + // Instead we access the generator bits straight from the binary representation in kGenerator // Also we don't use the itmp temporary buffer, instead filling codeword bit by bit // in the reordered order as we compute the result. @@ -140,7 +145,7 @@ void encode174(const uint8_t *message, uint8_t *codeword) { // For reference: // itmp(1:M)=pchecks // itmp(M+1:N)=message(1:K) - // codeword(colorder+1)=itmp(1:N) + // codeword(kColumn_order+1)=itmp(1:N) int colidx = 0; // track the current column in codeword @@ -152,16 +157,16 @@ void encode174(const uint8_t *message, uint8_t *codeword) { // Compute the first part of itmp (1:M) and store the result in codeword for (int i = 0; i < M; ++i) { // do i=1,M // Fast implementation of bitwise multiplication and parity checking - // Normally nsum would contain the result of dot product between message and G[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 < 11; ++j) { - uint8_t bits = message[j] & G[i][j]; // bitwise AND (bitwise multiplication) + for (int j = 0; j < 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) - uint8_t col = colorder[colidx]; // Index of the bit to set + uint8_t col = kColumn_order[colidx]; // Index of the bit to set codeword[col/8] |= (1 << (7 - col%8)); } ++colidx; @@ -172,7 +177,7 @@ void encode174(const uint8_t *message, uint8_t *codeword) { for (int j = 0; j < K; ++j) { // Copy the j-th bit from message to codeword if (message[j/8] & mask) { - uint8_t col = colorder[colidx]; // Index of the bit to set + uint8_t col = kColumn_order[colidx]; // Index of the bit to set codeword[col/8] |= (1 << (7 - col%8)); } ++colidx; @@ -242,9 +247,9 @@ void genft8(const uint8_t *payload, uint8_t i3, uint8_t *itone) { // Message structure: S7 D29 S7 D29 S7 for (int i = 0; i < 7; ++i) { - itone[i] = ICOS7[i]; - itone[36 + i] = ICOS7[i]; - itone[72 + i] = ICOS7[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 diff --git a/ft8/encode_v2.cpp b/ft8/encode_v2.cpp index f1402d5..304f325 100644 --- a/ft8/encode_v2.cpp +++ b/ft8/encode_v2.cpp @@ -4,14 +4,17 @@ namespace ft8_v2 { -constexpr int N = 174, K = 91, M = N-K; // Define the LDPC sizes +// 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 uint16_t POLYNOMIAL = 0x2757; // CRC-14 polynomial without the leading (MSB) 1 -constexpr int K_BYTES = (K + 7) / 8; +constexpr int K_BYTES = (K + 7) / 8; // Number of whole bytes needed to store K bits // Parity generator matrix for (174,91) LDPC code, stored in bitpacked format (MSB first) -const uint8_t G[M][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 }, @@ -98,7 +101,7 @@ const uint8_t G[M][K_BYTES] = { }; // Column order (permutation) in which the bits in codeword are stored -const uint8_t colorder[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, @@ -111,10 +114,10 @@ const uint8_t colorder[N] = { }; // Costas 7x7 tone pattern -const uint8_t ICOS7[] = { 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 GRAY[8] = { 0,1,3,2,5,6,4,7 }; +const uint8_t kGray_map[8] = { 0,1,3,2,5,6,4,7 }; // Returns 1 if an odd number of bits are set in x, zero otherwise uint8_t parity8(uint8_t x) { @@ -134,7 +137,7 @@ uint8_t parity8(uint8_t x) { // [OUT] codeword - array of 174 bits stored as 22 bytes (MSB first) 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 G + // Instead we access the generator bits straight from the binary representation in kGenerator // For reference: // codeword(1:K)=message @@ -157,11 +160,11 @@ void encode174(const uint8_t *message, uint8_t *codeword) { // Compute the first part of itmp (1:M) and store the result in codeword for (int i = 0; i < M; ++i) { // do i=1,M // Fast implementation of bitwise multiplication and parity checking - // Normally nsum would contain the result of dot product between message and G[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 < K_BYTES; ++j) { - uint8_t bits = message[j] & G[i][j]; // bitwise AND (bitwise multiplication) + 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 @@ -239,8 +242,6 @@ void genft8(const uint8_t *payload, uint8_t *itone) { // Calculate CRC of 12 bytes = 96 bits, see WSJT-X code uint16_t checksum = ft8_crc(a91, 96 - 14); - // 3dcf = 0011 1101 1100 1111 - // 111 1011 1001 111 // Store the CRC at the end of 77 bit message a91[9] |= (uint8_t)(checksum >> 11); @@ -253,9 +254,9 @@ void genft8(const uint8_t *payload, uint8_t *itone) { // Message structure: S7 D29 S7 D29 S7 for (int i = 0; i < 7; ++i) { - itone[i] = ICOS7[i]; - itone[36 + i] = ICOS7[i]; - itone[72 + i] = ICOS7[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 @@ -277,9 +278,9 @@ void genft8(const uint8_t *payload, uint8_t *itone) { if (codeword[i_byte] & mask) bits3 |= 1; if (0 == (mask >>= 1)) { mask = 0x80; i_byte++; } - itone[k] = GRAY[bits3]; + itone[k] = kGray_map[bits3]; ++k; } } -}; // namespace +}; // ft8_v2