Reformatted code

pull/21/head
chrnadig 2022-05-05 22:14:38 +02:00
rodzic e7184228b3
commit 7699003ade
14 zmienionych plików z 379 dodań i 898 usunięć

Wyświetl plik

@ -17,14 +17,14 @@ void save_wav(const float* signal, int num_samples, int sample_rate, const char*
uint32_t sampleRate = sample_rate;
uint16_t blockAlign = numChannels * bitsPerSample / 8;
uint32_t byteRate = sampleRate * blockAlign;
char subChunk2ID[4] = { 'd', 'a', 't', 'a' };
uint32_t subChunk2Size = num_samples * blockAlign;
char chunkID[4] = { 'R', 'I', 'F', 'F' };
uint32_t chunkSize = 4 + (8 + subChunk1Size) + (8 + subChunk2Size);
char format[4] = { 'W', 'A', 'V', 'E' };
int16_t* raw_data = (int16_t*)malloc(num_samples * blockAlign);
for (int i = 0; i < num_samples; i++) {
float x = signal[i];
@ -34,14 +34,14 @@ void save_wav(const float* signal, int num_samples, int sample_rate, const char*
x = -1.0;
raw_data[i] = (int)(0.5 + (x * 32767.0));
}
FILE* f = fopen(path, "wb");
// NOTE: works only on little-endian architecture
fwrite(chunkID, sizeof(chunkID), 1, f);
fwrite(&chunkSize, sizeof(chunkSize), 1, f);
fwrite(format, sizeof(format), 1, f);
fwrite(subChunk1ID, sizeof(subChunk1ID), 1, f);
fwrite(&subChunk1Size, sizeof(subChunk1Size), 1, f);
fwrite(&audioFormat, sizeof(audioFormat), 1, f);
@ -50,14 +50,14 @@ void save_wav(const float* signal, int num_samples, int sample_rate, const char*
fwrite(&byteRate, sizeof(byteRate), 1, f);
fwrite(&blockAlign, sizeof(blockAlign), 1, f);
fwrite(&bitsPerSample, sizeof(bitsPerSample), 1, f);
fwrite(subChunk2ID, sizeof(subChunk2ID), 1, f);
fwrite(&subChunk2Size, sizeof(subChunk2Size), 1, f);
fwrite(raw_data, blockAlign, num_samples, f);
fclose(f);
free(raw_data);
}
@ -72,59 +72,60 @@ int load_wav(float* signal, int* num_samples, int* sample_rate, const char* path
uint32_t sampleRate;
uint16_t blockAlign; // = numChannels * bitsPerSample / 8;
uint32_t byteRate; // = sampleRate * blockAlign;
char subChunk2ID[4]; // = {'d', 'a', 't', 'a'};
uint32_t subChunk2Size; // = num_samples * blockAlign;
char chunkID[4]; // = {'R', 'I', 'F', 'F'};
uint32_t chunkSize; // = 4 + (8 + subChunk1Size) + (8 + subChunk2Size);
char format[4]; // = {'W', 'A', 'V', 'E'};
FILE* f = fopen(path, "rb");
if (f == NULL) {
return -1;
}
// NOTE: works only on little-endian architecture
fread((void*)chunkID, sizeof(chunkID), 1, f);
fread((void*)&chunkSize, sizeof(chunkSize), 1, f);
fread((void*)format, sizeof(format), 1, f);
fread((void*)subChunk1ID, sizeof(subChunk1ID), 1, f);
fread((void*)&subChunk1Size, sizeof(subChunk1Size), 1, f);
if (subChunk1Size != 16)
return -1;
fread((void*)&audioFormat, sizeof(audioFormat), 1, f);
fread((void*)&numChannels, sizeof(numChannels), 1, f);
fread((void*)&sampleRate, sizeof(sampleRate), 1, f);
fread((void*)&byteRate, sizeof(byteRate), 1, f);
fread((void*)&blockAlign, sizeof(blockAlign), 1, f);
fread((void*)&bitsPerSample, sizeof(bitsPerSample), 1, f);
if (audioFormat != 1 || numChannels != 1 || bitsPerSample != 16)
return -1;
fread((void*)subChunk2ID, sizeof(subChunk2ID), 1, f);
fread((void*)&subChunk2Size, sizeof(subChunk2Size), 1, f);
if (subChunk2Size / blockAlign > *num_samples)
return -2;
*num_samples = subChunk2Size / blockAlign;
*sample_rate = sampleRate;
int16_t* raw_data = (int16_t*)malloc(*num_samples * blockAlign);
fread((void*)raw_data, blockAlign, *num_samples, f);
for (int i = 0; i < *num_samples; i++) {
signal[i] = raw_data[i] / 32768.0f;
}
free(raw_data);
fclose(f);
return 0;
}

Wyświetl plik

@ -1,18 +1,18 @@
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "ft8.h"
#include "common/wave.h"
#include "ft8.h"
static FILE *reference;
static FILE* reference;
static bool identical;
// decode callback, called by ft8_decode() for each decoded message
static void ft8_decode_callback(char *message, float frequency, float time_dev, float snr, int score, void *ctx)
static void ft8_decode_callback(char* message, float frequency, float time_dev, float snr, int score, void* ctx)
{
char buf[256], buf2[256];
printf("000000 %3d %+4.2f %4.1f %4.0f ~ %s\n", score, time_dev, snr, frequency, message);
@ -24,7 +24,7 @@ static void ft8_decode_callback(char *message, float frequency, float time_dev,
}
}
int main(int argc, char **argv)
int main(int argc, char** argv)
{
#if 0
// Expect one command-line argument
@ -59,13 +59,12 @@ int main(int argc, char **argv)
int16_t pcm[num_samples];
char buf[256];
int fd = open(argv[1], O_RDONLY);
if (fd == -1) {
printf("Could not load PCM (16 bit little endian) file (check format and size)\n");
return -1;
}
reference = fopen("/Users/nadig/Desktop/reference.txt", "r");
identical = true;
@ -91,3 +90,4 @@ int main(int argc, char **argv)
#endif
return 0;
}

Wyświetl plik

@ -4,20 +4,20 @@
#include <stdint.h>
#define FT8_SYMBOL_PERIOD (0.160f) ///< FT8 symbol duration, defines tone deviation in Hz and symbol rate
#define FT8_SLOT_TIME (15.0f) ///< FT8 slot period
#define FT8_SLOT_TIME (15.0f) ///< FT8 slot period
#define FT4_SYMBOL_PERIOD (0.048f) ///< FT4 symbol duration, defines tone deviation in Hz and symbol rate
#define FT4_SLOT_TIME (7.5f) ///< FT4 slot period
#define FT4_SLOT_TIME (7.5f) ///< FT4 slot period
// Define FT8 symbol counts
// FT8 message structure:
// S D1 S D2 S
// S - sync block (7 symbols of Costas pattern)
// D1 - first data block (29 symbols each encoding 3 bits)
#define FT8_ND (58) ///< Data symbols
#define FT8_NN (79) ///< Total channel symbols (FT8_NS + FT8_ND)
#define FT8_LENGTH_SYNC (7) ///< Length of each sync group
#define FT8_NUM_SYNC (3) ///< Number of sync groups
#define FT8_ND (58) ///< Data symbols
#define FT8_NN (79) ///< Total channel symbols (FT8_NS + FT8_ND)
#define FT8_LENGTH_SYNC (7) ///< Length of each sync group
#define FT8_NUM_SYNC (3) ///< Number of sync groups
#define FT8_SYNC_OFFSET (36) ///< Offset between sync groups
// Define FT4 symbol counts
@ -26,23 +26,23 @@
// R - ramping symbol (no payload information conveyed)
// Sx - one of four _different_ sync blocks (4 symbols of Costas pattern)
// Dy - data block (29 symbols each encoding 2 bits)
#define FT4_ND (87) ///< Data symbols
#define FT4_NR (2) ///< Ramp symbols (beginning + end)
#define FT4_NN (105) ///< Total channel symbols (FT4_NS + FT4_ND + FT4_NR)
#define FT4_LENGTH_SYNC (4) ///< Length of each sync group
#define FT4_NUM_SYNC (4) ///< Number of sync groups
#define FT4_SYNC_OFFSET (33) ///< Offset between sync groups
#define FT4_ND (87) ///< Data symbols
#define FT4_NR (2) ///< Ramp symbols (beginning + end)
#define FT4_NN (105) ///< Total channel symbols (FT4_NS + FT4_ND + FT4_NR)
#define FT4_LENGTH_SYNC (4) ///< Length of each sync group
#define FT4_NUM_SYNC (4) ///< Number of sync groups
#define FT4_SYNC_OFFSET (33) ///< Offset between sync groups
// Define LDPC parameters
#define FTX_LDPC_N (174) ///< Number of bits in the encoded message (payload with LDPC checksum bits)
#define FTX_LDPC_K (91) ///< Number of payload bits (including CRC)
#define FTX_LDPC_M (83) ///< Number of LDPC checksum bits (FTX_LDPC_N - FTX_LDPC_K)
#define FTX_LDPC_N (174) ///< Number of bits in the encoded message (payload with LDPC checksum bits)
#define FTX_LDPC_K (91) ///< Number of payload bits (including CRC)
#define FTX_LDPC_M (83) ///< Number of LDPC checksum bits (FTX_LDPC_N - FTX_LDPC_K)
#define FTX_LDPC_N_BYTES ((FTX_LDPC_N + 7) / 8) ///< Number of whole bytes needed to store 174 bits (full message)
#define FTX_LDPC_K_BYTES ((FTX_LDPC_K + 7) / 8) ///< Number of whole bytes needed to store 91 bits (payload + CRC only)
// Define CRC parameters
#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
extern const uint8_t kFT8_Costas_pattern[7];
@ -67,3 +67,4 @@ extern const uint8_t kFTX_LDPC_Mn[FTX_LDPC_N][3];
extern const uint8_t kFTX_LDPC_Num_rows[FTX_LDPC_M];
#endif // _INCLUDE_CONSTANTS_H_

Wyświetl plik

@ -1,5 +1,5 @@
#include "crc.h"
#include "constants.h"
#include "crc.h"
#define TOPBIT (1u << (FT8_CRC_WIDTH - 1))
@ -13,22 +13,17 @@ uint16_t ftx_compute_crc(const uint8_t message[], int num_bits)
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] << (FT8_CRC_WIDTH - 8));
++idx_byte;
}
// Try to divide the current data bit.
if (remainder & TOPBIT)
{
if (remainder & TOPBIT) {
remainder = (remainder << 1) ^ FT8_CRC_POLYNOMIAL;
}
else
{
} else {
remainder = (remainder << 1);
}
}
@ -61,3 +56,4 @@ void ftx_add_crc(const uint8_t payload[], uint8_t a91[])
a91[10] = (uint8_t)(checksum >> 3);
a91[11] = (uint8_t)(checksum << 5);
}

