kopia lustrzana https://github.com/kgoba/ft8_lib
First step of moving to C
rodzic
6ebf14342e
commit
2dbaa4672b
264
decode_ft8.cpp
264
decode_ft8.cpp
|
@ -13,9 +13,9 @@
|
|||
#include "common/debug.h"
|
||||
#include "fft/kiss_fftr.h"
|
||||
|
||||
#define LOG_LEVEL LOG_INFO
|
||||
#define LOG_LEVEL LOG_INFO
|
||||
|
||||
const int kMin_score = 40; // Minimum sync score threshold for candidates
|
||||
const int kMin_score = 40; // Minimum sync score threshold for candidates
|
||||
const int kMax_candidates = 120;
|
||||
const int kLDPC_iterations = 25;
|
||||
|
||||
|
@ -25,29 +25,30 @@ const int kMax_message_length = 25;
|
|||
const int kFreq_osr = 2;
|
||||
const int kTime_osr = 2;
|
||||
|
||||
const float kFSK_dev = 6.25f; // tone deviation in Hz and symbol rate
|
||||
const float kFSK_dev = 6.25f; // tone deviation in Hz and symbol rate
|
||||
|
||||
void usage() {
|
||||
void usage()
|
||||
{
|
||||
fprintf(stderr, "Decode a 15-second WAV file.\n");
|
||||
}
|
||||
|
||||
|
||||
float hann_i(int i, int N) {
|
||||
float hann_i(int i, int N)
|
||||
{
|
||||
float x = sinf((float)M_PI * i / (N - 1));
|
||||
return x*x;
|
||||
return x * x;
|
||||
}
|
||||
|
||||
|
||||
float hamming_i(int i, int N) {
|
||||
float hamming_i(int i, int N)
|
||||
{
|
||||
const float a0 = (float)25 / 46;
|
||||
const float a1 = 1 - a0;
|
||||
|
||||
float x1 = cosf(2 * (float)M_PI * i / (N - 1));
|
||||
return a0 - a1*x1;
|
||||
return a0 - a1 * x1;
|
||||
}
|
||||
|
||||
|
||||
float blackman_i(int i, int N) {
|
||||
float blackman_i(int i, int N)
|
||||
{
|
||||
const float alpha = 0.16f; // or 2860/18608
|
||||
const float a0 = (1 - alpha) / 2;
|
||||
const float a1 = 1.0f / 2;
|
||||
|
@ -55,28 +56,31 @@ float blackman_i(int i, int N) {
|
|||
|
||||
float x1 = cosf(2 * (float)M_PI * i / (N - 1));
|
||||
//float x2 = cosf(4 * (float)M_PI * i / (N - 1));
|
||||
float x2 = 2*x1*x1 - 1; // Use double angle formula
|
||||
float x2 = 2 * x1 * x1 - 1; // Use double angle formula
|
||||
|
||||
return a0 - a1*x1 + a2*x2;
|
||||
return a0 - a1 * x1 + a2 * x2;
|
||||
}
|
||||
|
||||
static float max2(float a, float b) {
|
||||
static float max2(float a, float b)
|
||||
{
|
||||
return (a >= b) ? a : b;
|
||||
}
|
||||
|
||||
// Compute FFT magnitudes (log power) for each timeslot in the signal
|
||||
void extract_power(const float signal[], ft8::MagArray * power) {
|
||||
void extract_power(const float signal[], MagArray *power)
|
||||
{
|
||||
const int block_size = 2 * power->num_bins; // Average over 2 bins per FSK tone
|
||||
const int subblock_size = block_size / power->time_osr;
|
||||
const int nfft = block_size * power->freq_osr; // We take FFT of two blocks, advancing by one
|
||||
const float fft_norm = 2.0f / nfft;
|
||||
|
||||
float window[nfft];
|
||||
for (int i = 0; i < nfft; ++i) {
|
||||
float window[nfft];
|
||||
for (int i = 0; i < nfft; ++i)
|
||||
{
|
||||
window[i] = hann_i(i, nfft);
|
||||
}
|
||||
|
||||
size_t fft_work_size;
|
||||
size_t fft_work_size;
|
||||
kiss_fftr_alloc(nfft, 0, 0, &fft_work_size);
|
||||
|
||||
LOG(LOG_INFO, "Block size = %d\n", block_size);
|
||||
|
@ -84,34 +88,40 @@ void extract_power(const float signal[], ft8::MagArray * power) {
|
|||
LOG(LOG_INFO, "N_FFT = %d\n", nfft);
|
||||
LOG(LOG_INFO, "FFT work area = %lu\n", fft_work_size);
|
||||
|
||||
void *fft_work = malloc(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);
|
||||
|
||||
int offset = 0;
|
||||
float max_mag = -100.0f;
|
||||
for (int i = 0; i < power->num_blocks; ++i) {
|
||||
for (int i = 0; i < power->num_blocks; ++i)
|
||||
{
|
||||
// Loop over two possible time offsets (0 and block_size/2)
|
||||
for (int time_sub = 0; time_sub < power->time_osr; ++time_sub) {
|
||||
for (int time_sub = 0; time_sub < power->time_osr; ++time_sub)
|
||||
{
|
||||
kiss_fft_scalar timedata[nfft];
|
||||
kiss_fft_cpx freqdata[nfft/2 + 1];
|
||||
float mag_db[nfft/2 + 1];
|
||||
kiss_fft_cpx freqdata[nfft / 2 + 1];
|
||||
float mag_db[nfft / 2 + 1];
|
||||
|
||||
// Extract windowed signal block
|
||||
for (int j = 0; j < nfft; ++j) {
|
||||
for (int j = 0; j < nfft; ++j)
|
||||
{
|
||||
timedata[j] = window[j] * signal[(i * block_size) + (j + time_sub * subblock_size)];
|
||||
}
|
||||
|
||||
kiss_fftr(fft_cfg, timedata, freqdata);
|
||||
|
||||
// Compute log magnitude in decibels
|
||||
for (int j = 0; j < nfft/2 + 1; ++j) {
|
||||
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);
|
||||
}
|
||||
|
||||
// Loop over two possible frequency bin offsets (for averaging)
|
||||
for (int freq_sub = 0; freq_sub < power->freq_osr; ++freq_sub) {
|
||||
for (int j = 0; j < power->num_bins; ++j) {
|
||||
for (int freq_sub = 0; freq_sub < power->freq_osr; ++freq_sub)
|
||||
{
|
||||
for (int j = 0; j < power->num_bins; ++j)
|
||||
{
|
||||
float db1 = mag_db[j * power->freq_osr + freq_sub];
|
||||
//float db2 = mag_db[j * 2 + freq_sub + 1];
|
||||
//float db = (db1 + db2) / 2;
|
||||
|
@ -123,7 +133,8 @@ void extract_power(const float signal[], ft8::MagArray * power) {
|
|||
power->mag[offset] = (scaled < 0) ? 0 : ((scaled > 255) ? 255 : scaled);
|
||||
++offset;
|
||||
|
||||
if (db > max_mag) max_mag = db;
|
||||
if (db > max_mag)
|
||||
max_mag = db;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -133,36 +144,44 @@ void extract_power(const float signal[], ft8::MagArray * power) {
|
|||
free(fft_work);
|
||||
}
|
||||
|
||||
|
||||
void normalize_signal(float *signal, int num_samples) {
|
||||
void normalize_signal(float *signal, int num_samples)
|
||||
{
|
||||
float max_amp = 1E-5f;
|
||||
for (int i = 0; i < num_samples; ++i) {
|
||||
for (int i = 0; i < num_samples; ++i)
|
||||
{
|
||||
float amp = fabsf(signal[i]);
|
||||
if (amp > max_amp) {
|
||||
if (amp > max_amp)
|
||||
{
|
||||
max_amp = amp;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < num_samples; ++i) {
|
||||
for (int i = 0; i < num_samples; ++i)
|
||||
{
|
||||
signal[i] /= max_amp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void print_tones(const uint8_t *code_map, const float *log174) {
|
||||
for (int k = 0; k < ft8::N; k += 3) {
|
||||
void print_tones(const uint8_t *code_map, const float *log174)
|
||||
{
|
||||
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;
|
||||
if (log174[k + 2] > 0) max |= 1;
|
||||
if (log174[k + 0] > 0)
|
||||
max |= 4;
|
||||
if (log174[k + 1] > 0)
|
||||
max |= 2;
|
||||
if (log174[k + 2] > 0)
|
||||
max |= 1;
|
||||
LOG(LOG_DEBUG, "%d", code_map[max]);
|
||||
}
|
||||
LOG(LOG_DEBUG, "\n");
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
// Expect one command-line argument
|
||||
if (argc < 2) {
|
||||
if (argc < 2)
|
||||
{
|
||||
usage();
|
||||
return -1;
|
||||
}
|
||||
|
@ -174,7 +193,8 @@ int main(int argc, char **argv) {
|
|||
float signal[num_samples];
|
||||
|
||||
int rc = load_wav(signal, num_samples, sample_rate, wav_path);
|
||||
if (rc < 0) {
|
||||
if (rc < 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
normalize_signal(signal, num_samples);
|
||||
|
@ -190,90 +210,112 @@ int main(int argc, char **argv) {
|
|||
|
||||
// Compute FFT over the whole signal and store it
|
||||
uint8_t mag_power[num_blocks * kFreq_osr * kTime_osr * num_bins];
|
||||
ft8::MagArray power = {
|
||||
.num_blocks = num_blocks,
|
||||
MagArray power = {
|
||||
.num_blocks = num_blocks,
|
||||
.num_bins = num_bins,
|
||||
.time_osr = kTime_osr,
|
||||
.freq_osr = kFreq_osr,
|
||||
.mag = mag_power
|
||||
};
|
||||
.mag = mag_power};
|
||||
extract_power(signal, &power);
|
||||
|
||||
// Find top candidates by Costas sync score and localize them in time and frequency
|
||||
ft8::Candidate candidate_list[kMax_candidates];
|
||||
int num_candidates = ft8::find_sync(&power, ft8::kCostas_map, kMax_candidates, candidate_list, kMin_score);
|
||||
Candidate candidate_list[kMax_candidates];
|
||||
int num_candidates = find_sync(&power, kCostas_map, kMax_candidates, candidate_list, kMin_score);
|
||||
|
||||
// TODO: sort the candidates by strongest sync first?
|
||||
|
||||
// Go over candidates and attempt to decode messages
|
||||
char decoded[kMax_decoded_messages][kMax_message_length];
|
||||
int num_decoded = 0;
|
||||
for (int idx = 0; idx < num_candidates; ++idx) {
|
||||
ft8::Candidate &cand = candidate_list[idx];
|
||||
if (cand.score < kMin_score) continue;
|
||||
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];
|
||||
if (cand.score < kMin_score)
|
||||
continue;
|
||||
|
||||
float freq_hz = (cand.freq_offset + (float)cand.freq_sub / kFreq_osr) * kFSK_dev;
|
||||
float freq_hz = (cand.freq_offset + (float)cand.freq_sub / kFreq_osr) * kFSK_dev;
|
||||
float time_sec = (cand.time_offset + (float)cand.time_sub / kTime_osr) / kFSK_dev;
|
||||
|
||||
float log174[ft8::N];
|
||||
ft8::extract_likelihood(&power, cand, ft8::kGray_map, log174);
|
||||
float log174[FT8_N];
|
||||
extract_likelihood(&power, cand, kGray_map, log174);
|
||||
|
||||
// bp_decode() produces better decodes, uses way less memory
|
||||
uint8_t plain[ft8::N];
|
||||
int n_errors = 0;
|
||||
ft8::bp_decode(log174, kLDPC_iterations, plain, &n_errors);
|
||||
//ft8::ldpc_decode(log174, kLDPC_iterations, plain, &n_errors);
|
||||
for (int k = 100; k < 174; ++k)
|
||||
{
|
||||
// bp_decode() produces better decodes, uses way less memory
|
||||
uint8_t plain[FT8_N];
|
||||
float log174_zeroed[FT8_N];
|
||||
int n_errors = 0;
|
||||
|
||||
if (n_errors > 0) {
|
||||
LOG(LOG_DEBUG, "ldpc_decode() = %d (%.0f Hz)\n", n_errors, freq_hz);
|
||||
continue;
|
||||
}
|
||||
|
||||
int sum_plain = 0;
|
||||
for (int i = 0; i < ft8::N; ++i) {
|
||||
sum_plain += plain[i];
|
||||
}
|
||||
if (sum_plain == 0) {
|
||||
// All zeroes message
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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::crc(a91, 96 - 14);
|
||||
if (chksum != chksum2) {
|
||||
LOG(LOG_DEBUG, "Checksum: message = %04x, CRC = %04x\n", chksum, chksum2);
|
||||
continue;
|
||||
}
|
||||
|
||||
char message[kMax_message_length];
|
||||
if (ft8::unpack77(a91, message) < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for duplicate messages (TODO: use hashing)
|
||||
bool found = false;
|
||||
for (int i = 0; i < num_decoded; ++i) {
|
||||
if (0 == strcmp(decoded[i], message)) {
|
||||
found = true;
|
||||
break;
|
||||
for (int m = 0; m < 174; ++m)
|
||||
{
|
||||
if (m < k)
|
||||
log174_zeroed[m] = log174[m];
|
||||
else
|
||||
log174_zeroed[m] = 0;
|
||||
}
|
||||
}
|
||||
bp_decode(log174_zeroed, kLDPC_iterations, plain, &n_errors);
|
||||
// ldpc_decode(log174, kLDPC_iterations, plain, &n_errors);
|
||||
|
||||
if (!found && num_decoded < kMax_decoded_messages) {
|
||||
strcpy(decoded[num_decoded], message);
|
||||
++num_decoded;
|
||||
if (n_errors > 0)
|
||||
{
|
||||
LOG(LOG_DEBUG, "ldpc_decode() = %d (%.0f Hz)\n", n_errors, freq_hz);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fake WSJT-X-like output for now
|
||||
int snr = 0; // TODO: compute SNR
|
||||
printf("000000 %3d %4.1f %4d ~ %s\n", cand.score, time_sec, (int)(freq_hz + 0.5f), message);
|
||||
int sum_plain = 0;
|
||||
for (int i = 0; i < FT8_N; ++i)
|
||||
{
|
||||
sum_plain += plain[i];
|
||||
}
|
||||
if (sum_plain == 0)
|
||||
{
|
||||
// All zeroes message
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract payload + CRC (first FT8_K bits)
|
||||
uint8_t a91[FT8_K_BYTES];
|
||||
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_crc(a91, 96 - 14);
|
||||
if (chksum != chksum2)
|
||||
{
|
||||
LOG(LOG_DEBUG, "Checksum: message = %04x, CRC = %04x\n", chksum, chksum2);
|
||||
continue;
|
||||
}
|
||||
|
||||
char message[kMax_message_length];
|
||||
if (unpack77(a91, message) < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for duplicate messages (TODO: use hashing)
|
||||
bool found = false;
|
||||
for (int i = 0; i < num_decoded; ++i)
|
||||
{
|
||||
if (0 == strcmp(decoded[i], message))
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found && num_decoded < kMax_decoded_messages)
|
||||
{
|
||||
strcpy(decoded[num_decoded], message);
|
||||
++num_decoded;
|
||||
|
||||
// Fake WSJT-X-like output for now
|
||||
int snr = 0; // TODO: compute SNR
|
||||
printf("000000 %3d [%2d] %4.1f %4d ~ %s\n", cand.score, k, time_sec, (int)(freq_hz + 0.5f), message);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG(LOG_INFO, "Decoded %d messages\n", num_decoded);
|
||||
|
|
|
@ -1,114 +1,109 @@
|
|||
#include "constants.h"
|
||||
|
||||
namespace ft8 {
|
||||
|
||||
// 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};
|
||||
|
||||
// Gray code map
|
||||
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)
|
||||
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 },
|
||||
{ 0x1b, 0x3f, 0x41, 0x78, 0x58, 0xcd, 0x2d, 0xd3, 0x3e, 0xc7, 0xf6, 0x20 },
|
||||
{ 0x09, 0xfd, 0xa4, 0xfe, 0xe0, 0x41, 0x95, 0xfd, 0x03, 0x47, 0x83, 0xa0 },
|
||||
{ 0x07, 0x7c, 0xcc, 0xc1, 0x1b, 0x88, 0x73, 0xed, 0x5c, 0x3d, 0x48, 0xa0 },
|
||||
{ 0x29, 0xb6, 0x2a, 0xfe, 0x3c, 0xa0, 0x36, 0xf4, 0xfe, 0x1a, 0x9d, 0xa0 },
|
||||
{ 0x60, 0x54, 0xfa, 0xf5, 0xf3, 0x5d, 0x96, 0xd3, 0xb0, 0xc8, 0xc3, 0xe0 },
|
||||
{ 0xe2, 0x07, 0x98, 0xe4, 0x31, 0x0e, 0xed, 0x27, 0x88, 0x4a, 0xe9, 0x00 },
|
||||
{ 0x77, 0x5c, 0x9c, 0x08, 0xe8, 0x0e, 0x26, 0xdd, 0xae, 0x56, 0x31, 0x80 },
|
||||
{ 0xb0, 0xb8, 0x11, 0x02, 0x8c, 0x2b, 0xf9, 0x97, 0x21, 0x34, 0x87, 0xc0 },
|
||||
{ 0x18, 0xa0, 0xc9, 0x23, 0x1f, 0xc6, 0x0a, 0xdf, 0x5c, 0x5e, 0xa3, 0x20 },
|
||||
{ 0x76, 0x47, 0x1e, 0x83, 0x02, 0xa0, 0x72, 0x1e, 0x01, 0xb1, 0x2b, 0x80 },
|
||||
{ 0xff, 0xbc, 0xcb, 0x80, 0xca, 0x83, 0x41, 0xfa, 0xfb, 0x47, 0xb2, 0xe0 },
|
||||
{ 0x66, 0xa7, 0x2a, 0x15, 0x8f, 0x93, 0x25, 0xa2, 0xbf, 0x67, 0x17, 0x00 },
|
||||
{ 0xc4, 0x24, 0x36, 0x89, 0xfe, 0x85, 0xb1, 0xc5, 0x13, 0x63, 0xa1, 0x80 },
|
||||
{ 0x0d, 0xff, 0x73, 0x94, 0x14, 0xd1, 0xa1, 0xb3, 0x4b, 0x1c, 0x27, 0x00 },
|
||||
{ 0x15, 0xb4, 0x88, 0x30, 0x63, 0x6c, 0x8b, 0x99, 0x89, 0x49, 0x72, 0xe0 },
|
||||
{ 0x29, 0xa8, 0x9c, 0x0d, 0x3d, 0xe8, 0x1d, 0x66, 0x54, 0x89, 0xb0, 0xe0 },
|
||||
{ 0x4f, 0x12, 0x6f, 0x37, 0xfa, 0x51, 0xcb, 0xe6, 0x1b, 0xd6, 0xb9, 0x40 },
|
||||
{ 0x99, 0xc4, 0x72, 0x39, 0xd0, 0xd9, 0x7d, 0x3c, 0x84, 0xe0, 0x94, 0x00 },
|
||||
{ 0x19, 0x19, 0xb7, 0x51, 0x19, 0x76, 0x56, 0x21, 0xbb, 0x4f, 0x1e, 0x80 },
|
||||
{ 0x09, 0xdb, 0x12, 0xd7, 0x31, 0xfa, 0xee, 0x0b, 0x86, 0xdf, 0x6b, 0x80 },
|
||||
{ 0x48, 0x8f, 0xc3, 0x3d, 0xf4, 0x3f, 0xbd, 0xee, 0xa4, 0xea, 0xfb, 0x40 },
|
||||
{ 0x82, 0x74, 0x23, 0xee, 0x40, 0xb6, 0x75, 0xf7, 0x56, 0xeb, 0x5f, 0xe0 },
|
||||
{ 0xab, 0xe1, 0x97, 0xc4, 0x84, 0xcb, 0x74, 0x75, 0x71, 0x44, 0xa9, 0xa0 },
|
||||
{ 0x2b, 0x50, 0x0e, 0x4b, 0xc0, 0xec, 0x5a, 0x6d, 0x2b, 0xdb, 0xdd, 0x00 },
|
||||
{ 0xc4, 0x74, 0xaa, 0x53, 0xd7, 0x02, 0x18, 0x76, 0x16, 0x69, 0x36, 0x00 },
|
||||
{ 0x8e, 0xba, 0x1a, 0x13, 0xdb, 0x33, 0x90, 0xbd, 0x67, 0x18, 0xce, 0xc0 },
|
||||
{ 0x75, 0x38, 0x44, 0x67, 0x3a, 0x27, 0x78, 0x2c, 0xc4, 0x20, 0x12, 0xe0 },
|
||||
{ 0x06, 0xff, 0x83, 0xa1, 0x45, 0xc3, 0x70, 0x35, 0xa5, 0xc1, 0x26, 0x80 },
|
||||
{ 0x3b, 0x37, 0x41, 0x78, 0x58, 0xcc, 0x2d, 0xd3, 0x3e, 0xc3, 0xf6, 0x20 },
|
||||
{ 0x9a, 0x4a, 0x5a, 0x28, 0xee, 0x17, 0xca, 0x9c, 0x32, 0x48, 0x42, 0xc0 },
|
||||
{ 0xbc, 0x29, 0xf4, 0x65, 0x30, 0x9c, 0x97, 0x7e, 0x89, 0x61, 0x0a, 0x40 },
|
||||
{ 0x26, 0x63, 0xae, 0x6d, 0xdf, 0x8b, 0x5c, 0xe2, 0xbb, 0x29, 0x48, 0x80 },
|
||||
{ 0x46, 0xf2, 0x31, 0xef, 0xe4, 0x57, 0x03, 0x4c, 0x18, 0x14, 0x41, 0x80 },
|
||||
{ 0x3f, 0xb2, 0xce, 0x85, 0xab, 0xe9, 0xb0, 0xc7, 0x2e, 0x06, 0xfb, 0xe0 },
|
||||
{ 0xde, 0x87, 0x48, 0x1f, 0x28, 0x2c, 0x15, 0x39, 0x71, 0xa0, 0xa2, 0xe0 },
|
||||
{ 0xfc, 0xd7, 0xcc, 0xf2, 0x3c, 0x69, 0xfa, 0x99, 0xbb, 0xa1, 0x41, 0x20 },
|
||||
{ 0xf0, 0x26, 0x14, 0x47, 0xe9, 0x49, 0x0c, 0xa8, 0xe4, 0x74, 0xce, 0xc0 },
|
||||
{ 0x44, 0x10, 0x11, 0x58, 0x18, 0x19, 0x6f, 0x95, 0xcd, 0xd7, 0x01, 0x20 },
|
||||
{ 0x08, 0x8f, 0xc3, 0x1d, 0xf4, 0xbf, 0xbd, 0xe2, 0xa4, 0xea, 0xfb, 0x40 },
|
||||
{ 0xb8, 0xfe, 0xf1, 0xb6, 0x30, 0x77, 0x29, 0xfb, 0x0a, 0x07, 0x8c, 0x00 },
|
||||
{ 0x5a, 0xfe, 0xa7, 0xac, 0xcc, 0xb7, 0x7b, 0xbc, 0x9d, 0x99, 0xa9, 0x00 },
|
||||
{ 0x49, 0xa7, 0x01, 0x6a, 0xc6, 0x53, 0xf6, 0x5e, 0xcd, 0xc9, 0x07, 0x60 },
|
||||
{ 0x19, 0x44, 0xd0, 0x85, 0xbe, 0x4e, 0x7d, 0xa8, 0xd6, 0xcc, 0x7d, 0x00 },
|
||||
{ 0x25, 0x1f, 0x62, 0xad, 0xc4, 0x03, 0x2f, 0x0e, 0xe7, 0x14, 0x00, 0x20 },
|
||||
{ 0x56, 0x47, 0x1f, 0x87, 0x02, 0xa0, 0x72, 0x1e, 0x00, 0xb1, 0x2b, 0x80 },
|
||||
{ 0x2b, 0x8e, 0x49, 0x23, 0xf2, 0xdd, 0x51, 0xe2, 0xd5, 0x37, 0xfa, 0x00 },
|
||||
{ 0x6b, 0x55, 0x0a, 0x40, 0xa6, 0x6f, 0x47, 0x55, 0xde, 0x95, 0xc2, 0x60 },
|
||||
{ 0xa1, 0x8a, 0xd2, 0x8d, 0x4e, 0x27, 0xfe, 0x92, 0xa4, 0xf6, 0xc8, 0x40 },
|
||||
{ 0x10, 0xc2, 0xe5, 0x86, 0x38, 0x8c, 0xb8, 0x2a, 0x3d, 0x80, 0x75, 0x80 },
|
||||
{ 0xef, 0x34, 0xa4, 0x18, 0x17, 0xee, 0x02, 0x13, 0x3d, 0xb2, 0xeb, 0x00 },
|
||||
{ 0x7e, 0x9c, 0x0c, 0x54, 0x32, 0x5a, 0x9c, 0x15, 0x83, 0x6e, 0x00, 0x00 },
|
||||
{ 0x36, 0x93, 0xe5, 0x72, 0xd1, 0xfd, 0xe4, 0xcd, 0xf0, 0x79, 0xe8, 0x60 },
|
||||
{ 0xbf, 0xb2, 0xce, 0xc5, 0xab, 0xe1, 0xb0, 0xc7, 0x2e, 0x07, 0xfb, 0xe0 },
|
||||
{ 0x7e, 0xe1, 0x82, 0x30, 0xc5, 0x83, 0xcc, 0xcc, 0x57, 0xd4, 0xb0, 0x80 },
|
||||
{ 0xa0, 0x66, 0xcb, 0x2f, 0xed, 0xaf, 0xc9, 0xf5, 0x26, 0x64, 0x12, 0x60 },
|
||||
{ 0xbb, 0x23, 0x72, 0x5a, 0xbc, 0x47, 0xcc, 0x5f, 0x4c, 0xc4, 0xcd, 0x20 },
|
||||
{ 0xde, 0xd9, 0xdb, 0xa3, 0xbe, 0xe4, 0x0c, 0x59, 0xb5, 0x60, 0x9b, 0x40 },
|
||||
{ 0xd9, 0xa7, 0x01, 0x6a, 0xc6, 0x53, 0xe6, 0xde, 0xcd, 0xc9, 0x03, 0x60 },
|
||||
{ 0x9a, 0xd4, 0x6a, 0xed, 0x5f, 0x70, 0x7f, 0x28, 0x0a, 0xb5, 0xfc, 0x40 },
|
||||
{ 0xe5, 0x92, 0x1c, 0x77, 0x82, 0x25, 0x87, 0x31, 0x6d, 0x7d, 0x3c, 0x20 },
|
||||
{ 0x4f, 0x14, 0xda, 0x82, 0x42, 0xa8, 0xb8, 0x6d, 0xca, 0x73, 0x35, 0x20 },
|
||||
{ 0x8b, 0x8b, 0x50, 0x7a, 0xd4, 0x67, 0xd4, 0x44, 0x1d, 0xf7, 0x70, 0xe0 },
|
||||
{ 0x22, 0x83, 0x1c, 0x9c, 0xf1, 0x16, 0x94, 0x67, 0xad, 0x04, 0xb6, 0x80 },
|
||||
{ 0x21, 0x3b, 0x83, 0x8f, 0xe2, 0xae, 0x54, 0xc3, 0x8e, 0xe7, 0x18, 0x00 },
|
||||
{ 0x5d, 0x92, 0x6b, 0x6d, 0xd7, 0x1f, 0x08, 0x51, 0x81, 0xa4, 0xe1, 0x20 },
|
||||
{ 0x66, 0xab, 0x79, 0xd4, 0xb2, 0x9e, 0xe6, 0xe6, 0x95, 0x09, 0xe5, 0x60 },
|
||||
{ 0x95, 0x81, 0x48, 0x68, 0x2d, 0x74, 0x8a, 0x38, 0xdd, 0x68, 0xba, 0xa0 },
|
||||
{ 0xb8, 0xce, 0x02, 0x0c, 0xf0, 0x69, 0xc3, 0x2a, 0x72, 0x3a, 0xb1, 0x40 },
|
||||
{ 0xf4, 0x33, 0x1d, 0x6d, 0x46, 0x16, 0x07, 0xe9, 0x57, 0x52, 0x74, 0x60 },
|
||||
{ 0x6d, 0xa2, 0x3b, 0xa4, 0x24, 0xb9, 0x59, 0x61, 0x33, 0xcf, 0x9c, 0x80 },
|
||||
{ 0xa6, 0x36, 0xbc, 0xbc, 0x7b, 0x30, 0xc5, 0xfb, 0xea, 0xe6, 0x7f, 0xe0 },
|
||||
{ 0x5c, 0xb0, 0xd8, 0x6a, 0x07, 0xdf, 0x65, 0x4a, 0x90, 0x89, 0xa2, 0x00 },
|
||||
{ 0xf1, 0x1f, 0x10, 0x68, 0x48, 0x78, 0x0f, 0xc9, 0xec, 0xdd, 0x80, 0xa0 },
|
||||
{ 0x1f, 0xbb, 0x53, 0x64, 0xfb, 0x8d, 0x2c, 0x9d, 0x73, 0x0d, 0x5b, 0xa0 },
|
||||
{ 0xfc, 0xb8, 0x6b, 0xc7, 0x0a, 0x50, 0xc9, 0xd0, 0x2a, 0x5d, 0x03, 0x40 },
|
||||
{ 0xa5, 0x34, 0x43, 0x30, 0x29, 0xea, 0xc1, 0x5f, 0x32, 0x2e, 0x34, 0xc0 },
|
||||
{ 0xc9, 0x89, 0xd9, 0xc7, 0xc3, 0xd3, 0xb8, 0xc5, 0x5d, 0x75, 0x13, 0x00 },
|
||||
{ 0x7b, 0xb3, 0x8b, 0x2f, 0x01, 0x86, 0xd4, 0x66, 0x43, 0xae, 0x96, 0x20 },
|
||||
{ 0x26, 0x44, 0xeb, 0xad, 0xeb, 0x44, 0xb9, 0x46, 0x7d, 0x1f, 0x42, 0xc0 },
|
||||
{ 0x60, 0x8c, 0xc8, 0x57, 0x59, 0x4b, 0xfb, 0xb5, 0x5d, 0x69, 0x60, 0x00 }
|
||||
};
|
||||
const uint8_t kGenerator[FT8_M][FT8_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},
|
||||
{0x1b, 0x3f, 0x41, 0x78, 0x58, 0xcd, 0x2d, 0xd3, 0x3e, 0xc7, 0xf6, 0x20},
|
||||
{0x09, 0xfd, 0xa4, 0xfe, 0xe0, 0x41, 0x95, 0xfd, 0x03, 0x47, 0x83, 0xa0},
|
||||
{0x07, 0x7c, 0xcc, 0xc1, 0x1b, 0x88, 0x73, 0xed, 0x5c, 0x3d, 0x48, 0xa0},
|
||||
{0x29, 0xb6, 0x2a, 0xfe, 0x3c, 0xa0, 0x36, 0xf4, 0xfe, 0x1a, 0x9d, 0xa0},
|
||||
{0x60, 0x54, 0xfa, 0xf5, 0xf3, 0x5d, 0x96, 0xd3, 0xb0, 0xc8, 0xc3, 0xe0},
|
||||
{0xe2, 0x07, 0x98, 0xe4, 0x31, 0x0e, 0xed, 0x27, 0x88, 0x4a, 0xe9, 0x00},
|
||||
{0x77, 0x5c, 0x9c, 0x08, 0xe8, 0x0e, 0x26, 0xdd, 0xae, 0x56, 0x31, 0x80},
|
||||
{0xb0, 0xb8, 0x11, 0x02, 0x8c, 0x2b, 0xf9, 0x97, 0x21, 0x34, 0x87, 0xc0},
|
||||
{0x18, 0xa0, 0xc9, 0x23, 0x1f, 0xc6, 0x0a, 0xdf, 0x5c, 0x5e, 0xa3, 0x20},
|
||||
{0x76, 0x47, 0x1e, 0x83, 0x02, 0xa0, 0x72, 0x1e, 0x01, 0xb1, 0x2b, 0x80},
|
||||
{0xff, 0xbc, 0xcb, 0x80, 0xca, 0x83, 0x41, 0xfa, 0xfb, 0x47, 0xb2, 0xe0},
|
||||
{0x66, 0xa7, 0x2a, 0x15, 0x8f, 0x93, 0x25, 0xa2, 0xbf, 0x67, 0x17, 0x00},
|
||||
{0xc4, 0x24, 0x36, 0x89, 0xfe, 0x85, 0xb1, 0xc5, 0x13, 0x63, 0xa1, 0x80},
|
||||
{0x0d, 0xff, 0x73, 0x94, 0x14, 0xd1, 0xa1, 0xb3, 0x4b, 0x1c, 0x27, 0x00},
|
||||
{0x15, 0xb4, 0x88, 0x30, 0x63, 0x6c, 0x8b, 0x99, 0x89, 0x49, 0x72, 0xe0},
|
||||
{0x29, 0xa8, 0x9c, 0x0d, 0x3d, 0xe8, 0x1d, 0x66, 0x54, 0x89, 0xb0, 0xe0},
|
||||
{0x4f, 0x12, 0x6f, 0x37, 0xfa, 0x51, 0xcb, 0xe6, 0x1b, 0xd6, 0xb9, 0x40},
|
||||
{0x99, 0xc4, 0x72, 0x39, 0xd0, 0xd9, 0x7d, 0x3c, 0x84, 0xe0, 0x94, 0x00},
|
||||
{0x19, 0x19, 0xb7, 0x51, 0x19, 0x76, 0x56, 0x21, 0xbb, 0x4f, 0x1e, 0x80},
|
||||
{0x09, 0xdb, 0x12, 0xd7, 0x31, 0xfa, 0xee, 0x0b, 0x86, 0xdf, 0x6b, 0x80},
|
||||
{0x48, 0x8f, 0xc3, 0x3d, 0xf4, 0x3f, 0xbd, 0xee, 0xa4, 0xea, 0xfb, 0x40},
|
||||
{0x82, 0x74, 0x23, 0xee, 0x40, 0xb6, 0x75, 0xf7, 0x56, 0xeb, 0x5f, 0xe0},
|
||||
{0xab, 0xe1, 0x97, 0xc4, 0x84, 0xcb, 0x74, 0x75, 0x71, 0x44, 0xa9, 0xa0},
|
||||
{0x2b, 0x50, 0x0e, 0x4b, 0xc0, 0xec, 0x5a, 0x6d, 0x2b, 0xdb, 0xdd, 0x00},
|
||||
{0xc4, 0x74, 0xaa, 0x53, 0xd7, 0x02, 0x18, 0x76, 0x16, 0x69, 0x36, 0x00},
|
||||
{0x8e, 0xba, 0x1a, 0x13, 0xdb, 0x33, 0x90, 0xbd, 0x67, 0x18, 0xce, 0xc0},
|
||||
{0x75, 0x38, 0x44, 0x67, 0x3a, 0x27, 0x78, 0x2c, 0xc4, 0x20, 0x12, 0xe0},
|
||||
{0x06, 0xff, 0x83, 0xa1, 0x45, 0xc3, 0x70, 0x35, 0xa5, 0xc1, 0x26, 0x80},
|
||||
{0x3b, 0x37, 0x41, 0x78, 0x58, 0xcc, 0x2d, 0xd3, 0x3e, 0xc3, 0xf6, 0x20},
|
||||
{0x9a, 0x4a, 0x5a, 0x28, 0xee, 0x17, 0xca, 0x9c, 0x32, 0x48, 0x42, 0xc0},
|
||||
{0xbc, 0x29, 0xf4, 0x65, 0x30, 0x9c, 0x97, 0x7e, 0x89, 0x61, 0x0a, 0x40},
|
||||
{0x26, 0x63, 0xae, 0x6d, 0xdf, 0x8b, 0x5c, 0xe2, 0xbb, 0x29, 0x48, 0x80},
|
||||
{0x46, 0xf2, 0x31, 0xef, 0xe4, 0x57, 0x03, 0x4c, 0x18, 0x14, 0x41, 0x80},
|
||||
{0x3f, 0xb2, 0xce, 0x85, 0xab, 0xe9, 0xb0, 0xc7, 0x2e, 0x06, 0xfb, 0xe0},
|
||||
{0xde, 0x87, 0x48, 0x1f, 0x28, 0x2c, 0x15, 0x39, 0x71, 0xa0, 0xa2, 0xe0},
|
||||
{0xfc, 0xd7, 0xcc, 0xf2, 0x3c, 0x69, 0xfa, 0x99, 0xbb, 0xa1, 0x41, 0x20},
|
||||
{0xf0, 0x26, 0x14, 0x47, 0xe9, 0x49, 0x0c, 0xa8, 0xe4, 0x74, 0xce, 0xc0},
|
||||
{0x44, 0x10, 0x11, 0x58, 0x18, 0x19, 0x6f, 0x95, 0xcd, 0xd7, 0x01, 0x20},
|
||||
{0x08, 0x8f, 0xc3, 0x1d, 0xf4, 0xbf, 0xbd, 0xe2, 0xa4, 0xea, 0xfb, 0x40},
|
||||
{0xb8, 0xfe, 0xf1, 0xb6, 0x30, 0x77, 0x29, 0xfb, 0x0a, 0x07, 0x8c, 0x00},
|
||||
{0x5a, 0xfe, 0xa7, 0xac, 0xcc, 0xb7, 0x7b, 0xbc, 0x9d, 0x99, 0xa9, 0x00},
|
||||
{0x49, 0xa7, 0x01, 0x6a, 0xc6, 0x53, 0xf6, 0x5e, 0xcd, 0xc9, 0x07, 0x60},
|
||||
{0x19, 0x44, 0xd0, 0x85, 0xbe, 0x4e, 0x7d, 0xa8, 0xd6, 0xcc, 0x7d, 0x00},
|
||||
{0x25, 0x1f, 0x62, 0xad, 0xc4, 0x03, 0x2f, 0x0e, 0xe7, 0x14, 0x00, 0x20},
|
||||
{0x56, 0x47, 0x1f, 0x87, 0x02, 0xa0, 0x72, 0x1e, 0x00, 0xb1, 0x2b, 0x80},
|
||||
{0x2b, 0x8e, 0x49, 0x23, 0xf2, 0xdd, 0x51, 0xe2, 0xd5, 0x37, 0xfa, 0x00},
|
||||
{0x6b, 0x55, 0x0a, 0x40, 0xa6, 0x6f, 0x47, 0x55, 0xde, 0x95, 0xc2, 0x60},
|
||||
{0xa1, 0x8a, 0xd2, 0x8d, 0x4e, 0x27, 0xfe, 0x92, 0xa4, 0xf6, 0xc8, 0x40},
|
||||
{0x10, 0xc2, 0xe5, 0x86, 0x38, 0x8c, 0xb8, 0x2a, 0x3d, 0x80, 0x75, 0x80},
|
||||
{0xef, 0x34, 0xa4, 0x18, 0x17, 0xee, 0x02, 0x13, 0x3d, 0xb2, 0xeb, 0x00},
|
||||
{0x7e, 0x9c, 0x0c, 0x54, 0x32, 0x5a, 0x9c, 0x15, 0x83, 0x6e, 0x00, 0x00},
|
||||
{0x36, 0x93, 0xe5, 0x72, 0xd1, 0xfd, 0xe4, 0xcd, 0xf0, 0x79, 0xe8, 0x60},
|
||||
{0xbf, 0xb2, 0xce, 0xc5, 0xab, 0xe1, 0xb0, 0xc7, 0x2e, 0x07, 0xfb, 0xe0},
|
||||
{0x7e, 0xe1, 0x82, 0x30, 0xc5, 0x83, 0xcc, 0xcc, 0x57, 0xd4, 0xb0, 0x80},
|
||||
{0xa0, 0x66, 0xcb, 0x2f, 0xed, 0xaf, 0xc9, 0xf5, 0x26, 0x64, 0x12, 0x60},
|
||||
{0xbb, 0x23, 0x72, 0x5a, 0xbc, 0x47, 0xcc, 0x5f, 0x4c, 0xc4, 0xcd, 0x20},
|
||||
{0xde, 0xd9, 0xdb, 0xa3, 0xbe, 0xe4, 0x0c, 0x59, 0xb5, 0x60, 0x9b, 0x40},
|
||||
{0xd9, 0xa7, 0x01, 0x6a, 0xc6, 0x53, 0xe6, 0xde, 0xcd, 0xc9, 0x03, 0x60},
|
||||
{0x9a, 0xd4, 0x6a, 0xed, 0x5f, 0x70, 0x7f, 0x28, 0x0a, 0xb5, 0xfc, 0x40},
|
||||
{0xe5, 0x92, 0x1c, 0x77, 0x82, 0x25, 0x87, 0x31, 0x6d, 0x7d, 0x3c, 0x20},
|
||||
{0x4f, 0x14, 0xda, 0x82, 0x42, 0xa8, 0xb8, 0x6d, 0xca, 0x73, 0x35, 0x20},
|
||||
{0x8b, 0x8b, 0x50, 0x7a, 0xd4, 0x67, 0xd4, 0x44, 0x1d, 0xf7, 0x70, 0xe0},
|
||||
{0x22, 0x83, 0x1c, 0x9c, 0xf1, 0x16, 0x94, 0x67, 0xad, 0x04, 0xb6, 0x80},
|
||||
{0x21, 0x3b, 0x83, 0x8f, 0xe2, 0xae, 0x54, 0xc3, 0x8e, 0xe7, 0x18, 0x00},
|
||||
{0x5d, 0x92, 0x6b, 0x6d, 0xd7, 0x1f, 0x08, 0x51, 0x81, 0xa4, 0xe1, 0x20},
|
||||
{0x66, 0xab, 0x79, 0xd4, 0xb2, 0x9e, 0xe6, 0xe6, 0x95, 0x09, 0xe5, 0x60},
|
||||
{0x95, 0x81, 0x48, 0x68, 0x2d, 0x74, 0x8a, 0x38, 0xdd, 0x68, 0xba, 0xa0},
|
||||
{0xb8, 0xce, 0x02, 0x0c, 0xf0, 0x69, 0xc3, 0x2a, 0x72, 0x3a, 0xb1, 0x40},
|
||||
{0xf4, 0x33, 0x1d, 0x6d, 0x46, 0x16, 0x07, 0xe9, 0x57, 0x52, 0x74, 0x60},
|
||||
{0x6d, 0xa2, 0x3b, 0xa4, 0x24, 0xb9, 0x59, 0x61, 0x33, 0xcf, 0x9c, 0x80},
|
||||
{0xa6, 0x36, 0xbc, 0xbc, 0x7b, 0x30, 0xc5, 0xfb, 0xea, 0xe6, 0x7f, 0xe0},
|
||||
{0x5c, 0xb0, 0xd8, 0x6a, 0x07, 0xdf, 0x65, 0x4a, 0x90, 0x89, 0xa2, 0x00},
|
||||
{0xf1, 0x1f, 0x10, 0x68, 0x48, 0x78, 0x0f, 0xc9, 0xec, 0xdd, 0x80, 0xa0},
|
||||
{0x1f, 0xbb, 0x53, 0x64, 0xfb, 0x8d, 0x2c, 0x9d, 0x73, 0x0d, 0x5b, 0xa0},
|
||||
{0xfc, 0xb8, 0x6b, 0xc7, 0x0a, 0x50, 0xc9, 0xd0, 0x2a, 0x5d, 0x03, 0x40},
|
||||
{0xa5, 0x34, 0x43, 0x30, 0x29, 0xea, 0xc1, 0x5f, 0x32, 0x2e, 0x34, 0xc0},
|
||||
{0xc9, 0x89, 0xd9, 0xc7, 0xc3, 0xd3, 0xb8, 0xc5, 0x5d, 0x75, 0x13, 0x00},
|
||||
{0x7b, 0xb3, 0x8b, 0x2f, 0x01, 0x86, 0xd4, 0x66, 0x43, 0xae, 0x96, 0x20},
|
||||
{0x26, 0x44, 0xeb, 0xad, 0xeb, 0x44, 0xb9, 0x46, 0x7d, 0x1f, 0x42, 0xc0},
|
||||
{0x60, 0x8c, 0xc8, 0x57, 0x59, 0x4b, 0xfb, 0xb5, 0x5d, 0x69, 0x60, 0x00}};
|
||||
|
||||
// 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[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,
|
||||
25, 56, 62, 64, 67, 69, 63, 68, 70, 72, 65, 73, 75, 74, 71, 77, 78, 76, 79, 80,
|
||||
53, 81, 83, 82, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99,
|
||||
100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,
|
||||
120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,
|
||||
140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,
|
||||
160,161,162,163,164,165,166,167,168,169,170,171,172,173
|
||||
};
|
||||
|
||||
const uint8_t kColumn_order[FT8_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,
|
||||
25, 56, 62, 64, 67, 69, 63, 68, 70, 72, 65, 73, 75, 74, 71, 77, 78, 76, 79, 80,
|
||||
53, 81, 83, 82, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99,
|
||||
100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
|
||||
120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139,
|
||||
140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
|
||||
160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173};
|
||||
|
||||
// this is the LDPC(174,91) parity check matrix.
|
||||
// 83 rows.
|
||||
|
@ -116,281 +111,276 @@ const uint8_t kColumn_order[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[M][7] = {
|
||||
{ 4, 31, 59, 91, 92, 96, 153 },
|
||||
{ 5, 32, 60, 93, 115, 146, 0 },
|
||||
{ 6, 24, 61, 94, 122, 151, 0 },
|
||||
{ 7, 33, 62, 95, 96, 143, 0 },
|
||||
{ 8, 25, 63, 83, 93, 96, 148 },
|
||||
{ 6, 32, 64, 97, 126, 138, 0 },
|
||||
{ 5, 34, 65, 78, 98, 107, 154 },
|
||||
{ 9, 35, 66, 99, 139, 146, 0 },
|
||||
{ 10, 36, 67, 100, 107, 126, 0 },
|
||||
{ 11, 37, 67, 87, 101, 139, 158 },
|
||||
{ 12, 38, 68, 102, 105, 155, 0 },
|
||||
{ 13, 39, 69, 103, 149, 162, 0 },
|
||||
{ 8, 40, 70, 82, 104, 114, 145 },
|
||||
{ 14, 41, 71, 88, 102, 123, 156 },
|
||||
{ 15, 42, 59, 106, 123, 159, 0 },
|
||||
{ 1, 33, 72, 106, 107, 157, 0 },
|
||||
{ 16, 43, 73, 108, 141, 160, 0 },
|
||||
{ 17, 37, 74, 81, 109, 131, 154 },
|
||||
{ 11, 44, 75, 110, 121, 166, 0 },
|
||||
{ 45, 55, 64, 111, 130, 161, 173 },
|
||||
{ 8, 46, 71, 112, 119, 166, 0 },
|
||||
{ 18, 36, 76, 89, 113, 114, 143 },
|
||||
{ 19, 38, 77, 104, 116, 163, 0 },
|
||||
{ 20, 47, 70, 92, 138, 165, 0 },
|
||||
{ 2, 48, 74, 113, 128, 160, 0 },
|
||||
{ 21, 45, 78, 83, 117, 121, 151 },
|
||||
{ 22, 47, 58, 118, 127, 164, 0 },
|
||||
{ 16, 39, 62, 112, 134, 158, 0 },
|
||||
{ 23, 43, 79, 120, 131, 145, 0 },
|
||||
{ 19, 35, 59, 73, 110, 125, 161 },
|
||||
{ 20, 36, 63, 94, 136, 161, 0 },
|
||||
{ 14, 31, 79, 98, 132, 164, 0 },
|
||||
{ 3, 44, 80, 124, 127, 169, 0 },
|
||||
{ 19, 46, 81, 117, 135, 167, 0 },
|
||||
{ 7, 49, 58, 90, 100, 105, 168 },
|
||||
{ 12, 50, 61, 118, 119, 144, 0 },
|
||||
{ 13, 51, 64, 114, 118, 157, 0 },
|
||||
{ 24, 52, 76, 129, 148, 149, 0 },
|
||||
{ 25, 53, 69, 90, 101, 130, 156 },
|
||||
{ 20, 46, 65, 80, 120, 140, 170 },
|
||||
{ 21, 54, 77, 100, 140, 171, 0 },
|
||||
{ 35, 82, 133, 142, 171, 174, 0 },
|
||||
{ 14, 30, 83, 113, 125, 170, 0 },
|
||||
{ 4, 29, 68, 120, 134, 173, 0 },
|
||||
{ 1, 4, 52, 57, 86, 136, 152 },
|
||||
{ 26, 51, 56, 91, 122, 137, 168 },
|
||||
{ 52, 84, 110, 115, 145, 168, 0 },
|
||||
{ 7, 50, 81, 99, 132, 173, 0 },
|
||||
{ 23, 55, 67, 95, 172, 174, 0 },
|
||||
{ 26, 41, 77, 109, 141, 148, 0 },
|
||||
{ 2, 27, 41, 61, 62, 115, 133 },
|
||||
{ 27, 40, 56, 124, 125, 126, 0 },
|
||||
{ 18, 49, 55, 124, 141, 167, 0 },
|
||||
{ 6, 33, 85, 108, 116, 156, 0 },
|
||||
{ 28, 48, 70, 85, 105, 129, 158 },
|
||||
{ 9, 54, 63, 131, 147, 155, 0 },
|
||||
{ 22, 53, 68, 109, 121, 174, 0 },
|
||||
{ 3, 13, 48, 78, 95, 123, 0 },
|
||||
{ 31, 69, 133, 150, 155, 169, 0 },
|
||||
{ 12, 43, 66, 89, 97, 135, 159 },
|
||||
{ 5, 39, 75, 102, 136, 167, 0 },
|
||||
{ 2, 54, 86, 101, 135, 164, 0 },
|
||||
{ 15, 56, 87, 108, 119, 171, 0 },
|
||||
{ 10, 44, 82, 91, 111, 144, 149 },
|
||||
{ 23, 34, 71, 94, 127, 153, 0 },
|
||||
{ 11, 49, 88, 92, 142, 157, 0 },
|
||||
{ 29, 34, 87, 97, 147, 162, 0 },
|
||||
{ 30, 50, 60, 86, 137, 142, 162 },
|
||||
{ 10, 53, 66, 84, 112, 128, 165 },
|
||||
{ 22, 57, 85, 93, 140, 159, 0 },
|
||||
{ 28, 32, 72, 103, 132, 166, 0 },
|
||||
{ 28, 29, 84, 88, 117, 143, 150 },
|
||||
{ 1, 26, 45, 80, 128, 147, 0 },
|
||||
{ 17, 27, 89, 103, 116, 153, 0 },
|
||||
{ 51, 57, 98, 163, 165, 172, 0 },
|
||||
{ 21, 37, 73, 138, 152, 169, 0 },
|
||||
{ 16, 47, 76, 130, 137, 154, 0 },
|
||||
{ 3, 24, 30, 72, 104, 139, 0 },
|
||||
{ 9, 40, 90, 106, 134, 151, 0 },
|
||||
{ 15, 58, 60, 74, 111, 150, 163 },
|
||||
{ 18, 42, 79, 144, 146, 152, 0 },
|
||||
{ 25, 38, 65, 99, 122, 160, 0 },
|
||||
{ 17, 42, 75, 129, 170, 172, 0 }
|
||||
};
|
||||
const uint8_t kNm[FT8_M][7] = {
|
||||
{4, 31, 59, 91, 92, 96, 153},
|
||||
{5, 32, 60, 93, 115, 146, 0},
|
||||
{6, 24, 61, 94, 122, 151, 0},
|
||||
{7, 33, 62, 95, 96, 143, 0},
|
||||
{8, 25, 63, 83, 93, 96, 148},
|
||||
{6, 32, 64, 97, 126, 138, 0},
|
||||
{5, 34, 65, 78, 98, 107, 154},
|
||||
{9, 35, 66, 99, 139, 146, 0},
|
||||
{10, 36, 67, 100, 107, 126, 0},
|
||||
{11, 37, 67, 87, 101, 139, 158},
|
||||
{12, 38, 68, 102, 105, 155, 0},
|
||||
{13, 39, 69, 103, 149, 162, 0},
|
||||
{8, 40, 70, 82, 104, 114, 145},
|
||||
{14, 41, 71, 88, 102, 123, 156},
|
||||
{15, 42, 59, 106, 123, 159, 0},
|
||||
{1, 33, 72, 106, 107, 157, 0},
|
||||
{16, 43, 73, 108, 141, 160, 0},
|
||||
{17, 37, 74, 81, 109, 131, 154},
|
||||
{11, 44, 75, 110, 121, 166, 0},
|
||||
{45, 55, 64, 111, 130, 161, 173},
|
||||
{8, 46, 71, 112, 119, 166, 0},
|
||||
{18, 36, 76, 89, 113, 114, 143},
|
||||
{19, 38, 77, 104, 116, 163, 0},
|
||||
{20, 47, 70, 92, 138, 165, 0},
|
||||
{2, 48, 74, 113, 128, 160, 0},
|
||||
{21, 45, 78, 83, 117, 121, 151},
|
||||
{22, 47, 58, 118, 127, 164, 0},
|
||||
{16, 39, 62, 112, 134, 158, 0},
|
||||
{23, 43, 79, 120, 131, 145, 0},
|
||||
{19, 35, 59, 73, 110, 125, 161},
|
||||
{20, 36, 63, 94, 136, 161, 0},
|
||||
{14, 31, 79, 98, 132, 164, 0},
|
||||
{3, 44, 80, 124, 127, 169, 0},
|
||||
{19, 46, 81, 117, 135, 167, 0},
|
||||
{7, 49, 58, 90, 100, 105, 168},
|
||||
{12, 50, 61, 118, 119, 144, 0},
|
||||
{13, 51, 64, 114, 118, 157, 0},
|
||||
{24, 52, 76, 129, 148, 149, 0},
|
||||
{25, 53, 69, 90, 101, 130, 156},
|
||||
{20, 46, 65, 80, 120, 140, 170},
|
||||
{21, 54, 77, 100, 140, 171, 0},
|
||||
{35, 82, 133, 142, 171, 174, 0},
|
||||
{14, 30, 83, 113, 125, 170, 0},
|
||||
{4, 29, 68, 120, 134, 173, 0},
|
||||
{1, 4, 52, 57, 86, 136, 152},
|
||||
{26, 51, 56, 91, 122, 137, 168},
|
||||
{52, 84, 110, 115, 145, 168, 0},
|
||||
{7, 50, 81, 99, 132, 173, 0},
|
||||
{23, 55, 67, 95, 172, 174, 0},
|
||||
{26, 41, 77, 109, 141, 148, 0},
|
||||
{2, 27, 41, 61, 62, 115, 133},
|
||||
{27, 40, 56, 124, 125, 126, 0},
|
||||
{18, 49, 55, 124, 141, 167, 0},
|
||||
{6, 33, 85, 108, 116, 156, 0},
|
||||
{28, 48, 70, 85, 105, 129, 158},
|
||||
{9, 54, 63, 131, 147, 155, 0},
|
||||
{22, 53, 68, 109, 121, 174, 0},
|
||||
{3, 13, 48, 78, 95, 123, 0},
|
||||
{31, 69, 133, 150, 155, 169, 0},
|
||||
{12, 43, 66, 89, 97, 135, 159},
|
||||
{5, 39, 75, 102, 136, 167, 0},
|
||||
{2, 54, 86, 101, 135, 164, 0},
|
||||
{15, 56, 87, 108, 119, 171, 0},
|
||||
{10, 44, 82, 91, 111, 144, 149},
|
||||
{23, 34, 71, 94, 127, 153, 0},
|
||||
{11, 49, 88, 92, 142, 157, 0},
|
||||
{29, 34, 87, 97, 147, 162, 0},
|
||||
{30, 50, 60, 86, 137, 142, 162},
|
||||
{10, 53, 66, 84, 112, 128, 165},
|
||||
{22, 57, 85, 93, 140, 159, 0},
|
||||
{28, 32, 72, 103, 132, 166, 0},
|
||||
{28, 29, 84, 88, 117, 143, 150},
|
||||
{1, 26, 45, 80, 128, 147, 0},
|
||||
{17, 27, 89, 103, 116, 153, 0},
|
||||
{51, 57, 98, 163, 165, 172, 0},
|
||||
{21, 37, 73, 138, 152, 169, 0},
|
||||
{16, 47, 76, 130, 137, 154, 0},
|
||||
{3, 24, 30, 72, 104, 139, 0},
|
||||
{9, 40, 90, 106, 134, 151, 0},
|
||||
{15, 58, 60, 74, 111, 150, 163},
|
||||
{18, 42, 79, 144, 146, 152, 0},
|
||||
{25, 38, 65, 99, 122, 160, 0},
|
||||
{17, 42, 75, 129, 170, 172, 0}};
|
||||
|
||||
// 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.
|
||||
const uint8_t kMn[N][3] = {
|
||||
{ 16, 45, 73 },
|
||||
{ 25, 51, 62 },
|
||||
{ 33, 58, 78 },
|
||||
{ 1, 44, 45 },
|
||||
{ 2, 7, 61 },
|
||||
{ 3, 6, 54 },
|
||||
{ 4, 35, 48 },
|
||||
{ 5, 13, 21 },
|
||||
{ 8, 56, 79 },
|
||||
{ 9, 64, 69 },
|
||||
{ 10, 19, 66 },
|
||||
{ 11, 36, 60 },
|
||||
{ 12, 37, 58 },
|
||||
{ 14, 32, 43 },
|
||||
{ 15, 63, 80 },
|
||||
{ 17, 28, 77 },
|
||||
{ 18, 74, 83 },
|
||||
{ 22, 53, 81 },
|
||||
{ 23, 30, 34 },
|
||||
{ 24, 31, 40 },
|
||||
{ 26, 41, 76 },
|
||||
{ 27, 57, 70 },
|
||||
{ 29, 49, 65 },
|
||||
{ 3, 38, 78 },
|
||||
{ 5, 39, 82 },
|
||||
{ 46, 50, 73 },
|
||||
{ 51, 52, 74 },
|
||||
{ 55, 71, 72 },
|
||||
{ 44, 67, 72 },
|
||||
{ 43, 68, 78 },
|
||||
{ 1, 32, 59 },
|
||||
{ 2, 6, 71 },
|
||||
{ 4, 16, 54 },
|
||||
{ 7, 65, 67 },
|
||||
{ 8, 30, 42 },
|
||||
{ 9, 22, 31 },
|
||||
{ 10, 18, 76 },
|
||||
{ 11, 23, 82 },
|
||||
{ 12, 28, 61 },
|
||||
{ 13, 52, 79 },
|
||||
{ 14, 50, 51 },
|
||||
{ 15, 81, 83 },
|
||||
{ 17, 29, 60 },
|
||||
{ 19, 33, 64 },
|
||||
{ 20, 26, 73 },
|
||||
{ 21, 34, 40 },
|
||||
{ 24, 27, 77 },
|
||||
{ 25, 55, 58 },
|
||||
{ 35, 53, 66 },
|
||||
{ 36, 48, 68 },
|
||||
{ 37, 46, 75 },
|
||||
{ 38, 45, 47 },
|
||||
{ 39, 57, 69 },
|
||||
{ 41, 56, 62 },
|
||||
{ 20, 49, 53 },
|
||||
{ 46, 52, 63 },
|
||||
{ 45, 70, 75 },
|
||||
{ 27, 35, 80 },
|
||||
{ 1, 15, 30 },
|
||||
{ 2, 68, 80 },
|
||||
{ 3, 36, 51 },
|
||||
{ 4, 28, 51 },
|
||||
{ 5, 31, 56 },
|
||||
{ 6, 20, 37 },
|
||||
{ 7, 40, 82 },
|
||||
{ 8, 60, 69 },
|
||||
{ 9, 10, 49 },
|
||||
{ 11, 44, 57 },
|
||||
{ 12, 39, 59 },
|
||||
{ 13, 24, 55 },
|
||||
{ 14, 21, 65 },
|
||||
{ 16, 71, 78 },
|
||||
{ 17, 30, 76 },
|
||||
{ 18, 25, 80 },
|
||||
{ 19, 61, 83 },
|
||||
{ 22, 38, 77 },
|
||||
{ 23, 41, 50 },
|
||||
{ 7, 26, 58 },
|
||||
{ 29, 32, 81 },
|
||||
{ 33, 40, 73 },
|
||||
{ 18, 34, 48 },
|
||||
{ 13, 42, 64 },
|
||||
{ 5, 26, 43 },
|
||||
{ 47, 69, 72 },
|
||||
{ 54, 55, 70 },
|
||||
{ 45, 62, 68 },
|
||||
{ 10, 63, 67 },
|
||||
{ 14, 66, 72 },
|
||||
{ 22, 60, 74 },
|
||||
{ 35, 39, 79 },
|
||||
{ 1, 46, 64 },
|
||||
{ 1, 24, 66 },
|
||||
{ 2, 5, 70 },
|
||||
{ 3, 31, 65 },
|
||||
{ 4, 49, 58 },
|
||||
{ 1, 4, 5 },
|
||||
{ 6, 60, 67 },
|
||||
{ 7, 32, 75 },
|
||||
{ 8, 48, 82 },
|
||||
{ 9, 35, 41 },
|
||||
{ 10, 39, 62 },
|
||||
{ 11, 14, 61 },
|
||||
{ 12, 71, 74 },
|
||||
{ 13, 23, 78 },
|
||||
{ 11, 35, 55 },
|
||||
{ 15, 16, 79 },
|
||||
{ 7, 9, 16 },
|
||||
{ 17, 54, 63 },
|
||||
{ 18, 50, 57 },
|
||||
{ 19, 30, 47 },
|
||||
{ 20, 64, 80 },
|
||||
{ 21, 28, 69 },
|
||||
{ 22, 25, 43 },
|
||||
{ 13, 22, 37 },
|
||||
{ 2, 47, 51 },
|
||||
{ 23, 54, 74 },
|
||||
{ 26, 34, 72 },
|
||||
{ 27, 36, 37 },
|
||||
{ 21, 36, 63 },
|
||||
{ 29, 40, 44 },
|
||||
{ 19, 26, 57 },
|
||||
{ 3, 46, 82 },
|
||||
{ 14, 15, 58 },
|
||||
{ 33, 52, 53 },
|
||||
{ 30, 43, 52 },
|
||||
{ 6, 9, 52 },
|
||||
{ 27, 33, 65 },
|
||||
{ 25, 69, 73 },
|
||||
{ 38, 55, 83 },
|
||||
{ 20, 39, 77 },
|
||||
{ 18, 29, 56 },
|
||||
{ 32, 48, 71 },
|
||||
{ 42, 51, 59 },
|
||||
{ 28, 44, 79 },
|
||||
{ 34, 60, 62 },
|
||||
{ 31, 45, 61 },
|
||||
{ 46, 68, 77 },
|
||||
{ 6, 24, 76 },
|
||||
{ 8, 10, 78 },
|
||||
{ 40, 41, 70 },
|
||||
{ 17, 50, 53 },
|
||||
{ 42, 66, 68 },
|
||||
{ 4, 22, 72 },
|
||||
{ 36, 64, 81 },
|
||||
{ 13, 29, 47 },
|
||||
{ 2, 8, 81 },
|
||||
{ 56, 67, 73 },
|
||||
{ 5, 38, 50 },
|
||||
{ 12, 38, 64 },
|
||||
{ 59, 72, 80 },
|
||||
{ 3, 26, 79 },
|
||||
{ 45, 76, 81 },
|
||||
{ 1, 65, 74 },
|
||||
{ 7, 18, 77 },
|
||||
{ 11, 56, 59 },
|
||||
{ 14, 39, 54 },
|
||||
{ 16, 37, 66 },
|
||||
{ 10, 28, 55 },
|
||||
{ 15, 60, 70 },
|
||||
{ 17, 25, 82 },
|
||||
{ 20, 30, 31 },
|
||||
{ 12, 67, 68 },
|
||||
{ 23, 75, 80 },
|
||||
{ 27, 32, 62 },
|
||||
{ 24, 69, 75 },
|
||||
{ 19, 21, 71 },
|
||||
{ 34, 53, 61 },
|
||||
{ 35, 46, 47 },
|
||||
{ 33, 59, 76 },
|
||||
{ 40, 43, 83 },
|
||||
{ 41, 42, 63 },
|
||||
{ 49, 75, 83 },
|
||||
{ 20, 44, 48 },
|
||||
{ 42, 49, 57 }
|
||||
};
|
||||
const uint8_t kMn[FT8_N][3] = {
|
||||
{16, 45, 73},
|
||||
{25, 51, 62},
|
||||
{33, 58, 78},
|
||||
{1, 44, 45},
|
||||
{2, 7, 61},
|
||||
{3, 6, 54},
|
||||
{4, 35, 48},
|
||||
{5, 13, 21},
|
||||
{8, 56, 79},
|
||||
{9, 64, 69},
|
||||
{10, 19, 66},
|
||||
{11, 36, 60},
|
||||
{12, 37, 58},
|
||||
{14, 32, 43},
|
||||
{15, 63, 80},
|
||||
{17, 28, 77},
|
||||
{18, 74, 83},
|
||||
{22, 53, 81},
|
||||
{23, 30, 34},
|
||||
{24, 31, 40},
|
||||
{26, 41, 76},
|
||||
{27, 57, 70},
|
||||
{29, 49, 65},
|
||||
{3, 38, 78},
|
||||
{5, 39, 82},
|
||||
{46, 50, 73},
|
||||
{51, 52, 74},
|
||||
{55, 71, 72},
|
||||
{44, 67, 72},
|
||||
{43, 68, 78},
|
||||
{1, 32, 59},
|
||||
{2, 6, 71},
|
||||
{4, 16, 54},
|
||||
{7, 65, 67},
|
||||
{8, 30, 42},
|
||||
{9, 22, 31},
|
||||
{10, 18, 76},
|
||||
{11, 23, 82},
|
||||
{12, 28, 61},
|
||||
{13, 52, 79},
|
||||
{14, 50, 51},
|
||||
{15, 81, 83},
|
||||
{17, 29, 60},
|
||||
{19, 33, 64},
|
||||
{20, 26, 73},
|
||||
{21, 34, 40},
|
||||
{24, 27, 77},
|
||||
{25, 55, 58},
|
||||
{35, 53, 66},
|
||||
{36, 48, 68},
|
||||
{37, 46, 75},
|
||||
{38, 45, 47},
|
||||
{39, 57, 69},
|
||||
{41, 56, 62},
|
||||
{20, 49, 53},
|
||||
{46, 52, 63},
|
||||
{45, 70, 75},
|
||||
{27, 35, 80},
|
||||
{1, 15, 30},
|
||||
{2, 68, 80},
|
||||
{3, 36, 51},
|
||||
{4, 28, 51},
|
||||
{5, 31, 56},
|
||||
{6, 20, 37},
|
||||
{7, 40, 82},
|
||||
{8, 60, 69},
|
||||
{9, 10, 49},
|
||||
{11, 44, 57},
|
||||
{12, 39, 59},
|
||||
{13, 24, 55},
|
||||
{14, 21, 65},
|
||||
{16, 71, 78},
|
||||
{17, 30, 76},
|
||||
{18, 25, 80},
|
||||
{19, 61, 83},
|
||||
{22, 38, 77},
|
||||
{23, 41, 50},
|
||||
{7, 26, 58},
|
||||
{29, 32, 81},
|
||||
{33, 40, 73},
|
||||
{18, 34, 48},
|
||||
{13, 42, 64},
|
||||
{5, 26, 43},
|
||||
{47, 69, 72},
|
||||
{54, 55, 70},
|
||||
{45, 62, 68},
|
||||
{10, 63, 67},
|
||||
{14, 66, 72},
|
||||
{22, 60, 74},
|
||||
{35, 39, 79},
|
||||
{1, 46, 64},
|
||||
{1, 24, 66},
|
||||
{2, 5, 70},
|
||||
{3, 31, 65},
|
||||
{4, 49, 58},
|
||||
{1, 4, 5},
|
||||
{6, 60, 67},
|
||||
{7, 32, 75},
|
||||
{8, 48, 82},
|
||||
{9, 35, 41},
|
||||
{10, 39, 62},
|
||||
{11, 14, 61},
|
||||
{12, 71, 74},
|
||||
{13, 23, 78},
|
||||
{11, 35, 55},
|
||||
{15, 16, 79},
|
||||
{7, 9, 16},
|
||||
{17, 54, 63},
|
||||
{18, 50, 57},
|
||||
{19, 30, 47},
|
||||
{20, 64, 80},
|
||||
{21, 28, 69},
|
||||
{22, 25, 43},
|
||||
{13, 22, 37},
|
||||
{2, 47, 51},
|
||||
{23, 54, 74},
|
||||
{26, 34, 72},
|
||||
{27, 36, 37},
|
||||
{21, 36, 63},
|
||||
{29, 40, 44},
|
||||
{19, 26, 57},
|
||||
{3, 46, 82},
|
||||
{14, 15, 58},
|
||||
{33, 52, 53},
|
||||
{30, 43, 52},
|
||||
{6, 9, 52},
|
||||
{27, 33, 65},
|
||||
{25, 69, 73},
|
||||
{38, 55, 83},
|
||||
{20, 39, 77},
|
||||
{18, 29, 56},
|
||||
{32, 48, 71},
|
||||
{42, 51, 59},
|
||||
{28, 44, 79},
|
||||
{34, 60, 62},
|
||||
{31, 45, 61},
|
||||
{46, 68, 77},
|
||||
{6, 24, 76},
|
||||
{8, 10, 78},
|
||||
{40, 41, 70},
|
||||
{17, 50, 53},
|
||||
{42, 66, 68},
|
||||
{4, 22, 72},
|
||||
{36, 64, 81},
|
||||
{13, 29, 47},
|
||||
{2, 8, 81},
|
||||
{56, 67, 73},
|
||||
{5, 38, 50},
|
||||
{12, 38, 64},
|
||||
{59, 72, 80},
|
||||
{3, 26, 79},
|
||||
{45, 76, 81},
|
||||
{1, 65, 74},
|
||||
{7, 18, 77},
|
||||
{11, 56, 59},
|
||||
{14, 39, 54},
|
||||
{16, 37, 66},
|
||||
{10, 28, 55},
|
||||
{15, 60, 70},
|
||||
{17, 25, 82},
|
||||
{20, 30, 31},
|
||||
{12, 67, 68},
|
||||
{23, 75, 80},
|
||||
{27, 32, 62},
|
||||
{24, 69, 75},
|
||||
{19, 21, 71},
|
||||
{34, 53, 61},
|
||||
{35, 46, 47},
|
||||
{33, 59, 76},
|
||||
{40, 43, 83},
|
||||
{41, 42, 63},
|
||||
{49, 75, 83},
|
||||
{20, 44, 48},
|
||||
{42, 49, 57}};
|
||||
|
||||
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,
|
||||
6,6,7,6,6,6,7,6,6,6,6,7,6,6,6,7,
|
||||
6,6,6,7,7,6,6,7,6,6,6,6,6,6,6,7,
|
||||
6,6,6
|
||||
};
|
||||
|
||||
} // namespace
|
||||
const uint8_t kNrw[FT8_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,
|
||||
6, 6, 7, 6, 6, 6, 7, 6, 6, 6, 6, 7, 6, 6, 6, 7,
|
||||
6, 6, 6, 7, 7, 6, 6, 7, 6, 6, 6, 6, 6, 6, 6, 7,
|
||||
6, 6, 6};
|
||||
|
|
|
@ -1,59 +1,52 @@
|
|||
#pragma once
|
||||
#ifndef _INCLUDE_CONSTANTS_H_
|
||||
#define _INCLUDE_CONSTANTS_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace ft8 {
|
||||
// Define FT8 symbol counts
|
||||
#define FT8_ND (58) // Data symbols
|
||||
#define FT8_NS (21) // Sync symbols (3 @ Costas 7x7)
|
||||
#define FT8_NN (FT8_NS + FT8_ND) // Total channel symbols (79)
|
||||
|
||||
// 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 the LDPC sizes
|
||||
#define FT8_N (174) // Number of bits in the encoded message (payload + checksum)
|
||||
#define FT8_K (91) // Number of payload bits
|
||||
#define FT8_M (FT8_N - FT8_K) // Number of checksum bits
|
||||
#define FT8_K_BYTES ((FT8_K + 7) / 8) // Number of whole bytes needed to store K bits
|
||||
|
||||
// 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
|
||||
// Define CRC parameters
|
||||
#define CRC_POLYNOMIAL ((uint16_t)0x2757u) // CRC-14 polynomial without the leading (MSB) 1
|
||||
#define CRC_WIDTH (14)
|
||||
|
||||
// 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];
|
||||
|
||||
// Costas 7x7 tone pattern
|
||||
extern const uint8_t kCostas_map[7];
|
||||
// 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];
|
||||
|
||||
// Gray code map
|
||||
extern const uint8_t kGray_map[8];
|
||||
// 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];
|
||||
|
||||
// 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];
|
||||
|
||||
// Parity generator matrix for (174,91) LDPC code, stored in bitpacked format (MSB first)
|
||||
extern const uint8_t kGenerator[M][K_BYTES];
|
||||
// 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];
|
||||
|
||||
// Number of rows (columns in C/C++) in the array Nm.
|
||||
extern const uint8_t kNrw[FT8_M];
|
||||
|
||||
// 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[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[N][3];
|
||||
|
||||
|
||||
// Number of rows (columns in C/C++) in the array Nm.
|
||||
extern const uint8_t kNrw[M];
|
||||
|
||||
}
|
||||
#endif // _INCLUDE_CONSTANTS_H_
|
||||
|
|
166
ft8/decode.cpp
166
ft8/decode.cpp
|
@ -1,10 +1,7 @@
|
|||
#include "decode.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "constants.h"
|
||||
|
||||
namespace ft8 {
|
||||
#include <math.h>
|
||||
|
||||
static float max2(float a, float b);
|
||||
static float max4(float a, float b, float c, float d);
|
||||
|
@ -13,32 +10,42 @@ 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);
|
||||
|
||||
static int get_index(const MagArray *power, int block, int time_sub, int freq_sub, int bin) {
|
||||
return ((((block * power->time_osr) + time_sub) * power->freq_osr + freq_sub) * power->num_bins) + bin;
|
||||
static int get_index(const MagArray *power, int block, int time_sub, int freq_sub, int bin)
|
||||
{
|
||||
return ((((block * power->time_osr) + time_sub) * power->freq_osr + freq_sub) * power->num_bins) + bin;
|
||||
}
|
||||
|
||||
// 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 MagArray *power, const uint8_t *sync_map, int num_candidates, Candidate *heap, int min_score) {
|
||||
int find_sync(const MagArray *power, const uint8_t *sync_map, int num_candidates, Candidate *heap, int min_score)
|
||||
{
|
||||
int heap_size = 0;
|
||||
int num_alt = power->time_osr * power->freq_osr;
|
||||
|
||||
// Here we allow time offsets that exceed signal boundaries, as long as we still have all data bits.
|
||||
// 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 time_sub = 0; time_sub < power->time_osr; ++time_sub) {
|
||||
for (int freq_sub = 0; freq_sub < power->freq_osr; ++freq_sub) {
|
||||
for (int time_offset = -7; time_offset < power->num_blocks - ft8::NN + 7; ++time_offset) {
|
||||
for (int freq_offset = 0; freq_offset < power->num_bins - 8; ++freq_offset) {
|
||||
for (int time_sub = 0; time_sub < power->time_osr; ++time_sub)
|
||||
{
|
||||
for (int freq_sub = 0; freq_sub < power->freq_osr; ++freq_sub)
|
||||
{
|
||||
for (int time_offset = -7; time_offset < power->num_blocks - FT8_NN + 7; ++time_offset)
|
||||
{
|
||||
for (int freq_offset = 0; freq_offset < power->num_bins - 8; ++freq_offset)
|
||||
{
|
||||
int score = 0;
|
||||
|
||||
// Compute average score over sync symbols (m+k = 0-7, 36-43, 72-79)
|
||||
int num_symbols = 0;
|
||||
for (int m = 0; m <= 72; m += 36) {
|
||||
for (int k = 0; k < 7; ++k) {
|
||||
for (int m = 0; m <= 72; m += 36)
|
||||
{
|
||||
for (int k = 0; k < 7; ++k)
|
||||
{
|
||||
// Check for time boundaries
|
||||
if (time_offset + k + m < 0) continue;
|
||||
if (time_offset + k + m >= power->num_blocks) break;
|
||||
if (time_offset + k + m < 0)
|
||||
continue;
|
||||
if (time_offset + k + m >= power->num_blocks)
|
||||
break;
|
||||
|
||||
// int offset = ((time_offset + k + m) * num_alt + alt) * power->num_bins + freq_offset;
|
||||
int offset = get_index(power, time_offset + k + m, time_sub, freq_sub, freq_offset);
|
||||
|
@ -51,34 +58,40 @@ int find_sync(const MagArray *power, const uint8_t *sync_map, int num_candidates
|
|||
// p8[4] - p8[5] - p8[6] - p8[7];
|
||||
|
||||
// Check only the neighbors of the expected symbol frequency- and time-wise
|
||||
int sm = sync_map[k]; // Index of the expected bin
|
||||
if (sm > 0) {
|
||||
int sm = sync_map[k]; // Index of the expected bin
|
||||
if (sm > 0)
|
||||
{
|
||||
// look at one frequency bin lower
|
||||
score += p8[sm] - p8[sm - 1];
|
||||
}
|
||||
if (sm < 7) {
|
||||
if (sm < 7)
|
||||
{
|
||||
// look at one frequency bin higher
|
||||
score += p8[sm] - p8[sm + 1];
|
||||
}
|
||||
if (k > 0) {
|
||||
if (k > 0)
|
||||
{
|
||||
// look one symbol back in time
|
||||
score += p8[sm] - p8[sm - num_alt * power->num_bins];
|
||||
}
|
||||
if (k < 6) {
|
||||
if (k < 6)
|
||||
{
|
||||
// look one symbol forward in time
|
||||
score += p8[sm] - p8[sm + num_alt * power->num_bins];
|
||||
}
|
||||
|
||||
|
||||
++num_symbols;
|
||||
}
|
||||
}
|
||||
score /= num_symbols;
|
||||
|
||||
if (score < min_score) continue;
|
||||
if (score < min_score)
|
||||
continue;
|
||||
|
||||
// If the heap is full AND the current candidate is better than
|
||||
// If the heap is full AND the current candidate is better than
|
||||
// the worst in the heap, we remove the worst and make space
|
||||
if (heap_size == num_candidates && score > heap[0].score) {
|
||||
if (heap_size == num_candidates && score > heap[0].score)
|
||||
{
|
||||
heap[0] = heap[heap_size - 1];
|
||||
--heap_size;
|
||||
|
||||
|
@ -86,7 +99,8 @@ int find_sync(const MagArray *power, const uint8_t *sync_map, int num_candidates
|
|||
}
|
||||
|
||||
// If there's free space in the heap, we add the current candidate
|
||||
if (heap_size < num_candidates) {
|
||||
if (heap_size < num_candidates)
|
||||
{
|
||||
heap[heap_size].score = score;
|
||||
heap[heap_size].time_offset = time_offset;
|
||||
heap[heap_size].freq_offset = freq_offset;
|
||||
|
@ -104,10 +118,10 @@ int find_sync(const MagArray *power, const uint8_t *sync_map, int num_candidates
|
|||
return heap_size;
|
||||
}
|
||||
|
||||
|
||||
// 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
|
||||
void extract_likelihood(const MagArray *power, const Candidate & cand, const uint8_t *code_map, float *log174) {
|
||||
void extract_likelihood(const MagArray *power, const Candidate &cand, const uint8_t *code_map, float *log174)
|
||||
{
|
||||
int num_alt = power->time_osr * power->freq_osr;
|
||||
// int offset = (cand.time_offset * num_alt + cand.time_sub * power->freq_osr + cand.freq_sub) * power->num_bins + cand.freq_offset;
|
||||
int offset = get_index(power, cand.time_offset, cand.time_sub, cand.freq_sub, cand.freq_offset);
|
||||
|
@ -116,9 +130,10 @@ void extract_likelihood(const MagArray *power, const Candidate & cand, const uin
|
|||
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) {
|
||||
for (int k = 0; k < FT8_ND; k += n_syms)
|
||||
{
|
||||
// Add either 7 or 14 extra symbols to account for sync
|
||||
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;
|
||||
|
||||
// Pointer to 8 bins of the current symbol
|
||||
|
@ -128,11 +143,12 @@ void extract_likelihood(const MagArray *power, const Candidate & cand, const uin
|
|||
}
|
||||
|
||||
// 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) {
|
||||
sum += log174[i];
|
||||
float sum = 0;
|
||||
float sum2 = 0;
|
||||
float inv_n = 1.0f / FT8_N;
|
||||
for (int i = 0; i < FT8_N; ++i)
|
||||
{
|
||||
sum += log174[i];
|
||||
sum2 += log174[i] * log174[i];
|
||||
}
|
||||
float variance = (sum2 - sum * sum * inv_n) * inv_n;
|
||||
|
@ -140,37 +156,42 @@ void extract_likelihood(const MagArray *power, const Candidate & cand, const uin
|
|||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static float max2(float a, float b) {
|
||||
static float max2(float a, float b)
|
||||
{
|
||||
return (a >= b) ? a : b;
|
||||
}
|
||||
|
||||
|
||||
static float max4(float a, float b, float c, float d) {
|
||||
static float max4(float a, float b, float c, float d)
|
||||
{
|
||||
return max2(max2(a, b), max2(c, d));
|
||||
}
|
||||
|
||||
|
||||
static void heapify_down(Candidate *heap, int heap_size) {
|
||||
static void heapify_down(Candidate *heap, int heap_size)
|
||||
{
|
||||
// heapify from the root down
|
||||
int current = 0;
|
||||
while (true) {
|
||||
while (true)
|
||||
{
|
||||
int largest = current;
|
||||
int left = 2 * current + 1;
|
||||
int right = left + 1;
|
||||
|
||||
if (left < heap_size && heap[left].score < heap[largest].score) {
|
||||
if (left < heap_size && heap[left].score < heap[largest].score)
|
||||
{
|
||||
largest = left;
|
||||
}
|
||||
if (right < heap_size && heap[right].score < heap[largest].score) {
|
||||
if (right < heap_size && heap[right].score < heap[largest].score)
|
||||
{
|
||||
largest = right;
|
||||
}
|
||||
if (largest == current) {
|
||||
if (largest == current)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -181,13 +202,15 @@ static void heapify_down(Candidate *heap, int heap_size) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
static void heapify_up(Candidate *heap, int heap_size) {
|
||||
static void heapify_up(Candidate *heap, int heap_size)
|
||||
{
|
||||
// heapify from the last node up
|
||||
int current = heap_size - 1;
|
||||
while (current > 0) {
|
||||
while (current > 0)
|
||||
{
|
||||
int parent = (current - 1) / 2;
|
||||
if (heap[current].score >= heap[parent].score) {
|
||||
if (heap[current].score >= heap[parent].score)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -198,13 +221,14 @@ static void heapify_up(Candidate *heap, int heap_size) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// Compute unnormalized log likelihood log(p(1) / p(0)) of 3 message bits (1 FSK symbol)
|
||||
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)
|
||||
{
|
||||
// Cleaned up code for the simple case of n_syms==1
|
||||
float s2[8];
|
||||
|
||||
for (int j = 0; j < 8; ++j) {
|
||||
for (int j = 0; j < 8; ++j)
|
||||
{
|
||||
s2[j] = (float)power[code_map[j]];
|
||||
}
|
||||
|
||||
|
@ -213,26 +237,29 @@ static void decode_symbol(const uint8_t *power, const uint8_t *code_map, int bit
|
|||
log174[bit_idx + 2] = max4(s2[1], s2[3], s2[5], s2[7]) - max4(s2[0], s2[2], s2[4], s2[6]);
|
||||
}
|
||||
|
||||
|
||||
// Compute unnormalized log likelihood log(p(1) / p(0)) of bits corresponding to several FSK symbols at once
|
||||
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)
|
||||
{
|
||||
// The following section implements what seems to be multiple-symbol decode at one go,
|
||||
// corresponding to WSJT-X's ft8b.f90. Experimentally found not to be any better than
|
||||
// corresponding to WSJT-X's ft8b.f90. Experimentally found not to be any better than
|
||||
// 1-symbol decode.
|
||||
|
||||
const int n_bits = 3 * n_syms;
|
||||
const int n_tones = (1 << n_bits);
|
||||
|
||||
|
||||
float s2[n_tones];
|
||||
|
||||
for (int j = 0; j < n_tones; ++j) {
|
||||
for (int j = 0; j < n_tones; ++j)
|
||||
{
|
||||
int j1 = j & 0x07;
|
||||
if (n_syms == 1) {
|
||||
if (n_syms == 1)
|
||||
{
|
||||
s2[j] = (float)power[code_map[j1]];
|
||||
continue;
|
||||
}
|
||||
int j2 = (j >> 3) & 0x07;
|
||||
if (n_syms == 2) {
|
||||
if (n_syms == 2)
|
||||
{
|
||||
s2[j] = (float)power[code_map[j2]];
|
||||
s2[j] += (float)power[code_map[j1] + 4 * num_bins];
|
||||
continue;
|
||||
|
@ -249,25 +276,28 @@ 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) {
|
||||
for (int i = 0; i < n_bits; ++i)
|
||||
{
|
||||
if (bit_idx + i >= FT8_N)
|
||||
{
|
||||
// Respect array size
|
||||
break;
|
||||
}
|
||||
|
||||
uint16_t mask = (n_tones >> (i + 1));
|
||||
float max_zero = -1000, max_one = -1000;
|
||||
for (int n = 0; n < n_tones; ++n) {
|
||||
if (n & mask) {
|
||||
for (int n = 0; n < n_tones; ++n)
|
||||
{
|
||||
if (n & mask)
|
||||
{
|
||||
max_one = max2(max_one, s2[n]);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
max_zero = max2(max_zero, s2[n]);
|
||||
}
|
||||
}
|
||||
|
||||
log174[bit_idx + i] = max_one - max_zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
45
ft8/decode.h
45
ft8/decode.h
|
@ -1,33 +1,32 @@
|
|||
#pragma once
|
||||
#ifndef _INCLUDE_DECODE_H_
|
||||
#define _INCLUDE_DECODE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace ft8 {
|
||||
|
||||
struct MagArray {
|
||||
int num_blocks; // number of total blocks (symbols)
|
||||
int num_bins; // number of FFT bins
|
||||
int time_osr; // number of time subdivisions
|
||||
int freq_osr; // number of frequency subdivisions
|
||||
uint8_t * mag; // FFT magnitudes as [blocks][time_sub][freq_sub][num_bins]
|
||||
};
|
||||
|
||||
struct Candidate {
|
||||
int16_t score;
|
||||
int16_t time_offset;
|
||||
int16_t freq_offset;
|
||||
uint8_t time_sub;
|
||||
uint8_t freq_sub;
|
||||
};
|
||||
typedef struct
|
||||
{
|
||||
int num_blocks; // number of total blocks (symbols)
|
||||
int num_bins; // number of FFT bins
|
||||
int time_osr; // number of time subdivisions
|
||||
int freq_osr; // number of frequency subdivisions
|
||||
uint8_t *mag; // FFT magnitudes as [blocks][time_sub][freq_sub][num_bins]
|
||||
} MagArray;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int16_t score;
|
||||
int16_t time_offset;
|
||||
int16_t freq_offset;
|
||||
uint8_t time_sub;
|
||||
uint8_t freq_sub;
|
||||
} Candidate;
|
||||
|
||||
// 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 MagArray * power, const uint8_t *sync_map, int num_candidates, Candidate *heap, int min_score = 0);
|
||||
int find_sync(const MagArray *power, const uint8_t *sync_map, int num_candidates, Candidate *heap, int min_score);
|
||||
|
||||
|
||||
// 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
|
||||
void extract_likelihood(const MagArray *power, const Candidate & cand, const uint8_t *code_map, float *log174);
|
||||
void extract_likelihood(const MagArray *power, const Candidate &cand, const uint8_t *code_map, float *log174);
|
||||
|
||||
}
|
||||
#endif // _INCLUDE_DECODE_H_
|
||||
|
|
141
ft8/encode.cpp
141
ft8/encode.cpp
|
@ -3,26 +3,26 @@
|
|||
|
||||
#include <stdio.h>
|
||||
|
||||
namespace ft8 {
|
||||
|
||||
#define TOPBIT (1 << (CRC_WIDTH - 1))
|
||||
|
||||
// Returns 1 if an odd number of bits are set in x, zero otherwise
|
||||
uint8_t parity8(uint8_t x) {
|
||||
x ^= x >> 4; // a b c d ae bf cg dh
|
||||
x ^= x >> 2; // a b ac bd cae dbf aecg bfdh
|
||||
x ^= x >> 1; // a ab bac acbd bdcae caedbf aecgbfdh
|
||||
return (x) & 1;
|
||||
uint8_t parity8(uint8_t x)
|
||||
{
|
||||
x ^= x >> 4; // a b c d ae bf cg dh
|
||||
x ^= x >> 2; // a b ac bd cae dbf aecg bfdh
|
||||
x ^= x >> 1; // a ab bac acbd bdcae caedbf aecgbfdh
|
||||
return (x)&1;
|
||||
}
|
||||
|
||||
|
||||
// Encode a 91-bit message and return a 174-bit codeword.
|
||||
// The generator matrix has dimensions (87,87).
|
||||
// Encode a 91-bit message and return a 174-bit codeword.
|
||||
// The generator matrix has dimensions (87,87).
|
||||
// The code is a (174,91) regular ldpc code with column weight 3.
|
||||
// The code was generated using the PEG algorithm.
|
||||
// Arguments:
|
||||
// [IN] message - array of 91 bits stored as 12 bytes (MSB first)
|
||||
// [OUT] codeword - array of 174 bits stored as 22 bytes (MSB first)
|
||||
void encode174(const uint8_t *message, uint8_t *codeword) {
|
||||
void encode174(const uint8_t *message, uint8_t *codeword)
|
||||
{
|
||||
// Here we don't generate the generator bit matrix as in WSJT-X implementation
|
||||
// Instead we access the generator bits straight from the binary representation in kGenerator
|
||||
|
||||
|
@ -31,91 +31,92 @@ 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],
|
||||
// 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) {
|
||||
uint8_t bits = message[j] & kGenerator[i][j]; // bitwise AND (bitwise multiplication)
|
||||
nsum ^= parity8(bits); // bitwise XOR (addition modulo 2)
|
||||
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)
|
||||
}
|
||||
// Check if we need to set a bit in codeword
|
||||
if (nsum % 2) { // pchecks(i)=mod(nsum,2)
|
||||
codeword[col_idx] |= col_mask;
|
||||
if (nsum % 2)
|
||||
{ // pchecks(i)=mod(nsum,2)
|
||||
codeword[col_idx] |= col_mask;
|
||||
}
|
||||
|
||||
col_mask >>= 1;
|
||||
if (col_mask == 0) {
|
||||
col_mask = 0x80;
|
||||
++col_idx;
|
||||
if (col_mask == 0)
|
||||
{
|
||||
col_mask = 0x80;
|
||||
++col_idx;
|
||||
}
|
||||
}
|
||||
|
||||
// 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");
|
||||
}
|
||||
|
||||
|
||||
// Compute 14-bit CRC for a sequence of given number of bits
|
||||
// Adapted from https://barrgroup.com/Embedded-Systems/How-To/CRC-Calculation-C-Code
|
||||
// [IN] message - byte sequence (MSB first)
|
||||
// [IN] num_bits - number of bits in the sequence
|
||||
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));
|
||||
|
||||
// printf("CRC, %d bits: ", num_bits);
|
||||
// for (int i = 0; i < (num_bits + 7) / 8; ++i) {
|
||||
// printf("%02x ", message[i]);
|
||||
// }
|
||||
// printf("\n");
|
||||
|
||||
uint16_t ft8_crc(uint8_t *message, int num_bits)
|
||||
{
|
||||
uint16_t remainder = 0;
|
||||
int idx_byte = 0;
|
||||
|
||||
// Perform modulo-2 division, a bit at a time.
|
||||
for (int idx_bit = 0; idx_bit < num_bits; ++idx_bit) {
|
||||
if (idx_bit % 8 == 0) {
|
||||
for (int idx_bit = 0; idx_bit < num_bits; ++idx_bit)
|
||||
{
|
||||
if (idx_bit % 8 == 0)
|
||||
{
|
||||
// Bring the next byte into the remainder.
|
||||
remainder ^= (message[idx_byte] << (CRC_WIDTH - 8));
|
||||
++idx_byte;
|
||||
}
|
||||
|
||||
// Try to divide the current data bit.
|
||||
if (remainder & TOPBIT) {
|
||||
if (remainder & TOPBIT)
|
||||
{
|
||||
remainder = (remainder << 1) ^ CRC_POLYNOMIAL;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
remainder = (remainder << 1);
|
||||
}
|
||||
}
|
||||
// printf("CRC = %04xh\n", remainder & ((1 << CRC_WIDTH) - 1));
|
||||
|
||||
return remainder & ((1 << CRC_WIDTH) - 1);
|
||||
}
|
||||
|
||||
|
||||
// Generate FT8 tone sequence from payload data
|
||||
// [IN] payload - 10 byte array consisting of 77 bit payload (MSB first)
|
||||
// [OUT] itone - array of NN (79) bytes to store the generated tones (encoded as 0..7)
|
||||
void genft8(const uint8_t *payload, uint8_t *itone) {
|
||||
uint8_t a91[12]; // Store 77 bits of payload + 14 bits CRC
|
||||
void genft8(const uint8_t *payload, uint8_t *itone)
|
||||
{
|
||||
uint8_t a91[12]; // Store 77 bits of payload + 14 bits CRC
|
||||
|
||||
// Copy 77 bits of payload data
|
||||
for (int i = 0; i < 10; i++)
|
||||
|
@ -127,7 +128,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);
|
||||
|
@ -139,34 +140,50 @@ void genft8(const uint8_t *payload, uint8_t *itone) {
|
|||
encode174(a91, codeword);
|
||||
|
||||
// Message structure: S7 D29 S7 D29 S7
|
||||
for (int i = 0; i < 7; ++i) {
|
||||
itone[i] = kCostas_map[i];
|
||||
for (int i = 0; i < 7; ++i)
|
||||
{
|
||||
itone[i] = kCostas_map[i];
|
||||
itone[36 + i] = kCostas_map[i];
|
||||
itone[72 + i] = kCostas_map[i];
|
||||
}
|
||||
|
||||
int k = 7; // Skip over the first set of Costas symbols
|
||||
int k = 7; // Skip over the first set of Costas symbols
|
||||
|
||||
uint8_t mask = 0x80;
|
||||
int i_byte = 0;
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
// Extract 3 bits from codeword at i-th position
|
||||
uint8_t bits3 = 0;
|
||||
|
||||
if (codeword[i_byte] & mask) bits3 |= 4;
|
||||
if (0 == (mask >>= 1)) { mask = 0x80; i_byte++; }
|
||||
if (codeword[i_byte] & mask) bits3 |= 2;
|
||||
if (0 == (mask >>= 1)) { mask = 0x80; i_byte++; }
|
||||
if (codeword[i_byte] & mask) bits3 |= 1;
|
||||
if (0 == (mask >>= 1)) { mask = 0x80; i_byte++; }
|
||||
if (codeword[i_byte] & mask)
|
||||
bits3 |= 4;
|
||||
if (0 == (mask >>= 1))
|
||||
{
|
||||
mask = 0x80;
|
||||
i_byte++;
|
||||
}
|
||||
if (codeword[i_byte] & mask)
|
||||
bits3 |= 2;
|
||||
if (0 == (mask >>= 1))
|
||||
{
|
||||
mask = 0x80;
|
||||
i_byte++;
|
||||
}
|
||||
if (codeword[i_byte] & mask)
|
||||
bits3 |= 1;
|
||||
if (0 == (mask >>= 1))
|
||||
{
|
||||
mask = 0x80;
|
||||
i_byte++;
|
||||
}
|
||||
|
||||
itone[k] = kGray_map[bits3];
|
||||
++k;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
44
ft8/encode.h
44
ft8/encode.h
|
@ -1,29 +1,27 @@
|
|||
#pragma once
|
||||
#ifndef _INCLUDE_ENCODE_H_
|
||||
#define _INCLUDE_ENCODE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace ft8 {
|
||||
// Generate FT8 tone sequence from payload data
|
||||
// [IN] payload - 9 byte array consisting of 72 bit payload
|
||||
// [OUT] itone - array of NN (79) bytes to store the generated tones (encoded as 0..7)
|
||||
void genft8(const uint8_t *payload, uint8_t *itone);
|
||||
|
||||
// Generate FT8 tone sequence from payload data
|
||||
// [IN] payload - 9 byte array consisting of 72 bit payload
|
||||
// [OUT] itone - array of NN (79) bytes to store the generated tones (encoded as 0..7)
|
||||
void genft8(const uint8_t *payload, uint8_t *itone);
|
||||
// Encode an 87-bit message and return a 174-bit codeword.
|
||||
// The generator matrix has dimensions (87,87).
|
||||
// The code is a (174,87) regular ldpc code with column weight 3.
|
||||
// The code was generated using the PEG algorithm.
|
||||
// After creating the codeword, the columns are re-ordered according to
|
||||
// "colorder" to make the codeword compatible with the parity-check matrix
|
||||
// Arguments:
|
||||
// * message - array of 87 bits stored as 11 bytes (MSB first)
|
||||
// * codeword - array of 174 bits stored as 22 bytes (MSB first)
|
||||
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);
|
||||
|
||||
// Encode an 87-bit message and return a 174-bit codeword.
|
||||
// The generator matrix has dimensions (87,87).
|
||||
// The code is a (174,87) regular ldpc code with column weight 3.
|
||||
// The code was generated using the PEG algorithm.
|
||||
// After creating the codeword, the columns are re-ordered according to
|
||||
// "colorder" to make the codeword compatible with the parity-check matrix
|
||||
// Arguments:
|
||||
// * message - array of 87 bits stored as 11 bytes (MSB first)
|
||||
// * codeword - array of 174 bits stored as 22 bytes (MSB first)
|
||||
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 crc(uint8_t *message, int num_bits);
|
||||
};
|
||||
#endif // _INCLUDE_ENCODE_H_
|
||||
|
|
222
ft8/ldpc.cpp
222
ft8/ldpc.cpp
|
@ -9,65 +9,76 @@
|
|||
// codeword[i] = log ( P(x=0) / P(x=1) )
|
||||
//
|
||||
|
||||
#include "ldpc.h"
|
||||
#include "constants.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include "constants.h"
|
||||
|
||||
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[],
|
||||
// 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 pack_bits(const uint8_t plain[], int num_bits, uint8_t packed[])
|
||||
{
|
||||
int num_bytes = (num_bits + 7) / 8;
|
||||
for (int i = 0; i < num_bytes; ++i) {
|
||||
for (int i = 0; i < num_bytes; ++i)
|
||||
{
|
||||
packed[i] = 0;
|
||||
}
|
||||
|
||||
uint8_t mask = 0x80;
|
||||
int byte_idx = 0;
|
||||
for (int i = 0; i < num_bits; ++i) {
|
||||
if (plain[i]) {
|
||||
int byte_idx = 0;
|
||||
for (int i = 0; i < num_bits; ++i)
|
||||
{
|
||||
if (plain[i])
|
||||
{
|
||||
packed[byte_idx] |= mask;
|
||||
}
|
||||
mask >>= 1;
|
||||
if (!mask) {
|
||||
if (!mask)
|
||||
{
|
||||
mask = 0x80;
|
||||
++byte_idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// codeword is 174 log-likelihoods.
|
||||
// plain is a return value, 174 ints, to be 0 or 1.
|
||||
// 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;
|
||||
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;
|
||||
|
||||
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 ii1 = 0; ii1 < kNrw[j]; ii1++) {
|
||||
for (int iter = 0; iter < max_iters; iter++)
|
||||
{
|
||||
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;
|
||||
for (int ii2 = 0; ii2 < kNrw[j]; ii2++) {
|
||||
for (int ii2 = 0; ii2 < kNrw[j]; ii2++)
|
||||
{
|
||||
int i2 = kNm[j][ii2] - 1;
|
||||
if (i2 != i1) {
|
||||
if (i2 != i1)
|
||||
{
|
||||
a *= fast_tanh(-m[j][i2] / 2.0f);
|
||||
}
|
||||
}
|
||||
|
@ -75,7 +86,8 @@ 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];
|
||||
|
@ -84,21 +96,27 @@ void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok) {
|
|||
|
||||
int errors = ldpc_check(plain);
|
||||
|
||||
if (errors < min_errors) {
|
||||
if (errors < min_errors)
|
||||
{
|
||||
// Update the current best result
|
||||
min_errors = errors;
|
||||
|
||||
if (errors == 0) {
|
||||
break; // Found a perfect answer
|
||||
if (errors == 0)
|
||||
{
|
||||
break; // Found a perfect answer
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < ft8::N; i++) {
|
||||
for (int ji1 = 0; ji1 < 3; ji1++) {
|
||||
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];
|
||||
for (int ji2 = 0; ji2 < 3; ji2++) {
|
||||
if (ji1 != ji2) {
|
||||
for (int ji2 = 0; ji2 < 3; ji2++)
|
||||
{
|
||||
if (ji1 != ji2)
|
||||
{
|
||||
int j2 = kMn[i][ji2] - 1;
|
||||
l += e[j2][i];
|
||||
}
|
||||
|
@ -111,55 +129,64 @@ void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok) {
|
|||
*ok = min_errors;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// does a 174-bit codeword pass the FT8's LDPC parity checks?
|
||||
// returns the number of parity errors.
|
||||
// 0 means total success.
|
||||
//
|
||||
static 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) {
|
||||
for (int i = 0; i < kNrw[j]; ++i)
|
||||
{
|
||||
x ^= codeword[kNm[j][i] - 1];
|
||||
}
|
||||
if (x != 0) {
|
||||
if (x != 0)
|
||||
{
|
||||
++errors;
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok)
|
||||
{
|
||||
float tov[FT8_N][3];
|
||||
float toc[FT8_M][7];
|
||||
|
||||
void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok) {
|
||||
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 j = 0; j < kNrw[i]; ++j) {
|
||||
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 j = 0; j < 3; ++j) {
|
||||
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];
|
||||
for (int iter = 0; iter < max_iters; ++iter)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
@ -167,42 +194,55 @@ void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok) {
|
|||
// Check to see if we have a codeword (check before we do any iter)
|
||||
int errors = ldpc_check(plain);
|
||||
|
||||
if (errors < min_errors) {
|
||||
if (errors < min_errors)
|
||||
{
|
||||
// we have a better guess - update the result
|
||||
min_errors = errors;
|
||||
|
||||
if (errors == 0) {
|
||||
break; // Found a perfect answer
|
||||
if (errors == 0)
|
||||
{
|
||||
break; // Found a perfect answer
|
||||
}
|
||||
}
|
||||
|
||||
// Send messages from bits to check nodes
|
||||
for (int i = 0; i < ft8::M; ++i) {
|
||||
for (int j = 0; j < kNrw[i]; ++j) {
|
||||
// Send messages from bits to check nodes
|
||||
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];
|
||||
for (int kk = 0; kk < 3; ++kk) {
|
||||
for (int kk = 0; kk < 3; ++kk)
|
||||
{
|
||||
// subtract off what the bit had received from the check
|
||||
if (kMn[ibj][kk] - 1 == i) {
|
||||
if (kMn[ibj][kk] - 1 == i)
|
||||
{
|
||||
toc[i][j] -= tov[ibj][kk];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// send messages from check nodes to variable nodes
|
||||
for (int i = 0; i < ft8::M; ++i) {
|
||||
for (int j = 0; j < kNrw[i]; ++j) {
|
||||
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 j = 0; j < 3; ++j) {
|
||||
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;
|
||||
for (int k = 0; k < kNrw[ichk]; ++k) {
|
||||
if (kNm[ichk][k] - 1 != i) {
|
||||
for (int k = 0; k < kNrw[ichk]; ++k)
|
||||
{
|
||||
if (kNm[ichk][k] - 1 != i)
|
||||
{
|
||||
Tmn *= toc[ichk][k];
|
||||
}
|
||||
}
|
||||
|
@ -218,14 +258,16 @@ void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok) {
|
|||
// http://functions.wolfram.com/ElementaryFunctions/ArcTanh/10/0001/
|
||||
// https://mathr.co.uk/blog/2017-09-06_approximating_hyperbolic_tangent.html
|
||||
|
||||
|
||||
// thank you Douglas Bagnall
|
||||
// https://math.stackexchange.com/a/446411
|
||||
static float fast_tanh(float x) {
|
||||
if (x < -4.97f) {
|
||||
static float fast_tanh(float x)
|
||||
{
|
||||
if (x < -4.97f)
|
||||
{
|
||||
return -1.0f;
|
||||
}
|
||||
if (x > 4.97f) {
|
||||
if (x > 4.97f)
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
float x2 = x * x;
|
||||
|
@ -238,8 +280,8 @@ static float fast_tanh(float x) {
|
|||
return a / b;
|
||||
}
|
||||
|
||||
|
||||
static 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)));
|
||||
|
@ -250,48 +292,56 @@ static float fast_atanh(float x) {
|
|||
return a / b;
|
||||
}
|
||||
|
||||
|
||||
static float pltanh(float x) {
|
||||
static float pltanh(float x)
|
||||
{
|
||||
float isign = +1;
|
||||
if (x < 0) {
|
||||
if (x < 0)
|
||||
{
|
||||
isign = -1;
|
||||
x = -x;
|
||||
}
|
||||
if (x < 0.8f) {
|
||||
if (x < 0.8f)
|
||||
{
|
||||
return isign * 0.83 * x;
|
||||
}
|
||||
if (x < 1.6f) {
|
||||
if (x < 1.6f)
|
||||
{
|
||||
return isign * (0.322f * x + 0.4064f);
|
||||
}
|
||||
if (x < 3.0f) {
|
||||
if (x < 3.0f)
|
||||
{
|
||||
return isign * (0.0524f * x + 0.8378f);
|
||||
}
|
||||
if (x < 7.0f) {
|
||||
if (x < 7.0f)
|
||||
{
|
||||
return isign * (0.0012f * x + 0.9914f);
|
||||
}
|
||||
return isign*0.9998f;
|
||||
return isign * 0.9998f;
|
||||
}
|
||||
|
||||
|
||||
static float platanh(float x) {
|
||||
static float platanh(float x)
|
||||
{
|
||||
float isign = +1;
|
||||
if (x < 0) {
|
||||
if (x < 0)
|
||||
{
|
||||
isign = -1;
|
||||
x = -x;
|
||||
}
|
||||
if (x < 0.664f) {
|
||||
if (x < 0.664f)
|
||||
{
|
||||
return isign * x / 0.83f;
|
||||
}
|
||||
if (x < 0.9217f) {
|
||||
if (x < 0.9217f)
|
||||
{
|
||||
return isign * (x - 0.4064f) / 0.322f;
|
||||
}
|
||||
if (x < 0.9951f) {
|
||||
if (x < 0.9951f)
|
||||
{
|
||||
return isign * (x - 0.8378f) / 0.0524f;
|
||||
}
|
||||
if (x < 0.9998f) {
|
||||
if (x < 0.9998f)
|
||||
{
|
||||
return isign * (x - 0.9914f) / 0.0012f;
|
||||
}
|
||||
return isign * 7.0f;
|
||||
}
|
||||
|
||||
} // namespace
|
25
ft8/ldpc.h
25
ft8/ldpc.h
|
@ -1,17 +1,18 @@
|
|||
#pragma once
|
||||
#ifndef _INCLUDE_LDPC_H_
|
||||
#define _INCLUDE_LDPC_H_
|
||||
|
||||
namespace ft8 {
|
||||
#include <stdint.h>
|
||||
|
||||
// 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);
|
||||
// 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);
|
||||
|
||||
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);
|
||||
|
||||
// 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[]);
|
||||
// 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[]);
|
||||
|
||||
}
|
||||
#endif // _INCLUDE_LDPC_H_
|
||||
|
|
208
ft8/pack.cpp
208
ft8/pack.cpp
|
@ -1,12 +1,14 @@
|
|||
#include "pack.h"
|
||||
|
||||
#include "text.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
namespace ft8 {
|
||||
#define NTOKENS ((uint32_t)2063592L)
|
||||
#define MAX22 ((uint32_t)4194304L)
|
||||
#define MAXGRID4 ((uint16_t)32400)
|
||||
|
||||
// TODO: This is wasteful, should figure out something more elegant
|
||||
const char A0[] = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+-./?";
|
||||
|
@ -15,19 +17,20 @@ const char A2[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|||
const char A3[] = "0123456789";
|
||||
const char A4[] = " ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
|
||||
|
||||
// Pack a special token, a 22-bit hash code, or a valid base call
|
||||
// Pack a special token, a 22-bit hash code, or a valid base call
|
||||
// into a 28-bit integer.
|
||||
int32_t pack28(const char *callsign) {
|
||||
constexpr int32_t NTOKENS = 2063592L;
|
||||
constexpr int32_t MAX22 = 4194304L;
|
||||
|
||||
int32_t pack28(const char *callsign)
|
||||
{
|
||||
// Check for special tokens first
|
||||
if (starts_with(callsign, "DE ")) return 0;
|
||||
if (starts_with(callsign, "QRZ ")) return 1;
|
||||
if (starts_with(callsign, "CQ ")) return 2;
|
||||
if (starts_with(callsign, "DE "))
|
||||
return 0;
|
||||
if (starts_with(callsign, "QRZ "))
|
||||
return 1;
|
||||
if (starts_with(callsign, "CQ "))
|
||||
return 2;
|
||||
|
||||
if (starts_with(callsign, "CQ_")) {
|
||||
if (starts_with(callsign, "CQ_"))
|
||||
{
|
||||
int nnum = 0, nlet = 0;
|
||||
|
||||
// TODO:
|
||||
|
@ -43,27 +46,33 @@ int32_t pack28(const char *callsign) {
|
|||
char c6[6] = {' ', ' ', ' ', ' ', ' ', ' '};
|
||||
|
||||
int length = 0; // strlen(callsign); // We will need it later
|
||||
while (callsign[length] != ' ' && callsign[length] != 0) {
|
||||
while (callsign[length] != ' ' && callsign[length] != 0)
|
||||
{
|
||||
length++;
|
||||
}
|
||||
|
||||
// Copy callsign to 6 character buffer
|
||||
if (starts_with(callsign, "3DA0") && length <= 7) {
|
||||
if (starts_with(callsign, "3DA0") && length <= 7)
|
||||
{
|
||||
// Work-around for Swaziland prefix: 3DA0XYZ -> 3D0XYZ
|
||||
memcpy(c6, "3D0", 3);
|
||||
memcpy(c6 + 3, callsign + 4, length - 4);
|
||||
}
|
||||
else if (starts_with(callsign, "3X") && is_letter(callsign[2]) && length <= 7) {
|
||||
else if (starts_with(callsign, "3X") && is_letter(callsign[2]) && length <= 7)
|
||||
{
|
||||
// Work-around for Guinea prefixes: 3XA0XYZ -> QA0XYZ
|
||||
memcpy(c6, "Q", 1);
|
||||
memcpy(c6 + 1, callsign + 2, length - 2);
|
||||
}
|
||||
else {
|
||||
if (is_digit(callsign[2]) && length <= 6) {
|
||||
else
|
||||
{
|
||||
if (is_digit(callsign[2]) && length <= 6)
|
||||
{
|
||||
// AB0XYZ
|
||||
memcpy(c6, callsign, length);
|
||||
}
|
||||
else if (is_digit(callsign[1]) && length <= 5) {
|
||||
else if (is_digit(callsign[1]) && length <= 5)
|
||||
{
|
||||
// A0XYZ -> " A0XYZ"
|
||||
memcpy(c6 + 1, callsign, length);
|
||||
}
|
||||
|
@ -73,7 +82,7 @@ int32_t pack28(const char *callsign) {
|
|||
int i0, i1, i2, i3, i4, i5;
|
||||
if ((i0 = char_index(A1, c6[0])) >= 0 && (i1 = char_index(A2, c6[1])) >= 0 &&
|
||||
(i2 = char_index(A3, c6[2])) >= 0 && (i3 = char_index(A4, c6[3])) >= 0 &&
|
||||
(i4 = char_index(A4, c6[4])) >= 0 && (i5 = char_index(A4, c6[5])) >= 0)
|
||||
(i4 = char_index(A4, c6[4])) >= 0 && (i5 = char_index(A4, c6[5])) >= 0)
|
||||
{
|
||||
//printf("Pack28: idx=[%d, %d, %d, %d, %d, %d]\n", i0, i1, i2, i3, i4, i5);
|
||||
// This is a standard callsign
|
||||
|
@ -100,48 +109,56 @@ int32_t pack28(const char *callsign) {
|
|||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// Check if a string could be a valid standard callsign or a valid
|
||||
// compound callsign.
|
||||
// Return base call "bc" and a logical "cok" indicator.
|
||||
bool chkcall(const char *call, char *bc) {
|
||||
int length = strlen(call); // n1=len_trim(w)
|
||||
if (length > 11) return false;
|
||||
if (0 != strchr(call, '.')) return false;
|
||||
if (0 != strchr(call, '+')) return false;
|
||||
if (0 != strchr(call, '-')) return false;
|
||||
if (0 != strchr(call, '?')) return false;
|
||||
if (length > 6 && 0 != strchr(call, '/')) return false;
|
||||
bool chkcall(const char *call, char *bc)
|
||||
{
|
||||
int length = strlen(call); // n1=len_trim(w)
|
||||
if (length > 11)
|
||||
return false;
|
||||
if (0 != strchr(call, '.'))
|
||||
return false;
|
||||
if (0 != strchr(call, '+'))
|
||||
return false;
|
||||
if (0 != strchr(call, '-'))
|
||||
return false;
|
||||
if (0 != strchr(call, '?'))
|
||||
return false;
|
||||
if (length > 6 && 0 != strchr(call, '/'))
|
||||
return false;
|
||||
|
||||
// TODO: implement suffix parsing (or rework?)
|
||||
//bc=w(1:6)
|
||||
//i0=char_index(w,'/')
|
||||
//if(max(i0-1,n1-i0).gt.6) go to 100 !Base call must be < 7 characters
|
||||
//if(i0.ge.2 .and. i0.le.n1-1) then !Extract base call from compound call
|
||||
// if(i0-1.le.n1-i0) bc=w(i0+1:n1)//' '
|
||||
// if(i0-1.gt.n1-i0) bc=w(1:i0-1)//' '
|
||||
//bc=w(1:6)
|
||||
//i0=char_index(w,'/')
|
||||
//if(max(i0-1,n1-i0).gt.6) go to 100 !Base call must be < 7 characters
|
||||
//if(i0.ge.2 .and. i0.le.n1-1) then !Extract base call from compound call
|
||||
// if(i0-1.le.n1-i0) bc=w(i0+1:n1)//' '
|
||||
// if(i0-1.gt.n1-i0) bc=w(1:i0-1)//' '
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
uint16_t packgrid(const char *grid4) {
|
||||
constexpr uint16_t MAXGRID4 = 32400;
|
||||
|
||||
if (grid4 == 0) {
|
||||
uint16_t packgrid(const char *grid4)
|
||||
{
|
||||
if (grid4 == 0)
|
||||
{
|
||||
// Two callsigns only, no report/grid
|
||||
return MAXGRID4 + 1;
|
||||
}
|
||||
|
||||
// Take care of special cases
|
||||
if (equals(grid4, "RRR")) return MAXGRID4 + 2;
|
||||
if (equals(grid4, "RR73")) return MAXGRID4 + 3;
|
||||
if (equals(grid4, "73")) return MAXGRID4 + 4;
|
||||
if (equals(grid4, "RRR"))
|
||||
return MAXGRID4 + 2;
|
||||
if (equals(grid4, "RR73"))
|
||||
return MAXGRID4 + 3;
|
||||
if (equals(grid4, "73"))
|
||||
return MAXGRID4 + 4;
|
||||
|
||||
// Check for standard 4 letter grid
|
||||
if (in_range(grid4[0], 'A', 'R') &&
|
||||
if (in_range(grid4[0], 'A', 'R') &&
|
||||
in_range(grid4[1], 'A', 'R') &&
|
||||
is_digit(grid4[2]) && is_digit(grid4[3]))
|
||||
is_digit(grid4[2]) && is_digit(grid4[3]))
|
||||
{
|
||||
//if (w(3).eq.'R ') ir=1
|
||||
uint16_t igrid4 = (grid4[0] - 'A');
|
||||
|
@ -153,48 +170,55 @@ uint16_t packgrid(const char *grid4) {
|
|||
|
||||
// Parse report: +dd / -dd / R+dd / R-dd
|
||||
// TODO: check the range of dd
|
||||
if (grid4[0] == 'R') {
|
||||
if (grid4[0] == 'R')
|
||||
{
|
||||
int dd = dd_to_int(grid4 + 1, 3);
|
||||
uint16_t irpt = 35 + dd;
|
||||
return (MAXGRID4 + irpt) | 0x8000; // ir = 1
|
||||
return (MAXGRID4 + irpt) | 0x8000; // ir = 1
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
int dd = dd_to_int(grid4, 3);
|
||||
uint16_t irpt = 35 + dd;
|
||||
return (MAXGRID4 + irpt); // ir = 0
|
||||
return (MAXGRID4 + irpt); // ir = 0
|
||||
}
|
||||
|
||||
return MAXGRID4 + 1;
|
||||
}
|
||||
|
||||
// Pack Type 1 (Standard 77-bit message) and Type 2 (ditto, with a "/P" call)
|
||||
int pack77_1(const char *msg, uint8_t *b77) {
|
||||
int pack77_1(const char *msg, uint8_t *b77)
|
||||
{
|
||||
// Locate the first delimiter
|
||||
const char *s1 = strchr(msg, ' ');
|
||||
if (s1 == 0) return -1;
|
||||
if (s1 == 0)
|
||||
return -1;
|
||||
|
||||
const char *call1 = msg; // 1st call
|
||||
const char *call2 = s1 + 1; // 2nd call
|
||||
const char *call1 = msg; // 1st call
|
||||
const char *call2 = s1 + 1; // 2nd call
|
||||
|
||||
int32_t n28a = pack28(call1);
|
||||
int32_t n28b = pack28(call2);
|
||||
|
||||
if (n28a < 0 || n28b < 0) return -1;
|
||||
|
||||
if (n28a < 0 || n28b < 0)
|
||||
return -1;
|
||||
|
||||
uint16_t igrid4;
|
||||
|
||||
// Locate the second delimiter
|
||||
const char *s2 = strchr(s1 + 1, ' ');
|
||||
if (s2 != 0) {
|
||||
if (s2 != 0)
|
||||
{
|
||||
igrid4 = packgrid(s2 + 1);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// Two callsigns, no grid/report
|
||||
igrid4 = packgrid(0);
|
||||
}
|
||||
|
||||
uint8_t i3 = 1; // No suffix or /R
|
||||
|
||||
|
||||
// TODO: check for suffixes
|
||||
// if(char_index(w(1),'/P').ge.4 .or. char_index(w(2),'/P').ge.4) i3=2 !Type 2, with "/P"
|
||||
// if(char_index(w(1),'/P').ge.4 .or. char_index(w(1),'/R').ge.4) ipa=1
|
||||
|
@ -206,7 +230,7 @@ int pack77_1(const char *msg, uint8_t *b77) {
|
|||
|
||||
// Pack into (28 + 1) + (28 + 1) + (1 + 15) + 3 bits
|
||||
// write(c77,1000) n28a,ipa,n28b,ipb,ir,igrid4,i3
|
||||
// 1000 format(2(b28.28,b1),b1,b15.15,b3.3)
|
||||
// 1000 format(2(b28.28,b1),b1,b15.15,b3.3)
|
||||
|
||||
b77[0] = (n28a >> 21);
|
||||
b77[1] = (n28a >> 13);
|
||||
|
@ -222,41 +246,48 @@ int pack77_1(const char *msg, uint8_t *b77) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void packtext77(const char *text, uint8_t *b77) {
|
||||
void packtext77(const char *text, uint8_t *b77)
|
||||
{
|
||||
int length = strlen(text);
|
||||
|
||||
// Skip leading and trailing spaces
|
||||
while (*text == ' ' && *text != 0) {
|
||||
while (*text == ' ' && *text != 0)
|
||||
{
|
||||
++text;
|
||||
--length;
|
||||
}
|
||||
while (length > 0 && text[length - 1] == ' ') {
|
||||
while (length > 0 && text[length - 1] == ' ')
|
||||
{
|
||||
--length;
|
||||
}
|
||||
|
||||
// Clear the first 72 bits representing a long number
|
||||
for (int i = 0; i < 9; ++i) {
|
||||
for (int i = 0; i < 9; ++i)
|
||||
{
|
||||
b77[i] = 0;
|
||||
}
|
||||
|
||||
// Now express the text as base-42 number stored
|
||||
// Now express the text as base-42 number stored
|
||||
// in the first 72 bits of b77
|
||||
for (int j = 0; j < 13; ++j) {
|
||||
for (int j = 0; j < 13; ++j)
|
||||
{
|
||||
// Multiply the long integer in b77 by 42
|
||||
uint16_t x = 0;
|
||||
for (int i = 8; i >= 0; --i) {
|
||||
for (int i = 8; i >= 0; --i)
|
||||
{
|
||||
x += b77[i] * (uint16_t)42;
|
||||
b77[i] = (x & 0xFF);
|
||||
x >>= 8;
|
||||
}
|
||||
|
||||
// Get the index of the current char
|
||||
if (j < length) {
|
||||
if (j < length)
|
||||
{
|
||||
int q = char_index(A0, text[j]);
|
||||
x = (q > 0) ? q : 0;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
x = 0;
|
||||
}
|
||||
// Here we double each added number in order to have the result multiplied
|
||||
|
@ -264,8 +295,10 @@ void packtext77(const char *text, uint8_t *b77) {
|
|||
x <<= 1;
|
||||
|
||||
// Now add the number to our long number
|
||||
for (int i = 8; i >= 0; --i) {
|
||||
if (x == 0) break;
|
||||
for (int i = 8; i >= 0; --i)
|
||||
{
|
||||
if (x == 0)
|
||||
break;
|
||||
x += b77[i];
|
||||
b77[i] = (x & 0xFF);
|
||||
x >>= 8;
|
||||
|
@ -277,10 +310,11 @@ void packtext77(const char *text, uint8_t *b77) {
|
|||
b77[9] &= 0x00;
|
||||
}
|
||||
|
||||
|
||||
int pack77(const char *msg, uint8_t *c77) {
|
||||
int pack77(const char *msg, uint8_t *c77)
|
||||
{
|
||||
// Check Type 1 (Standard 77-bit message) or Type 2, with optional "/P"
|
||||
if (0 == pack77_1(msg, c77)) {
|
||||
if (0 == pack77_1(msg, c77))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -297,15 +331,12 @@ int pack77(const char *msg, uint8_t *c77) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
}; // namespace
|
||||
|
||||
#ifdef UNIT_TEST
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
bool test1() {
|
||||
bool test1()
|
||||
{
|
||||
const char *inputs[] = {
|
||||
"",
|
||||
" ",
|
||||
|
@ -317,10 +348,10 @@ bool test1() {
|
|||
"LL3JG",
|
||||
"LL3AJG",
|
||||
"CQ ",
|
||||
0
|
||||
};
|
||||
0};
|
||||
|
||||
for (int i = 0; inputs[i]; ++i) {
|
||||
for (int i = 0; inputs[i]; ++i)
|
||||
{
|
||||
int32_t result = ft8_v2::pack28(inputs[i]);
|
||||
printf("pack28(\"%s\") = %d\n", inputs[i], result);
|
||||
}
|
||||
|
@ -328,7 +359,8 @@ bool test1() {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool test2() {
|
||||
bool test2()
|
||||
{
|
||||
const char *inputs[] = {
|
||||
"CQ LL3JG",
|
||||
"CQ LL3JG KO26",
|
||||
|
@ -336,14 +368,15 @@ bool test2() {
|
|||
"L0UAA LL3JG +02",
|
||||
"L0UAA LL3JG RRR",
|
||||
"L0UAA LL3JG 73",
|
||||
0
|
||||
};
|
||||
0};
|
||||
|
||||
for (int i = 0; inputs[i]; ++i) {
|
||||
for (int i = 0; inputs[i]; ++i)
|
||||
{
|
||||
uint8_t result[10];
|
||||
int rc = ft8_v2::pack77_1(inputs[i], result);
|
||||
printf("pack77_1(\"%s\") = %d\t[", inputs[i], rc);
|
||||
for (int j = 0; j < 10; ++j) {
|
||||
for (int j = 0; j < 10; ++j)
|
||||
{
|
||||
printf("%02x ", result[j]);
|
||||
}
|
||||
printf("]\n");
|
||||
|
@ -352,7 +385,8 @@ bool test2() {
|
|||
return true;
|
||||
}
|
||||
|
||||
int main() {
|
||||
int main()
|
||||
{
|
||||
test1();
|
||||
test2();
|
||||
return 0;
|
||||
|
|
15
ft8/pack.h
15
ft8/pack.h
|
@ -1,12 +1,11 @@
|
|||
#pragma once
|
||||
#ifndef _INCLUDE_PACK_H_
|
||||
#define _INCLUDE_PACK_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
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);
|
||||
|
||||
// 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);
|
||||
|
||||
}
|
||||
#endif // _INCLUDE_PACK_H_
|
||||
|
|
203
ft8/text.cpp
203
ft8/text.cpp
|
@ -2,80 +2,94 @@
|
|||
|
||||
#include <string.h>
|
||||
|
||||
namespace ft8 {
|
||||
|
||||
const char * trim_front(const char *str) {
|
||||
const char *trim_front(const char *str)
|
||||
{
|
||||
// Skip leading whitespace
|
||||
while (*str == ' ') {
|
||||
while (*str == ' ')
|
||||
{
|
||||
str++;
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
void trim_back(char *str) {
|
||||
void trim_back(char *str)
|
||||
{
|
||||
// Skip trailing whitespace by replacing it with '\0' characters
|
||||
int idx = strlen(str) - 1;
|
||||
while (idx >= 0 && str[idx] == ' ') {
|
||||
while (idx >= 0 && str[idx] == ' ')
|
||||
{
|
||||
str[idx--] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
// 1) trims a string from the back by changing whitespaces to '\0'
|
||||
// 2) trims a string from the front by skipping whitespaces
|
||||
char * trim(char *str) {
|
||||
char *trim(char *str)
|
||||
{
|
||||
str = (char *)trim_front(str);
|
||||
trim_back(str);
|
||||
// return a pointer to the first non-whitespace character
|
||||
return str;
|
||||
return str;
|
||||
}
|
||||
|
||||
char to_upper(char c) {
|
||||
char to_upper(char c)
|
||||
{
|
||||
return (c >= 'a' && c <= 'z') ? (c - 'a' + 'A') : c;
|
||||
}
|
||||
|
||||
bool is_digit(char c) {
|
||||
bool is_digit(char c)
|
||||
{
|
||||
return (c >= '0') && (c <= '9');
|
||||
}
|
||||
|
||||
bool is_letter(char c) {
|
||||
bool is_letter(char c)
|
||||
{
|
||||
return ((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z'));
|
||||
}
|
||||
|
||||
bool is_space(char c) {
|
||||
bool is_space(char c)
|
||||
{
|
||||
return (c == ' ');
|
||||
}
|
||||
|
||||
bool in_range(char c, char min, char max) {
|
||||
bool in_range(char c, char min, char max)
|
||||
{
|
||||
return (c >= min) && (c <= max);
|
||||
}
|
||||
|
||||
bool starts_with(const char *string, const char *prefix) {
|
||||
bool starts_with(const char *string, const char *prefix)
|
||||
{
|
||||
return 0 == memcmp(string, prefix, strlen(prefix));
|
||||
}
|
||||
|
||||
bool equals(const char *string1, const char *string2) {
|
||||
bool equals(const char *string1, const char *string2)
|
||||
{
|
||||
return 0 == strcmp(string1, string2);
|
||||
}
|
||||
|
||||
|
||||
int char_index(const char *string, char c) {
|
||||
for (int i = 0; *string; ++i, ++string) {
|
||||
if (c == *string) {
|
||||
int char_index(const char *string, char c)
|
||||
{
|
||||
for (int i = 0; *string; ++i, ++string)
|
||||
{
|
||||
if (c == *string)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1; // Not found
|
||||
return -1; // Not found
|
||||
}
|
||||
|
||||
|
||||
// Text message formatting:
|
||||
// Text message formatting:
|
||||
// - replaces lowercase letters with uppercase
|
||||
// - merges consecutive spaces into single space
|
||||
void fmtmsg(char *msg_out, const char *msg_in) {
|
||||
void fmtmsg(char *msg_out, const char *msg_in)
|
||||
{
|
||||
char c;
|
||||
char last_out = 0;
|
||||
while ( (c = *msg_in) ) {
|
||||
if (c != ' ' || last_out != ' ') {
|
||||
while ((c = *msg_in))
|
||||
{
|
||||
if (c != ' ' || last_out != ' ')
|
||||
{
|
||||
last_out = to_upper(c);
|
||||
*msg_out = last_out;
|
||||
++msg_out;
|
||||
|
@ -85,24 +99,29 @@ void fmtmsg(char *msg_out, const char *msg_in) {
|
|||
*msg_out = 0; // Add zero termination
|
||||
}
|
||||
|
||||
|
||||
// Parse a 2 digit integer from string
|
||||
int dd_to_int(const char *str, int length) {
|
||||
int dd_to_int(const char *str, int length)
|
||||
{
|
||||
int result = 0;
|
||||
bool negative;
|
||||
int i;
|
||||
if (str[0] == '-') {
|
||||
if (str[0] == '-')
|
||||
{
|
||||
negative = true;
|
||||
i = 1; // Consume the - sign
|
||||
i = 1; // Consume the - sign
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
negative = false;
|
||||
i = (str[0] == '+') ? 1 : 0; // Consume a + sign if found
|
||||
i = (str[0] == '+') ? 1 : 0; // Consume a + sign if found
|
||||
}
|
||||
|
||||
while (i < length) {
|
||||
if (str[i] == 0) break;
|
||||
if (!is_digit(str[i])) break;
|
||||
|
||||
while (i < length)
|
||||
{
|
||||
if (str[i] == 0)
|
||||
break;
|
||||
if (!is_digit(str[i]))
|
||||
break;
|
||||
result *= 10;
|
||||
result += (str[i] - '0');
|
||||
++i;
|
||||
|
@ -111,25 +130,29 @@ int dd_to_int(const char *str, int length) {
|
|||
return negative ? -result : result;
|
||||
}
|
||||
|
||||
|
||||
// Convert a 2 digit integer to string
|
||||
void int_to_dd(char *str, int value, int width, bool full_sign) {
|
||||
if (value < 0) {
|
||||
void int_to_dd(char *str, int value, int width, bool full_sign)
|
||||
{
|
||||
if (value < 0)
|
||||
{
|
||||
*str = '-';
|
||||
++str;
|
||||
value = -value;
|
||||
}
|
||||
else if (full_sign) {
|
||||
else if (full_sign)
|
||||
{
|
||||
*str = '+';
|
||||
++str;
|
||||
}
|
||||
|
||||
int divisor = 1;
|
||||
for (int i = 0; i < width - 1; ++i) {
|
||||
for (int i = 0; i < width - 1; ++i)
|
||||
{
|
||||
divisor *= 10;
|
||||
}
|
||||
|
||||
while (divisor >= 1) {
|
||||
while (divisor >= 1)
|
||||
{
|
||||
int digit = value / divisor;
|
||||
|
||||
*str = '0' + digit;
|
||||
|
@ -138,7 +161,7 @@ void int_to_dd(char *str, int value, int width, bool full_sign) {
|
|||
value -= digit * divisor;
|
||||
divisor /= 10;
|
||||
}
|
||||
*str = 0; // Add zero terminator
|
||||
*str = 0; // Add zero terminator
|
||||
}
|
||||
|
||||
// convert integer index to ASCII character according to one of 6 tables:
|
||||
|
@ -148,59 +171,83 @@ void int_to_dd(char *str, int value, int width, bool full_sign) {
|
|||
// table 3: "0123456789"
|
||||
// table 4: " ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
// table 5: " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ/"
|
||||
char charn(int c, int table_idx) {
|
||||
if (table_idx != 2 && table_idx != 3) {
|
||||
if (c == 0) return ' ';
|
||||
char charn(int c, int table_idx)
|
||||
{
|
||||
if (table_idx != 2 && table_idx != 3)
|
||||
{
|
||||
if (c == 0)
|
||||
return ' ';
|
||||
c -= 1;
|
||||
}
|
||||
if (table_idx != 4) {
|
||||
if (c < 10) return '0' + c;
|
||||
if (table_idx != 4)
|
||||
{
|
||||
if (c < 10)
|
||||
return '0' + c;
|
||||
c -= 10;
|
||||
}
|
||||
if (table_idx != 3) {
|
||||
if (c < 26) return 'A' + c;
|
||||
if (table_idx != 3)
|
||||
{
|
||||
if (c < 26)
|
||||
return 'A' + c;
|
||||
c -= 26;
|
||||
}
|
||||
|
||||
if (table_idx == 0) {
|
||||
if (c < 5) return "+-./?" [c];
|
||||
|
||||
if (table_idx == 0)
|
||||
{
|
||||
if (c < 5)
|
||||
return "+-./?"[c];
|
||||
}
|
||||
else if (table_idx == 5) {
|
||||
if (c == 0) return '/';
|
||||
else if (table_idx == 5)
|
||||
{
|
||||
if (c == 0)
|
||||
return '/';
|
||||
}
|
||||
|
||||
return '_'; // unknown character, should never get here
|
||||
}
|
||||
|
||||
// Convert character to its index (charn in reverse) according to a table
|
||||
int nchar(char c, int table_idx) {
|
||||
int nchar(char c, int table_idx)
|
||||
{
|
||||
int n = 0;
|
||||
if (table_idx != 2 && table_idx != 3) {
|
||||
if (c == ' ') return n + 0;
|
||||
if (table_idx != 2 && table_idx != 3)
|
||||
{
|
||||
if (c == ' ')
|
||||
return n + 0;
|
||||
n += 1;
|
||||
}
|
||||
if (table_idx != 4) {
|
||||
if (c >= '0' && c <= '9') return n + (c - '0');
|
||||
if (table_idx != 4)
|
||||
{
|
||||
if (c >= '0' && c <= '9')
|
||||
return n + (c - '0');
|
||||
n += 10;
|
||||
}
|
||||
if (table_idx != 3) {
|
||||
if (c >= 'A' && c <= 'Z') return n + (c - 'A');
|
||||
if (table_idx != 3)
|
||||
{
|
||||
if (c >= 'A' && c <= 'Z')
|
||||
return n + (c - 'A');
|
||||
n += 26;
|
||||
}
|
||||
|
||||
if (table_idx == 0) {
|
||||
if (c == '+') return n + 0;
|
||||
if (c == '-') return n + 1;
|
||||
if (c == '.') return n + 2;
|
||||
if (c == '/') return n + 3;
|
||||
if (c == '?') return n + 4;
|
||||
}
|
||||
else if (table_idx == 5) {
|
||||
if (c == '/') return n + 0;
|
||||
}
|
||||
|
||||
// Character not found
|
||||
return -1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
if (table_idx == 0)
|
||||
{
|
||||
if (c == '+')
|
||||
return n + 0;
|
||||
if (c == '-')
|
||||
return n + 1;
|
||||
if (c == '.')
|
||||
return n + 2;
|
||||
if (c == '/')
|
||||
return n + 3;
|
||||
if (c == '?')
|
||||
return n + 4;
|
||||
}
|
||||
else if (table_idx == 5)
|
||||
{
|
||||
if (c == '/')
|
||||
return n + 0;
|
||||
}
|
||||
|
||||
// Character not found
|
||||
return -1;
|
||||
}
|
||||
|
|
54
ft8/text.h
54
ft8/text.h
|
@ -1,33 +1,37 @@
|
|||
#pragma once
|
||||
#ifndef _INCLUDE_TEXT_H_
|
||||
#define _INCLUDE_TEXT_H_
|
||||
|
||||
namespace ft8 {
|
||||
// Utility functions for characters and strings
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
const char * trim_front(const char *str);
|
||||
void trim_back(char *str);
|
||||
char * trim(char *str);
|
||||
// Utility functions for characters and strings
|
||||
|
||||
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);
|
||||
const char *trim_front(const char *str);
|
||||
void trim_back(char *str);
|
||||
char *trim(char *str);
|
||||
|
||||
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);
|
||||
|
||||
char charn(int c, int table_idx);
|
||||
int nchar(char c, int table_idx);
|
||||
}
|
||||
// Convert a 2 digit integer to string
|
||||
void int_to_dd(char *str, int value, int width, bool full_sign = false);
|
||||
|
||||
char charn(int c, int table_idx);
|
||||
int nchar(char c, int table_idx);
|
||||
|
||||
#endif // _INCLUDE_TEXT_H_
|
||||
|
|
270
ft8/unpack.cpp
270
ft8/unpack.cpp
|
@ -3,53 +3,61 @@
|
|||
|
||||
#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;
|
||||
const uint16_t MAXGRID4 = 32400L;
|
||||
|
||||
//#define NBASE (uint32_t)(37L*36L*10L*27L*27L*27L)
|
||||
#define MAX22 ((uint32_t)4194304L)
|
||||
#define NTOKENS ((uint32_t)2063592L)
|
||||
#define MAXGRID4 ((uint16_t)32400L)
|
||||
|
||||
// n28 is a 28-bit integer, e.g. n28a or n28b, containing all the
|
||||
// call sign bits from a packed message.
|
||||
int unpack28(uint32_t n28, uint8_t ip, uint8_t i3, char *result) {
|
||||
int unpack28(uint32_t n28, uint8_t ip, uint8_t i3, char *result)
|
||||
{
|
||||
// Check for special tokens DE, QRZ, CQ, CQ_nnn, CQ_aaaa
|
||||
if (n28 < NTOKENS) {
|
||||
if (n28 <= 2) {
|
||||
if (n28 == 0) strcpy(result, "DE");
|
||||
if (n28 == 1) strcpy(result, "QRZ");
|
||||
if (n28 == 2) strcpy(result, "CQ");
|
||||
return 0; // Success
|
||||
if (n28 < NTOKENS)
|
||||
{
|
||||
if (n28 <= 2)
|
||||
{
|
||||
if (n28 == 0)
|
||||
strcpy(result, "DE");
|
||||
if (n28 == 1)
|
||||
strcpy(result, "QRZ");
|
||||
if (n28 == 2)
|
||||
strcpy(result, "CQ");
|
||||
return 0; // Success
|
||||
}
|
||||
if (n28 <= 1002) {
|
||||
if (n28 <= 1002)
|
||||
{
|
||||
// CQ_nnn with 3 digits
|
||||
strcpy(result, "CQ ");
|
||||
int_to_dd(result + 3, n28 - 3, 3);
|
||||
return 0; // Success
|
||||
return 0; // Success
|
||||
}
|
||||
if (n28 <= 532443L) {
|
||||
if (n28 <= 532443L)
|
||||
{
|
||||
// CQ_aaaa with 4 alphanumeric symbols
|
||||
uint32_t n = n28 - 1003;
|
||||
char aaaa[5];
|
||||
|
||||
aaaa[4] = '\0';
|
||||
for (int i = 3; /* */; --i) {
|
||||
for (int i = 3; /* */; --i)
|
||||
{
|
||||
aaaa[i] = charn(n % 27, 4);
|
||||
if (i == 0) break;
|
||||
if (i == 0)
|
||||
break;
|
||||
n /= 27;
|
||||
}
|
||||
|
||||
strcpy(result, "CQ ");
|
||||
strcat(result, trim_front(aaaa));
|
||||
return 0; // Success
|
||||
return 0; // Success
|
||||
}
|
||||
// ? TODO: unspecified in the WSJT-X code
|
||||
return -1;
|
||||
}
|
||||
|
||||
n28 = n28 - NTOKENS;
|
||||
if (n28 < MAX22) {
|
||||
if (n28 < MAX22)
|
||||
{
|
||||
// This is a 22-bit hash of a result
|
||||
//call hash22(n22,c13) !Retrieve result from hash table
|
||||
// TODO: implement
|
||||
|
@ -80,49 +88,55 @@ int unpack28(uint32_t n28, uint8_t ip, uint8_t i3, char *result) {
|
|||
|
||||
// Skip trailing and leading whitespace in case of a short callsign
|
||||
strcpy(result, trim(callsign));
|
||||
if (strlen(result) == 0) return -1;
|
||||
if (strlen(result) == 0)
|
||||
return -1;
|
||||
|
||||
// Check if we should append /R or /P suffix
|
||||
if (ip) {
|
||||
if (i3 == 1) {
|
||||
if (ip)
|
||||
{
|
||||
if (i3 == 1)
|
||||
{
|
||||
strcat(result, "/R");
|
||||
}
|
||||
else if (i3 == 2) {
|
||||
else if (i3 == 2)
|
||||
{
|
||||
strcat(result, "/P");
|
||||
}
|
||||
}
|
||||
|
||||
return 0; // Success
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
|
||||
int unpack_type1(const uint8_t *a77, uint8_t i3, char *field1, char *field2, char *field3) {
|
||||
int unpack_type1(const uint8_t *a77, uint8_t i3, char *field1, char *field2, char *field3)
|
||||
{
|
||||
uint32_t n28a, n28b;
|
||||
uint16_t igrid4;
|
||||
uint8_t ir;
|
||||
|
||||
uint8_t ir;
|
||||
|
||||
// Extract packed fields
|
||||
// read(c77,1000) n28a,ipa,n28b,ipb,ir,igrid4,i3
|
||||
// 1000 format(2(b28,b1),b1,b15,b3)
|
||||
n28a = (a77[0] << 21);
|
||||
n28a = (a77[0] << 21);
|
||||
n28a |= (a77[1] << 13);
|
||||
n28a |= (a77[2] << 5);
|
||||
n28a |= (a77[3] >> 3);
|
||||
n28b = ((a77[3] & 0x07) << 26);
|
||||
n28b = ((a77[3] & 0x07) << 26);
|
||||
n28b |= (a77[4] << 18);
|
||||
n28b |= (a77[5] << 10);
|
||||
n28b |= (a77[6] << 2);
|
||||
n28b |= (a77[7] >> 6);
|
||||
ir = ((a77[7] & 0x20) >> 5);
|
||||
igrid4 = ((a77[7] & 0x1F) << 10);
|
||||
ir = ((a77[7] & 0x20) >> 5);
|
||||
igrid4 = ((a77[7] & 0x1F) << 10);
|
||||
igrid4 |= (a77[8] << 2);
|
||||
igrid4 |= (a77[9] >> 6);
|
||||
|
||||
// Unpack both callsigns
|
||||
if (unpack28(n28a >> 1, n28a & 0x01, i3, field1) < 0) {
|
||||
if (unpack28(n28a >> 1, n28a & 0x01, i3, field1) < 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (unpack28(n28b >> 1, n28b & 0x01, i3, field2) < 0) {
|
||||
if (unpack28(n28b >> 1, n28b & 0x01, i3, field2) < 0)
|
||||
{
|
||||
return -2;
|
||||
}
|
||||
// Fix "CQ_" to "CQ " -> already done in unpack28()
|
||||
|
@ -135,11 +149,13 @@ int unpack_type1(const uint8_t *a77, uint8_t i3, char *field1, char *field2, cha
|
|||
// save_hash_call(field2)
|
||||
// }
|
||||
|
||||
if (igrid4 <= MAXGRID4) {
|
||||
if (igrid4 <= MAXGRID4)
|
||||
{
|
||||
// Extract 4 symbol grid locator
|
||||
char *dst = field3;
|
||||
uint16_t n = igrid4;
|
||||
if (ir > 0) {
|
||||
if (ir > 0)
|
||||
{
|
||||
// In case of ir=1 add an "R" before grid
|
||||
dst = stpcpy(dst, "R ");
|
||||
}
|
||||
|
@ -155,19 +171,26 @@ int unpack_type1(const uint8_t *a77, uint8_t i3, char *field1, char *field2, cha
|
|||
// if(msg(1:3).eq.'CQ ' .and. ir.eq.1) unpk77_success=.false.
|
||||
// if (ir > 0 && strncmp(field1, "CQ", 2) == 0) return -1;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// Extract report
|
||||
int irpt = igrid4 - MAXGRID4;
|
||||
|
||||
// Check special cases first
|
||||
if (irpt == 1) field3[0] = '\0';
|
||||
else if (irpt == 2) strcpy(field3, "RRR");
|
||||
else if (irpt == 3) strcpy(field3, "RR73");
|
||||
else if (irpt == 4) strcpy(field3, "73");
|
||||
else if (irpt >= 5) {
|
||||
if (irpt == 1)
|
||||
field3[0] = '\0';
|
||||
else if (irpt == 2)
|
||||
strcpy(field3, "RRR");
|
||||
else if (irpt == 3)
|
||||
strcpy(field3, "RR73");
|
||||
else if (irpt == 4)
|
||||
strcpy(field3, "73");
|
||||
else if (irpt >= 5)
|
||||
{
|
||||
char *dst = field3;
|
||||
// Extract signal report as a two digit number with a + or - sign
|
||||
if (ir > 0) {
|
||||
if (ir > 0)
|
||||
{
|
||||
*dst++ = 'R'; // Add "R" before report
|
||||
}
|
||||
int_to_dd(dst, irpt - 35, 2, true);
|
||||
|
@ -176,50 +199,55 @@ int unpack_type1(const uint8_t *a77, uint8_t i3, char *field1, char *field2, cha
|
|||
// if (irpt >= 2 && strncmp(field1, "CQ", 2) == 0) return -1;
|
||||
}
|
||||
|
||||
return 0; // Success
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
|
||||
int unpack_text(const uint8_t *a71, char *text) {
|
||||
int unpack_text(const uint8_t *a71, char *text)
|
||||
{
|
||||
// TODO: test
|
||||
uint8_t b71[9];
|
||||
|
||||
uint8_t carry = 0;
|
||||
for (int i = 0; i < 9; ++i) {
|
||||
for (int i = 0; i < 9; ++i)
|
||||
{
|
||||
b71[i] = carry | (a71[i] >> 1);
|
||||
carry = (a71[i] & 1) ? 0x80 : 0;
|
||||
}
|
||||
|
||||
char c14[14];
|
||||
c14[13] = 0;
|
||||
for (int idx = 12; idx >= 0; --idx) {
|
||||
char c14[14];
|
||||
c14[13] = 0;
|
||||
for (int idx = 12; idx >= 0; --idx)
|
||||
{
|
||||
// Divide the long integer in b71 by 42
|
||||
uint16_t rem = 0;
|
||||
for (int i = 0; i < 9; ++i) {
|
||||
for (int i = 0; i < 9; ++i)
|
||||
{
|
||||
rem = (rem << 8) | b71[i];
|
||||
b71[i] = rem / 42;
|
||||
rem = rem % 42;
|
||||
rem = rem % 42;
|
||||
}
|
||||
c14[idx] = charn(rem, 0);
|
||||
}
|
||||
|
||||
strcpy(text, trim(c14));
|
||||
return 0; // Success
|
||||
strcpy(text, trim(c14));
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
|
||||
int unpack_telemetry(const uint8_t *a71, char *telemetry) {
|
||||
int unpack_telemetry(const uint8_t *a71, char *telemetry)
|
||||
{
|
||||
uint8_t b71[9];
|
||||
|
||||
// Shift bits in a71 right by 1
|
||||
uint8_t carry = 0;
|
||||
for (int i = 0; i < 9; ++i) {
|
||||
for (int i = 0; i < 9; ++i)
|
||||
{
|
||||
b71[i] = (carry << 7) | (a71[i] >> 1);
|
||||
carry = (a71[i] & 0x01);
|
||||
}
|
||||
|
||||
// Convert b71 to hexadecimal string
|
||||
for (int i = 0; i < 9; ++i) {
|
||||
for (int i = 0; i < 9; ++i)
|
||||
{
|
||||
uint8_t nibble1 = (b71[i] >> 4);
|
||||
uint8_t nibble2 = (b71[i] & 0x0F);
|
||||
char c1 = (nibble1 > 9) ? (nibble1 - 10 + 'A') : nibble1 + '0';
|
||||
|
@ -232,45 +260,46 @@ int unpack_telemetry(const uint8_t *a71, char *telemetry) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
|
||||
//none standard for wsjt-x 2.0
|
||||
//by KD8CEC
|
||||
int unpack_nonstandard(const uint8_t *a77, char *field1, char *field2, char *field3)
|
||||
int unpack_nonstandard(const uint8_t *a77, char *field1, char *field2, char *field3)
|
||||
{
|
||||
/*
|
||||
/*
|
||||
wsjt-x 2.1.0 rc5
|
||||
read(c77,1050) n12,n58,iflip,nrpt,icq
|
||||
1050 format(b12,b58,b1,b2,b1)
|
||||
*/
|
||||
uint32_t n12, iflip, nrpt, icq;
|
||||
uint64_t n58;
|
||||
n12 = (a77[0] << 4); //11 ~4 : 8
|
||||
n12 |= (a77[1] >> 4); //3~0 : 12
|
||||
uint32_t n12, iflip, nrpt, icq;
|
||||
uint64_t n58;
|
||||
n12 = (a77[0] << 4); //11 ~4 : 8
|
||||
n12 |= (a77[1] >> 4); //3~0 : 12
|
||||
|
||||
n58 = ((uint64_t)(a77[1] & 0x0F) << 54); //57 ~ 54 : 4
|
||||
n58 |= ((uint64_t)a77[2] << 46); //53 ~ 46 : 12
|
||||
n58 |= ((uint64_t)a77[3] << 38); //45 ~ 38 : 12
|
||||
n58 |= ((uint64_t)a77[4] << 30); //37 ~ 30 : 12
|
||||
n58 |= ((uint64_t)a77[5] << 22); //29 ~ 22 : 12
|
||||
n58 |= ((uint64_t)a77[6] << 14); //21 ~ 14 : 12
|
||||
n58 |= ((uint64_t)a77[7] << 6); //13 ~ 6 : 12
|
||||
n58 |= ((uint64_t)a77[8] >> 2); //5 ~ 0 : 765432 10
|
||||
n58 = ((uint64_t)(a77[1] & 0x0F) << 54); //57 ~ 54 : 4
|
||||
n58 |= ((uint64_t)a77[2] << 46); //53 ~ 46 : 12
|
||||
n58 |= ((uint64_t)a77[3] << 38); //45 ~ 38 : 12
|
||||
n58 |= ((uint64_t)a77[4] << 30); //37 ~ 30 : 12
|
||||
n58 |= ((uint64_t)a77[5] << 22); //29 ~ 22 : 12
|
||||
n58 |= ((uint64_t)a77[6] << 14); //21 ~ 14 : 12
|
||||
n58 |= ((uint64_t)a77[7] << 6); //13 ~ 6 : 12
|
||||
n58 |= ((uint64_t)a77[8] >> 2); //5 ~ 0 : 765432 10
|
||||
|
||||
iflip = (a77[8] >> 1) & 0x01; //76543210
|
||||
nrpt = ((a77[8] & 0x01) << 1);
|
||||
nrpt |= (a77[9] >> 7); //76543210
|
||||
icq = ((a77[9] >> 6) & 0x01);
|
||||
iflip = (a77[8] >> 1) & 0x01; //76543210
|
||||
nrpt = ((a77[8] & 0x01) << 1);
|
||||
nrpt |= (a77[9] >> 7); //76543210
|
||||
icq = ((a77[9] >> 6) & 0x01);
|
||||
|
||||
char c11[12];
|
||||
c11[11] = '\0';
|
||||
char c11[12];
|
||||
c11[11] = '\0';
|
||||
|
||||
for (int i = 10; /* no condition */ ; --i) {
|
||||
c11[i] = charn(n58 % 38, 5);
|
||||
if (i == 0) break;
|
||||
n58 /= 38;
|
||||
for (int i = 10; /* no condition */; --i)
|
||||
{
|
||||
c11[i] = charn(n58 % 38, 5);
|
||||
if (i == 0)
|
||||
break;
|
||||
n58 /= 38;
|
||||
}
|
||||
|
||||
char call_3[15];
|
||||
char call_3[15];
|
||||
// should replace with hash12(n12, call_3);
|
||||
// strcpy(call_3, "<...>");
|
||||
call_3[0] = '<';
|
||||
|
@ -278,32 +307,37 @@ int unpack_nonstandard(const uint8_t *a77, char *field1, char *field2, char *fie
|
|||
call_3[5] = '>';
|
||||
call_3[6] = '\0';
|
||||
|
||||
char * call_1 = (iflip) ? c11 : call_3;
|
||||
char * call_2 = (iflip) ? call_3 : c11;
|
||||
//save_hash_call(c11_trimmed);
|
||||
char *call_1 = (iflip) ? c11 : call_3;
|
||||
char *call_2 = (iflip) ? call_3 : c11;
|
||||
//save_hash_call(c11_trimmed);
|
||||
|
||||
if (icq == 0) {
|
||||
strcpy(field1, trim(call_1));
|
||||
if (nrpt == 1)
|
||||
strcpy(field3, "RRR");
|
||||
else if (nrpt == 2)
|
||||
strcpy(field3, "RR73");
|
||||
else if (nrpt == 3)
|
||||
strcpy(field3, "73");
|
||||
else {
|
||||
if (icq == 0)
|
||||
{
|
||||
strcpy(field1, trim(call_1));
|
||||
if (nrpt == 1)
|
||||
strcpy(field3, "RRR");
|
||||
else if (nrpt == 2)
|
||||
strcpy(field3, "RR73");
|
||||
else if (nrpt == 3)
|
||||
strcpy(field3, "73");
|
||||
else
|
||||
{
|
||||
field3[0] = '\0';
|
||||
}
|
||||
} else {
|
||||
strcpy(field1, "CQ");
|
||||
}
|
||||
else
|
||||
{
|
||||
strcpy(field1, "CQ");
|
||||
field3[0] = '\0';
|
||||
}
|
||||
}
|
||||
strcpy(field2, trim(call_2));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int unpack77_fields(const uint8_t *a77, char *field1, char *field2, char *field3) {
|
||||
uint8_t n3, i3;
|
||||
int unpack77_fields(const uint8_t *a77, char *field1, char *field2, char *field3)
|
||||
{
|
||||
uint8_t n3, i3;
|
||||
|
||||
// Extract n3 (bits 71..73) and i3 (bits 74..76)
|
||||
n3 = ((a77[8] << 2) & 0x04) | ((a77[9] >> 6) & 0x03);
|
||||
|
@ -311,7 +345,8 @@ int unpack77_fields(const uint8_t *a77, char *field1, char *field2, char *field3
|
|||
|
||||
field1[0] = field2[0] = field3[0] = '\0';
|
||||
|
||||
if (i3 == 0 && n3 == 0) {
|
||||
if (i3 == 0 && n3 == 0)
|
||||
{
|
||||
// 0.0 Free text
|
||||
return unpack_text(a77, field1);
|
||||
}
|
||||
|
@ -325,22 +360,25 @@ int unpack77_fields(const uint8_t *a77, char *field1, char *field2, char *field3
|
|||
// // 0.3 WA9XYZ KA1ABC R 16A EMA 28 28 1 4 3 7 71 ARRL Field Day
|
||||
// // 0.4 WA9XYZ KA1ABC R 32A EMA 28 28 1 4 3 7 71 ARRL Field Day
|
||||
// }
|
||||
else if (i3 == 0 && n3 == 5) {
|
||||
else if (i3 == 0 && n3 == 5)
|
||||
{
|
||||
// 0.5 0123456789abcdef01 71 71 Telemetry (18 hex)
|
||||
return unpack_telemetry(a77, field1);
|
||||
}
|
||||
else if (i3 == 1 || i3 == 2) {
|
||||
else if (i3 == 1 || i3 == 2)
|
||||
{
|
||||
// Type 1 (standard message) or Type 2 ("/P" form for EU VHF contest)
|
||||
return unpack_type1(a77, i3, field1, field2, field3);
|
||||
}
|
||||
// else if (i3 == 3) {
|
||||
// // Type 3: ARRL RTTY Contest
|
||||
// }
|
||||
else if (i3 == 4) {
|
||||
// // Type 4: Nonstandard calls, e.g. <WA9XYZ> PJ4/KA1ABC RR73
|
||||
// // One hashed call or "CQ"; one compound or nonstandard call with up
|
||||
// // to 11 characters; and (if not "CQ") an optional RRR, RR73, or 73.
|
||||
return unpack_nonstandard(a77, field1, field2, field3);
|
||||
else if (i3 == 4)
|
||||
{
|
||||
// // Type 4: Nonstandard calls, e.g. <WA9XYZ> PJ4/KA1ABC RR73
|
||||
// // One hashed call or "CQ"; one compound or nonstandard call with up
|
||||
// // to 11 characters; and (if not "CQ") an optional RRR, RR73, or 73.
|
||||
return unpack_nonstandard(a77, field1, field2, field3);
|
||||
}
|
||||
// else if (i3 == 5) {
|
||||
// // Type 5: TU; W9XYZ K1ABC R-09 FN 1 28 28 1 7 9 74 WWROF contest
|
||||
|
@ -350,12 +388,14 @@ int unpack77_fields(const uint8_t *a77, char *field1, char *field2, char *field3
|
|||
return -1;
|
||||
}
|
||||
|
||||
int unpack77(const uint8_t *a77, char *message) {
|
||||
int unpack77(const uint8_t *a77, char *message)
|
||||
{
|
||||
char field1[14];
|
||||
char field2[14];
|
||||
char field3[7];
|
||||
int rc = unpack77_fields(a77, field1, field2, field3);
|
||||
if (rc < 0) return rc;
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
char *dst = message;
|
||||
// int msg_sz = strlen(field1) + strlen(field2) + strlen(field3) + 2;
|
||||
|
@ -369,5 +409,3 @@ int unpack77(const uint8_t *a77, char *message) {
|
|||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
19
ft8/unpack.h
19
ft8/unpack.h
|
@ -1,15 +1,14 @@
|
|||
#pragma once
|
||||
#ifndef _INCLUDE_UNPACK_H_
|
||||
#define _INCLUDE_UNPACK_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace ft8 {
|
||||
// field1 - at least 14 bytes
|
||||
// field2 - at least 14 bytes
|
||||
// field3 - at least 7 bytes
|
||||
int unpack77_fields(const uint8_t *a77, char *field1, char *field2, char *field3);
|
||||
|
||||
// field1 - at least 14 bytes
|
||||
// field2 - at least 14 bytes
|
||||
// field3 - at least 7 bytes
|
||||
int unpack77_fields(const uint8_t *a77, char *field1, char *field2, char *field3);
|
||||
|
||||
// message should have at least 35 bytes allocated (34 characters + zero terminator)
|
||||
int unpack77(const uint8_t *a77, char *message);
|
||||
// message should have at least 35 bytes allocated (34 characters + zero terminator)
|
||||
int unpack77(const uint8_t *a77, char *message);
|
||||
|
||||
}
|
||||
#endif // _INCLUDE_UNPACK_H_
|
||||
|
|
100
gen_ft8.cpp
100
gen_ft8.cpp
|
@ -11,84 +11,94 @@
|
|||
#include "ft8/encode.h"
|
||||
#include "ft8/constants.h"
|
||||
|
||||
#define LOG_LEVEL LOG_INFO
|
||||
#define LOG_LEVEL LOG_INFO
|
||||
|
||||
void gfsk_pulse(int n_spsym, float b, float *pulse) {
|
||||
void gfsk_pulse(int n_spsym, float b, float *pulse)
|
||||
{
|
||||
const float c = M_PI * sqrtf(2 / logf(2));
|
||||
|
||||
for (int i = 0; i < 3*n_spsym; ++i) {
|
||||
float t = i/(float)n_spsym - 1.5f;
|
||||
for (int i = 0; i < 3 * n_spsym; ++i)
|
||||
{
|
||||
float t = i / (float)n_spsym - 1.5f;
|
||||
pulse[i] = (erff(c * b * (t + 0.5f)) - erff(c * b * (t - 0.5f))) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Same as synth_fsk, but uses GFSK phase shaping
|
||||
void synth_gfsk(const uint8_t *symbols, int n_sym, float f0, int n_spsym, int signal_rate, float *signal)
|
||||
void synth_gfsk(const uint8_t *symbols, int n_sym, float f0, int n_spsym, int signal_rate, float *signal)
|
||||
{
|
||||
LOG(LOG_DEBUG, "n_spsym = %d\n", n_spsym);
|
||||
int n_wave = n_sym * n_spsym;
|
||||
float hmod = 1.0f;
|
||||
|
||||
// Compute the smoothed frequency waveform.
|
||||
// Length = (nsym+2)*nsps samples, first and last symbols extended
|
||||
// Length = (nsym+2)*nsps samples, first and last symbols extended
|
||||
float dphi_peak = 2 * M_PI * hmod / n_spsym;
|
||||
float dphi[n_wave + 2*n_spsym];
|
||||
float dphi[n_wave + 2 * n_spsym];
|
||||
|
||||
// Shift frequency up by f0
|
||||
for (int i = 0; i < n_wave + 2*n_spsym; ++i) {
|
||||
for (int i = 0; i < n_wave + 2 * n_spsym; ++i)
|
||||
{
|
||||
dphi[i] = 2 * M_PI * f0 / signal_rate;
|
||||
}
|
||||
|
||||
float pulse[3 * n_spsym];
|
||||
gfsk_pulse(n_spsym, 2.0f, pulse);
|
||||
|
||||
for (int i = 0; i < n_sym; ++i) {
|
||||
for (int i = 0; i < n_sym; ++i)
|
||||
{
|
||||
int ib = i * n_spsym;
|
||||
for (int j = 0; j < 3*n_spsym; ++j) {
|
||||
dphi[j + ib] += dphi_peak*symbols[i]*pulse[j];
|
||||
for (int j = 0; j < 3 * n_spsym; ++j)
|
||||
{
|
||||
dphi[j + ib] += dphi_peak * symbols[i] * pulse[j];
|
||||
}
|
||||
}
|
||||
|
||||
// Add dummy symbols at beginning and end with tone values equal to 1st and last symbol, respectively
|
||||
for (int j = 0; j < 2*n_spsym; ++j) {
|
||||
dphi[j] += dphi_peak*pulse[j + n_spsym]*symbols[0];
|
||||
dphi[j + n_sym * n_spsym] += dphi_peak*pulse[j]*symbols[n_sym - 1];
|
||||
for (int j = 0; j < 2 * n_spsym; ++j)
|
||||
{
|
||||
dphi[j] += dphi_peak * pulse[j + n_spsym] * symbols[0];
|
||||
dphi[j + n_sym * n_spsym] += dphi_peak * pulse[j] * symbols[n_sym - 1];
|
||||
}
|
||||
|
||||
// Calculate and insert the audio waveform
|
||||
float phi = 0;
|
||||
for (int k = 0; k < n_wave; ++k) { // Don't include dummy symbols
|
||||
for (int k = 0; k < n_wave; ++k)
|
||||
{ // Don't include dummy symbols
|
||||
signal[k] = sinf(phi);
|
||||
phi = fmodf(phi + dphi[k + n_spsym], 2*M_PI);
|
||||
phi = fmodf(phi + dphi[k + n_spsym], 2 * M_PI);
|
||||
}
|
||||
|
||||
// Apply envelope shaping to the first and last symbols
|
||||
int n_ramp = n_spsym / 8;
|
||||
for (int i = 0; i < n_ramp; ++i) {
|
||||
for (int i = 0; i < n_ramp; ++i)
|
||||
{
|
||||
float env = (1 - cosf(2 * M_PI * i / (2 * n_ramp))) / 2;
|
||||
signal[i] *= env;
|
||||
signal[n_wave - 1 - i] *= env;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Convert a sequence of symbols (tones) into a sinewave of continuous phase (FSK).
|
||||
// Symbol 0 gets encoded as a sine of frequency f0, the others are spaced in increasing
|
||||
// fashion.
|
||||
void synth_fsk(const uint8_t *symbols, int num_symbols, float f0, float spacing,
|
||||
float symbol_rate, float signal_rate, float *signal) {
|
||||
void synth_fsk(const uint8_t *symbols, int num_symbols, float f0, float spacing,
|
||||
float symbol_rate, float signal_rate, float *signal)
|
||||
{
|
||||
float phase = 0;
|
||||
float dt = 1/signal_rate;
|
||||
float dt_sym = 1/symbol_rate;
|
||||
float dt = 1 / signal_rate;
|
||||
float dt_sym = 1 / symbol_rate;
|
||||
float t = 0;
|
||||
int j = 0;
|
||||
int i = 0;
|
||||
while (j < num_symbols) {
|
||||
while (j < num_symbols)
|
||||
{
|
||||
float f = f0 + symbols[j] * spacing;
|
||||
phase = fmodf(phase + 2 * M_PI * f / signal_rate, 2 * M_PI);
|
||||
signal[i] = sinf(phase);
|
||||
t += dt;
|
||||
if (t >= dt_sym) {
|
||||
if (t >= dt_sym)
|
||||
{
|
||||
// Move to the next symbol
|
||||
t -= dt_sym;
|
||||
++j;
|
||||
|
@ -97,8 +107,8 @@ void synth_fsk(const uint8_t *symbols, int num_symbols, float f0, float spacing,
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void usage() {
|
||||
void usage()
|
||||
{
|
||||
printf("Generate a 15-second WAV file encoding a given message.\n");
|
||||
printf("Usage:\n");
|
||||
printf("\n");
|
||||
|
@ -107,10 +117,11 @@ void usage() {
|
|||
printf("(Note that you might have to enclose your message in quote marks if it contains spaces)\n");
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
// Expect two command-line arguments
|
||||
if (argc < 3) {
|
||||
if (argc < 3)
|
||||
{
|
||||
usage();
|
||||
return -1;
|
||||
}
|
||||
|
@ -118,33 +129,37 @@ int main(int argc, char **argv) {
|
|||
const char *message = argv[1];
|
||||
const char *wav_path = argv[2];
|
||||
float frequency = 1000.0;
|
||||
if (argc > 3) {
|
||||
frequency = atof(argv[3]);
|
||||
if (argc > 3)
|
||||
{
|
||||
frequency = atof(argv[3]);
|
||||
}
|
||||
|
||||
// First, pack the text data into binary message
|
||||
uint8_t packed[ft8::K_BYTES];
|
||||
uint8_t packed[FT8_K_BYTES];
|
||||
//int rc = packmsg(message, packed);
|
||||
int rc = ft8::pack77(message, packed);
|
||||
if (rc < 0) {
|
||||
int rc = pack77(message, packed);
|
||||
if (rc < 0)
|
||||
{
|
||||
printf("Cannot parse message!\n");
|
||||
printf("RC = %d\n", rc);
|
||||
return -2;
|
||||
}
|
||||
|
||||
printf("Packed data: ");
|
||||
for (int j = 0; j < 10; ++j) {
|
||||
for (int j = 0; j < 10; ++j)
|
||||
{
|
||||
printf("%02x ", packed[j]);
|
||||
}
|
||||
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::genft8(packed, tones);
|
||||
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");
|
||||
|
@ -152,15 +167,16 @@ 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++) {
|
||||
for (int i = 0; i < num_silence + num_samples + num_silence; i++)
|
||||
{
|
||||
signal[i] = 0;
|
||||
}
|
||||
|
||||
// synth_fsk(tones, ft8::NN, frequency, symbol_rate, symbol_rate, sample_rate, signal + num_silence);
|
||||
synth_gfsk(tones, ft8::NN, frequency, sample_rate / symbol_rate, sample_rate, signal + num_silence);
|
||||
// synth_fsk(tones, FT8_NN, frequency, symbol_rate, symbol_rate, sample_rate, signal + num_silence);
|
||||
synth_gfsk(tones, FT8_NN, frequency, sample_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
|
@ -14,29 +14,34 @@
|
|||
#include "fft/kiss_fftr.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
#define LOG_LEVEL LOG_INFO
|
||||
#define LOG_LEVEL LOG_INFO
|
||||
|
||||
|
||||
void convert_8bit_to_6bit(uint8_t *dst, const uint8_t *src, int nBits) {
|
||||
void convert_8bit_to_6bit(uint8_t *dst, const uint8_t *src, int nBits)
|
||||
{
|
||||
// Zero-fill the destination array as we will only be setting bits later
|
||||
for (int j = 0; j < (nBits + 5) / 6; ++j) {
|
||||
for (int j = 0; j < (nBits + 5) / 6; ++j)
|
||||
{
|
||||
dst[j] = 0;
|
||||
}
|
||||
|
||||
// Set the relevant bits
|
||||
uint8_t mask_src = (1 << 7);
|
||||
uint8_t mask_dst = (1 << 5);
|
||||
for (int i = 0, j = 0; nBits > 0; --nBits) {
|
||||
if (src[i] & mask_src) {
|
||||
for (int i = 0, j = 0; nBits > 0; --nBits)
|
||||
{
|
||||
if (src[i] & mask_src)
|
||||
{
|
||||
dst[j] |= mask_dst;
|
||||
}
|
||||
mask_src >>= 1;
|
||||
if (mask_src == 0) {
|
||||
if (mask_src == 0)
|
||||
{
|
||||
mask_src = (1 << 7);
|
||||
++i;
|
||||
}
|
||||
mask_dst >>= 1;
|
||||
if (mask_dst == 0) {
|
||||
if (mask_dst == 0)
|
||||
{
|
||||
mask_dst = (1 << 5);
|
||||
++j;
|
||||
}
|
||||
|
@ -97,53 +102,57 @@ void test3() {
|
|||
}
|
||||
*/
|
||||
|
||||
void test_tones(float *log174) {
|
||||
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];
|
||||
log174[3 * i] = (b3 & 4) ? +1.0 : -1.0;
|
||||
log174[3 * i] = (b3 & 4) ? +1.0 : -1.0;
|
||||
log174[3 * i + 1] = (b3 & 2) ? +1.0 : -1.0;
|
||||
log174[3 * i + 2] = (b3 & 1) ? +1.0 : -1.0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void test4() {
|
||||
void test4()
|
||||
{
|
||||
const int nfft = 128;
|
||||
const float fft_norm = 2.0 / nfft;
|
||||
|
||||
size_t fft_work_size;
|
||||
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);
|
||||
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) {
|
||||
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_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) {
|
||||
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();
|
||||
test4();
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue