kopia lustrzana https://github.com/kgoba/ft8_lib
Added prefixes to ft8/ft4/ftx functions and clang-formatted code
rodzic
550f3dc29c
commit
26decf92f7
|
@ -0,0 +1,31 @@
|
||||||
|
BasedOnStyle: WebKit
|
||||||
|
# Cpp11BracedListStyle: false
|
||||||
|
# ColumnLimit: 120
|
||||||
|
IndentCaseLabels: false
|
||||||
|
IndentWidth: 4
|
||||||
|
TabWidth: 8
|
||||||
|
UseTab: Never
|
||||||
|
PointerAlignment: Left
|
||||||
|
SortIncludes: false
|
||||||
|
AlignConsecutiveMacros: true
|
||||||
|
AllowShortBlocksOnASingleLine: false
|
||||||
|
AllowShortCaseLabelsOnASingleLine: false
|
||||||
|
AllowShortIfStatementsOnASingleLine: false
|
||||||
|
AllowShortLoopsOnASingleLine: false
|
||||||
|
AllowShortFunctionsOnASingleLine: false
|
||||||
|
BreakConstructorInitializers: BeforeColon
|
||||||
|
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||||
|
ConstructorInitializerIndentWidth: 0
|
||||||
|
BreakBeforeBraces: Custom
|
||||||
|
BreakBeforeBinaryOperators: All
|
||||||
|
BraceWrapping:
|
||||||
|
AfterControlStatement: true
|
||||||
|
AfterClass: true
|
||||||
|
AfterEnum: true
|
||||||
|
AfterFunction: true
|
||||||
|
AfterNamespace: true
|
||||||
|
AfterStruct: true
|
||||||
|
AfterUnion: true
|
||||||
|
AfterExternBlock: true
|
||||||
|
BeforeElse: true
|
||||||
|
BeforeCatch: true
|
4
Makefile
4
Makefile
|
@ -1,6 +1,6 @@
|
||||||
CFLAGS = -O3
|
CFLAGS = -O3 -ggdb3 -fsanitize=address
|
||||||
CPPFLAGS = -std=c11 -I.
|
CPPFLAGS = -std=c11 -I.
|
||||||
LDFLAGS = -lm
|
LDFLAGS = -lm -fsanitize=address
|
||||||
|
|
||||||
TARGETS = gen_ft8 decode_ft8 test
|
TARGETS = gen_ft8 decode_ft8 test
|
||||||
|
|
||||||
|
|
|
@ -7,25 +7,25 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
// Save signal in floating point format (-1 .. +1) as a WAVE file using 16-bit signed integers.
|
// Save signal in floating point format (-1 .. +1) as a WAVE file using 16-bit signed integers.
|
||||||
void save_wav(const float *signal, int num_samples, int sample_rate, const char *path)
|
void save_wav(const float* signal, int num_samples, int sample_rate, const char* path)
|
||||||
{
|
{
|
||||||
char subChunk1ID[4] = {'f', 'm', 't', ' '};
|
char subChunk1ID[4] = { 'f', 'm', 't', ' ' };
|
||||||
uint32_t subChunk1Size = 16; // 16 for PCM
|
uint32_t subChunk1Size = 16; // 16 for PCM
|
||||||
uint16_t audioFormat = 1; // PCM = 1
|
uint16_t audioFormat = 1; // PCM = 1
|
||||||
uint16_t numChannels = 1;
|
uint16_t numChannels = 1;
|
||||||
uint16_t bitsPerSample = 16;
|
uint16_t bitsPerSample = 16;
|
||||||
uint32_t sampleRate = sample_rate;
|
uint32_t sampleRate = sample_rate;
|
||||||
uint16_t blockAlign = numChannels * bitsPerSample / 8;
|
uint16_t blockAlign = numChannels * bitsPerSample / 8;
|
||||||
uint32_t byteRate = sampleRate * blockAlign;
|
uint32_t byteRate = sampleRate * blockAlign;
|
||||||
|
|
||||||
char subChunk2ID[4] = {'d', 'a', 't', 'a'};
|
char subChunk2ID[4] = { 'd', 'a', 't', 'a' };
|
||||||
uint32_t subChunk2Size = num_samples * blockAlign;
|
uint32_t subChunk2Size = num_samples * blockAlign;
|
||||||
|
|
||||||
char chunkID[4] = {'R', 'I', 'F', 'F'};
|
char chunkID[4] = { 'R', 'I', 'F', 'F' };
|
||||||
uint32_t chunkSize = 4 + (8 + subChunk1Size) + (8 + subChunk2Size);
|
uint32_t chunkSize = 4 + (8 + subChunk1Size) + (8 + subChunk2Size);
|
||||||
char format[4] = {'W', 'A', 'V', 'E'};
|
char format[4] = { 'W', 'A', 'V', 'E' };
|
||||||
|
|
||||||
int16_t *raw_data = (int16_t *)malloc(num_samples * blockAlign);
|
int16_t* raw_data = (int16_t*)malloc(num_samples * blockAlign);
|
||||||
for (int i = 0; i < num_samples; i++)
|
for (int i = 0; i < num_samples; i++)
|
||||||
{
|
{
|
||||||
float x = signal[i];
|
float x = signal[i];
|
||||||
|
@ -36,7 +36,7 @@ void save_wav(const float *signal, int num_samples, int sample_rate, const char
|
||||||
raw_data[i] = (int)(0.5 + (x * 32767.0));
|
raw_data[i] = (int)(0.5 + (x * 32767.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
FILE *f = fopen(path, "wb");
|
FILE* f = fopen(path, "wb");
|
||||||
|
|
||||||
// NOTE: works only on little-endian architecture
|
// NOTE: works only on little-endian architecture
|
||||||
fwrite(chunkID, sizeof(chunkID), 1, f);
|
fwrite(chunkID, sizeof(chunkID), 1, f);
|
||||||
|
@ -63,48 +63,48 @@ void save_wav(const float *signal, int num_samples, int sample_rate, const char
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load signal in floating point format (-1 .. +1) as a WAVE file using 16-bit signed integers.
|
// Load signal in floating point format (-1 .. +1) as a WAVE file using 16-bit signed integers.
|
||||||
int load_wav(float *signal, int *num_samples, int *sample_rate, const char *path)
|
int load_wav(float* signal, int* num_samples, int* sample_rate, const char* path)
|
||||||
{
|
{
|
||||||
char subChunk1ID[4]; // = {'f', 'm', 't', ' '};
|
char subChunk1ID[4]; // = {'f', 'm', 't', ' '};
|
||||||
uint32_t subChunk1Size; // = 16; // 16 for PCM
|
uint32_t subChunk1Size; // = 16; // 16 for PCM
|
||||||
uint16_t audioFormat; // = 1; // PCM = 1
|
uint16_t audioFormat; // = 1; // PCM = 1
|
||||||
uint16_t numChannels; // = 1;
|
uint16_t numChannels; // = 1;
|
||||||
uint16_t bitsPerSample; // = 16;
|
uint16_t bitsPerSample; // = 16;
|
||||||
uint32_t sampleRate;
|
uint32_t sampleRate;
|
||||||
uint16_t blockAlign; // = numChannels * bitsPerSample / 8;
|
uint16_t blockAlign; // = numChannels * bitsPerSample / 8;
|
||||||
uint32_t byteRate; // = sampleRate * blockAlign;
|
uint32_t byteRate; // = sampleRate * blockAlign;
|
||||||
|
|
||||||
char subChunk2ID[4]; // = {'d', 'a', 't', 'a'};
|
char subChunk2ID[4]; // = {'d', 'a', 't', 'a'};
|
||||||
uint32_t subChunk2Size; // = num_samples * blockAlign;
|
uint32_t subChunk2Size; // = num_samples * blockAlign;
|
||||||
|
|
||||||
char chunkID[4]; // = {'R', 'I', 'F', 'F'};
|
char chunkID[4]; // = {'R', 'I', 'F', 'F'};
|
||||||
uint32_t chunkSize; // = 4 + (8 + subChunk1Size) + (8 + subChunk2Size);
|
uint32_t chunkSize; // = 4 + (8 + subChunk1Size) + (8 + subChunk2Size);
|
||||||
char format[4]; // = {'W', 'A', 'V', 'E'};
|
char format[4]; // = {'W', 'A', 'V', 'E'};
|
||||||
|
|
||||||
FILE *f = fopen(path, "rb");
|
FILE* f = fopen(path, "rb");
|
||||||
|
|
||||||
// NOTE: works only on little-endian architecture
|
// NOTE: works only on little-endian architecture
|
||||||
fread((void *)chunkID, sizeof(chunkID), 1, f);
|
fread((void*)chunkID, sizeof(chunkID), 1, f);
|
||||||
fread((void *)&chunkSize, sizeof(chunkSize), 1, f);
|
fread((void*)&chunkSize, sizeof(chunkSize), 1, f);
|
||||||
fread((void *)format, sizeof(format), 1, f);
|
fread((void*)format, sizeof(format), 1, f);
|
||||||
|
|
||||||
fread((void *)subChunk1ID, sizeof(subChunk1ID), 1, f);
|
fread((void*)subChunk1ID, sizeof(subChunk1ID), 1, f);
|
||||||
fread((void *)&subChunk1Size, sizeof(subChunk1Size), 1, f);
|
fread((void*)&subChunk1Size, sizeof(subChunk1Size), 1, f);
|
||||||
if (subChunk1Size != 16)
|
if (subChunk1Size != 16)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
fread((void *)&audioFormat, sizeof(audioFormat), 1, f);
|
fread((void*)&audioFormat, sizeof(audioFormat), 1, f);
|
||||||
fread((void *)&numChannels, sizeof(numChannels), 1, f);
|
fread((void*)&numChannels, sizeof(numChannels), 1, f);
|
||||||
fread((void *)&sampleRate, sizeof(sampleRate), 1, f);
|
fread((void*)&sampleRate, sizeof(sampleRate), 1, f);
|
||||||
fread((void *)&byteRate, sizeof(byteRate), 1, f);
|
fread((void*)&byteRate, sizeof(byteRate), 1, f);
|
||||||
fread((void *)&blockAlign, sizeof(blockAlign), 1, f);
|
fread((void*)&blockAlign, sizeof(blockAlign), 1, f);
|
||||||
fread((void *)&bitsPerSample, sizeof(bitsPerSample), 1, f);
|
fread((void*)&bitsPerSample, sizeof(bitsPerSample), 1, f);
|
||||||
|
|
||||||
if (audioFormat != 1 || numChannels != 1 || bitsPerSample != 16)
|
if (audioFormat != 1 || numChannels != 1 || bitsPerSample != 16)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
fread((void *)subChunk2ID, sizeof(subChunk2ID), 1, f);
|
fread((void*)subChunk2ID, sizeof(subChunk2ID), 1, f);
|
||||||
fread((void *)&subChunk2Size, sizeof(subChunk2Size), 1, f);
|
fread((void*)&subChunk2Size, sizeof(subChunk2Size), 1, f);
|
||||||
|
|
||||||
if (subChunk2Size / blockAlign > *num_samples)
|
if (subChunk2Size / blockAlign > *num_samples)
|
||||||
return -2;
|
return -2;
|
||||||
|
@ -112,9 +112,9 @@ int load_wav(float *signal, int *num_samples, int *sample_rate, const char *path
|
||||||
*num_samples = subChunk2Size / blockAlign;
|
*num_samples = subChunk2Size / blockAlign;
|
||||||
*sample_rate = sampleRate;
|
*sample_rate = sampleRate;
|
||||||
|
|
||||||
int16_t *raw_data = (int16_t *)malloc(*num_samples * blockAlign);
|
int16_t* raw_data = (int16_t*)malloc(*num_samples * blockAlign);
|
||||||
|
|
||||||
fread((void *)raw_data, blockAlign, *num_samples, f);
|
fread((void*)raw_data, blockAlign, *num_samples, f);
|
||||||
for (int i = 0; i < *num_samples; i++)
|
for (int i = 0; i < *num_samples; i++)
|
||||||
{
|
{
|
||||||
signal[i] = raw_data[i] / 32768.0f;
|
signal[i] = raw_data[i] / 32768.0f;
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
#define _INCLUDE_WAVE_H_
|
#define _INCLUDE_WAVE_H_
|
||||||
|
|
||||||
// Save signal in floating point format (-1 .. +1) as a WAVE file using 16-bit signed integers.
|
// Save signal in floating point format (-1 .. +1) as a WAVE file using 16-bit signed integers.
|
||||||
void save_wav(const float *signal, int num_samples, int sample_rate, const char *path);
|
void save_wav(const float* signal, int num_samples, int sample_rate, const char* path);
|
||||||
|
|
||||||
// Load signal in floating point format (-1 .. +1) as a WAVE file using 16-bit signed integers.
|
// Load signal in floating point format (-1 .. +1) as a WAVE file using 16-bit signed integers.
|
||||||
int load_wav(float *signal, int *num_samples, int *sample_rate, const char *path);
|
int load_wav(float* signal, int* num_samples, int* sample_rate, const char* path);
|
||||||
|
|
||||||
#endif // _INCLUDE_WAVE_H_
|
#endif // _INCLUDE_WAVE_H_
|
||||||
|
|
21
decode_ft8.c
21
decode_ft8.c
|
@ -68,7 +68,7 @@ static float max2(float a, float b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute FFT magnitudes (log power) for each timeslot in the signal
|
// Compute FFT magnitudes (log power) for each timeslot in the signal
|
||||||
void extract_power(const float signal[], waterfall_t *power, int block_size)
|
void extract_power(const float signal[], waterfall_t* power, int block_size)
|
||||||
{
|
{
|
||||||
const int subblock_size = block_size / power->time_osr;
|
const int subblock_size = block_size / power->time_osr;
|
||||||
const int nfft = block_size * power->freq_osr;
|
const int nfft = block_size * power->freq_osr;
|
||||||
|
@ -93,7 +93,7 @@ void extract_power(const float signal[], waterfall_t *power, int block_size)
|
||||||
LOG(LOG_INFO, "N_FFT = %d\n", nfft);
|
LOG(LOG_INFO, "N_FFT = %d\n", nfft);
|
||||||
LOG(LOG_INFO, "FFT work area = %lu\n", fft_work_size);
|
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);
|
kiss_fftr_cfg fft_cfg = kiss_fftr_alloc(nfft, 0, fft_work, &fft_work_size);
|
||||||
|
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
|
@ -146,7 +146,7 @@ void extract_power(const float signal[], waterfall_t *power, int block_size)
|
||||||
free(fft_work);
|
free(fft_work);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char** argv)
|
||||||
{
|
{
|
||||||
// Expect one command-line argument
|
// Expect one command-line argument
|
||||||
if (argc < 2)
|
if (argc < 2)
|
||||||
|
@ -155,7 +155,7 @@ int main(int argc, char **argv)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *wav_path = argv[1];
|
const char* wav_path = argv[1];
|
||||||
|
|
||||||
int sample_rate = 12000;
|
int sample_rate = 12000;
|
||||||
int num_samples = 15 * sample_rate;
|
int num_samples = 15 * sample_rate;
|
||||||
|
@ -169,7 +169,7 @@ int main(int argc, char **argv)
|
||||||
|
|
||||||
// Compute DSP parameters that depend on the sample rate
|
// Compute DSP parameters that depend on the sample rate
|
||||||
const int num_bins = (int)(sample_rate / (2 * kFSK_dev)); // number bins of FSK tone width that the spectrum can be divided into
|
const int num_bins = (int)(sample_rate / (2 * kFSK_dev)); // number bins of FSK tone width that the spectrum can be divided into
|
||||||
const int block_size = (int)(sample_rate / kFSK_dev); // samples corresponding to one FSK symbol
|
const int block_size = (int)(sample_rate / kFSK_dev); // samples corresponding to one FSK symbol
|
||||||
const int subblock_size = block_size / kTime_osr;
|
const int subblock_size = block_size / kTime_osr;
|
||||||
const int nfft = block_size * kFreq_osr;
|
const int nfft = block_size * kFreq_osr;
|
||||||
const int num_blocks = (num_samples - nfft + subblock_size) / block_size;
|
const int num_blocks = (num_samples - nfft + subblock_size) / block_size;
|
||||||
|
@ -183,17 +183,18 @@ int main(int argc, char **argv)
|
||||||
.num_bins = num_bins,
|
.num_bins = num_bins,
|
||||||
.time_osr = kTime_osr,
|
.time_osr = kTime_osr,
|
||||||
.freq_osr = kFreq_osr,
|
.freq_osr = kFreq_osr,
|
||||||
.mag = mag_power};
|
.mag = mag_power
|
||||||
|
};
|
||||||
extract_power(signal, &power, block_size);
|
extract_power(signal, &power, block_size);
|
||||||
|
|
||||||
// Find top candidates by Costas sync score and localize them in time and frequency
|
// Find top candidates by Costas sync score and localize them in time and frequency
|
||||||
candidate_t candidate_list[kMax_candidates];
|
candidate_t candidate_list[kMax_candidates];
|
||||||
int num_candidates = find_sync(&power, kMax_candidates, candidate_list, kMin_score);
|
int num_candidates = ft8_find_sync(&power, kMax_candidates, candidate_list, kMin_score);
|
||||||
|
|
||||||
// Hash table for decoded messages (to check for duplicates)
|
// Hash table for decoded messages (to check for duplicates)
|
||||||
int num_decoded = 0;
|
int num_decoded = 0;
|
||||||
message_t decoded[kMax_decoded_messages];
|
message_t decoded[kMax_decoded_messages];
|
||||||
message_t *decoded_hashtable[kMax_decoded_messages];
|
message_t* decoded_hashtable[kMax_decoded_messages];
|
||||||
|
|
||||||
// Initialize hash table pointers
|
// Initialize hash table pointers
|
||||||
for (int i = 0; i < kMax_decoded_messages; ++i)
|
for (int i = 0; i < kMax_decoded_messages; ++i)
|
||||||
|
@ -204,7 +205,7 @@ int main(int argc, char **argv)
|
||||||
// Go over candidates and attempt to decode messages
|
// Go over candidates and attempt to decode messages
|
||||||
for (int idx = 0; idx < num_candidates; ++idx)
|
for (int idx = 0; idx < num_candidates; ++idx)
|
||||||
{
|
{
|
||||||
const candidate_t *cand = &candidate_list[idx];
|
const candidate_t* cand = &candidate_list[idx];
|
||||||
if (cand->score < kMin_score)
|
if (cand->score < kMin_score)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
@ -213,7 +214,7 @@ int main(int argc, char **argv)
|
||||||
|
|
||||||
message_t message;
|
message_t message;
|
||||||
decode_status_t status;
|
decode_status_t status;
|
||||||
if (!decode(&power, cand, &message, kLDPC_iterations, &status))
|
if (!ft8_decode(&power, cand, &message, kLDPC_iterations, &status))
|
||||||
{
|
{
|
||||||
if (status.ldpc_errors > 0)
|
if (status.ldpc_errors > 0)
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,21 +9,21 @@
|
||||||
#define FT8_NN (79) ///< Total channel symbols (FT8_NS + FT8_ND)
|
#define FT8_NN (79) ///< Total channel symbols (FT8_NS + FT8_ND)
|
||||||
|
|
||||||
// Define FT4 symbol counts
|
// Define FT4 symbol counts
|
||||||
#define FT4_ND (87) ///< Data symbols
|
#define FT4_ND (87) ///< Data symbols
|
||||||
#define FT4_NS (16) ///< Sync symbols (3 @ Costas 7x7)
|
#define FT4_NS (16) ///< Sync symbols (3 @ Costas 7x7)
|
||||||
#define FT4_NR (2) ///< Ramp symbols (beginning + end)
|
#define FT4_NR (2) ///< Ramp symbols (beginning + end)
|
||||||
#define FT4_NN (105) ///< Total channel symbols (FT4_NS + FT4_ND + FT4_NR)
|
#define FT4_NN (105) ///< Total channel symbols (FT4_NS + FT4_ND + FT4_NR)
|
||||||
|
|
||||||
// Define LDPC parameters
|
// Define LDPC parameters
|
||||||
#define FT8_LDPC_N (174) ///< Number of bits in the encoded message (payload with LDPC checksum bits)
|
#define FT8_LDPC_N (174) ///< Number of bits in the encoded message (payload with LDPC checksum bits)
|
||||||
#define FT8_LDPC_K (91) ///< Number of payload bits (including CRC)
|
#define FT8_LDPC_K (91) ///< Number of payload bits (including CRC)
|
||||||
#define FT8_LDPC_M (83) ///< Number of LDPC checksum bits (FT8_LDPC_N - FT8_LDPC_K)
|
#define FT8_LDPC_M (83) ///< Number of LDPC checksum bits (FT8_LDPC_N - FT8_LDPC_K)
|
||||||
#define FT8_LDPC_N_BYTES ((FT8_LDPC_N + 7) / 8) ///< Number of whole bytes needed to store 174 bits (full message)
|
#define FT8_LDPC_N_BYTES ((FT8_LDPC_N + 7) / 8) ///< Number of whole bytes needed to store 174 bits (full message)
|
||||||
#define FT8_LDPC_K_BYTES ((FT8_LDPC_K + 7) / 8) ///< Number of whole bytes needed to store 91 bits (payload + CRC only)
|
#define FT8_LDPC_K_BYTES ((FT8_LDPC_K + 7) / 8) ///< Number of whole bytes needed to store 91 bits (payload + CRC only)
|
||||||
|
|
||||||
// Define CRC parameters
|
// Define CRC parameters
|
||||||
#define FT8_CRC_POLYNOMIAL ((uint16_t)0x2757u) ///< CRC-14 polynomial without the leading (MSB) 1
|
#define FT8_CRC_POLYNOMIAL ((uint16_t)0x2757u) ///< CRC-14 polynomial without the leading (MSB) 1
|
||||||
#define FT8_CRC_WIDTH (14)
|
#define FT8_CRC_WIDTH (14)
|
||||||
|
|
||||||
/// Costas 7x7 tone pattern for synchronization
|
/// Costas 7x7 tone pattern for synchronization
|
||||||
extern const uint8_t kFT8_Costas_pattern[7];
|
extern const uint8_t kFT8_Costas_pattern[7];
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
// Adapted from https://barrgroup.com/Embedded-Systems/How-To/CRC-Calculation-C-Code
|
// Adapted from https://barrgroup.com/Embedded-Systems/How-To/CRC-Calculation-C-Code
|
||||||
// [IN] message - byte sequence (MSB first)
|
// [IN] message - byte sequence (MSB first)
|
||||||
// [IN] num_bits - number of bits in the sequence
|
// [IN] num_bits - number of bits in the sequence
|
||||||
uint16_t ft8_crc(const uint8_t message[], int num_bits)
|
uint16_t ftx_compute_crc(const uint8_t message[], int num_bits)
|
||||||
{
|
{
|
||||||
uint16_t remainder = 0;
|
uint16_t remainder = 0;
|
||||||
int idx_byte = 0;
|
int idx_byte = 0;
|
||||||
|
@ -36,13 +36,13 @@ uint16_t ft8_crc(const uint8_t message[], int num_bits)
|
||||||
return remainder & ((TOPBIT << 1) - 1u);
|
return remainder & ((TOPBIT << 1) - 1u);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t extract_crc(const uint8_t a91[])
|
uint16_t ftx_extract_crc(const uint8_t a91[])
|
||||||
{
|
{
|
||||||
uint16_t chksum = ((a91[9] & 0x07) << 11) | (a91[10] << 3) | (a91[11] >> 5);
|
uint16_t chksum = ((a91[9] & 0x07) << 11) | (a91[10] << 3) | (a91[11] >> 5);
|
||||||
return chksum;
|
return chksum;
|
||||||
}
|
}
|
||||||
|
|
||||||
void add_crc(const uint8_t payload[], uint8_t a91[])
|
void ftx_add_crc(const uint8_t payload[], uint8_t a91[])
|
||||||
{
|
{
|
||||||
// Copy 77 bits of payload data
|
// Copy 77 bits of payload data
|
||||||
for (int i = 0; i < 10; i++)
|
for (int i = 0; i < 10; i++)
|
||||||
|
@ -54,7 +54,7 @@ void add_crc(const uint8_t payload[], uint8_t a91[])
|
||||||
|
|
||||||
// Calculate CRC of 82 bits (77 + 5 zeros)
|
// Calculate CRC of 82 bits (77 + 5 zeros)
|
||||||
// 'The CRC is calculated on the source-encoded message, zero-extended from 77 to 82 bits'
|
// 'The CRC is calculated on the source-encoded message, zero-extended from 77 to 82 bits'
|
||||||
uint16_t checksum = ft8_crc(a91, 96 - 14);
|
uint16_t checksum = ftx_compute_crc(a91, 96 - 14);
|
||||||
|
|
||||||
// Store the CRC at the end of 77 bit message
|
// Store the CRC at the end of 77 bit message
|
||||||
a91[9] |= (uint8_t)(checksum >> 11);
|
a91[9] |= (uint8_t)(checksum >> 11);
|
||||||
|
|
12
ft8/crc.h
12
ft8/crc.h
|
@ -4,19 +4,19 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
// Compute 14-bit CRC for a sequence of given number of bits
|
// Compute 14-bit CRC for a sequence of given number of bits using FT8/FT4 CRC polynomial
|
||||||
// [IN] message - byte sequence (MSB first)
|
// [IN] message - byte sequence (MSB first)
|
||||||
// [IN] num_bits - number of bits in the sequence
|
// [IN] num_bits - number of bits in the sequence
|
||||||
uint16_t ft8_crc(const uint8_t message[], int num_bits);
|
uint16_t ftx_compute_crc(const uint8_t message[], int num_bits);
|
||||||
|
|
||||||
/// Extract the FT8 CRC of a packed message (during decoding)
|
/// Extract the FT8/FT4 CRC of a packed message (during decoding)
|
||||||
/// @param[in] a91 77 bits of payload data + CRC
|
/// @param[in] a91 77 bits of payload data + CRC
|
||||||
/// @return Extracted CRC
|
/// @return Extracted CRC
|
||||||
uint16_t extract_crc(const uint8_t a91[]);
|
uint16_t ftx_extract_crc(const uint8_t a91[]);
|
||||||
|
|
||||||
/// Add the FT8 CRC to a packed message (during encoding)
|
/// Add FT8/FT4 CRC to a packed message (during encoding)
|
||||||
/// @param[in] payload 77 bits of payload data
|
/// @param[in] payload 77 bits of payload data
|
||||||
/// @param[out] a91 91 bits of payload data + CRC
|
/// @param[out] a91 91 bits of payload data + CRC
|
||||||
void add_crc(const uint8_t payload[], uint8_t a91[]);
|
void ftx_add_crc(const uint8_t payload[], uint8_t a91[]);
|
||||||
|
|
||||||
#endif // _INCLUDE_CRC_H_
|
#endif // _INCLUDE_CRC_H_
|
62
ft8/decode.c
62
ft8/decode.c
|
@ -12,21 +12,28 @@
|
||||||
/// @param[in] cand Candidate to extract the message from
|
/// @param[in] cand Candidate to extract the message from
|
||||||
/// @param[in] code_map Symbol encoding map
|
/// @param[in] code_map Symbol encoding map
|
||||||
/// @param[out] log174 Output of decoded log likelihoods for each of the 174 message bits
|
/// @param[out] log174 Output of decoded log likelihoods for each of the 174 message bits
|
||||||
static void extract_likelihood(const waterfall_t *power, const candidate_t *cand, float *log174);
|
static void ft8_extract_likelihood(const waterfall_t* power, const candidate_t* cand, float* log174);
|
||||||
|
|
||||||
|
/// Packs a string of bits each represented as a zero/non-zero byte in bit_array[],
|
||||||
|
/// as a string of packed bits starting from the MSB of the first byte of packed[]
|
||||||
|
/// @param[in] plain Array of bits (0 and nonzero values) with num_bits entires
|
||||||
|
/// @param[in] num_bits Number of bits (entries) passed in bit_array
|
||||||
|
/// @param[out] packed Byte-packed bits representing the data in bit_array
|
||||||
|
static void pack_bits(const uint8_t bit_array[], int num_bits, uint8_t packed[]);
|
||||||
|
|
||||||
static float max2(float a, float b);
|
static float max2(float a, float b);
|
||||||
static float max4(float a, float b, float c, float d);
|
static float max4(float a, float b, float c, float d);
|
||||||
static void heapify_down(candidate_t heap[], int heap_size);
|
static void heapify_down(candidate_t heap[], int heap_size);
|
||||||
static void heapify_up(candidate_t heap[], int heap_size);
|
static void heapify_up(candidate_t heap[], int heap_size);
|
||||||
static void decode_symbol(const uint8_t *power, int bit_idx, float *log174);
|
static void decode_symbol(const uint8_t* power, int bit_idx, float* log174);
|
||||||
static void decode_multi_symbols(const uint8_t *power, int num_bins, int n_syms, int bit_idx, float *log174);
|
static void decode_multi_symbols(const uint8_t* power, int num_bins, int n_syms, int bit_idx, float* log174);
|
||||||
|
|
||||||
static int get_index(const waterfall_t *power, int block, int time_sub, int freq_sub, int bin)
|
static int get_index(const waterfall_t* 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;
|
return ((((block * power->time_osr) + time_sub) * power->freq_osr + freq_sub) * power->num_bins) + bin;
|
||||||
}
|
}
|
||||||
|
|
||||||
int find_sync(const waterfall_t *power, int num_candidates, candidate_t heap[], int min_score)
|
int ft8_find_sync(const waterfall_t* power, int num_candidates, candidate_t heap[], int min_score)
|
||||||
{
|
{
|
||||||
int heap_size = 0;
|
int heap_size = 0;
|
||||||
int sym_stride = power->time_osr * power->freq_osr * power->num_bins;
|
int sym_stride = power->time_osr * power->freq_osr * power->num_bins;
|
||||||
|
@ -58,7 +65,7 @@ int find_sync(const waterfall_t *power, int num_candidates, candidate_t heap[],
|
||||||
break;
|
break;
|
||||||
|
|
||||||
int offset = get_index(power, block, time_sub, freq_sub, freq_offset);
|
int offset = get_index(power, block, time_sub, freq_sub, freq_offset);
|
||||||
const uint8_t *p8 = power->mag + offset;
|
const uint8_t* p8 = power->mag + offset;
|
||||||
|
|
||||||
// Weighted difference between the expected and all other symbols
|
// Weighted difference between the expected and all other symbols
|
||||||
// Does not work as well as the alternative score below
|
// Does not work as well as the alternative score below
|
||||||
|
@ -143,7 +150,7 @@ int find_sync(const waterfall_t *power, int num_candidates, candidate_t heap[],
|
||||||
return heap_size;
|
return heap_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
void extract_likelihood(const waterfall_t *power, const candidate_t *cand, float *log174)
|
void ft8_extract_likelihood(const waterfall_t* power, const candidate_t* cand, float* log174)
|
||||||
{
|
{
|
||||||
int sym_stride = power->time_osr * power->freq_osr * power->num_bins;
|
int sym_stride = power->time_osr * power->freq_osr * power->num_bins;
|
||||||
int offset = get_index(power, cand->time_offset, cand->time_sub, cand->freq_sub, cand->freq_offset);
|
int offset = get_index(power, cand->time_offset, cand->time_sub, cand->freq_sub, cand->freq_offset);
|
||||||
|
@ -168,7 +175,7 @@ void extract_likelihood(const waterfall_t *power, const candidate_t *cand, float
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Pointer to 8 bins of the current symbol
|
// Pointer to 8 bins of the current symbol
|
||||||
const uint8_t *ps = power->mag + offset + (sym_idx * sym_stride);
|
const uint8_t* ps = power->mag + offset + (sym_idx * sym_stride);
|
||||||
|
|
||||||
decode_symbol(ps, bit_idx, log174);
|
decode_symbol(ps, bit_idx, log174);
|
||||||
}
|
}
|
||||||
|
@ -193,10 +200,10 @@ void extract_likelihood(const waterfall_t *power, const candidate_t *cand, float
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool decode(const waterfall_t *power, const candidate_t *cand, message_t *message, int max_iterations, decode_status_t *status)
|
bool ft8_decode(const waterfall_t* power, const candidate_t* cand, message_t* message, int max_iterations, decode_status_t* status)
|
||||||
{
|
{
|
||||||
float log174[FT8_LDPC_N]; // message bits encoded as likelihood
|
float log174[FT8_LDPC_N]; // message bits encoded as likelihood
|
||||||
extract_likelihood(power, cand, log174);
|
ft8_extract_likelihood(power, cand, log174);
|
||||||
|
|
||||||
uint8_t plain174[FT8_LDPC_N]; // message bits (0/1)
|
uint8_t plain174[FT8_LDPC_N]; // message bits (0/1)
|
||||||
bp_decode(log174, max_iterations, plain174, &status->ldpc_errors);
|
bp_decode(log174, max_iterations, plain174, &status->ldpc_errors);
|
||||||
|
@ -212,11 +219,11 @@ bool decode(const waterfall_t *power, const candidate_t *cand, message_t *messag
|
||||||
pack_bits(plain174, FT8_LDPC_K, a91);
|
pack_bits(plain174, FT8_LDPC_K, a91);
|
||||||
|
|
||||||
// Extract CRC and check it
|
// Extract CRC and check it
|
||||||
status->crc_extracted = extract_crc(a91);
|
status->crc_extracted = ftx_extract_crc(a91);
|
||||||
// [1]: 'The CRC is calculated on the source-encoded message, zero-extended from 77 to 82 bits.'
|
// [1]: 'The CRC is calculated on the source-encoded message, zero-extended from 77 to 82 bits.'
|
||||||
a91[9] &= 0xF8;
|
a91[9] &= 0xF8;
|
||||||
a91[10] &= 0x00;
|
a91[10] &= 0x00;
|
||||||
status->crc_calculated = ft8_crc(a91, 96 - 14);
|
status->crc_calculated = ftx_compute_crc(a91, 96 - 14);
|
||||||
|
|
||||||
if (status->crc_extracted != status->crc_calculated)
|
if (status->crc_extracted != status->crc_calculated)
|
||||||
{
|
{
|
||||||
|
@ -296,7 +303,7 @@ static void heapify_up(candidate_t heap[], int heap_size)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute unnormalized log likelihood log(p(1) / p(0)) of 3 message bits (1 FSK symbol)
|
// Compute unnormalized log likelihood log(p(1) / p(0)) of 3 message bits (1 FSK symbol)
|
||||||
static void decode_symbol(const uint8_t *power, int bit_idx, float *log174)
|
static void decode_symbol(const uint8_t* power, int bit_idx, float* log174)
|
||||||
{
|
{
|
||||||
// Cleaned up code for the simple case of n_syms==1
|
// Cleaned up code for the simple case of n_syms==1
|
||||||
float s2[8];
|
float s2[8];
|
||||||
|
@ -312,7 +319,7 @@ static void decode_symbol(const uint8_t *power, int bit_idx, float *log174)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute unnormalized log likelihood log(p(1) / p(0)) of bits corresponding to several FSK symbols at once
|
// 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, int bit_idx, float *log174)
|
static void decode_multi_symbols(const uint8_t* power, int num_bins, int n_syms, int bit_idx, float* log174)
|
||||||
{
|
{
|
||||||
const int n_bits = 3 * n_syms;
|
const int n_bits = 3 * n_syms;
|
||||||
const int n_tones = (1 << n_bits);
|
const int n_tones = (1 << n_bits);
|
||||||
|
@ -367,3 +374,30 @@ static void decode_multi_symbols(const uint8_t *power, int num_bins, int n_syms,
|
||||||
log174[bit_idx + i] = max_one - max_zero;
|
log174[bit_idx + i] = max_one - max_zero;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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[]
|
||||||
|
static void pack_bits(const uint8_t bit_array[], int num_bits, uint8_t packed[])
|
||||||
|
{
|
||||||
|
int num_bytes = (num_bits + 7) / 8;
|
||||||
|
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 (bit_array[i])
|
||||||
|
{
|
||||||
|
packed[byte_idx] |= mask;
|
||||||
|
}
|
||||||
|
mask >>= 1;
|
||||||
|
if (!mask)
|
||||||
|
{
|
||||||
|
mask = 0x80;
|
||||||
|
++byte_idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
ft8/decode.h
22
ft8/decode.h
|
@ -4,7 +4,7 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
/// Input structure to find_sync() function. This structure describes stored waterfall data over the whole message slot.
|
/// Input structure to ft8_find_sync() function. This structure describes stored waterfall data over the whole message slot.
|
||||||
/// Fields time_osr and freq_osr specify additional oversampling rate for time and frequency resolution.
|
/// Fields time_osr and freq_osr specify additional oversampling rate for time and frequency resolution.
|
||||||
/// If time_osr=1, FFT magnitude data is collected once for every symbol transmitted, i.e. every 1/6.25 = 0.16 seconds.
|
/// If time_osr=1, FFT magnitude data is collected once for every symbol transmitted, i.e. every 1/6.25 = 0.16 seconds.
|
||||||
/// Values time_osr > 1 mean each symbol is further subdivided in time.
|
/// Values time_osr > 1 mean each symbol is further subdivided in time.
|
||||||
|
@ -13,21 +13,21 @@
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
int num_blocks; ///< number of total blocks (symbols) in terms of 160 ms time periods
|
int num_blocks; ///< number of total blocks (symbols) in terms of 160 ms time periods
|
||||||
int num_bins; ///< number of FFT bins in terms of 6.25 Hz
|
int num_bins; ///< number of FFT bins in terms of 6.25 Hz
|
||||||
int time_osr; ///< number of time subdivisions
|
int time_osr; ///< number of time subdivisions
|
||||||
int freq_osr; ///< number of frequency subdivisions
|
int freq_osr; ///< number of frequency subdivisions
|
||||||
uint8_t *mag; ///< FFT magnitudes stored as uint8_t[blocks][time_osr][freq_osr][num_bins]
|
uint8_t* mag; ///< FFT magnitudes stored as uint8_t[blocks][time_osr][freq_osr][num_bins]
|
||||||
} waterfall_t;
|
} waterfall_t;
|
||||||
|
|
||||||
/// Output structure of find_sync() and input structure of extract_likelihood().
|
/// Output structure of ft8_find_sync() and input structure of ft8_decode().
|
||||||
/// Holds the position of potential start of a message in time and frequency.
|
/// Holds the position of potential start of a message in time and frequency.
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
int16_t score; ///< Candidate score (non-negative number; higher score means higher likelihood)
|
int16_t score; ///< Candidate score (non-negative number; higher score means higher likelihood)
|
||||||
int16_t time_offset; ///< Index of the time block
|
int16_t time_offset; ///< Index of the time block
|
||||||
int16_t freq_offset; ///< Index of the frequency bin
|
int16_t freq_offset; ///< Index of the frequency bin
|
||||||
uint8_t time_sub; ///< Index of the time subdivision used
|
uint8_t time_sub; ///< Index of the time subdivision used
|
||||||
uint8_t freq_sub; ///< Index of the frequency subdivision used
|
uint8_t freq_sub; ///< Index of the frequency subdivision used
|
||||||
} candidate_t;
|
} candidate_t;
|
||||||
|
|
||||||
/// Structure that holds the decoded message
|
/// Structure that holds the decoded message
|
||||||
|
@ -55,7 +55,7 @@ typedef struct
|
||||||
/// @param[in,out] heap Array of candidate_t type entries (with num_candidates allocated entries)
|
/// @param[in,out] heap Array of candidate_t type entries (with num_candidates allocated entries)
|
||||||
/// @param[in] min_score Minimal score allowed for pruning unlikely candidates (can be zero for no effect)
|
/// @param[in] min_score Minimal score allowed for pruning unlikely candidates (can be zero for no effect)
|
||||||
/// @return Number of candidates filled in the heap
|
/// @return Number of candidates filled in the heap
|
||||||
int find_sync(const waterfall_t *power, int num_candidates, candidate_t heap[], int min_score);
|
int ft8_find_sync(const waterfall_t* power, int num_candidates, candidate_t heap[], int min_score);
|
||||||
|
|
||||||
/// Attempt to decode a message candidate. Extracts the bit probabilities, runs LDPC decoder, checks CRC and unpacks the message in plain text.
|
/// Attempt to decode a message candidate. Extracts the bit probabilities, runs LDPC decoder, checks CRC and unpacks the message in plain text.
|
||||||
/// @param[in] power Waterfall data collected during message slot
|
/// @param[in] power Waterfall data collected during message slot
|
||||||
|
@ -64,6 +64,6 @@ int find_sync(const waterfall_t *power, int num_candidates, candidate_t heap[],
|
||||||
/// @param[in] max_iterations Maximum allowed LDPC iterations (lower number means faster decode, but less precise)
|
/// @param[in] max_iterations Maximum allowed LDPC iterations (lower number means faster decode, but less precise)
|
||||||
/// @param[out] status decode_status_t structure that will be filled with the status of various decoding steps
|
/// @param[out] status decode_status_t structure that will be filled with the status of various decoding steps
|
||||||
/// @return True if the decoding was successful, false otherwise (check status for details)
|
/// @return True if the decoding was successful, false otherwise (check status for details)
|
||||||
bool decode(const waterfall_t *power, const candidate_t *cand, message_t *message, int max_iterations, decode_status_t *status);
|
bool ft8_decode(const waterfall_t* power, const candidate_t* cand, message_t* message, int max_iterations, decode_status_t* status);
|
||||||
|
|
||||||
#endif // _INCLUDE_DECODE_H_
|
#endif // _INCLUDE_DECODE_H_
|
||||||
|
|
28
ft8/encode.c
28
ft8/encode.c
|
@ -5,21 +5,21 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
// Returns 1 if an odd number of bits are set in x, zero otherwise
|
// Returns 1 if an odd number of bits are set in x, zero otherwise
|
||||||
uint8_t parity8(uint8_t x)
|
static uint8_t parity8(uint8_t x)
|
||||||
{
|
{
|
||||||
x ^= x >> 4; // a b c d ae bf cg dh
|
x ^= x >> 4; // a b c d ae bf cg dh
|
||||||
x ^= x >> 2; // a b ac bd cae dbf aecg bfdh
|
x ^= x >> 2; // a b ac bd cae dbf aecg bfdh
|
||||||
x ^= x >> 1; // a ab bac acbd bdcae caedbf aecgbfdh
|
x ^= x >> 1; // a ab bac acbd bdcae caedbf aecgbfdh
|
||||||
return x % 2; // modulo 2
|
return x % 2; // modulo 2
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode a 91-bit message and return a 174-bit codeword.
|
// Encode via LDPC a 91-bit message and return a 174-bit codeword.
|
||||||
// The generator matrix has dimensions (87,87).
|
// The generator matrix has dimensions (87,87).
|
||||||
// The code is a (174,91) regular LDPC code with column weight 3.
|
// The code is a (174,91) regular LDPC code with column weight 3.
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// [IN] message - array of 91 bits stored as 12 bytes (MSB first)
|
// [IN] message - array of 91 bits stored as 12 bytes (MSB first)
|
||||||
// [OUT] codeword - array of 174 bits stored as 22 bytes (MSB first)
|
// [OUT] codeword - array of 174 bits stored as 22 bytes (MSB first)
|
||||||
void encode174(const uint8_t *message, uint8_t *codeword)
|
static void encode174(const uint8_t* message, uint8_t* codeword)
|
||||||
{
|
{
|
||||||
// This implementation accesses the generator bits straight from the packed binary representation in kFT8_LDPC_generator
|
// This implementation accesses the generator bits straight from the packed binary representation in kFT8_LDPC_generator
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ void encode174(const uint8_t *message, uint8_t *codeword)
|
||||||
|
|
||||||
// Compute the byte index and bit mask for the first checksum bit
|
// Compute the byte index and bit mask for the first checksum bit
|
||||||
uint8_t col_mask = (0x80u >> (FT8_LDPC_K % 8u)); // bitmask of current byte
|
uint8_t col_mask = (0x80u >> (FT8_LDPC_K % 8u)); // bitmask of current byte
|
||||||
uint8_t col_idx = FT8_LDPC_K_BYTES - 1; // index into byte array
|
uint8_t col_idx = FT8_LDPC_K_BYTES - 1; // index into byte array
|
||||||
|
|
||||||
// Compute the LDPC checksum bits and store them in codeword
|
// Compute the LDPC checksum bits and store them in codeword
|
||||||
for (int i = 0; i < FT8_LDPC_M; ++i)
|
for (int i = 0; i < FT8_LDPC_M; ++i)
|
||||||
|
@ -43,7 +43,7 @@ void encode174(const uint8_t *message, uint8_t *codeword)
|
||||||
for (int j = 0; j < FT8_LDPC_K_BYTES; ++j)
|
for (int j = 0; j < FT8_LDPC_K_BYTES; ++j)
|
||||||
{
|
{
|
||||||
uint8_t bits = message[j] & kFT8_LDPC_generator[i][j]; // bitwise AND (bitwise multiplication)
|
uint8_t bits = message[j] & kFT8_LDPC_generator[i][j]; // bitwise AND (bitwise multiplication)
|
||||||
nsum ^= parity8(bits); // bitwise XOR (addition modulo 2)
|
nsum ^= parity8(bits); // bitwise XOR (addition modulo 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the current checksum bit in codeword if nsum is odd
|
// Set the current checksum bit in codeword if nsum is odd
|
||||||
|
@ -62,13 +62,13 @@ void encode174(const uint8_t *message, uint8_t *codeword)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void genft8(const uint8_t *payload, uint8_t *tones)
|
void ft8_encode(const uint8_t* payload, uint8_t* tones)
|
||||||
{
|
{
|
||||||
uint8_t a91[12]; // Store 77 bits of payload + 14 bits CRC
|
uint8_t a91[12]; // Store 77 bits of payload + 14 bits CRC
|
||||||
|
|
||||||
// Compute and add CRC at the end of the message
|
// Compute and add CRC at the end of the message
|
||||||
// a91 contains 77 bits of payload + 14 bits of CRC
|
// a91 contains 77 bits of payload + 14 bits of CRC
|
||||||
add_crc(payload, a91);
|
ftx_add_crc(payload, a91);
|
||||||
|
|
||||||
uint8_t codeword[22];
|
uint8_t codeword[22];
|
||||||
encode174(a91, codeword);
|
encode174(a91, codeword);
|
||||||
|
@ -77,7 +77,7 @@ void genft8(const uint8_t *payload, uint8_t *tones)
|
||||||
// Total symbols: 79 (FT8_NN)
|
// Total symbols: 79 (FT8_NN)
|
||||||
|
|
||||||
uint8_t mask = 0x80u; // Mask to extract 1 bit from codeword
|
uint8_t mask = 0x80u; // Mask to extract 1 bit from codeword
|
||||||
int i_byte = 0; // Index of the current byte of the codeword
|
int i_byte = 0; // Index of the current byte of the codeword
|
||||||
for (int i_tone = 0; i_tone < FT8_NN; ++i_tone)
|
for (int i_tone = 0; i_tone < FT8_NN; ++i_tone)
|
||||||
{
|
{
|
||||||
if ((i_tone >= 0) && (i_tone < 7))
|
if ((i_tone >= 0) && (i_tone < 7))
|
||||||
|
@ -124,13 +124,13 @@ void genft8(const uint8_t *payload, uint8_t *tones)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void genft4(const uint8_t *payload, uint8_t *tones)
|
void ft4_encode(const uint8_t* payload, uint8_t* tones)
|
||||||
{
|
{
|
||||||
uint8_t a91[12]; // Store 77 bits of payload + 14 bits CRC
|
uint8_t a91[12]; // Store 77 bits of payload + 14 bits CRC
|
||||||
|
|
||||||
// Compute and add CRC at the end of the message
|
// Compute and add CRC at the end of the message
|
||||||
// a91 contains 77 bits of payload + 14 bits of CRC
|
// a91 contains 77 bits of payload + 14 bits of CRC
|
||||||
add_crc(payload, a91);
|
ftx_add_crc(payload, a91);
|
||||||
|
|
||||||
uint8_t codeword[22];
|
uint8_t codeword[22];
|
||||||
encode174(a91, codeword); // 91 bits -> 174 bits
|
encode174(a91, codeword); // 91 bits -> 174 bits
|
||||||
|
@ -139,7 +139,7 @@ void genft4(const uint8_t *payload, uint8_t *tones)
|
||||||
// Total symbols: 105 (FT4_NN)
|
// Total symbols: 105 (FT4_NN)
|
||||||
|
|
||||||
uint8_t mask = 0x80u; // Mask to extract 1 bit from codeword
|
uint8_t mask = 0x80u; // Mask to extract 1 bit from codeword
|
||||||
int i_byte = 0; // Index of the current byte of the codeword
|
int i_byte = 0; // Index of the current byte of the codeword
|
||||||
for (int i_tone = 0; i_tone < FT4_NN; ++i_tone)
|
for (int i_tone = 0; i_tone < FT4_NN; ++i_tone)
|
||||||
{
|
{
|
||||||
if ((i_tone == 0) || (i_tone == 104))
|
if ((i_tone == 0) || (i_tone == 104))
|
||||||
|
|
|
@ -22,11 +22,11 @@
|
||||||
/// Generate FT8 tone sequence from payload data
|
/// Generate FT8 tone sequence from payload data
|
||||||
/// @param[in] payload - 10 byte array consisting of 77 bit payload
|
/// @param[in] payload - 10 byte array consisting of 77 bit payload
|
||||||
/// @param[out] tones - array of FT8_NN (79) bytes to store the generated tones (encoded as 0..7)
|
/// @param[out] tones - array of FT8_NN (79) bytes to store the generated tones (encoded as 0..7)
|
||||||
void genft8(const uint8_t *payload, uint8_t *tones);
|
void ft8_encode(const uint8_t* payload, uint8_t* tones);
|
||||||
|
|
||||||
/// Generate FT4 tone sequence from payload data
|
/// Generate FT4 tone sequence from payload data
|
||||||
/// @param[in] payload - 10 byte array consisting of 77 bit payload
|
/// @param[in] payload - 10 byte array consisting of 77 bit payload
|
||||||
/// @param[out] tones - array of FT4_NN (105) bytes to store the generated tones (encoded as 0..3)
|
/// @param[out] tones - array of FT4_NN (105) bytes to store the generated tones (encoded as 0..3)
|
||||||
void genft4(const uint8_t *payload, uint8_t *tones);
|
void ft4_encode(const uint8_t* payload, uint8_t* tones);
|
||||||
|
|
||||||
#endif // _INCLUDE_ENCODE_H_
|
#endif // _INCLUDE_ENCODE_H_
|
||||||
|
|
31
ft8/ldpc.c
31
ft8/ldpc.c
|
@ -21,38 +21,11 @@ static int ldpc_check(uint8_t codeword[]);
|
||||||
static float fast_tanh(float x);
|
static float fast_tanh(float x);
|
||||||
static float fast_atanh(float x);
|
static float fast_atanh(float x);
|
||||||
|
|
||||||
// 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[])
|
|
||||||
{
|
|
||||||
int num_bytes = (num_bits + 7) / 8;
|
|
||||||
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])
|
|
||||||
{
|
|
||||||
packed[byte_idx] |= mask;
|
|
||||||
}
|
|
||||||
mask >>= 1;
|
|
||||||
if (!mask)
|
|
||||||
{
|
|
||||||
mask = 0x80;
|
|
||||||
++byte_idx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// codeword is 174 log-likelihoods.
|
// codeword is 174 log-likelihoods.
|
||||||
// plain is a return value, 174 ints, to be 0 or 1.
|
// plain is a return value, 174 ints, to be 0 or 1.
|
||||||
// max_iters is how hard to try.
|
// max_iters is how hard to try.
|
||||||
// ok == 87 means success.
|
// ok == 87 means success.
|
||||||
void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok)
|
void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int* ok)
|
||||||
{
|
{
|
||||||
float m[FT8_LDPC_M][FT8_LDPC_N]; // ~60 kB
|
float m[FT8_LDPC_M][FT8_LDPC_N]; // ~60 kB
|
||||||
float e[FT8_LDPC_M][FT8_LDPC_N]; // ~60 kB
|
float e[FT8_LDPC_M][FT8_LDPC_N]; // ~60 kB
|
||||||
|
@ -154,7 +127,7 @@ static int ldpc_check(uint8_t codeword[])
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok)
|
void bp_decode(float codeword[], int max_iters, uint8_t plain[], int* ok)
|
||||||
{
|
{
|
||||||
float tov[FT8_LDPC_N][3];
|
float tov[FT8_LDPC_N][3];
|
||||||
float toc[FT8_LDPC_M][7];
|
float toc[FT8_LDPC_M][7];
|
||||||
|
|
|
@ -7,12 +7,8 @@
|
||||||
// plain is a return value, 174 ints, to be 0 or 1.
|
// plain is a return value, 174 ints, to be 0 or 1.
|
||||||
// iters is how hard to try.
|
// iters is how hard to try.
|
||||||
// ok == 87 means success.
|
// ok == 87 means success.
|
||||||
void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok);
|
void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int* ok);
|
||||||
|
|
||||||
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[]);
|
|
||||||
|
|
||||||
#endif // _INCLUDE_LDPC_H_
|
#endif // _INCLUDE_LDPC_H_
|
||||||
|
|
44
ft8/pack.c
44
ft8/pack.c
|
@ -6,8 +6,8 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#define NTOKENS ((uint32_t)2063592L)
|
#define NTOKENS ((uint32_t)2063592L)
|
||||||
#define MAX22 ((uint32_t)4194304L)
|
#define MAX22 ((uint32_t)4194304L)
|
||||||
#define MAXGRID4 ((uint16_t)32400)
|
#define MAXGRID4 ((uint16_t)32400)
|
||||||
|
|
||||||
// TODO: This is wasteful, should figure out something more elegant
|
// TODO: This is wasteful, should figure out something more elegant
|
||||||
|
@ -19,7 +19,7 @@ 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.
|
// into a 28-bit integer.
|
||||||
int32_t pack28(const char *callsign)
|
int32_t pack28(const char* callsign)
|
||||||
{
|
{
|
||||||
// Check for special tokens first
|
// Check for special tokens first
|
||||||
if (starts_with(callsign, "DE "))
|
if (starts_with(callsign, "DE "))
|
||||||
|
@ -38,7 +38,7 @@ int32_t pack28(const char *callsign)
|
||||||
|
|
||||||
// TODO: Check for <...> callsign
|
// TODO: Check for <...> callsign
|
||||||
|
|
||||||
char c6[6] = {' ', ' ', ' ', ' ', ' ', ' '};
|
char c6[6] = { ' ', ' ', ' ', ' ', ' ', ' ' };
|
||||||
|
|
||||||
int length = 0; // strlen(callsign); // We will need it later
|
int length = 0; // strlen(callsign); // We will need it later
|
||||||
while (callsign[length] != ' ' && callsign[length] != 0)
|
while (callsign[length] != ' ' && callsign[length] != 0)
|
||||||
|
@ -75,9 +75,7 @@ int32_t pack28(const char *callsign)
|
||||||
|
|
||||||
// Check for standard callsign
|
// Check for standard callsign
|
||||||
int i0, i1, i2, i3, i4, i5;
|
int i0, i1, i2, i3, i4, i5;
|
||||||
if ((i0 = char_index(A1, c6[0])) >= 0 && (i1 = char_index(A2, c6[1])) >= 0 &&
|
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)
|
||||||
(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)
|
|
||||||
{
|
{
|
||||||
// This is a standard callsign
|
// This is a standard callsign
|
||||||
int32_t n28 = i0;
|
int32_t n28 = i0;
|
||||||
|
@ -100,7 +98,7 @@ int32_t pack28(const char *callsign)
|
||||||
// Check if a string could be a valid standard callsign or a valid
|
// Check if a string could be a valid standard callsign or a valid
|
||||||
// compound callsign.
|
// compound callsign.
|
||||||
// Return base call "bc" and a logical "cok" indicator.
|
// Return base call "bc" and a logical "cok" indicator.
|
||||||
bool chkcall(const char *call, char *bc)
|
bool chkcall(const char* call, char* bc)
|
||||||
{
|
{
|
||||||
int length = strlen(call); // n1=len_trim(w)
|
int length = strlen(call); // n1=len_trim(w)
|
||||||
if (length > 11)
|
if (length > 11)
|
||||||
|
@ -121,7 +119,7 @@ bool chkcall(const char *call, char *bc)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t packgrid(const char *grid4)
|
uint16_t packgrid(const char* grid4)
|
||||||
{
|
{
|
||||||
if (grid4 == 0)
|
if (grid4 == 0)
|
||||||
{
|
{
|
||||||
|
@ -138,9 +136,7 @@ uint16_t packgrid(const char *grid4)
|
||||||
return MAXGRID4 + 4;
|
return MAXGRID4 + 4;
|
||||||
|
|
||||||
// Check for standard 4 letter grid
|
// 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]))
|
||||||
in_range(grid4[1], 'A', 'R') &&
|
|
||||||
is_digit(grid4[2]) && is_digit(grid4[3]))
|
|
||||||
{
|
{
|
||||||
uint16_t igrid4 = (grid4[0] - 'A');
|
uint16_t igrid4 = (grid4[0] - 'A');
|
||||||
igrid4 = igrid4 * 18 + (grid4[1] - 'A');
|
igrid4 = igrid4 * 18 + (grid4[1] - 'A');
|
||||||
|
@ -168,15 +164,15 @@ uint16_t packgrid(const char *grid4)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pack Type 1 (Standard 77-bit message) and Type 2 (ditto, with a "/P" call)
|
// 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
|
// Locate the first delimiter
|
||||||
const char *s1 = strchr(msg, ' ');
|
const char* s1 = strchr(msg, ' ');
|
||||||
if (s1 == 0)
|
if (s1 == 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
const char *call1 = msg; // 1st call
|
const char* call1 = msg; // 1st call
|
||||||
const char *call2 = s1 + 1; // 2nd call
|
const char* call2 = s1 + 1; // 2nd call
|
||||||
|
|
||||||
int32_t n28a = pack28(call1);
|
int32_t n28a = pack28(call1);
|
||||||
int32_t n28b = pack28(call2);
|
int32_t n28b = pack28(call2);
|
||||||
|
@ -187,7 +183,7 @@ int pack77_1(const char *msg, uint8_t *b77)
|
||||||
uint16_t igrid4;
|
uint16_t igrid4;
|
||||||
|
|
||||||
// Locate the second delimiter
|
// Locate the second delimiter
|
||||||
const char *s2 = strchr(s1 + 1, ' ');
|
const char* s2 = strchr(s1 + 1, ' ');
|
||||||
if (s2 != 0)
|
if (s2 != 0)
|
||||||
{
|
{
|
||||||
igrid4 = packgrid(s2 + 1);
|
igrid4 = packgrid(s2 + 1);
|
||||||
|
@ -221,7 +217,7 @@ int pack77_1(const char *msg, uint8_t *b77)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void packtext77(const char *text, uint8_t *b77)
|
void packtext77(const char* text, uint8_t* b77)
|
||||||
{
|
{
|
||||||
int length = strlen(text);
|
int length = strlen(text);
|
||||||
|
|
||||||
|
@ -285,7 +281,7 @@ void packtext77(const char *text, uint8_t *b77)
|
||||||
b77[9] &= 0x00;
|
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"
|
// 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))
|
||||||
|
@ -310,7 +306,7 @@ int pack77(const char *msg, uint8_t *c77)
|
||||||
|
|
||||||
bool test1()
|
bool test1()
|
||||||
{
|
{
|
||||||
const char *inputs[] = {
|
const char* inputs[] = {
|
||||||
"",
|
"",
|
||||||
" ",
|
" ",
|
||||||
"ABC",
|
"ABC",
|
||||||
|
@ -321,7 +317,8 @@ bool test1()
|
||||||
"LL3JG",
|
"LL3JG",
|
||||||
"LL3AJG",
|
"LL3AJG",
|
||||||
"CQ ",
|
"CQ ",
|
||||||
0};
|
0
|
||||||
|
};
|
||||||
|
|
||||||
for (int i = 0; inputs[i]; ++i)
|
for (int i = 0; inputs[i]; ++i)
|
||||||
{
|
{
|
||||||
|
@ -334,14 +331,15 @@ bool test1()
|
||||||
|
|
||||||
bool test2()
|
bool test2()
|
||||||
{
|
{
|
||||||
const char *inputs[] = {
|
const char* inputs[] = {
|
||||||
"CQ LL3JG",
|
"CQ LL3JG",
|
||||||
"CQ LL3JG KO26",
|
"CQ LL3JG KO26",
|
||||||
"L0UAA LL3JG KO26",
|
"L0UAA LL3JG KO26",
|
||||||
"L0UAA LL3JG +02",
|
"L0UAA LL3JG +02",
|
||||||
"L0UAA LL3JG RRR",
|
"L0UAA LL3JG RRR",
|
||||||
"L0UAA LL3JG 73",
|
"L0UAA LL3JG 73",
|
||||||
0};
|
0
|
||||||
|
};
|
||||||
|
|
||||||
for (int i = 0; inputs[i]; ++i)
|
for (int i = 0; inputs[i]; ++i)
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,6 +6,6 @@
|
||||||
// Pack FT8 text message into 72 bits
|
// Pack FT8 text message into 72 bits
|
||||||
// [IN] msg - FT8 message (e.g. "CQ TE5T KN01")
|
// [IN] msg - FT8 message (e.g. "CQ TE5T KN01")
|
||||||
// [OUT] c77 - 10 byte array to store the 77 bit payload (MSB first)
|
// [OUT] c77 - 10 byte array to store the 77 bit payload (MSB first)
|
||||||
int pack77(const char *msg, uint8_t *c77);
|
int pack77(const char* msg, uint8_t* c77);
|
||||||
|
|
||||||
#endif // _INCLUDE_PACK_H_
|
#endif // _INCLUDE_PACK_H_
|
||||||
|
|
20
ft8/text.c
20
ft8/text.c
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
const char *trim_front(const char *str)
|
const char* trim_front(const char* str)
|
||||||
{
|
{
|
||||||
// Skip leading whitespace
|
// Skip leading whitespace
|
||||||
while (*str == ' ')
|
while (*str == ' ')
|
||||||
|
@ -12,7 +12,7 @@ const char *trim_front(const char *str)
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
void trim_back(char *str)
|
void trim_back(char* str)
|
||||||
{
|
{
|
||||||
// Skip trailing whitespace by replacing it with '\0' characters
|
// Skip trailing whitespace by replacing it with '\0' characters
|
||||||
int idx = strlen(str) - 1;
|
int idx = strlen(str) - 1;
|
||||||
|
@ -24,9 +24,9 @@ void trim_back(char *str)
|
||||||
|
|
||||||
// 1) trims a string from the back by changing whitespaces to '\0'
|
// 1) trims a string from the back by changing whitespaces to '\0'
|
||||||
// 2) trims a string from the front by skipping whitespaces
|
// 2) trims a string from the front by skipping whitespaces
|
||||||
char *trim(char *str)
|
char* trim(char* str)
|
||||||
{
|
{
|
||||||
str = (char *)trim_front(str);
|
str = (char*)trim_front(str);
|
||||||
trim_back(str);
|
trim_back(str);
|
||||||
// return a pointer to the first non-whitespace character
|
// return a pointer to the first non-whitespace character
|
||||||
return str;
|
return str;
|
||||||
|
@ -57,17 +57,17 @@ bool in_range(char c, char min, char max)
|
||||||
return (c >= min) && (c <= 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));
|
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);
|
return 0 == strcmp(string1, string2);
|
||||||
}
|
}
|
||||||
|
|
||||||
int char_index(const char *string, char c)
|
int char_index(const char* string, char c)
|
||||||
{
|
{
|
||||||
for (int i = 0; *string; ++i, ++string)
|
for (int i = 0; *string; ++i, ++string)
|
||||||
{
|
{
|
||||||
|
@ -82,7 +82,7 @@ int char_index(const char *string, char c)
|
||||||
// Text message formatting:
|
// Text message formatting:
|
||||||
// - replaces lowercase letters with uppercase
|
// - replaces lowercase letters with uppercase
|
||||||
// - merges consecutive spaces into single space
|
// - 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 c;
|
||||||
char last_out = 0;
|
char last_out = 0;
|
||||||
|
@ -100,7 +100,7 @@ void fmtmsg(char *msg_out, const char *msg_in)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse a 2 digit integer from string
|
// 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;
|
int result = 0;
|
||||||
bool negative;
|
bool negative;
|
||||||
|
@ -131,7 +131,7 @@ int dd_to_int(const char *str, int length)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert a 2 digit integer to string
|
// Convert a 2 digit integer to string
|
||||||
void int_to_dd(char *str, int value, int width, bool full_sign)
|
void int_to_dd(char* str, int value, int width, bool full_sign)
|
||||||
{
|
{
|
||||||
if (value < 0)
|
if (value < 0)
|
||||||
{
|
{
|
||||||
|
|
18
ft8/text.h
18
ft8/text.h
|
@ -6,30 +6,30 @@
|
||||||
|
|
||||||
// Utility functions for characters and strings
|
// Utility functions for characters and strings
|
||||||
|
|
||||||
const char *trim_front(const char *str);
|
const char* trim_front(const char* str);
|
||||||
void trim_back(char *str);
|
void trim_back(char* str);
|
||||||
char *trim(char *str);
|
char* trim(char* str);
|
||||||
|
|
||||||
char to_upper(char c);
|
char to_upper(char c);
|
||||||
bool is_digit(char c);
|
bool is_digit(char c);
|
||||||
bool is_letter(char c);
|
bool is_letter(char c);
|
||||||
bool is_space(char c);
|
bool is_space(char c);
|
||||||
bool in_range(char c, char min, char max);
|
bool in_range(char c, char min, char max);
|
||||||
bool starts_with(const char *string, const char *prefix);
|
bool starts_with(const char* string, const char* prefix);
|
||||||
bool equals(const char *string1, const char *string2);
|
bool equals(const char* string1, const char* string2);
|
||||||
|
|
||||||
int char_index(const char *string, char c);
|
int char_index(const char* string, char c);
|
||||||
|
|
||||||
// Text message formatting:
|
// Text message formatting:
|
||||||
// - replaces lowercase letters with uppercase
|
// - replaces lowercase letters with uppercase
|
||||||
// - merges consecutive spaces into single space
|
// - merges consecutive spaces into single space
|
||||||
void fmtmsg(char *msg_out, const char *msg_in);
|
void fmtmsg(char* msg_out, const char* msg_in);
|
||||||
|
|
||||||
// Parse a 2 digit integer from string
|
// 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);
|
||||||
|
|
||||||
// Convert a 2 digit integer to string
|
// Convert a 2 digit integer to string
|
||||||
void int_to_dd(char *str, int value, int width, bool full_sign);
|
void int_to_dd(char* str, int value, int width, bool full_sign);
|
||||||
|
|
||||||
char charn(int c, int table_idx);
|
char charn(int c, int table_idx);
|
||||||
int nchar(char c, int table_idx);
|
int nchar(char c, int table_idx);
|
||||||
|
|
44
ft8/unpack.c
44
ft8/unpack.c
|
@ -1,18 +1,18 @@
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
#define _GNU_SOURCE
|
#define _GNU_SOURCE
|
||||||
#endif
|
#endif
|
||||||
#include "unpack.h"
|
#include "unpack.h"
|
||||||
#include "text.h"
|
#include "text.h"
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#define MAX22 ((uint32_t)4194304L)
|
#define MAX22 ((uint32_t)4194304L)
|
||||||
#define NTOKENS ((uint32_t)2063592L)
|
#define NTOKENS ((uint32_t)2063592L)
|
||||||
#define MAXGRID4 ((uint16_t)32400L)
|
#define MAXGRID4 ((uint16_t)32400L)
|
||||||
|
|
||||||
// n28 is a 28-bit integer, e.g. n28a or n28b, containing all the
|
// n28 is a 28-bit integer, e.g. n28a or n28b, containing all the
|
||||||
// call sign bits from a packed message.
|
// call sign bits from a packed message.
|
||||||
int unpack_callsign(uint32_t n28, uint8_t ip, uint8_t i3, char *result)
|
int unpack_callsign(uint32_t n28, uint8_t ip, uint8_t i3, char* result)
|
||||||
{
|
{
|
||||||
// Check for special tokens DE, QRZ, CQ, CQ_nnn, CQ_aaaa
|
// Check for special tokens DE, QRZ, CQ, CQ_nnn, CQ_aaaa
|
||||||
if (n28 < NTOKENS)
|
if (n28 < NTOKENS)
|
||||||
|
@ -108,7 +108,7 @@ int unpack_callsign(uint32_t n28, uint8_t ip, uint8_t i3, char *result)
|
||||||
return 0; // Success
|
return 0; // Success
|
||||||
}
|
}
|
||||||
|
|
||||||
int unpack_type1(const uint8_t *a77, uint8_t i3, char *call_to, char *call_de, char *extra)
|
int unpack_type1(const uint8_t* a77, uint8_t i3, char* call_to, char* call_de, char* extra)
|
||||||
{
|
{
|
||||||
uint32_t n28a, n28b;
|
uint32_t n28a, n28b;
|
||||||
uint16_t igrid4;
|
uint16_t igrid4;
|
||||||
|
@ -148,7 +148,7 @@ int unpack_type1(const uint8_t *a77, uint8_t i3, char *call_to, char *call_de, c
|
||||||
// save_hash_call(call_de)
|
// save_hash_call(call_de)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
char *dst = extra;
|
char* dst = extra;
|
||||||
|
|
||||||
if (igrid4 <= MAXGRID4)
|
if (igrid4 <= MAXGRID4)
|
||||||
{
|
{
|
||||||
|
@ -205,7 +205,7 @@ int unpack_type1(const uint8_t *a77, uint8_t i3, char *call_to, char *call_de, c
|
||||||
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
|
// TODO: test
|
||||||
uint8_t b71[9];
|
uint8_t b71[9];
|
||||||
|
@ -237,7 +237,7 @@ int unpack_text(const uint8_t *a71, char *text)
|
||||||
return 0; // Success
|
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];
|
uint8_t b71[9];
|
||||||
|
|
||||||
|
@ -266,21 +266,21 @@ int unpack_telemetry(const uint8_t *a71, char *telemetry)
|
||||||
|
|
||||||
//none standard for wsjt-x 2.0
|
//none standard for wsjt-x 2.0
|
||||||
//by KD8CEC
|
//by KD8CEC
|
||||||
int unpack_nonstandard(const uint8_t *a77, char *call_to, char *call_de, char *extra)
|
int unpack_nonstandard(const uint8_t* a77, char* call_to, char* call_de, char* extra)
|
||||||
{
|
{
|
||||||
uint32_t n12, iflip, nrpt, icq;
|
uint32_t n12, iflip, nrpt, icq;
|
||||||
uint64_t n58;
|
uint64_t n58;
|
||||||
n12 = (a77[0] << 4); //11 ~4 : 8
|
n12 = (a77[0] << 4); //11 ~4 : 8
|
||||||
n12 |= (a77[1] >> 4); //3~0 : 12
|
n12 |= (a77[1] >> 4); //3~0 : 12
|
||||||
|
|
||||||
n58 = ((uint64_t)(a77[1] & 0x0F) << 54); //57 ~ 54 : 4
|
n58 = ((uint64_t)(a77[1] & 0x0F) << 54); //57 ~ 54 : 4
|
||||||
n58 |= ((uint64_t)a77[2] << 46); //53 ~ 46 : 12
|
n58 |= ((uint64_t)a77[2] << 46); //53 ~ 46 : 12
|
||||||
n58 |= ((uint64_t)a77[3] << 38); //45 ~ 38 : 12
|
n58 |= ((uint64_t)a77[3] << 38); //45 ~ 38 : 12
|
||||||
n58 |= ((uint64_t)a77[4] << 30); //37 ~ 30 : 12
|
n58 |= ((uint64_t)a77[4] << 30); //37 ~ 30 : 12
|
||||||
n58 |= ((uint64_t)a77[5] << 22); //29 ~ 22 : 12
|
n58 |= ((uint64_t)a77[5] << 22); //29 ~ 22 : 12
|
||||||
n58 |= ((uint64_t)a77[6] << 14); //21 ~ 14 : 12
|
n58 |= ((uint64_t)a77[6] << 14); //21 ~ 14 : 12
|
||||||
n58 |= ((uint64_t)a77[7] << 6); //13 ~ 6 : 12
|
n58 |= ((uint64_t)a77[7] << 6); //13 ~ 6 : 12
|
||||||
n58 |= ((uint64_t)a77[8] >> 2); //5 ~ 0 : 765432 10
|
n58 |= ((uint64_t)a77[8] >> 2); //5 ~ 0 : 765432 10
|
||||||
|
|
||||||
iflip = (a77[8] >> 1) & 0x01; //76543210
|
iflip = (a77[8] >> 1) & 0x01; //76543210
|
||||||
nrpt = ((a77[8] & 0x01) << 1);
|
nrpt = ((a77[8] & 0x01) << 1);
|
||||||
|
@ -306,8 +306,8 @@ int unpack_nonstandard(const uint8_t *a77, char *call_to, char *call_de, char *e
|
||||||
// call_3[5] = '>';
|
// call_3[5] = '>';
|
||||||
// call_3[6] = '\0';
|
// call_3[6] = '\0';
|
||||||
|
|
||||||
char *call_1 = (iflip) ? c11 : call_3;
|
char* call_1 = (iflip) ? c11 : call_3;
|
||||||
char *call_2 = (iflip) ? call_3 : c11;
|
char* call_2 = (iflip) ? call_3 : c11;
|
||||||
//save_hash_call(c11_trimmed);
|
//save_hash_call(c11_trimmed);
|
||||||
|
|
||||||
if (icq == 0)
|
if (icq == 0)
|
||||||
|
@ -334,7 +334,7 @@ int unpack_nonstandard(const uint8_t *a77, char *call_to, char *call_de, char *e
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int unpack77_fields(const uint8_t *a77, char *call_to, char *call_de, char *extra)
|
int unpack77_fields(const uint8_t* a77, char* call_to, char* call_de, char* extra)
|
||||||
{
|
{
|
||||||
call_to[0] = call_de[0] = extra[0] = '\0';
|
call_to[0] = call_de[0] = extra[0] = '\0';
|
||||||
|
|
||||||
|
@ -390,7 +390,7 @@ int unpack77_fields(const uint8_t *a77, char *call_to, char *call_de, char *extr
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int unpack77(const uint8_t *a77, char *message)
|
int unpack77(const uint8_t* a77, char* message)
|
||||||
{
|
{
|
||||||
char call_to[14];
|
char call_to[14];
|
||||||
char call_de[14];
|
char call_de[14];
|
||||||
|
@ -401,7 +401,7 @@ int unpack77(const uint8_t *a77, char *message)
|
||||||
return rc;
|
return rc;
|
||||||
|
|
||||||
// int msg_sz = strlen(call_to) + strlen(call_de) + strlen(extra) + 2;
|
// int msg_sz = strlen(call_to) + strlen(call_de) + strlen(extra) + 2;
|
||||||
char *dst = message;
|
char* dst = message;
|
||||||
|
|
||||||
dst[0] = '\0';
|
dst[0] = '\0';
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,9 @@
|
||||||
// field1 - at least 14 bytes
|
// field1 - at least 14 bytes
|
||||||
// field2 - at least 14 bytes
|
// field2 - at least 14 bytes
|
||||||
// field3 - at least 7 bytes
|
// field3 - at least 7 bytes
|
||||||
int unpack77_fields(const uint8_t *a77, char *field1, char *field2, char *field3);
|
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)
|
// message should have at least 35 bytes allocated (34 characters + zero terminator)
|
||||||
int unpack77(const uint8_t *a77, char *message);
|
int unpack77(const uint8_t* a77, char* message);
|
||||||
|
|
||||||
#endif // _INCLUDE_UNPACK_H_
|
#endif // _INCLUDE_UNPACK_H_
|
||||||
|
|
28
gen_ft8.c
28
gen_ft8.c
|
@ -13,13 +13,13 @@
|
||||||
|
|
||||||
#define LOG_LEVEL LOG_INFO
|
#define LOG_LEVEL LOG_INFO
|
||||||
|
|
||||||
#define FT8_SLOT_TIME 15.0f // total length of output waveform in seconds
|
#define FT8_SLOT_TIME 15.0f // total length of output waveform in seconds
|
||||||
#define FT8_SYMBOL_RATE 6.25f // tone deviation (and symbol rate) in Hz
|
#define FT8_SYMBOL_RATE 6.25f // tone deviation (and symbol rate) in Hz
|
||||||
#define FT8_SYMBOL_BT 2.0f // symbol smoothing filter bandwidth factor (BT)
|
#define FT8_SYMBOL_BT 2.0f // symbol smoothing filter bandwidth factor (BT)
|
||||||
|
|
||||||
#define FT4_SLOT_TIME 7.5f // total length of output waveform in seconds
|
#define FT4_SLOT_TIME 7.5f // total length of output waveform in seconds
|
||||||
#define FT4_SYMBOL_RATE 20.833333f // tone deviation (and symbol rate) in Hz
|
#define FT4_SYMBOL_RATE 20.833333f // tone deviation (and symbol rate) in Hz
|
||||||
#define FT4_SYMBOL_BT 1.0f // symbol smoothing filter bandwidth factor (BT)
|
#define FT4_SYMBOL_BT 1.0f // symbol smoothing filter bandwidth factor (BT)
|
||||||
|
|
||||||
#define GFSK_CONST_K 5.336446f // pi * sqrt(2 / log(2))
|
#define GFSK_CONST_K 5.336446f // pi * sqrt(2 / log(2))
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@
|
||||||
/// @param[in] b Shape parameter (values defined for FT8/FT4)
|
/// @param[in] b Shape parameter (values defined for FT8/FT4)
|
||||||
/// @param[out] pulse Output array of pulse samples
|
/// @param[out] pulse Output array of pulse samples
|
||||||
///
|
///
|
||||||
void gfsk_pulse(int n_spsym, float symbol_bt, float *pulse)
|
void gfsk_pulse(int n_spsym, float symbol_bt, float* pulse)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < 3 * n_spsym; ++i)
|
for (int i = 0; i < 3 * n_spsym; ++i)
|
||||||
{
|
{
|
||||||
|
@ -51,10 +51,10 @@ void gfsk_pulse(int n_spsym, float symbol_bt, float *pulse)
|
||||||
/// @param[in] signal_rate Sample rate of synthesized signal, Hertz
|
/// @param[in] signal_rate Sample rate of synthesized signal, Hertz
|
||||||
/// @param[out] signal Output array of signal waveform samples (should have space for n_sym*n_spsym samples)
|
/// @param[out] signal Output array of signal waveform samples (should have space for n_sym*n_spsym samples)
|
||||||
///
|
///
|
||||||
void synth_gfsk(const uint8_t *symbols, int n_sym, float f0, float symbol_bt, float symbol_rate, int signal_rate, float *signal)
|
void synth_gfsk(const uint8_t* symbols, int n_sym, float f0, float symbol_bt, float symbol_rate, int signal_rate, float* signal)
|
||||||
{
|
{
|
||||||
int n_spsym = (int)(0.5f + signal_rate / symbol_rate); // Samples per symbol
|
int n_spsym = (int)(0.5f + signal_rate / symbol_rate); // Samples per symbol
|
||||||
int n_wave = n_sym * n_spsym; // Number of output samples
|
int n_wave = n_sym * n_spsym; // Number of output samples
|
||||||
float hmod = 1.0f;
|
float hmod = 1.0f;
|
||||||
|
|
||||||
LOG(LOG_DEBUG, "n_spsym = %d\n", n_spsym);
|
LOG(LOG_DEBUG, "n_spsym = %d\n", n_spsym);
|
||||||
|
@ -116,7 +116,7 @@ void usage()
|
||||||
printf("(Note that you might have to enclose your message in quote marks if it contains spaces)\n");
|
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
|
// Expect two command-line arguments
|
||||||
if (argc < 3)
|
if (argc < 3)
|
||||||
|
@ -125,8 +125,8 @@ int main(int argc, char **argv)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *message = argv[1];
|
const char* message = argv[1];
|
||||||
const char *wav_path = argv[2];
|
const char* wav_path = argv[2];
|
||||||
float frequency = 1000.0;
|
float frequency = 1000.0;
|
||||||
if (argc > 3)
|
if (argc > 3)
|
||||||
{
|
{
|
||||||
|
@ -170,11 +170,11 @@ int main(int argc, char **argv)
|
||||||
uint8_t tones[num_tones]; // Array of 79 tones (symbols)
|
uint8_t tones[num_tones]; // Array of 79 tones (symbols)
|
||||||
if (is_ft4)
|
if (is_ft4)
|
||||||
{
|
{
|
||||||
genft4(packed, tones);
|
ft4_encode(packed, tones);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
genft8(packed, tones);
|
ft8_encode(packed, tones);
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("FSK tones: ");
|
printf("FSK tones: ");
|
||||||
|
@ -187,8 +187,8 @@ int main(int argc, char **argv)
|
||||||
// Third, convert the FSK tones into an audio signal
|
// Third, convert the FSK tones into an audio signal
|
||||||
int sample_rate = 12000;
|
int sample_rate = 12000;
|
||||||
int num_samples = (int)(0.5f + num_tones / symbol_rate * sample_rate); // Number of samples in the data signal
|
int num_samples = (int)(0.5f + num_tones / symbol_rate * sample_rate); // Number of samples in the data signal
|
||||||
int num_silence = (slot_time * sample_rate - num_samples) / 2; // Silence padding at both ends to make 15 seconds
|
int num_silence = (slot_time * sample_rate - num_samples) / 2; // Silence padding at both ends to make 15 seconds
|
||||||
int num_total_samples = num_silence + num_samples + num_silence; // Number of samples in the padded signal
|
int num_total_samples = num_silence + num_samples + num_silence; // Number of samples in the padded signal
|
||||||
float signal[num_total_samples];
|
float signal[num_total_samples];
|
||||||
for (int i = 0; i < num_silence; i++)
|
for (int i = 0; i < num_silence; i++)
|
||||||
{
|
{
|
||||||
|
|
10
test.c
10
test.c
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
#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
|
// 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)
|
||||||
|
@ -96,17 +96,17 @@ void test2() {
|
||||||
|
|
||||||
void test3() {
|
void test3() {
|
||||||
uint8_t test_in2[10] = { 0x11, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x10, 0x04, 0x01, 0x00 };
|
uint8_t test_in2[10] = { 0x11, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x10, 0x04, 0x01, 0x00 };
|
||||||
uint16_t crc1 = ft8_crc(test_in2, 76); // Calculate CRC of 76 bits only
|
uint16_t crc1 = ftx_compute_crc(test_in2, 76); // Calculate CRC of 76 bits only
|
||||||
LOG(LOG_INFO, "CRC: %04x\n", crc1); // should be 0x0708
|
LOG(LOG_INFO, "CRC: %04x\n", crc1); // should be 0x0708
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
void test_tones(float *log174)
|
void test_tones(float* log174)
|
||||||
{
|
{
|
||||||
// Just a test case
|
// Just a test case
|
||||||
for (int i = 0; i < FT8_ND; ++i)
|
for (int i = 0; i < FT8_ND; ++i)
|
||||||
{
|
{
|
||||||
const uint8_t inv_map[8] = {0, 1, 3, 2, 6, 4, 5, 7};
|
const uint8_t inv_map[8] = { 0, 1, 3, 2, 6, 4, 5, 7 };
|
||||||
uint8_t tone = ("0000000011721762454112705354533170166234757420515470163426"[i]) - '0';
|
uint8_t tone = ("0000000011721762454112705354533170166234757420515470163426"[i]) - '0';
|
||||||
uint8_t b3 = inv_map[tone];
|
uint8_t b3 = inv_map[tone];
|
||||||
log174[3 * i] = (b3 & 4) ? +1.0 : -1.0;
|
log174[3 * i] = (b3 & 4) ? +1.0 : -1.0;
|
||||||
|
@ -126,7 +126,7 @@ void test4()
|
||||||
printf("N_FFT = %d\n", nfft);
|
printf("N_FFT = %d\n", nfft);
|
||||||
printf("FFT work area = %lu\n", fft_work_size);
|
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_fftr_cfg fft_cfg = kiss_fftr_alloc(nfft, 0, fft_work, &fft_work_size);
|
||||||
|
|
||||||
kiss_fft_scalar window[nfft];
|
kiss_fft_scalar window[nfft];
|
||||||
|
|
Ładowanie…
Reference in New Issue