Wyświetl plik

@ -1,13 +1,13 @@
#include "ft8.h"
#include "constants.h"
#include "crc.h"
#include "ft8.h"
#include "ldpc.h"
#include "unpack.h"
#include "fft/kiss_fftr.h"
#include <stdbool.h>
#include <math.h>
#include <stdbool.h>
#include <string.h>
const int kMin_score = 10; // Minimum sync score threshold for candidates
@ -25,73 +25,67 @@ const int kTime_osr = 2; // Time oversampling rate (symbol subdivision)
/// Values time_osr > 1 mean each symbol is further subdivided in time.
/// If freq_osr=1, each bin in the FFT magnitude data corresponds to 6.25 Hz, which is the tone spacing.
/// Values freq_osr > 1 mean the tone spacing is further subdivided by FFT analysis.
typedef struct
{
int max_blocks; ///< number of blocks (symbols) allocated in the mag array
int num_blocks; ///< number of blocks (symbols) stored in the mag array
int num_bins; ///< number of FFT bins in terms of 6.25 Hz
int time_osr; ///< number of time subdivisions
int freq_osr; ///< number of frequency subdivisions
uint8_t *mag; ///< FFT magnitudes stored as uint8_t[blocks][time_osr][freq_osr][num_bins]
int block_stride; ///< Helper value = time_osr * freq_osr * num_bins
typedef struct {
int max_blocks; ///< number of blocks (symbols) allocated in the mag array
int num_blocks; ///< number of blocks (symbols) stored in the mag array
int num_bins; ///< number of FFT bins in terms of 6.25 Hz
int time_osr; ///< number of time subdivisions
int freq_osr; ///< number of frequency subdivisions
uint8_t* mag; ///< FFT magnitudes stored as uint8_t[blocks][time_osr][freq_osr][num_bins]
int block_stride; ///< Helper value = time_osr * freq_osr * num_bins
ftx_protocol_t protocol; ///< Indicate if using FT4 or FT8
} waterfall_t;
/// 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.
typedef struct
{
int score; ///< Candidate score (non-negative number; higher score means higher likelihood)
typedef struct {
int score; ///< Candidate score (non-negative number; higher score means higher likelihood)
int time_offset; ///< Index of the time block
int freq_offset; ///< Index of the frequency bin
int time_sub; ///< Index of the time subdivision used
int freq_sub; ///< Index of the frequency subdivision used
int time_sub; ///< Index of the time subdivision used
int freq_sub; ///< Index of the frequency subdivision used
} candidate_t;
/// Structure that holds the decoded message
typedef struct
{
typedef struct {
// TODO: check again that this size is enough
char text[32]; ///< Plain text
uint16_t hash; ///< Hash value to be used in hash table and quick checking for duplicates
} message_t;
/// Structure that contains the status of various steps during decoding of a message
typedef struct
{
int ldpc_errors; ///< Number of LDPC errors during decoding
uint16_t crc_extracted; ///< CRC value recovered from the message
typedef struct {
int ldpc_errors; ///< Number of LDPC errors during decoding
uint16_t crc_extracted; ///< CRC value recovered from the message
uint16_t crc_calculated; ///< CRC value calculated over the payload
int unpack_status; ///< Return value of the unpack routine
int unpack_status; ///< Return value of the unpack routine
} decode_status_t;
/// Configuration options for FT4/FT8 monitor
typedef struct
{
float f_min; ///< Lower frequency bound for analysis
float f_max; ///< Upper frequency bound for analysis
int sample_rate; ///< Sample rate in Hertz
int time_osr; ///< Number of time subdivisions
int freq_osr; ///< Number of frequency subdivisions
typedef struct {
float f_min; ///< Lower frequency bound for analysis
float f_max; ///< Upper frequency bound for analysis
int sample_rate; ///< Sample rate in Hertz
int time_osr; ///< Number of time subdivisions
int freq_osr; ///< Number of frequency subdivisions
ftx_protocol_t protocol; ///< Protocol: FT4 or FT8
} monitor_config_t;
/// FT4/FT8 monitor object that manages DSP processing of incoming audio data
/// and prepares a waterfall object
typedef struct
{
typedef struct {
float symbol_period; ///< FT4/FT8 symbol period in seconds
int block_size; ///< Number of samples per symbol (block)
int subblock_size; ///< Analysis shift size (number of samples)
int nfft; ///< FFT size
float fft_norm; ///< FFT normalization factor
float* window; ///< Window function for STFT analysis (nfft samples)
float* last_frame; ///< Current STFT analysis frame (nfft samples)
waterfall_t wf; ///< Waterfall object
float max_mag; ///< Maximum detected magnitude (debug stats)
int block_size; ///< Number of samples per symbol (block)
int subblock_size; ///< Analysis shift size (number of samples)
int nfft; ///< FFT size
float fft_norm; ///< FFT normalization factor
float* window; ///< Window function for STFT analysis (nfft samples)
float* last_frame; ///< Current STFT analysis frame (nfft samples)
waterfall_t wf; ///< Waterfall object
float max_mag; ///< Maximum detected magnitude (debug stats)
// KISS FFT housekeeping variables
void* fft_work; ///< Work area required by Kiss FFT
void* fft_work; ///< Work area required by Kiss FFT
kiss_fftr_cfg fft_cfg; ///< Kiss FFT housekeeping object
} monitor_t;
@ -115,10 +109,7 @@ static void waterfall_init(waterfall_t* me, int max_blocks, int num_bins, int ti
me->mag = calloc(max_blocks * time_osr * freq_osr * num_bins, sizeof(me->mag[0]));
}
static void waterfall_free(waterfall_t* me)
{
free(me->mag);
}
static void waterfall_free(waterfall_t* me) { free(me->mag); }
static void monitor_init(monitor_t* me, const monitor_config_t* cfg)
{
@ -132,8 +123,7 @@ static void monitor_init(monitor_t* me, const monitor_config_t* cfg)
// const int len_window = 1.8f * me->block_size; // hand-picked and optimized
me->window = calloc(me->nfft, sizeof(me->window[0]));
for (int i = 0; i < me->nfft; ++i)
{
for (int i = 0; i < me->nfft; ++i) {
// hann window
me->window[i] = powf(sinf(M_PI * i / me->nfft), 2.0);
}
@ -173,35 +163,29 @@ static void monitor_process(monitor_t* me, const float* frame)
int frame_pos = 0;
// Loop over block subdivisions
for (int time_sub = 0; time_sub < me->wf.time_osr; ++time_sub)
{
for (int time_sub = 0; time_sub < me->wf.time_osr; ++time_sub) {
kiss_fft_scalar timedata[me->nfft];
kiss_fft_cpx freqdata[me->nfft / 2 + 1];
// Shift the new data into analysis frame
for (int pos = 0; pos < me->nfft - me->subblock_size; ++pos)
{
for (int pos = 0; pos < me->nfft - me->subblock_size; ++pos) {
me->last_frame[pos] = me->last_frame[pos + me->subblock_size];
}
for (int pos = me->nfft - me->subblock_size; pos < me->nfft; ++pos)
{
for (int pos = me->nfft - me->subblock_size; pos < me->nfft; ++pos) {
me->last_frame[pos] = frame[frame_pos];
++frame_pos;
}
// Compute windowed analysis frame
for (int pos = 0; pos < me->nfft; ++pos)
{
for (int pos = 0; pos < me->nfft; ++pos) {
timedata[pos] = me->fft_norm * me->window[pos] * me->last_frame[pos];
}
kiss_fftr(me->fft_cfg, timedata, freqdata);
// Loop over two possible frequency bin offsets (for averaging)
for (int freq_sub = 0; freq_sub < me->wf.freq_osr; ++freq_sub)
{
for (int bin = 0; bin < me->wf.num_bins; ++bin)
{
for (int freq_sub = 0; freq_sub < me->wf.freq_osr; ++freq_sub) {
for (int bin = 0; bin < me->wf.num_bins; ++bin) {
int src_bin = (bin * me->wf.freq_osr) + freq_sub;
float mag2 = (freqdata[src_bin].i * freqdata[src_bin].i) + (freqdata[src_bin].r * freqdata[src_bin].r);
float db = 10.0f * log10f(1E-12f + mag2);
@ -235,22 +219,18 @@ static int get_index(const waterfall_t* wf, const candidate_t* candidate)
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)
{
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])
{
for (int i = 0; i < num_bits; ++i) {
if (bit_array[i]) {
packed[byte_idx] |= mask;
}
mask >>= 1;
if (!mask)
{
if (!mask) {
mask = 0x80;
++byte_idx;
}
@ -266,11 +246,9 @@ static int ft8_sync_score(const waterfall_t* wf, const candidate_t* candidate)
const uint8_t* mag_cand = wf->mag + get_index(wf, candidate);
// Compute average score over sync symbols (m+k = 0-7, 36-43, 72-79)
for (int m = 0; m < FT8_NUM_SYNC; ++m)
{
for (int k = 0; k < FT8_LENGTH_SYNC; ++k)
{
int block = (FT8_SYNC_OFFSET * m) + k; // relative to the message
for (int m = 0; m < FT8_NUM_SYNC; ++m) {
for (int k = 0; k < FT8_LENGTH_SYNC; ++k) {
int block = (FT8_SYNC_OFFSET * m) + k; // relative to the message
int block_abs = candidate->time_offset + block; // relative to the captured signal
// Check for time boundaries
if (block_abs < 0)
@ -279,7 +257,7 @@ static int ft8_sync_score(const waterfall_t* wf, const candidate_t* candidate)
break;
// Get the pointer to symbol 'block' of the candidate
const uint8_t *p8 = mag_cand + (block * wf->block_stride);
const uint8_t* p8 = mag_cand + (block * wf->block_stride);
#if 0
// Weighted difference between the expected and all other symbols
@ -292,26 +270,22 @@ static int ft8_sync_score(const waterfall_t* wf, const candidate_t* candidate)
// Check only the neighbors of the expected symbol frequency- and time-wise
#else
int sm = kFT8_Costas_pattern[k]; // Index of the expected bin
if (sm > 0)
{
if (sm > 0) {
// look at one frequency bin lower
score += p8[sm] - p8[sm - 1];
++num_average;
}
if (sm < 7)
{
if (sm < 7) {
// look at one frequency bin higher
score += p8[sm] - p8[sm + 1];
++num_average;
}
if ((k > 0) && (block_abs > 0))
{
if ((k > 0) && (block_abs > 0)) {
// look one symbol back in time
score += p8[sm] - p8[sm - wf->block_stride];
++num_average;
}
if (((k + 1) < FT8_LENGTH_SYNC) && ((block_abs + 1) < wf->num_blocks))
{
if (((k + 1) < FT8_LENGTH_SYNC) && ((block_abs + 1) < wf->num_blocks)) {
// look one symbol forward in time
score += p8[sm] - p8[sm + wf->block_stride];
++num_average;
@ -335,10 +309,8 @@ static int ft4_sync_score(const waterfall_t* wf, const candidate_t* candidate)
const uint8_t* mag_cand = wf->mag + get_index(wf, candidate);
// Compute average score over sync symbols (block = 1-4, 34-37, 67-70, 100-103)
for (int m = 0; m < FT4_NUM_SYNC; ++m)
{
for (int k = 0; k < FT4_LENGTH_SYNC; ++k)
{
for (int m = 0; m < FT4_NUM_SYNC; ++m) {
for (int k = 0; k < FT4_LENGTH_SYNC; ++k) {
int block = 1 + (FT4_SYNC_OFFSET * m) + k;
int block_abs = candidate->time_offset + block;
// Check for time boundaries
@ -356,26 +328,22 @@ static int ft4_sync_score(const waterfall_t* wf, const candidate_t* candidate)
// num_average += 4;
// Check only the neighbors of the expected symbol frequency- and time-wise
if (sm > 0)
{
if (sm > 0) {
// look at one frequency bin lower
score += p4[sm] - p4[sm - 1];
++num_average;
}
if (sm < 3)
{
if (sm < 3) {
// look at one frequency bin higher
score += p4[sm] - p4[sm + 1];
++num_average;
}
if ((k > 0) && (block_abs > 0))
{
if ((k > 0) && (block_abs > 0)) {
// look one symbol back in time
score += p4[sm] - p4[sm - wf->block_stride];
++num_average;
}
if (((k + 1) < FT4_LENGTH_SYNC) && ((block_abs + 1) < wf->num_blocks))
{
if (((k + 1) < FT4_LENGTH_SYNC) && ((block_abs + 1) < wf->num_blocks)) {
// look one symbol forward in time
score += p4[sm] - p4[sm + wf->block_stride];
++num_average;
@ -398,20 +366,13 @@ int ft8_find_sync(const waterfall_t* wf, int num_candidates, candidate_t heap[],
// 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 (candidate.time_sub = 0; candidate.time_sub < wf->time_osr; ++candidate.time_sub)
{
for (candidate.freq_sub = 0; candidate.freq_sub < wf->freq_osr; ++candidate.freq_sub)
{
for (candidate.time_offset = -12; candidate.time_offset < 24; ++candidate.time_offset)
{
for (candidate.freq_offset = 0; (candidate.freq_offset + 7) < wf->num_bins; ++candidate.freq_offset)
{
if (wf->protocol == PROTO_FT4)
{
for (candidate.time_sub = 0; candidate.time_sub < wf->time_osr; ++candidate.time_sub) {
for (candidate.freq_sub = 0; candidate.freq_sub < wf->freq_osr; ++candidate.freq_sub) {
for (candidate.time_offset = -12; candidate.time_offset < 24; ++candidate.time_offset) {
for (candidate.freq_offset = 0; (candidate.freq_offset + 7) < wf->num_bins; ++candidate.freq_offset) {
if (wf->protocol == PROTO_FT4) {
candidate.score = ft4_sync_score(wf, &candidate);
}
else
{
} else {
candidate.score = ft8_sync_score(wf, &candidate);
}
@ -420,16 +381,14 @@ int ft8_find_sync(const waterfall_t* wf, int num_candidates, candidate_t heap[],
// 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 && candidate.score > heap[0].score)
{
if (heap_size == num_candidates && candidate.score > heap[0].score) {
heap[0] = heap[heap_size - 1];
--heap_size;
heapify_down(heap, heap_size);
}
// 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] = candidate;
++heap_size;
heapify_up(heap, heap_size);
@ -441,8 +400,7 @@ int ft8_find_sync(const waterfall_t* wf, int num_candidates, candidate_t heap[],
// Sort the candidates by sync strength - here we benefit from the heap structure
int len_unsorted = heap_size;
while (len_unsorted > 1)
{
while (len_unsorted > 1) {
candidate_t tmp = heap[len_unsorted - 1];
heap[len_unsorted - 1] = heap[0];
heap[0] = tmp;
@ -458,8 +416,7 @@ static void ft4_extract_likelihood(const waterfall_t* wf, const candidate_t* can
const uint8_t* mag_cand = wf->mag + get_index(wf, cand);
// Go over FSK tones and skip Costas sync symbols
for (int k = 0; k < FT4_ND; ++k)
{
for (int k = 0; k < FT4_ND; ++k) {
// Skip either 5, 9 or 13 sync symbols
// TODO: replace magic numbers with constants
int sym_idx = k + ((k < 29) ? 5 : ((k < 58) ? 9 : 13));
@ -467,13 +424,10 @@ static void ft4_extract_likelihood(const waterfall_t* wf, const candidate_t* can
// Check for time boundaries
int block = cand->time_offset + sym_idx;
if ((block < 0) || (block >= wf->num_blocks))
{
if ((block < 0) || (block >= wf->num_blocks)) {
log174[bit_idx + 0] = 0;
log174[bit_idx + 1] = 0;
}
else
{
} else {
// Pointer to 4 bins of the current symbol
const uint8_t* ps = mag_cand + (sym_idx * wf->block_stride);
@ -487,8 +441,7 @@ static void ft8_extract_likelihood(const waterfall_t* wf, const candidate_t* can
const uint8_t* mag_cand = wf->mag + get_index(wf, cand);
// Go over FSK tones and skip Costas sync symbols
for (int k = 0; k < FT8_ND; ++k)
{
for (int k = 0; k < FT8_ND; ++k) {
// Skip either 7 or 14 sync symbols
// TODO: replace magic numbers with constants
int sym_idx = k + ((k < 29) ? 7 : 14);
@ -496,14 +449,11 @@ static void ft8_extract_likelihood(const waterfall_t* wf, const candidate_t* can
// Check for time boundaries
int block = cand->time_offset + sym_idx;
if ((block < 0) || (block >= wf->num_blocks))
{
if ((block < 0) || (block >= wf->num_blocks)) {
log174[bit_idx + 0] = 0;
log174[bit_idx + 1] = 0;
log174[bit_idx + 2] = 0;
}
else
{
} else {
// Pointer to 8 bins of the current symbol
const uint8_t* ps = mag_cand + (sym_idx * wf->block_stride);
@ -517,8 +467,7 @@ static void ftx_normalize_logl(float* log174)
// Compute the variance of log174
float sum = 0;
float sum2 = 0;
for (int i = 0; i < FTX_LDPC_N; ++i)
{
for (int i = 0; i < FTX_LDPC_N; ++i) {
sum += log174[i];
sum2 += log174[i] * log174[i];
}
@ -527,8 +476,7 @@ static void ftx_normalize_logl(float* log174)
// Normalize log174 distribution and scale it with experimentally found coefficient
float norm_factor = sqrtf(24.0f / variance);
for (int i = 0; i < FTX_LDPC_N; ++i)
{
for (int i = 0; i < FTX_LDPC_N; ++i) {
log174[i] *= norm_factor;
}
}
@ -536,12 +484,9 @@ static void ftx_normalize_logl(float* log174)
bool ft8_decode(const waterfall_t* wf, const candidate_t* cand, message_t* message, int max_iterations, decode_status_t* status)
{
float log174[FTX_LDPC_N]; // message bits encoded as likelihood
if (wf->protocol == PROTO_FT4)
{
if (wf->protocol == PROTO_FT4) {
ft4_extract_likelihood(wf, cand, log174);
}
else
{
} else {
ft8_extract_likelihood(wf, cand, log174);
}
@ -551,8 +496,7 @@ bool ft8_decode(const waterfall_t* wf, const candidate_t* cand, message_t* messa
bp_decode(log174, max_iterations, plain174, &status->ldpc_errors);
// ldpc_decode(log174, max_iterations, plain174, &status->ldpc_errors);
if (status->ldpc_errors > 0)
{
if (status->ldpc_errors > 0) {
return false;
}
@ -567,25 +511,21 @@ bool ft8_decode(const waterfall_t* wf, const candidate_t* cand, message_t* messa
a91[10] &= 0x00;
status->crc_calculated = ftx_compute_crc(a91, 96 - 14);
if (status->crc_extracted != status->crc_calculated)
{
if (status->crc_extracted != status->crc_calculated) {
return false;
}
if (wf->protocol == PROTO_FT4)
{
if (wf->protocol == PROTO_FT4) {
// '[..] for FT4 only, in order to avoid transmitting a long string of zeros when sending CQ messages,
// the assembled 77-bit message is bitwise exclusive-ORed with [a] pseudorandom sequence before computing the CRC and FEC parity bits'
for (int i = 0; i < 10; ++i)
{
for (int i = 0; i < 10; ++i) {
a91[i] ^= kFT4_XOR_sequence[i];
}
}
status->unpack_status = unpack77(a91, message->text);
if (status->unpack_status < 0)
{
if (status->unpack_status < 0) {
return false;
}
@ -595,36 +535,26 @@ bool ft8_decode(const waterfall_t* wf, const candidate_t* cand, message_t* messa
return true;
}
static float max2(float a, float b)
{
return (a >= b) ? a : b;
}
static float max2(float a, float b) { return (a >= b) ? a : b; }
static float max4(float a, float b, float c, float d)
{
return max2(max2(a, b), max2(c, 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_t 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;
}
@ -639,11 +569,9 @@ static void heapify_up(candidate_t 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;
}
@ -660,8 +588,7 @@ static void ft4_extract_symbol(const uint8_t* wf, float* logl)
// Cleaned up code for the simple case of n_syms==1
float s2[4];
for (int j = 0; j < 4; ++j)
{
for (int j = 0; j < 4; ++j) {
s2[j] = (float)wf[kFT4_Gray_map[j]];
}
@ -675,8 +602,7 @@ static void ft8_extract_symbol(const uint8_t* wf, float* logl)
// 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)wf[kFT8_Gray_map[j]];
}
@ -686,22 +612,14 @@ static void ft8_extract_symbol(const uint8_t* wf, float* logl)
}
// decode FT4 or FT8 signal, call callback for every decoded message
int ftx_decode(float *signal, int num_samples, int sample_rate, ftx_protocol_t protocol, ftx_decode_callback_t callback, void *ctx)
int ftx_decode(float* signal, int num_samples, int sample_rate, ftx_protocol_t protocol, ftx_decode_callback_t callback, void* ctx)
{
// Compute FFT over the whole signal and store it
monitor_t mon;
monitor_config_t mon_cfg = {
.f_min = 0.0,
.f_max = 4000.0,
.sample_rate = sample_rate,
.time_osr = kTime_osr,
.freq_osr = kFreq_osr,
.protocol = protocol
};
monitor_config_t mon_cfg = { .f_min = 0.0, .f_max = 4000.0, .sample_rate = sample_rate, .time_osr = kTime_osr, .freq_osr = kFreq_osr, .protocol = protocol };
monitor_init(&mon, &mon_cfg);
for (int frame_pos = 0; frame_pos + mon.block_size <= num_samples; frame_pos += mon.block_size)
{
for (int frame_pos = 0; frame_pos + mon.block_size <= num_samples; frame_pos += mon.block_size) {
// Process the waveform data frame by frame - you could have a live loop here with data from an audio device
monitor_process(&mon, signal + frame_pos);
}
@ -716,14 +634,12 @@ int ftx_decode(float *signal, int num_samples, int sample_rate, ftx_protocol_t p
message_t* decoded_hashtable[kMax_decoded_messages];
// Initialize hash table pointers
for (int i = 0; i < kMax_decoded_messages; ++i)
{
for (int i = 0; i < kMax_decoded_messages; ++i) {
decoded_hashtable[i] = NULL;
}
// 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];
float freq_hz = (cand->freq_offset + (float)cand->freq_sub / mon.wf.freq_osr) / mon.symbol_period;
@ -731,8 +647,7 @@ int ftx_decode(float *signal, int num_samples, int sample_rate, ftx_protocol_t p
message_t message;
decode_status_t status;
if (!ft8_decode(&mon.wf, cand, &message, kLDPC_iterations, &status))
{
if (!ft8_decode(&mon.wf, cand, &message, kLDPC_iterations, &status)) {
#if 0
// printf("000000 %3d %+4.2f %4.0f ~ ---\n", cand->score, time_sec, freq_hz);
if (status.ldpc_errors > 0)
@ -751,32 +666,25 @@ int ftx_decode(float *signal, int num_samples, int sample_rate, ftx_protocol_t p
int idx_hash = message.hash % kMax_decoded_messages;
bool found_empty_slot = false;
bool found_duplicate = false;
do
{
if (decoded_hashtable[idx_hash] == NULL)
{
do {
if (decoded_hashtable[idx_hash] == NULL) {
found_empty_slot = true;
}
else if ((decoded_hashtable[idx_hash]->hash == message.hash) && (0 == strcmp(decoded_hashtable[idx_hash]->text, message.text)))
{
} else if ((decoded_hashtable[idx_hash]->hash == message.hash) && (0 == strcmp(decoded_hashtable[idx_hash]->text, message.text))) {
found_duplicate = true;
}
else
{
} else {
// Move on to check the next entry in hash table
idx_hash = (idx_hash + 1) % kMax_decoded_messages;
}
} while (!found_empty_slot && !found_duplicate);
if (found_empty_slot)
{
if (found_empty_slot) {
// Fill the empty hashtable slot
memcpy(&decoded[idx_hash], &message, sizeof(message));
decoded_hashtable[idx_hash] = &decoded[idx_hash];
++num_decoded;
float signal = 1.0, noise = 1.0;
// report message through callback
callback(message.text, freq_hz, time_sec, 10.0 * log10f(signal / noise), cand->score, ctx);
}
@ -786,3 +694,4 @@ int ftx_decode(float *signal, int num_samples, int sample_rate, ftx_protocol_t p
return num_decoded;
}

Wyświetl plik

@ -1,11 +1,11 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include "ft8.h"
#include "constants.h"
#include "crc.h"
#include "ft8.h"
#include "pack.h"
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FT8_SYMBOL_BT 2.0f ///< symbol smoothing filter bandwidth factor (BT)
#define FT4_SYMBOL_BT 1.0f ///< symbol smoothing filter bandwidth factor (BT)
@ -16,13 +16,12 @@
/// The pulse is theoretically infinitely long, however, here it's truncated at 3 times the symbol length.
/// This means the pulse array has to have space for 3*n_spsym elements.
/// @param[in] n_spsym Number of samples per symbol
/// @param[in] bt Shape parameter (values defined for FT8/FT4)
/// @param[in] symbol_bt Shape parameter (values defined for FT8/FT4)
/// @param[out] pulse Output array of pulse samples
///
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) {
float t = i / (float)n_spsym - 1.5f;
float arg1 = GFSK_CONST_K * symbol_bt * (t + 0.5f);
float arg2 = GFSK_CONST_K * symbol_bt * (t - 0.5f);
@ -43,66 +42,60 @@ void gfsk_pulse(int n_spsym, float symbol_bt, float* pulse)
void synth_gfsk(const uint8_t* symbols, int n_sym, float f0, float symbol_bt, float symbol_period, int signal_rate, float* signal)
{
int n_spsym = (int)(0.5f + signal_rate * symbol_period); // 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;
// Compute the smoothed frequency waveform.
// Length = (nsym+2)*n_spsym samples, first and last symbols extended
float *dphi = malloc((n_wave + 2 * n_spsym) * sizeof(float));
float* dphi = malloc((n_wave + 2 * n_spsym) * sizeof(float));
if (dphi != NULL) {
float dphi_peak = 2 * M_PI * hmod / 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] = fmodf(2 * M_PI * f0 / signal_rate, 2.0 * M_PI);
}
float pulse[3 * n_spsym];
gfsk_pulse(n_spsym, symbol_bt, 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)
{
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)
{
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);
}
// 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;
}
free(dphi);
}
}
// Returns 1 if an odd number of bits are set in x, zero otherwise
static 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
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 % 2; // modulo 2
}
@ -117,38 +110,33 @@ static void encode174(const uint8_t* message, uint8_t* codeword)
// This implementation accesses the generator bits straight from the packed binary representation in kFTX_LDPC_generator
// Fill the codeword with message and zeros, as we will only update binary ones later
for (int j = 0; j < FTX_LDPC_N_BYTES; ++j)
{
for (int j = 0; j < FTX_LDPC_N_BYTES; ++j) {
codeword[j] = (j < FTX_LDPC_K_BYTES) ? message[j] : 0;
}
// Compute the byte index and bit mask for the first checksum bit
uint8_t col_mask = (0x80u >> (FTX_LDPC_K % 8u)); // bitmask of current byte
uint8_t col_idx = FTX_LDPC_K_BYTES - 1; // index into byte array
uint8_t col_idx = FTX_LDPC_K_BYTES - 1; // index into byte array
// Compute the LDPC checksum bits and store them in codeword
for (int i = 0; i < FTX_LDPC_M; ++i)
{
for (int i = 0; i < FTX_LDPC_M; ++i) {
// Fast implementation of bitwise multiplication and parity checking
// Normally nsum would contain the result of dot product between message and kFTX_LDPC_generator[i],
// but we only compute the sum modulo 2.
uint8_t nsum = 0;
for (int j = 0; j < FTX_LDPC_K_BYTES; ++j)
{
for (int j = 0; j < FTX_LDPC_K_BYTES; ++j) {
uint8_t bits = message[j] & kFTX_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
if (nsum % 2)
{
if (nsum % 2) {
codeword[col_idx] |= col_mask;
}
// Update the byte index and bit mask for the next checksum bit
col_mask >>= 1;
if (col_mask == 0)
{
if (col_mask == 0) {
col_mask = 0x80u;
++col_idx;
}
@ -170,44 +158,33 @@ void ft8_encode(const uint8_t* payload, uint8_t* tones)
// Total symbols: 79 (FT8_NN)
uint8_t mask = 0x80u; // Mask to extract 1 bit from codeword
int i_byte = 0; // Index of the current byte of the codeword
for (int i_tone = 0; i_tone < FT8_NN; ++i_tone)
{
if ((i_tone >= 0) && (i_tone < 7))
{
int i_byte = 0; // Index of the current byte of the codeword
for (int i_tone = 0; i_tone < FT8_NN; ++i_tone) {
if ((i_tone >= 0) && (i_tone < 7)) {
tones[i_tone] = kFT8_Costas_pattern[i_tone];
}
else if ((i_tone >= 36) && (i_tone < 43))
{
} else if ((i_tone >= 36) && (i_tone < 43)) {
tones[i_tone] = kFT8_Costas_pattern[i_tone - 36];
}
else if ((i_tone >= 72) && (i_tone < 79))
{
} else if ((i_tone >= 72) && (i_tone < 79)) {
tones[i_tone] = kFT8_Costas_pattern[i_tone - 72];
}
else
{
} else {
// Extract 3 bits from codeword at i-th position
uint8_t bits3 = 0;
if (codeword[i_byte] & mask)
bits3 |= 4;
if (0 == (mask >>= 1))
{
if (0 == (mask >>= 1)) {
mask = 0x80u;
i_byte++;
}
if (codeword[i_byte] & mask)
bits3 |= 2;
if (0 == (mask >>= 1))
{
if (0 == (mask >>= 1)) {
mask = 0x80u;
i_byte++;
}
if (codeword[i_byte] & mask)
bits3 |= 1;
if (0 == (mask >>= 1))
{
if (0 == (mask >>= 1)) {
mask = 0x80u;
i_byte++;
}
@ -220,12 +197,11 @@ void ft8_encode(const uint8_t* payload, uint8_t* tones)
void ft4_encode(const uint8_t* payload, uint8_t* tones)
{
uint8_t a91[FTX_LDPC_K_BYTES]; // Store 77 bits of payload + 14 bits CRC
uint8_t payload_xor[10]; // Encoded payload data
uint8_t payload_xor[10]; // Encoded payload data
// '[..] for FT4 only, in order to avoid transmitting a long string of zeros when sending CQ messages,
// the assembled 77-bit message is bitwise exclusive-ORed with [a] pseudorandom sequence before computing the CRC and FEC parity bits'
for (int i = 0; i < 10; ++i)
{
for (int i = 0; i < 10; ++i) {
payload_xor[i] = payload[i] ^ kFT4_XOR_sequence[i];
}
@ -240,45 +216,31 @@ void ft4_encode(const uint8_t* payload, uint8_t* tones)
// Total symbols: 105 (FT4_NN)
uint8_t mask = 0x80u; // Mask to extract 1 bit from codeword
int i_byte = 0; // Index of the current byte of the codeword
for (int i_tone = 0; i_tone < FT4_NN; ++i_tone)
{
if ((i_tone == 0) || (i_tone == 104))
{
int i_byte = 0; // Index of the current byte of the codeword
for (int i_tone = 0; i_tone < FT4_NN; ++i_tone) {
if ((i_tone == 0) || (i_tone == 104)) {
tones[i_tone] = 0; // R (ramp) symbol
}
else if ((i_tone >= 1) && (i_tone < 5))
{
} else if ((i_tone >= 1) && (i_tone < 5)) {
tones[i_tone] = kFT4_Costas_pattern[0][i_tone - 1];
}
else if ((i_tone >= 34) && (i_tone < 38))
{
} else if ((i_tone >= 34) && (i_tone < 38)) {
tones[i_tone] = kFT4_Costas_pattern[1][i_tone - 34];
}
else if ((i_tone >= 67) && (i_tone < 71))
{
} else if ((i_tone >= 67) && (i_tone < 71)) {
tones[i_tone] = kFT4_Costas_pattern[2][i_tone - 67];
}
else if ((i_tone >= 100) && (i_tone < 104))
{
} else if ((i_tone >= 100) && (i_tone < 104)) {
tones[i_tone] = kFT4_Costas_pattern[3][i_tone - 100];
}
else
{
} else {
// Extract 2 bits from codeword at i-th position
uint8_t bits2 = 0;
if (codeword[i_byte] & mask)
bits2 |= 2;
if (0 == (mask >>= 1))
{
if (0 == (mask >>= 1)) {
mask = 0x80u;
i_byte++;
}
if (codeword[i_byte] & mask)
bits2 |= 1;
if (0 == (mask >>= 1))
{
if (0 == (mask >>= 1)) {
mask = 0x80u;
i_byte++;
}
@ -288,7 +250,7 @@ void ft4_encode(const uint8_t* payload, uint8_t* tones)
}
// generate FT4 or FT8 signal for message
int ftx_encode(char *message, float *signal, int num_samples, float frequency, int sample_rate, ftx_protocol_t protocol)
int ftx_encode(char* message, float* signal, int num_samples, float frequency, int sample_rate, ftx_protocol_t protocol)
{
// First, pack the text data into binary message
uint8_t packed[FTX_LDPC_K_BYTES];
@ -301,20 +263,20 @@ int ftx_encode(char *message, float *signal, int num_samples, float frequency, i
const float symbol_bt = protocol == PROTO_FT4 ? FT4_SYMBOL_BT : FT8_SYMBOL_BT;
int real_num_samples = num_tones * symbol_period * sample_rate + 0.5;
uint8_t tones[num_tones];
// check if we have enough space
if (num_samples >= real_num_samples) {
if (protocol == PROTO_FT4) {
ft4_encode(packed, tones);
} else {
ft8_encode(packed, tones);
}
// Third, convert the FSK tones into an audio signal
// Synthesize waveform data (signal) and save it as WAV file
synth_gfsk(tones, num_tones, frequency, symbol_bt, symbol_period, sample_rate, signal);
// clear extra samples
memset(signal + real_num_samples, 0, (num_samples - real_num_samples) * sizeof(float));
@ -323,3 +285,4 @@ int ftx_encode(char *message, float *signal, int num_samples, float frequency, i
}
return -1;
}

Wyświetl plik

@ -9,13 +9,13 @@
// codeword[i] = log ( P(x=0) / P(x=1) )
//
#include "ldpc.h"
#include "constants.h"
#include "ldpc.h"
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
static int ldpc_check(uint8_t codeword[]);
static float fast_tanh(float x);
@ -31,28 +31,21 @@ void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int* ok)
float e[FTX_LDPC_M][FTX_LDPC_N]; // ~60 kB
int min_errors = FTX_LDPC_M;
for (int j = 0; j < FTX_LDPC_M; j++)
{
for (int i = 0; i < FTX_LDPC_N; i++)
{
for (int j = 0; j < FTX_LDPC_M; j++) {
for (int i = 0; i < FTX_LDPC_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 < FTX_LDPC_M; j++)
{
for (int ii1 = 0; ii1 < kFTX_LDPC_Num_rows[j]; ii1++)
{
for (int iter = 0; iter < max_iters; iter++) {
for (int j = 0; j < FTX_LDPC_M; j++) {
for (int ii1 = 0; ii1 < kFTX_LDPC_Num_rows[j]; ii1++) {
int i1 = kFTX_LDPC_Nm[j][ii1] - 1;
float a = 1.0f;
for (int ii2 = 0; ii2 < kFTX_LDPC_Num_rows[j]; ii2++)
{
for (int ii2 = 0; ii2 < kFTX_LDPC_Num_rows[j]; ii2++) {
int i2 = kFTX_LDPC_Nm[j][ii2] - 1;
if (i2 != i1)
{
if (i2 != i1) {
a *= fast_tanh(-m[j][i2] / 2.0f);
}
}
@ -60,8 +53,7 @@ void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int* ok)
}
}
for (int i = 0; i < FTX_LDPC_N; i++)
{
for (int i = 0; i < FTX_LDPC_N; i++) {
float l = codeword[i];
for (int j = 0; j < 3; j++)
l += e[kFTX_LDPC_Mn[i][j] - 1][i];
@ -70,27 +62,21 @@ 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)
{
if (errors == 0) {
break; // Found a perfect answer
}
}
for (int i = 0; i < FTX_LDPC_N; i++)
{
for (int ji1 = 0; ji1 < 3; ji1++)
{
for (int i = 0; i < FTX_LDPC_N; i++) {
for (int ji1 = 0; ji1 < 3; ji1++) {
int j1 = kFTX_LDPC_Mn[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 = kFTX_LDPC_Mn[i][ji2] - 1;
l += e[j2][i];
}
@ -112,15 +98,12 @@ static int ldpc_check(uint8_t codeword[])
{
int errors = 0;
for (int m = 0; m < FTX_LDPC_M; ++m)
{
for (int m = 0; m < FTX_LDPC_M; ++m) {
uint8_t x = 0;
for (int i = 0; i < kFTX_LDPC_Num_rows[m]; ++i)
{
for (int i = 0; i < kFTX_LDPC_Num_rows[m]; ++i) {
x ^= codeword[kFTX_LDPC_Nm[m][i] - 1];
}
if (x != 0)
{
if (x != 0) {
++errors;
}
}
@ -135,23 +118,19 @@ void bp_decode(float codeword[], int max_iters, uint8_t plain[], int* ok)
int min_errors = FTX_LDPC_M;
// initialize message data
for (int n = 0; n < FTX_LDPC_N; ++n)
{
for (int n = 0; n < FTX_LDPC_N; ++n) {
tov[n][0] = tov[n][1] = tov[n][2] = 0;
}
for (int iter = 0; iter < max_iters; ++iter)
{
for (int iter = 0; iter < max_iters; ++iter) {
// Do a hard decision guess (tov=0 in iter 0)
int plain_sum = 0;
for (int n = 0; n < FTX_LDPC_N; ++n)
{
for (int n = 0; n < FTX_LDPC_N; ++n) {
plain[n] = ((codeword[n] + tov[n][0] + tov[n][1] + tov[n][2]) > 0) ? 1 : 0;
plain_sum += plain[n];
}
if (plain_sum == 0)
{
if (plain_sum == 0) {
// message converged to all-zeros, which is prohibited
break;
}
@ -159,29 +138,23 @@ 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)
{
if (errors == 0) {
break; // Found a perfect answer
}
}
// Send messages from bits to check nodes
for (int m = 0; m < FTX_LDPC_M; ++m)
{
for (int n_idx = 0; n_idx < kFTX_LDPC_Num_rows[m]; ++n_idx)
{
for (int m = 0; m < FTX_LDPC_M; ++m) {
for (int n_idx = 0; n_idx < kFTX_LDPC_Num_rows[m]; ++n_idx) {
int n = kFTX_LDPC_Nm[m][n_idx] - 1;
// for each (n, m)
float Tnm = codeword[n];
for (int m_idx = 0; m_idx < 3; ++m_idx)
{
if ((kFTX_LDPC_Mn[n][m_idx] - 1) != m)
{
for (int m_idx = 0; m_idx < 3; ++m_idx) {
if ((kFTX_LDPC_Mn[n][m_idx] - 1) != m) {
Tnm += tov[n][m_idx];
}
}
@ -190,17 +163,13 @@ void bp_decode(float codeword[], int max_iters, uint8_t plain[], int* ok)
}
// send messages from check nodes to variable nodes
for (int n = 0; n < FTX_LDPC_N; ++n)
{
for (int m_idx = 0; m_idx < 3; ++m_idx)
{
for (int n = 0; n < FTX_LDPC_N; ++n) {
for (int m_idx = 0; m_idx < 3; ++m_idx) {
int m = kFTX_LDPC_Mn[n][m_idx] - 1;
// for each (n, m)
float Tmn = 1.0f;
for (int n_idx = 0; n_idx < kFTX_LDPC_Num_rows[m]; ++n_idx)
{
if ((kFTX_LDPC_Nm[m][n_idx] - 1) != n)
{
for (int n_idx = 0; n_idx < kFTX_LDPC_Num_rows[m]; ++n_idx) {
if ((kFTX_LDPC_Nm[m][n_idx] - 1) != n) {
Tmn *= toc[m][n_idx];
}
}
@ -220,12 +189,10 @@ void bp_decode(float codeword[], int max_iters, uint8_t plain[], int* ok)
static float fast_tanh(float x)
{
if (x < -4.97f)
{
if (x < -4.97f) {
return -1.0f;
}
if (x > 4.97f)
{
if (x > 4.97f) {
return 1.0f;
}
float x2 = x * x;

Wyświetl plik

@ -1,14 +1,14 @@
#include "pack.h"
#include "text.h"
#include <ctype.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#define NTOKENS ((uint32_t)2063592L)
#define MAX22 ((uint32_t)4194304L)
#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
@ -30,8 +30,7 @@ int32_t pack28(const char* callsign)
if (starts_with(callsign, "CQ "))
return 2;
if (starts_with(callsign, "CQ_"))
{
if (starts_with(callsign, "CQ_")) {
int nnum = 0, nlet = 0;
// TODO:
@ -42,33 +41,24 @@ 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") && isalpha(callsign[2]) && length <= 7)
{
} else if (starts_with(callsign, "3X") && isalpha(callsign[2]) && length <= 7) {
// Work-around for Guinea prefixes: 3XA0XYZ -> QA0XYZ
memcpy(c6, "Q", 1);
memcpy(c6 + 1, callsign + 2, length - 2);
}
else
{
if (isdigit(callsign[2]) && length <= 6)
{
} else {
if (isdigit(callsign[2]) && length <= 6) {
// AB0XYZ
memcpy(c6, callsign, length);
}
else if (isdigit(callsign[1]) && length <= 5)
{
} else if (isdigit(callsign[1]) && length <= 5) {
// A0XYZ -> " A0XYZ"
memcpy(c6 + 1, callsign, length);
}
@ -76,8 +66,7 @@ int32_t pack28(const char* callsign)
// Check for standard 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)
{
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) {
// This is a standard callsign
int32_t n28 = i0;
n28 = n28 * 36 + i1;
@ -88,8 +77,8 @@ int32_t pack28(const char* callsign)
return NTOKENS + MAX22 + n28;
}
//char text[13];
//if (length > 13) return -1;
// char text[13];
// if (length > 13) return -1;
// TODO:
// Treat this as a nonstandard callsign: compute its 22-bit hash
@ -122,8 +111,7 @@ bool chkcall(const char* call, char* bc)
uint16_t packgrid(const char* grid4)
{
if (grid4 == 0)
{
if (grid4 == 0) {
// Two callsigns only, no report/grid
return MAXGRID4 + 1;
}
@ -136,10 +124,9 @@ uint16_t packgrid(const char* grid4)
} else if (strcmp(grid4, "73") == 0) {
return MAXGRID4 + 4;
}
// Check for standard 4 letter grid
if (in_range(grid4[0], 'A', 'R') && in_range(grid4[1], 'A', 'R') && isdigit(grid4[2]) && isdigit(grid4[3]))
{
if (in_range(grid4[0], 'A', 'R') && in_range(grid4[1], 'A', 'R') && isdigit(grid4[2]) && isdigit(grid4[3])) {
uint16_t igrid4 = (grid4[0] - 'A');
igrid4 = igrid4 * 18 + (grid4[1] - 'A');
igrid4 = igrid4 * 10 + (grid4[2] - '0');
@ -149,14 +136,11 @@ 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
}
else
{
} else {
int dd = dd_to_int(grid4, 3);
uint16_t irpt = 35 + dd;
return (MAXGRID4 + irpt); // ir = 0
@ -186,12 +170,9 @@ int pack77_1(const char* msg, uint8_t* b77)
// 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);
}
@ -224,43 +205,35 @@ 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
// 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
@ -268,8 +241,7 @@ 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)
{
for (int i = 8; i >= 0; --i) {
if (x == 0)
break;
x += b77[i];
@ -286,8 +258,7 @@ void packtext77(const char* text, uint8_t* b77)
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;
}
@ -301,69 +272,3 @@ int pack77(const char* msg, uint8_t* c77)
packtext77(msg, c77);
return 0;
}
#ifdef UNIT_TEST
#include <iostream>
bool test1()
{
const char* inputs[] = {
"",
" ",
"ABC",
"A9",
"L9A",
"L7BC",
"L0ABC",
"LL3JG",
"LL3AJG",
"CQ ",
0
};
for (int i = 0; inputs[i]; ++i)
{
int32_t result = ft8_v2::pack28(inputs[i]);
printf("pack28(\"%s\") = %d\n", inputs[i], result);
}
return true;
}
bool test2()
{
const char* inputs[] = {
"CQ LL3JG",
"CQ LL3JG KO26",
"L0UAA LL3JG KO26",
"L0UAA LL3JG +02",
"L0UAA LL3JG RRR",
"L0UAA LL3JG 73",
0
};
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)
{
printf("%02x ", result[j]);
}
printf("]\n");
}
return true;
}
int main()
{
test1();
test2();
return 0;
}
#endif

Wyświetl plik

@ -1,12 +1,11 @@
#include "text.h"
#include <string.h>
#include <ctype.h>
#include <string.h>
const char* trim_front(const char* str)
{
// Skip leading whitespace
while (*str == ' ')
{
while (*str == ' ') {
str++;
}
return str;
@ -16,8 +15,7 @@ void trim_back(char* str)
{
// Skip trailing whitespace by replacing it with '\0' characters
size_t idx = strlen(str) - 1;
while (idx >= 0 && str[idx] == ' ')
{
while (idx >= 0 && str[idx] == ' ') {
str[idx--] = '\0';
}
}
@ -32,22 +30,14 @@ char* trim(char* str)
return str;
}
bool in_range(char c, char min, char max)
{
return (c >= min) && (c <= max);
}
bool in_range(char c, char min, char max) { return (c >= min) && (c <= max); }
bool starts_with(const char* string, const char* prefix)
{
return 0 == memcmp(string, prefix, strlen(prefix));
}
bool starts_with(const char* string, const char* prefix) { return 0 == memcmp(string, prefix, strlen(prefix)); }
int char_index(const char* string, char c)
{
for (int i = 0; *string; ++i, ++string)
{
if (c == *string)
{
for (int i = 0; *string; ++i, ++string) {
if (c == *string) {
return i;
}
}
@ -60,19 +50,15 @@ 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
}
else
{
} else {
negative = false;
i = (str[0] == '+') ? 1 : 0; // Consume a + sign if found
}
while (i < length)
{
while (i < length) {
if (str[i] == 0)
break;
if (!isdigit(str[i]))
@ -88,26 +74,21 @@ int dd_to_int(const char* str, int length)
// Convert a 2 digit integer to string
void int_to_dd(char* str, int value, int width, bool full_sign)
{
if (value < 0)
{
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;
@ -128,32 +109,26 @@ void int_to_dd(char* str, int value, int width, bool full_sign)
// table 5: " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ/"
char charn(int c, int table_idx)
{
if (table_idx != 2 && table_idx != 3)
{
if (table_idx != 2 && table_idx != 3) {
if (c == 0)
return ' ';
c -= 1;
}
if (table_idx != 4)
{
if (table_idx != 4) {
if (c < 10)
return '0' + c;
c -= 10;
}
if (table_idx != 3)
{
if (table_idx != 3) {
if (c < 26)
return 'A' + c;
c -= 26;
}
if (table_idx == 0)
{
if (table_idx == 0) {
if (c < 5)
return "+-./?"[c];
}
else if (table_idx == 5)
{
} else if (table_idx == 5) {
if (c == 0)
return '/';
}
@ -165,27 +140,23 @@ char charn(int c, int table_idx)
int nchar(char c, int table_idx)
{
int n = 0;
if (table_idx != 2 && table_idx != 3)
{
if (table_idx != 2 && table_idx != 3) {
if (c == ' ')
return n + 0;
n += 1;
}
if (table_idx != 4)
{
if (table_idx != 4) {
if (c >= '0' && c <= '9')
return n + (c - '0');
n += 10;
}
if (table_idx != 3)
{
if (table_idx != 3) {
if (c >= 'A' && c <= 'Z')
return n + (c - 'A');
n += 26;
}
if (table_idx == 0)
{
if (table_idx == 0) {
if (c == '+')
return n + 0;
if (c == '-')
@ -196,9 +167,7 @@ int nchar(char c, int table_idx)
return n + 3;
if (c == '?')
return n + 4;
}
else if (table_idx == 5)
{
} else if (table_idx == 5) {
if (c == '/')
return n + 0;
}

Wyświetl plik

@ -1,16 +1,10 @@
#ifdef __linux__
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#endif
#include "unpack.h"
#include "text.h"
#include "unpack.h"
#include <string.h>
#define MAX22 ((uint32_t)4194304L)
#define NTOKENS ((uint32_t)2063592L)
#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
@ -18,10 +12,8 @@
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
if (n28 < NTOKENS)
{
if (n28 <= 2)
{
if (n28 < NTOKENS) {
if (n28 <= 2) {
if (n28 == 0)
strcpy(result, "DE");
if (n28 == 1)
@ -30,22 +22,19 @@ int unpack_callsign(uint32_t n28, uint8_t ip, uint8_t i3, char* result)
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, false);
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;
@ -61,8 +50,7 @@ int unpack_callsign(uint32_t n28, uint8_t ip, uint8_t i3, char* result)
}
n28 = n28 - NTOKENS;
if (n28 < MAX22)
{
if (n28 < MAX22) {
// This is a 22-bit hash of a result
// TODO: implement
strcpy(result, "<...>");
@ -96,14 +84,10 @@ int unpack_callsign(uint32_t n28, uint8_t ip, uint8_t i3, char* result)
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");
}
}
@ -133,12 +117,10 @@ int unpack_type1(const uint8_t* a77, uint8_t i3, char* call_to, char* call_de, c
igrid4 |= (a77[9] >> 6);
// Unpack both callsigns
if (unpack_callsign(n28a >> 1, n28a & 0x01, i3, call_to) < 0)
{
if (unpack_callsign(n28a >> 1, n28a & 0x01, i3, call_to) < 0) {
return -1;
}
if (unpack_callsign(n28b >> 1, n28b & 0x01, i3, call_de) < 0)
{
if (unpack_callsign(n28b >> 1, n28b & 0x01, i3, call_de) < 0) {
return -2;
}
// Fix "CQ_" to "CQ " -> already done in unpack_callsign()
@ -153,11 +135,9 @@ int unpack_type1(const uint8_t* a77, uint8_t i3, char* call_to, char* call_de, c
char* dst = extra;
if (igrid4 <= MAXGRID4)
{
if (igrid4 <= MAXGRID4) {
// Extract 4 symbol grid locator
if (ir > 0)
{
if (ir > 0) {
// In case of ir=1 add an "R" before grid
dst = stpcpy(dst, "R ");
}
@ -172,15 +152,12 @@ int unpack_type1(const uint8_t* a77, uint8_t i3, char* call_to, char* call_de, c
n /= 18;
dst[0] = 'A' + (n % 18);
// if (ir > 0 && strncmp(call_to, "CQ", 2) == 0) return -1;
}
else
{
} else {
// Extract report
int irpt = igrid4 - MAXGRID4;
// Check special cases first (irpt > 0 always)
switch (irpt)
{
switch (irpt) {
case 1:
extra[0] = '\0';
break;
@ -195,8 +172,7 @@ int unpack_type1(const uint8_t* a77, uint8_t i3, char* call_to, char* call_de, c
break;
default:
// 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);
@ -215,20 +191,17 @@ int unpack_text(const uint8_t* a71, char* text)
// Shift 71 bits right by 1 bit, so that it's right-aligned in the byte array
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)
{
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;
@ -246,15 +219,13 @@ int unpack_telemetry(const uint8_t* a71, char* telemetry)
// Shift bits in a71 right by 1 bit
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';
@ -267,34 +238,33 @@ int unpack_telemetry(const uint8_t* a71, char* telemetry)
return 0;
}
//none standard for wsjt-x 2.0
//by KD8CEC
// none standard for wsjt-x 2.0
// by KD8CEC
int unpack_nonstandard(const uint8_t* a77, char* call_to, char* call_de, char* extra)
{
uint32_t n12, iflip, nrpt, icq;
uint64_t n58;
n12 = (a77[0] << 4); //11 ~4 : 8
n12 |= (a77[1] >> 4); //3~0 : 12
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
iflip = (a77[8] >> 1) & 0x01; // 76543210
nrpt = ((a77[8] & 0x01) << 1);
nrpt |= (a77[9] >> 7); //76543210
nrpt |= (a77[9] >> 7); // 76543210
icq = ((a77[9] >> 6) & 0x01);
char c11[12];
c11[11] = '\0';
for (int i = 10; /* no condition */; --i)
{
for (int i = 10; /* no condition */; --i) {
c11[i] = charn(n58 % 38, 5);
if (i == 0)
break;
@ -311,10 +281,9 @@ int unpack_nonstandard(const uint8_t* a77, char* call_to, char* call_de, char* e
char* call_1 = (iflip) ? c11 : call_3;
char* call_2 = (iflip) ? call_3 : c11;
//save_hash_call(c11_trimmed);
// save_hash_call(c11_trimmed);
if (icq == 0)
{
if (icq == 0) {
strcpy(call_to, trim(call_1));
if (nrpt == 1)
strcpy(extra, "RRR");
@ -322,13 +291,10 @@ int unpack_nonstandard(const uint8_t* a77, char* call_to, char* call_de, char* e
strcpy(extra, "RR73");
else if (nrpt == 3)
strcpy(extra, "73");
else
{
else {
extra[0] = '\0';
}
}
else
{
} else {
strcpy(call_to, "CQ");
extra[0] = '\0';
}
@ -344,13 +310,11 @@ int unpack77_fields(const uint8_t* a77, char* call_to, char* call_de, char* extr
// Extract i3 (bits 74..76)
uint8_t i3 = (a77[9] >> 3) & 0x07;
if (i3 == 0)
{
if (i3 == 0) {
// Extract n3 (bits 71..73)
uint8_t n3 = ((a77[8] << 2) & 0x04) | ((a77[9] >> 6) & 0x03);
if (n3 == 0)
{
if (n3 == 0) {
// 0.0 Free text
return unpack_text(a77, extra);
}
@ -364,22 +328,18 @@ int unpack77_fields(const uint8_t* a77, char* call_to, char* call_de, char* extr
// // 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 (n3 == 5)
{
else if (n3 == 5) {
// 0.5 0123456789abcdef01 71 71 Telemetry (18 hex)
return unpack_telemetry(a77, extra);
}
}
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, call_to, call_de, extra);
}
// else if (i3 == 3) {
// // Type 3: ARRL RTTY Contest
// }
else if (i3 == 4)
{
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.
@ -408,14 +368,12 @@ int unpack77(const uint8_t* a77, char* message)
dst[0] = '\0';
if (call_to[0] != '\0')
{
if (call_to[0] != '\0') {
dst = stpcpy(dst, call_to);
*dst++ = ' ';
}
if (call_de[0] != '\0')
{
if (call_de[0] != '\0') {
dst = stpcpy(dst, call_de);
*dst++ = ' ';
}
@ -425,4 +383,3 @@ int unpack77(const uint8_t* a77, char* message)
return 0;
}

Wyświetl plik

@ -28,7 +28,6 @@
6598F2272737F0A80060A608 /* PBXTargetDependency */,
6598F2292737F0AA0060A608 /* PBXTargetDependency */,
6598F22B2737F0AB0060A608 /* PBXTargetDependency */,
6598F22D2737F0AE0060A608 /* PBXTargetDependency */,
);
name = "Demos and Tests";
productName = "Demos and Tests";
@ -53,9 +52,7 @@
6552D4772735B68E00EE69E8 /* libft8.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 659888982731363800F64B3C /* libft8.dylib */; };
6552D4782735B6D500EE69E8 /* libft8.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 659888982731363800F64B3C /* libft8.dylib */; };
6552D4792735B6DE00EE69E8 /* libft8.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 659888982731363800F64B3C /* libft8.dylib */; };
6552D47A2735B6E200EE69E8 /* libft8.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 659888982731363800F64B3C /* libft8.dylib */; };
6590878C2731487E007667A1 /* gen_ft8.c in Sources */ = {isa = PBXBuildFile; fileRef = 659888B4273147F400F64B3C /* gen_ft8.c */; };
6590878D27314881007667A1 /* test.c in Sources */ = {isa = PBXBuildFile; fileRef = 659888B2273147F400F64B3C /* test.c */; };
6598883B27312EB100F64B3C /* kiss_fft.c in Sources */ = {isa = PBXBuildFile; fileRef = 6598883627312EB100F64B3C /* kiss_fft.c */; };
6598883D27312EB100F64B3C /* kiss_fftr.c in Sources */ = {isa = PBXBuildFile; fileRef = 6598883727312EB100F64B3C /* kiss_fftr.c */; };
6598883F27312EB100F64B3C /* _kiss_fft_guts.h in Headers */ = {isa = PBXBuildFile; fileRef = 6598883827312EB100F64B3C /* _kiss_fft_guts.h */; };
@ -97,7 +94,6 @@
659888B127313A5C00F64B3C /* wave.c in Sources */ = {isa = PBXBuildFile; fileRef = 6598887827312ECE00F64B3C /* wave.c */; };
659888B72731481500F64B3C /* decode_ft8.c in Sources */ = {isa = PBXBuildFile; fileRef = 659888B3273147F400F64B3C /* decode_ft8.c */; };
659888BB2731482C00F64B3C /* wave.c in Sources */ = {isa = PBXBuildFile; fileRef = 6598887827312ECE00F64B3C /* wave.c */; };
659888C52731483800F64B3C /* wave.c in Sources */ = {isa = PBXBuildFile; fileRef = 6598887827312ECE00F64B3C /* wave.c */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -136,13 +132,6 @@
remoteGlobalIDString = 650D013227355AB800811CC1;
remoteInfo = looptest;
};
6598F22C2737F0AE0060A608 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 652CE595273127D200C91652 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 659888C22731483800F64B3C;
remoteInfo = test;
};
6598F22E2737F0B00060A608 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 652CE595273127D200C91652 /* Project object */;
@ -187,15 +176,6 @@
);
runOnlyForDeploymentPostprocessing = 1;
};
659888C72731483800F64B3C /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = /usr/share/man/man1/;
dstSubfolderSpec = 0;
files = (
);
runOnlyForDeploymentPostprocessing = 1;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
@ -226,11 +206,9 @@
6598887927312ECE00F64B3C /* wave.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wave.h; sourceTree = "<group>"; };
659888842731305000F64B3C /* decode_ft8 */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = decode_ft8; sourceTree = BUILT_PRODUCTS_DIR; };
659888982731363800F64B3C /* libft8.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libft8.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
659888B2273147F400F64B3C /* test.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = test.c; sourceTree = "<group>"; };
659888B3273147F400F64B3C /* decode_ft8.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = decode_ft8.c; sourceTree = "<group>"; };
659888B4273147F400F64B3C /* gen_ft8.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gen_ft8.c; sourceTree = "<group>"; };
659888C12731482C00F64B3C /* gen_ft8 */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = gen_ft8; sourceTree = BUILT_PRODUCTS_DIR; };
659888CB2731483800F64B3C /* test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = test; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -272,14 +250,6 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
659888C62731483800F64B3C /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
6552D47A2735B6E200EE69E8 /* libft8.dylib in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@ -302,7 +272,6 @@
659888842731305000F64B3C /* decode_ft8 */,
659888982731363800F64B3C /* libft8.dylib */,
659888C12731482C00F64B3C /* gen_ft8 */,
659888CB2731483800F64B3C /* test */,
650D013B27355AB800811CC1 /* looptest */,
);
name = Products;
@ -364,7 +333,6 @@
659888B3273147F400F64B3C /* decode_ft8.c */,
659888B4273147F400F64B3C /* gen_ft8.c */,
650D012D2735596F00811CC1 /* looptest.c */,
659888B2273147F400F64B3C /* test.c */,
);
name = "Demos and Tests";
sourceTree = "<group>";
@ -492,23 +460,6 @@
productReference = 659888C12731482C00F64B3C /* gen_ft8 */;
productType = "com.apple.product-type.tool";
};
659888C22731483800F64B3C /* test */ = {
isa = PBXNativeTarget;
buildConfigurationList = 659888C82731483800F64B3C /* Build configuration list for PBXNativeTarget "test" */;
buildPhases = (
659888C32731483800F64B3C /* Sources */,
659888C62731483800F64B3C /* Frameworks */,
659888C72731483800F64B3C /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = test;
productName = decode_ft8;
productReference = 659888CB2731483800F64B3C /* test */;
productType = "com.apple.product-type.tool";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@ -558,7 +509,6 @@
659888832731305000F64B3C /* decode_ft8 */,
659888B82731482C00F64B3C /* gen_ft8 */,
650D013227355AB800811CC1 /* looptest */,
659888C22731483800F64B3C /* test */,
);
};
/* End PBXProject section */
@ -625,15 +575,6 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
659888C32731483800F64B3C /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
659888C52731483800F64B3C /* wave.c in Sources */,
6590878D27314881007667A1 /* test.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
@ -662,11 +603,6 @@
target = 650D013227355AB800811CC1 /* looptest */;
targetProxy = 6598F22A2737F0AB0060A608 /* PBXContainerItemProxy */;
};
6598F22D2737F0AE0060A608 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 659888C22731483800F64B3C /* test */;
targetProxy = 6598F22C2737F0AE0060A608 /* PBXContainerItemProxy */;
};
6598F22F2737F0B00060A608 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 659888972731363800F64B3C /* libft8.dylib */;
@ -948,26 +884,6 @@
};
name = Release;
};
659888C92731483800F64B3C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = DVX9JXSKVT;
ENABLE_HARDENED_RUNTIME = YES;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
659888CA2731483800F64B3C /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = DVX9JXSKVT;
ENABLE_HARDENED_RUNTIME = YES;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
6598F2202737EFC40060A608 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -1061,15 +977,6 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
659888C82731483800F64B3C /* Build configuration list for PBXNativeTarget "test" */ = {
isa = XCConfigurationList;
buildConfigurations = (
659888C92731483800F64B3C /* Debug */,
659888CA2731483800F64B3C /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
6598F21F2737EFC40060A608 /* Build configuration list for PBXAggregateTarget "Build all" */ = {
isa = XCConfigurationList;
buildConfigurations = (

Wyświetl plik

@ -1,5 +1,5 @@
#include <stdio.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -9,7 +9,7 @@
#define FT4_SLOT_TIME 6.0f // total length of output waveform in seconds
#define FT8_SLOT_TIME 15.0f // total length of output waveform in seconds
int main(int argc, char **argv)
int main(int argc, char** argv)
{
int sample_rate = 8000;
@ -43,3 +43,4 @@ int main(int argc, char **argv)
printf("(Note that you might have to enclose your message in quote marks if it contains spaces)\n");
return -1;
}

Wyświetl plik

@ -6,7 +6,6 @@
#include "ft8.h"
#warning remove again
#include <fcntl.h>
#include <unistd.h>
@ -25,7 +24,7 @@ struct context {
float frequency;
};
static char *random_callsign(char *callsign)
static char* random_callsign(char* callsign)
{
int x = rand();
switch (x >> 29) {
@ -45,14 +44,14 @@ static char *random_callsign(char *callsign)
return callsign;
}
static char *random_locator(char* locator)
static char* random_locator(char* locator)
{
int x = rand();
sprintf(locator, "%c%c%d%d", 'A' + RP(x, 1, 18), 'A' + RP(x, 18, 18), RP(x, 180, 10), RP(x, 1800, 10));
return locator;
}
static char *random_message(char* message)
static char* random_message(char* message)
{
int x = rand();
char callsign1[8], callsign2[8], locator[5];
@ -80,14 +79,14 @@ static char *random_message(char* message)
}
// decode callback, called by ft8_decode() for each decoded message
static void ft8_decode_callback(char *message, float frequency, float time_dev, float snr, int score, void *ctx)
static void ft8_decode_callback(char* message, float frequency, float time_dev, float snr, int score, void* ctx)
{
struct context *context = ctx;
struct context* context = ctx;
bool ok = strcmp(context->message, message) == 0;
// printf("%-8s000000 %3d %+4.2f %4.0f ~ %s (%s)\n", ok ? "OK" : "ERROR", score, time_dev, frequency, message, context->message);
printf("%-8s000000 %3d %+4.2f %4.0f ~ %s (%s)\n", ok ? "OK" : "ERROR", score, time_dev, frequency, message, context->message);
}
int main(int argc, char *argv[])
int main(int argc, char* argv[])
{
int iterations = 1000;
int sample_rate = 8000;
@ -95,14 +94,13 @@ int main(int argc, char *argv[])
ftx_protocol_t protocol = PROTO_FT4;
int num_samples = (protocol == PROTO_FT4 ? FT4_SLOT_TIME : FT8_SLOT_TIME) * sample_rate;
float signal[num_samples];
// start time measurement
clock_t start = clock();
// initialize random number generator
srand((unsigned int)time(NULL));
// run loop test
for (int i = 0; i < iterations; i++) {
struct context ctx;

93
test.c
Wyświetl plik

@ -1,93 +0,0 @@
#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ft8/constants.h"
#include "ft8/pack.h"
#include "ft8/text.h"
#include "fft/kiss_fftr.h"
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) {
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) {
dst[j] |= mask_dst;
}
mask_src >>= 1;
if (mask_src == 0) {
mask_src = (1 << 7);
++i;
}
mask_dst >>= 1;
if (mask_dst == 0) {
mask_dst = (1 << 5);
++j;
}
}
}
void test_tones(float *log174)
{
// Just a test case
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 + 1] = (b3 & 2) ? +1.0 : -1.0;
log174[3 * i + 2] = (b3 & 1) ? +1.0 : -1.0;
}
}
void test4(void)
{
const int nfft = 128;
const float fft_norm = 2.0 / nfft;
size_t fft_work_size;
kiss_fftr_alloc(nfft, 0, 0, &fft_work_size);
printf("N_FFT = %d\n", nfft);
printf("FFT work area = %lu\n", fft_work_size);
void* fft_work = malloc(fft_work_size);
kiss_fftr_cfg fft_cfg = kiss_fftr_alloc(nfft, 0, fft_work, &fft_work_size);
kiss_fft_scalar window[nfft];
for (int i = 0; i < nfft; ++i) {
window[i] = sinf(i * 2 * (float)M_PI / nfft);
}
kiss_fft_cpx freqdata[nfft / 2 + 1];
kiss_fftr(fft_cfg, window, freqdata);
float mag_db[nfft];
// Compute log magnitude in decibels
for (int j = 0; j < nfft / 2 + 1; ++j) {
float mag2 = (freqdata[j].i * freqdata[j].i + freqdata[j].r * freqdata[j].r);
mag_db[j] = 10.0f * log10f(1E-10f + mag2 * fft_norm * fft_norm);
}
printf("F[0] = %.1f dB\n", mag_db[0]);
printf("F[1] = %.3f dB\n", mag_db[1]);
}
int main()
{
// test1();
test4();
return 0;
}