Dropped _v2 suffix and moved everything to ft8:: namespace
This commit is contained in:
parent
7818b0f5e0
commit
3f84b984fe
19 changed files with 244 additions and 169 deletions
6
Makefile
6
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:
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
#include <cstdio>
|
||||
#include <cmath>
|
||||
|
||||
#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;
|
||||
|
|
|
@ -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
|
|
@ -1,53 +1,59 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// 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];
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
@ -241,3 +242,5 @@ static void decode_multi_symbols(const uint8_t *power, int num_bins, int n_syms,
|
|||
log174[bit_idx + i] = max_one - max_zero;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
#include <stdint.h>
|
||||
|
||||
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);
|
||||
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
#include "encode_v2.h"
|
||||
#include "encode.h"
|
||||
#include "constants.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
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
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#include <stdint.h>
|
||||
|
||||
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);
|
||||
};
|
58
ft8/ldpc.cpp
58
ft8/ldpc.cpp
|
@ -14,9 +14,11 @@
|
|||
#include <stdlib.h>
|
||||
#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;
|
||||
|
@ -291,3 +293,5 @@ float platanh(float x) {
|
|||
}
|
||||
return isign * 7.0f;
|
||||
}
|
||||
|
||||
} // namespace
|
22
ft8/ldpc.h
22
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[]);
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
#include "pack_v2.h"
|
||||
#include "pack.h"
|
||||
|
||||
#include "text.h"
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
|||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
namespace ft8_v2 {
|
||||
namespace ft8 {
|
||||
|
||||
// TODO: This is wasteful, should figure out something more elegant
|
||||
const char A0[] = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+-./?";
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
#include <stdint.h>
|
||||
|
||||
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);
|
||||
|
||||
};
|
||||
}
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
#include <string.h>
|
||||
|
||||
namespace ft8 {
|
||||
|
||||
// Utility functions for characters and strings
|
||||
|
||||
char to_upper(char c) {
|
||||
|
@ -115,3 +117,5 @@ void int_to_dd(char *str, int value, int width, bool full_sign) {
|
|||
}
|
||||
*str = 0; // Add zero terminator
|
||||
}
|
||||
|
||||
} // namespace
|
36
ft8/text.h
36
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);
|
||||
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
#include "unpack_v2.h"
|
||||
#include "unpack.h"
|
||||
#include "text.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
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
|
10
ft8/unpack.h
Normal file
10
ft8/unpack.h
Normal file
|
@ -0,0 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace ft8 {
|
||||
|
||||
// message should have at least 19 bytes allocated (18 characters + zero terminator)
|
||||
int unpack77(const uint8_t *a77, char *message);
|
||||
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
// message should have at least 19 bytes allocated (18 characters + zero terminator)
|
||||
int unpack77(const uint8_t *a77, char *message);
|
18
gen_ft8.cpp
18
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;
|
||||
|
|
53
test.cpp
53
test.cpp
|
@ -4,13 +4,14 @@
|
|||
#include <cmath>
|
||||
|
||||
#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;
|
||||
}
|
Loading…
Reference in a new issue