Dropped _v2 suffix and moved everything to ft8:: namespace

This commit is contained in:
Karlis Goba 2019-01-02 20:54:18 +02:00
parent 7818b0f5e0
commit 3f84b984fe
19 changed files with 244 additions and 169 deletions

View file

@ -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:

View file

@ -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;

View file

@ -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

View file

@ -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];
}

View file

@ -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

View file

@ -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);
}

View file

@ -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

View file

@ -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);
};

View file

@ -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

View file

@ -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[]);
}

View file

@ -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+-./?";

View file

@ -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);
};
}

View file

@ -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

View file

@ -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);
}

View file

@ -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
View 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);
}

View file

@ -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);

View file

@ -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;

View file

@ -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;
}