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
|
run_tests: test
|
||||||
@./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 $@ $^
|
$(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 $@ $^
|
$(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 $@ $^
|
$(CXX) $(LDFLAGS) -o $@ $^
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
|
|
@ -3,11 +3,11 @@
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
#include "ft8/unpack_v2.h"
|
#include "ft8/unpack.h"
|
||||||
#include "ft8/ldpc.h"
|
#include "ft8/ldpc.h"
|
||||||
#include "ft8/decode.h"
|
#include "ft8/decode.h"
|
||||||
#include "ft8/constants.h"
|
#include "ft8/constants.h"
|
||||||
#include "ft8/encode_v2.h"
|
#include "ft8/encode.h"
|
||||||
|
|
||||||
#include "common/wave.h"
|
#include "common/wave.h"
|
||||||
#include "common/debug.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) {
|
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;
|
uint8_t max = 0;
|
||||||
if (log174[k + 0] > 0) max |= 4;
|
if (log174[k + 0] > 0) max |= 4;
|
||||||
if (log174[k + 1] > 0) max |= 2;
|
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);
|
extract_power(signal, num_blocks, num_bins, power);
|
||||||
|
|
||||||
// Find top candidates by Costas sync score and localize them in time and frequency
|
// Find top candidates by Costas sync score and localize them in time and frequency
|
||||||
Candidate candidate_list[kMax_candidates];
|
ft8::Candidate candidate_list[kMax_candidates];
|
||||||
int num_candidates = find_sync(power, num_blocks, num_bins, kCostas_map, kMax_candidates, candidate_list);
|
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?
|
// 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];
|
char decoded[kMax_decoded_messages][kMax_message_length];
|
||||||
int num_decoded = 0;
|
int num_decoded = 0;
|
||||||
for (int idx = 0; idx < num_candidates; ++idx) {
|
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 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 time_sec = (cand.time_offset + cand.time_sub / 2.0f) / fsk_dev;
|
||||||
|
|
||||||
float log174[FT8_N];
|
float log174[ft8::N];
|
||||||
extract_likelihood(power, num_bins, cand, kGray_map, log174);
|
ft8::extract_likelihood(power, num_bins, cand, ft8::kGray_map, log174);
|
||||||
|
|
||||||
// bp_decode() produces better decodes, uses way less memory
|
// bp_decode() produces better decodes, uses way less memory
|
||||||
uint8_t plain[FT8_N];
|
uint8_t plain[ft8::N];
|
||||||
int n_errors = 0;
|
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);
|
//ldpc_decode(log174, kLDPC_iterations, plain, &n_errors);
|
||||||
|
|
||||||
if (n_errors > 0) {
|
if (n_errors > 0) {
|
||||||
|
@ -207,23 +207,23 @@ int main(int argc, char **argv) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract payload + CRC (first FT8_K bits)
|
// Extract payload + CRC (first ft8::K bits)
|
||||||
uint8_t a91[FT8_K_BYTES];
|
uint8_t a91[ft8::K_BYTES];
|
||||||
pack_bits(plain, FT8_K, a91);
|
ft8::pack_bits(plain, ft8::K, a91);
|
||||||
|
|
||||||
// Extract CRC and check it
|
// Extract CRC and check it
|
||||||
uint16_t chksum = ((a91[9] & 0x07) << 11) | (a91[10] << 3) | (a91[11] >> 5);
|
uint16_t chksum = ((a91[9] & 0x07) << 11) | (a91[10] << 3) | (a91[11] >> 5);
|
||||||
a91[9] &= 0xF8;
|
a91[9] &= 0xF8;
|
||||||
a91[10] = 0;
|
a91[10] = 0;
|
||||||
a91[11] = 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) {
|
if (chksum != chksum2) {
|
||||||
LOG(LOG_DEBUG, "Checksum: message = %04x, CRC = %04x\n", chksum, chksum2);
|
LOG(LOG_DEBUG, "Checksum: message = %04x, CRC = %04x\n", chksum, chksum2);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
char message[kMax_message_length];
|
char message[kMax_message_length];
|
||||||
unpack77(a91, message);
|
ft8::unpack77(a91, message);
|
||||||
|
|
||||||
// Check for duplicate messages (TODO: use hashing)
|
// Check for duplicate messages (TODO: use hashing)
|
||||||
bool found = false;
|
bool found = false;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#include "constants.h"
|
#include "constants.h"
|
||||||
|
|
||||||
|
namespace ft8 {
|
||||||
|
|
||||||
// Costas 7x7 tone pattern
|
// Costas 7x7 tone pattern
|
||||||
const uint8_t kCostas_map[7] = { 3,1,4,0,6,5,2 };
|
const uint8_t kCostas_map[7] = { 3,1,4,0,6,5,2 };
|
||||||
|
|
||||||
|
@ -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 };
|
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)
|
// 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 },
|
{ 0x83, 0x29, 0xce, 0x11, 0xbf, 0x31, 0xea, 0xf5, 0x09, 0xf2, 0x7f, 0xc0 },
|
||||||
{ 0x76, 0x1c, 0x26, 0x4e, 0x25, 0xc2, 0x59, 0x33, 0x54, 0x93, 0x13, 0x20 },
|
{ 0x76, 0x1c, 0x26, 0x4e, 0x25, 0xc2, 0x59, 0x33, 0x54, 0x93, 0x13, 0x20 },
|
||||||
{ 0xdc, 0x26, 0x59, 0x02, 0xfb, 0x27, 0x7c, 0x64, 0x10, 0xa1, 0xbd, 0xc0 },
|
{ 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
|
// 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)
|
// (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,
|
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,
|
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,
|
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).
|
// each number is an index into the codeword (1-origin).
|
||||||
// the codeword bits mentioned in each row must xor to zero.
|
// the codeword bits mentioned in each row must xor to zero.
|
||||||
// From WSJT-X's ldpc_174_91_c_reordered_parity.f90.
|
// 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 },
|
{ 4, 31, 59, 91, 92, 96, 153 },
|
||||||
{ 5, 32, 60, 93, 115, 146, 0 },
|
{ 5, 32, 60, 93, 115, 146, 0 },
|
||||||
{ 6, 24, 61, 94, 122, 151, 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
|
// the numbers indicate which three parity
|
||||||
// checks (rows in Nm) refer to the codeword bit.
|
// checks (rows in Nm) refer to the codeword bit.
|
||||||
// 1-origin.
|
// 1-origin.
|
||||||
const uint8_t kMn[FT8_N][3] = {
|
const uint8_t kMn[N][3] = {
|
||||||
{ 16, 45, 73 },
|
{ 16, 45, 73 },
|
||||||
{ 25, 51, 62 },
|
{ 25, 51, 62 },
|
||||||
{ 33, 58, 78 },
|
{ 33, 58, 78 },
|
||||||
|
@ -382,7 +384,7 @@ const uint8_t kMn[FT8_N][3] = {
|
||||||
{ 42, 49, 57 }
|
{ 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,
|
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,7,6,7,6,7,6,6,6,7,6,6,6,7,6,6,
|
||||||
6,6,7,6,6,6,7,7,6,6,6,6,7,7,6,6,
|
6,6,7,6,6,6,7,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,7,7,6,6,7,6,6,6,6,6,6,6,7,
|
||||||
6,6,6
|
6,6,6
|
||||||
};
|
};
|
||||||
|
|
||||||
|
} // namespace
|
|
@ -1,53 +1,59 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
// Define FT8 symbol counts
|
namespace ft8 {
|
||||||
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)
|
|
||||||
|
|
||||||
// Define the LDPC sizes
|
// Define FT8 symbol counts
|
||||||
constexpr int FT8_N = 174; // Number of bits in the encoded message
|
constexpr int ND = 58; // Data symbols
|
||||||
constexpr int FT8_K = 91; // Number of payload bits
|
constexpr int NS = 21; // Sync symbols (3 @ Costas 7x7)
|
||||||
constexpr int FT8_M = FT8_N - FT8_K; // Number of checksum bits
|
constexpr int NN = NS + ND; // Total channel symbols (79)
|
||||||
constexpr int FT8_K_BYTES = (FT8_K + 7) / 8; // Number of whole bytes needed to store K bits
|
|
||||||
|
|
||||||
// Define CRC parameters
|
// Define the LDPC sizes
|
||||||
constexpr uint16_t CRC_POLYNOMIAL = 0x2757; // CRC-14 polynomial without the leading (MSB) 1
|
constexpr int N = 174; // Number of bits in the encoded message
|
||||||
constexpr int CRC_WIDTH = 14;
|
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
|
// Define CRC parameters
|
||||||
extern const uint8_t kCostas_map[7];
|
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
|
// Gray code map
|
||||||
extern const uint8_t kGray_map[8];
|
extern const uint8_t kGray_map[8];
|
||||||
|
|
||||||
|
|
||||||
// Parity generator matrix for (174,91) LDPC code, stored in bitpacked format (MSB first)
|
// Parity generator matrix for (174,91) LDPC code, stored in bitpacked format (MSB first)
|
||||||
extern const uint8_t kGenerator[FT8_M][FT8_K_BYTES];
|
extern const uint8_t kGenerator[M][K_BYTES];
|
||||||
|
|
||||||
|
|
||||||
// Column order (permutation) in which the bits in codeword are stored
|
// 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)
|
// (Not really used in FT8 v2 - instead the Nm, Mn and generator matrices are already permuted)
|
||||||
extern const uint8_t kColumn_order[FT8_N];
|
extern const uint8_t kColumn_order[N];
|
||||||
|
|
||||||
|
|
||||||
// this is the LDPC(174,91) parity check matrix.
|
// this is the LDPC(174,91) parity check matrix.
|
||||||
// 83 rows.
|
// 83 rows.
|
||||||
// each row describes one parity check.
|
// each row describes one parity check.
|
||||||
// each number is an index into the codeword (1-origin).
|
// each number is an index into the codeword (1-origin).
|
||||||
// the codeword bits mentioned in each row must xor to zero.
|
// the codeword bits mentioned in each row must xor to zero.
|
||||||
// From WSJT-X's ldpc_174_91_c_reordered_parity.f90.
|
// From WSJT-X's ldpc_174_91_c_reordered_parity.f90.
|
||||||
extern const uint8_t kNm[FT8_M][7];
|
extern const uint8_t kNm[M][7];
|
||||||
|
|
||||||
|
|
||||||
// Mn from WSJT-X's bpdecode174.f90.
|
// Mn from WSJT-X's bpdecode174.f90.
|
||||||
// each row corresponds to a codeword bit.
|
// each row corresponds to a codeword bit.
|
||||||
// the numbers indicate which three parity
|
// the numbers indicate which three parity
|
||||||
// checks (rows in Nm) refer to the codeword bit.
|
// checks (rows in Nm) refer to the codeword bit.
|
||||||
// 1-origin.
|
// 1-origin.
|
||||||
extern const uint8_t kMn[FT8_N][3];
|
extern const uint8_t kMn[N][3];
|
||||||
|
|
||||||
|
|
||||||
// Number of rows (columns in C/C++) in the array Nm.
|
// Number of rows (columns in C/C++) in the array Nm.
|
||||||
extern const uint8_t kNrw[FT8_M];
|
extern const uint8_t kNrw[M];
|
||||||
|
|
||||||
|
}
|
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
#include "constants.h"
|
#include "constants.h"
|
||||||
|
|
||||||
|
namespace ft8 {
|
||||||
|
|
||||||
static float max2(float a, float b);
|
static float max2(float a, float b);
|
||||||
static float max4(float a, float b, float c, float d);
|
static float max4(float a, float b, float c, float d);
|
||||||
static void heapify_down(Candidate *heap, int heap_size);
|
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_symbol(const uint8_t *power, const uint8_t *code_map, int bit_idx, float *log174);
|
||||||
static void decode_multi_symbols(const uint8_t *power, int num_bins, int n_syms, const uint8_t *code_map, int bit_idx, float *log174);
|
static 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)
|
// 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).
|
// 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) {
|
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
|
// 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.
|
// sync symbols we included in the score, so the score is averaged.
|
||||||
for (int alt = 0; alt < 4; ++alt) {
|
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) {
|
for (int freq_offset = 0; freq_offset < num_bins - 8; ++freq_offset) {
|
||||||
int score = 0;
|
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_syms = 1;
|
||||||
const int n_bits = 3 * n_syms;
|
const int n_bits = 3 * n_syms;
|
||||||
const int n_tones = (1 << n_bits);
|
const int n_tones = (1 << n_bits);
|
||||||
for (int k = 0; k < FT8_ND; k += n_syms) {
|
for (int k = 0; k < ft8::ND; k += n_syms) {
|
||||||
int sym_idx = (k < FT8_ND / 2) ? (k + 7) : (k + 14);
|
int sym_idx = (k < ft8::ND / 2) ? (k + 7) : (k + 14);
|
||||||
int bit_idx = 3 * k;
|
int bit_idx = 3 * k;
|
||||||
|
|
||||||
// Pointer to 8 bins of the current symbol
|
// 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
|
// Compute the variance of log174
|
||||||
float sum = 0;
|
float sum = 0;
|
||||||
float sum2 = 0;
|
float sum2 = 0;
|
||||||
float inv_n = 1.0f / FT8_N;
|
float inv_n = 1.0f / ft8::N;
|
||||||
for (int i = 0; i < FT8_N; ++i) {
|
for (int i = 0; i < ft8::N; ++i) {
|
||||||
sum += log174[i];
|
sum += log174[i];
|
||||||
sum2 += log174[i] * 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)
|
// 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.
|
// Seems to be 2.83 = sqrt(8). Experimentally sqrt(16) works better.
|
||||||
float norm_factor = sqrtf(16.0f / variance);
|
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;
|
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)
|
// Extract bit significance (and convert them to float)
|
||||||
// 8 FSK tones = 3 bits
|
// 8 FSK tones = 3 bits
|
||||||
for (int i = 0; i < n_bits; ++i) {
|
for (int i = 0; i < n_bits; ++i) {
|
||||||
if (bit_idx + i >= FT8_N) {
|
if (bit_idx + i >= ft8::N) {
|
||||||
// Respect array size
|
// Respect array size
|
||||||
break;
|
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;
|
log174[bit_idx + i] = max_one - max_zero;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
namespace ft8 {
|
||||||
|
|
||||||
struct Candidate {
|
struct Candidate {
|
||||||
int16_t score;
|
int16_t score;
|
||||||
int16_t time_offset;
|
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
|
// Compute log likelihood log(p(1) / p(0)) of 174 message bits
|
||||||
// for later use in soft-decision LDPC decoding
|
// 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);
|
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 "constants.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
namespace ft8_v2 {
|
namespace ft8 {
|
||||||
|
|
||||||
|
|
||||||
// Returns 1 if an odd number of bits are set in x, zero otherwise
|
// 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
|
// codeword(K+1:N)=pchecks
|
||||||
|
|
||||||
// printf("Encode ");
|
// 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("%02x ", message[i]);
|
||||||
// }
|
// }
|
||||||
// printf("\n");
|
// printf("\n");
|
||||||
|
|
||||||
// Fill the codeword with message and zeros, as we will only update binary ones later
|
// 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) {
|
for (int j = 0; j < (7 + ft8::N) / 8; ++j) {
|
||||||
codeword[j] = (j < FT8_K_BYTES) ? message[j] : 0;
|
codeword[j] = (j < ft8::K_BYTES) ? message[j] : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t col_mask = (0x80 >> (FT8_K % 8)); // bitmask of current byte
|
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_idx = ft8::K_BYTES - 1; // index into byte array
|
||||||
|
|
||||||
// Compute the first part of itmp (1:FT8_M) and store the result in codeword
|
// 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
|
for (int i = 0; i < ft8::M; ++i) { // do i=1,ft8::M
|
||||||
// Fast implementation of bitwise multiplication and parity checking
|
// Fast implementation of bitwise multiplication and parity checking
|
||||||
// Normally nsum would contain the result of dot product between message and kGenerator[i],
|
// Normally nsum would contain the result of dot product between message and kGenerator[i],
|
||||||
// but we only compute the sum modulo 2.
|
// but we only compute the sum modulo 2.
|
||||||
uint8_t nsum = 0;
|
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)
|
uint8_t bits = message[j] & kGenerator[i][j]; // bitwise AND (bitwise multiplication)
|
||||||
nsum ^= parity8(bits); // bitwise XOR (addition modulo 2)
|
nsum ^= parity8(bits); // bitwise XOR (addition modulo 2)
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ void encode174(const uint8_t *message, uint8_t *codeword) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// printf("Result ");
|
// 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("%02x ", codeword[i]);
|
||||||
// }
|
// }
|
||||||
// printf("\n");
|
// 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
|
// Compute 14-bit CRC for a sequence of given number of bits
|
||||||
// [IN] message - byte sequence (MSB first)
|
// [IN] message - byte sequence (MSB first)
|
||||||
// [IN] num_bits - number of bits in the sequence
|
// [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
|
// Adapted from https://barrgroup.com/Embedded-Systems/How-To/CRC-Calculation-C-Code
|
||||||
constexpr uint16_t TOPBIT = (1 << (CRC_WIDTH - 1));
|
constexpr uint16_t TOPBIT = (1 << (CRC_WIDTH - 1));
|
||||||
|
|
||||||
|
@ -127,7 +127,7 @@ void genft8(const uint8_t *payload, uint8_t *itone) {
|
||||||
a91[11] = 0;
|
a91[11] = 0;
|
||||||
|
|
||||||
// Calculate CRC of 12 bytes = 96 bits, see WSJT-X code
|
// 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
|
// Store the CRC at the end of 77 bit message
|
||||||
a91[9] |= (uint8_t)(checksum >> 11);
|
a91[9] |= (uint8_t)(checksum >> 11);
|
||||||
|
@ -149,7 +149,7 @@ void genft8(const uint8_t *payload, uint8_t *itone) {
|
||||||
|
|
||||||
uint8_t mask = 0x80;
|
uint8_t mask = 0x80;
|
||||||
int i_byte = 0;
|
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) {
|
if (j == 29) {
|
||||||
k += 7; // Skip over the second set of Costas symbols
|
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>
|
#include <stdint.h>
|
||||||
|
|
||||||
namespace ft8_v2 {
|
namespace ft8 {
|
||||||
|
|
||||||
// Generate FT8 tone sequence from payload data
|
// Generate FT8 tone sequence from payload data
|
||||||
// [IN] payload - 9 byte array consisting of 72 bit payload
|
// [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
|
// Compute 14-bit CRC for a sequence of given number of bits
|
||||||
// [IN] message - byte sequence (MSB first)
|
// [IN] message - byte sequence (MSB first)
|
||||||
// [IN] num_bits - number of bits in the sequence
|
// [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 <stdlib.h>
|
||||||
#include "constants.h"
|
#include "constants.h"
|
||||||
|
|
||||||
int ldpc_check(uint8_t codeword[]);
|
namespace ft8 {
|
||||||
float fast_tanh(float x);
|
|
||||||
float fast_atanh(float x);
|
static int ldpc_check(uint8_t codeword[]);
|
||||||
|
static float fast_tanh(float x);
|
||||||
|
static float fast_atanh(float x);
|
||||||
|
|
||||||
|
|
||||||
// Packs a string of bits each represented as a zero/non-zero byte in plain[],
|
// Packs a string of bits each represented as a zero/non-zero byte in plain[],
|
||||||
|
@ -47,19 +49,19 @@ void pack_bits(const uint8_t plain[], int num_bits, uint8_t packed[]) {
|
||||||
// max_iters is how hard to try.
|
// max_iters is how hard to try.
|
||||||
// ok == 87 means success.
|
// ok == 87 means success.
|
||||||
void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok) {
|
void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok) {
|
||||||
float m[FT8_M][FT8_N]; // ~60 kB
|
float m[ft8::M][ft8::N]; // ~60 kB
|
||||||
float e[FT8_M][FT8_N]; // ~60 kB
|
float e[ft8::M][ft8::N]; // ~60 kB
|
||||||
int min_errors = FT8_M;
|
int min_errors = ft8::M;
|
||||||
|
|
||||||
for (int j = 0; j < FT8_M; j++) {
|
for (int j = 0; j < ft8::M; j++) {
|
||||||
for (int i = 0; i < FT8_N; i++) {
|
for (int i = 0; i < ft8::N; i++) {
|
||||||
m[j][i] = codeword[i];
|
m[j][i] = codeword[i];
|
||||||
e[j][i] = 0.0f;
|
e[j][i] = 0.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int iter = 0; iter < max_iters; iter++) {
|
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++) {
|
for (int ii1 = 0; ii1 < kNrw[j]; ii1++) {
|
||||||
int i1 = kNm[j][ii1] - 1;
|
int i1 = kNm[j][ii1] - 1;
|
||||||
float a = 1.0f;
|
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];
|
float l = codeword[i];
|
||||||
for (int j = 0; j < 3; j++)
|
for (int j = 0; j < 3; j++)
|
||||||
l += e[kMn[i][j] - 1][i];
|
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++) {
|
for (int ji1 = 0; ji1 < 3; ji1++) {
|
||||||
int j1 = kMn[i][ji1] - 1;
|
int j1 = kMn[i][ji1] - 1;
|
||||||
float l = codeword[i];
|
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.
|
// returns the number of parity errors.
|
||||||
// 0 means total success.
|
// 0 means total success.
|
||||||
//
|
//
|
||||||
int ldpc_check(uint8_t codeword[]) {
|
static int ldpc_check(uint8_t codeword[]) {
|
||||||
int errors = 0;
|
int errors = 0;
|
||||||
|
|
||||||
for (int j = 0; j < FT8_M; ++j) {
|
for (int j = 0; j < ft8::M; ++j) {
|
||||||
uint8_t x = 0;
|
uint8_t x = 0;
|
||||||
for (int i = 0; i < kNrw[j]; ++i) {
|
for (int i = 0; i < kNrw[j]; ++i) {
|
||||||
x ^= codeword[kNm[j][i] - 1];
|
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) {
|
void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok) {
|
||||||
float tov[FT8_N][3];
|
float tov[ft8::N][3];
|
||||||
float toc[FT8_M][7];
|
float toc[ft8::M][7];
|
||||||
|
|
||||||
int min_errors = FT8_M;
|
int min_errors = ft8::M;
|
||||||
|
|
||||||
int nclast = 0;
|
int nclast = 0;
|
||||||
int ncnt = 0;
|
int ncnt = 0;
|
||||||
|
|
||||||
// initialize messages to checks
|
// 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) {
|
for (int j = 0; j < kNrw[i]; ++j) {
|
||||||
toc[i][j] = codeword[kNm[i][j] - 1];
|
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) {
|
for (int j = 0; j < 3; ++j) {
|
||||||
tov[i][j] = 0;
|
tov[i][j] = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int iter = 0; iter < max_iters; ++iter) {
|
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)
|
// 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];
|
zn[i] = codeword[i] + tov[i][0] + tov[i][1] + tov[i][2];
|
||||||
plain[i] = (zn[i] > 0) ? 1 : 0;
|
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
|
// 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) {
|
for (int j = 0; j < kNrw[i]; ++j) {
|
||||||
int ibj = kNm[i][j] - 1;
|
int ibj = kNm[i][j] - 1;
|
||||||
toc[i][j] = zn[ibj];
|
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
|
// 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) {
|
for (int j = 0; j < kNrw[i]; ++j) {
|
||||||
toc[i][j] = fast_tanh(-toc[i][j] / 2);
|
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) {
|
for (int j = 0; j < 3; ++j) {
|
||||||
int ichk = kMn[i][j] - 1; // kMn(:,j) are the checks that include bit j
|
int ichk = kMn[i][j] - 1; // kMn(:,j) are the checks that include bit j
|
||||||
float Tmn = 1.0f;
|
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
|
// thank you Douglas Bagnall
|
||||||
// https://math.stackexchange.com/a/446411
|
// https://math.stackexchange.com/a/446411
|
||||||
float fast_tanh(float x) {
|
static float fast_tanh(float x) {
|
||||||
if (x < -4.97f) {
|
if (x < -4.97f) {
|
||||||
return -1.0f;
|
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 x2 = x * x;
|
||||||
//float a = x * (-15015.0f + x2 * (19250.0f + x2 * (-5943.0f + x2 * 256.0f)));
|
//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)));
|
//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;
|
float isign = +1;
|
||||||
if (x < 0) {
|
if (x < 0) {
|
||||||
isign = -1;
|
isign = -1;
|
||||||
|
@ -271,7 +273,7 @@ float pltanh(float x) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
float platanh(float x) {
|
static float platanh(float x) {
|
||||||
float isign = +1;
|
float isign = +1;
|
||||||
if (x < 0) {
|
if (x < 0) {
|
||||||
isign = -1;
|
isign = -1;
|
||||||
|
@ -291,3 +293,5 @@ float platanh(float x) {
|
||||||
}
|
}
|
||||||
return isign * 7.0f;
|
return isign * 7.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace
|
22
ft8/ldpc.h
22
ft8/ldpc.h
|
@ -1,13 +1,17 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
// codeword is 174 log-likelihoods.
|
namespace ft8 {
|
||||||
// plain is a return value, 174 ints, to be 0 or 1.
|
|
||||||
// iters is how hard to try.
|
|
||||||
// ok == 87 means success.
|
|
||||||
void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok);
|
|
||||||
|
|
||||||
void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok);
|
// 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[],
|
void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok);
|
||||||
// as a string of packed bits starting from the MSB of the first byte of packed[]
|
|
||||||
void pack_bits(const uint8_t plain[], int num_bits, uint8_t packed[]);
|
// Packs a string of bits each represented as a zero/non-zero byte in plain[],
|
||||||
|
// as a string of packed bits starting from the MSB of the first byte of packed[]
|
||||||
|
void pack_bits(const uint8_t plain[], int num_bits, uint8_t packed[]);
|
||||||
|
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
#include "pack_v2.h"
|
#include "pack.h"
|
||||||
|
|
||||||
#include "text.h"
|
#include "text.h"
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
namespace ft8_v2 {
|
namespace ft8 {
|
||||||
|
|
||||||
// TODO: This is wasteful, should figure out something more elegant
|
// TODO: This is wasteful, should figure out something more elegant
|
||||||
const char A0[] = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+-./?";
|
const char A0[] = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+-./?";
|
|
@ -2,11 +2,11 @@
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
namespace ft8_v2 {
|
namespace ft8 {
|
||||||
|
|
||||||
// Pack FT8 text message into 72 bits
|
// Pack FT8 text message into 72 bits
|
||||||
// [IN] msg - FT8 message (e.g. "CQ TE5T KN01")
|
// [IN] msg - FT8 message (e.g. "CQ TE5T KN01")
|
||||||
// [OUT] c77 - 10 byte array to store the 77 bit payload (MSB first)
|
// [OUT] c77 - 10 byte array to store the 77 bit payload (MSB first)
|
||||||
int pack77(const char *msg, uint8_t *c77);
|
int pack77(const char *msg, uint8_t *c77);
|
||||||
|
|
||||||
};
|
}
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
namespace ft8 {
|
||||||
|
|
||||||
// Utility functions for characters and strings
|
// Utility functions for characters and strings
|
||||||
|
|
||||||
char to_upper(char c) {
|
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
|
*str = 0; // Add zero terminator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace
|
36
ft8/text.h
36
ft8/text.h
|
@ -1,22 +1,26 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
char to_upper(char c);
|
namespace ft8 {
|
||||||
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);
|
|
||||||
|
|
||||||
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:
|
int char_index(const char *string, char c);
|
||||||
// - replaces lowercase letters with uppercase
|
|
||||||
// - merges consecutive spaces into single space
|
|
||||||
void fmtmsg(char *msg_out, const char *msg_in);
|
|
||||||
|
|
||||||
// Parse a 2 digit integer from string
|
// Text message formatting:
|
||||||
int dd_to_int(const char *str, int length);
|
// - 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
|
// Parse a 2 digit integer from string
|
||||||
void int_to_dd(char *str, int value, int width, bool full_sign = false);
|
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 "text.h"
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
namespace ft8 {
|
||||||
|
|
||||||
//const uint32_t NBASE = 37L*36L*10L*27L*27L*27L;
|
//const uint32_t NBASE = 37L*36L*10L*27L*27L*27L;
|
||||||
const uint32_t MAX22 = 4194304L;
|
const uint32_t MAX22 = 4194304L;
|
||||||
const uint32_t NTOKENS = 2063592L;
|
const uint32_t NTOKENS = 2063592L;
|
||||||
|
@ -327,3 +329,5 @@ int unpack77(const uint8_t *a77, char *message) {
|
||||||
|
|
||||||
return 0;
|
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 "common/wave.h"
|
||||||
//#include "ft8/v1/pack.h"
|
//#include "ft8/v1/pack.h"
|
||||||
//#include "ft8/v1/encode.h"
|
//#include "ft8/v1/encode.h"
|
||||||
#include "ft8/pack_v2.h"
|
#include "ft8/pack.h"
|
||||||
#include "ft8/encode_v2.h"
|
#include "ft8/encode.h"
|
||||||
#include "ft8/constants.h"
|
#include "ft8/constants.h"
|
||||||
|
|
||||||
// Convert a sequence of symbols (tones) into a sinewave of continuous phase (FSK).
|
// 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];
|
const char *wav_path = argv[2];
|
||||||
|
|
||||||
// First, pack the text data into binary message
|
// 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 = packmsg(message, packed);
|
||||||
int rc = ft8_v2::pack77(message, packed);
|
int rc = ft8::pack77(message, packed);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
printf("Cannot parse message!\n");
|
printf("Cannot parse message!\n");
|
||||||
printf("RC = %d\n", rc);
|
printf("RC = %d\n", rc);
|
||||||
|
@ -73,12 +73,12 @@ int main(int argc, char **argv) {
|
||||||
printf("\n");
|
printf("\n");
|
||||||
|
|
||||||
// Second, encode the binary message as a sequence of FSK tones
|
// 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);
|
//genft8(packed, 0, tones);
|
||||||
ft8_v2::genft8(packed, tones);
|
ft8::genft8(packed, tones);
|
||||||
|
|
||||||
printf("FSK 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("%d", tones[j]);
|
||||||
}
|
}
|
||||||
printf("\n");
|
printf("\n");
|
||||||
|
@ -86,14 +86,14 @@ int main(int argc, char **argv) {
|
||||||
// Third, convert the FSK tones into an audio signal
|
// Third, convert the FSK tones into an audio signal
|
||||||
const int sample_rate = 12000;
|
const int sample_rate = 12000;
|
||||||
const float symbol_rate = 6.25f;
|
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;
|
const int num_silence = (15 * sample_rate - num_samples) / 2;
|
||||||
float signal[num_silence + num_samples + num_silence];
|
float signal[num_silence + num_samples + num_silence];
|
||||||
for (int i = 0; i < num_silence + num_samples + num_silence; i++) {
|
for (int i = 0; i < num_silence + num_samples + num_silence; i++) {
|
||||||
signal[i] = 0;
|
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);
|
save_wav(signal, num_silence + num_samples + num_silence, sample_rate, wav_path);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
53
test.cpp
53
test.cpp
|
@ -4,13 +4,14 @@
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
#include "ft8/text.h"
|
#include "ft8/text.h"
|
||||||
#include "ft8/v1/pack.h"
|
//#include "ft8/v1/pack.h"
|
||||||
#include "ft8/v1/unpack.h"
|
//#include "ft8/v1/unpack.h"
|
||||||
#include "ft8/v1/encode.h"
|
//#include "ft8/v1/encode.h"
|
||||||
#include "ft8/pack_v2.h"
|
#include "ft8/pack.h"
|
||||||
#include "ft8/encode_v2.h"
|
#include "ft8/encode.h"
|
||||||
#include "ft8/constants.h"
|
#include "ft8/constants.h"
|
||||||
|
|
||||||
|
#include "fft/kiss_fftr.h"
|
||||||
#include "common/debug.h"
|
#include "common/debug.h"
|
||||||
|
|
||||||
#define LOG_LEVEL LOG_INFO
|
#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() {
|
bool test1() {
|
||||||
//const char *msg = "CQ DL7ACA JO40"; // 62, 32, 32, 49, 37, 27, 59, 2, 30, 19, 49, 16
|
//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
|
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
|
uint16_t crc1 = ft8_crc(test_in2, 76); // Calculate CRC of 76 bits only
|
||||||
LOG(LOG_INFO, "CRC: %04x\n", crc1); // should be 0x0708
|
LOG(LOG_INFO, "CRC: %04x\n", crc1); // should be 0x0708
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
void test_tones(float *log174) {
|
void test_tones(float *log174) {
|
||||||
// Just a test case
|
// 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};
|
const uint8_t inv_map[8] = {0, 1, 3, 2, 6, 4, 5, 7};
|
||||||
uint8_t tone = ("0000000011721762454112705354533170166234757420515470163426"[i]) - '0';
|
uint8_t tone = ("0000000011721762454112705354533170166234757420515470163426"[i]) - '0';
|
||||||
uint8_t b3 = inv_map[tone];
|
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() {
|
int main() {
|
||||||
test1();
|
//test1();
|
||||||
|
test4();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
Loading…
Reference in a new issue