Move decode/encode/test.c into apps folder and compile with libfft, libft8 library link

pull/12/head
Kimi Jin 2021-10-09 19:58:43 +08:00
rodzic b095d79590
commit 46f0832952
8 zmienionych plików z 835 dodań i 23 usunięć

Wyświetl plik

@ -1,27 +1,15 @@
CFLAGS = -O3
CPPFLAGS = -std=c11 -I.
LDFLAGS = -lm
SUBDIRS := libs
SUBDIRS += apps
TARGETS = gen_ft8 decode_ft8 test
all install:
echo $(SUBDIRS)
@for i in `echo $(SUBDIRS)`; do \
$(MAKE) -C $$i $@ || exit 1; \
done
.PHONY: run_tests all clean
all: $(TARGETS)
clean mrproper:
@for i in `echo $(SUBDIRS)`; do \
$(MAKE) -C $$i $@ || exit 1; \
done
run_tests: test
@./test
gen_ft8: gen_ft8.o ft8/constants.o ft8/text.o ft8/pack.o ft8/encode.o ft8/crc.o common/wave.o
$(CXX) $(LDFLAGS) -o $@ $^
test: test.o ft8/pack.o ft8/encode.o ft8/crc.o ft8/text.o ft8/constants.o fft/kiss_fftr.o fft/kiss_fft.o
$(CXX) $(LDFLAGS) -o $@ $^
decode_ft8: decode_ft8.o fft/kiss_fftr.o fft/kiss_fft.o ft8/decode.o ft8/encode.o ft8/crc.o ft8/ldpc.o ft8/unpack.o ft8/text.o ft8/constants.o common/wave.o
$(CXX) $(LDFLAGS) -o $@ $^
clean:
rm -f *.o ft8/*.o common/*.o fft/*.o $(TARGETS)
install:
$(AR) rc libft8.a ft8/constants.o ft8/encode.o ft8/pack.o ft8/text.o common/wave.o
install libft8.a /usr/lib/libft8.a

33
apps/Makefile 100644
Wyświetl plik

@ -0,0 +1,33 @@
CC = gcc
CFLAGS = -std=gnu11 -O3
CFLAGS += -I./common
CFLAGS += -I../libs/libfft/include
CFLAGS += -I../libs/libft8/include
LDFLAGS = -lm
LDFLAGS += -L../libs/libfft/bin -lfft
LDFLAGS += -L../libs/libft8/bin -lft8
TARGETS = gen_ft8 decode_ft8 test
.PHONY: run_tests all clean
all: $(TARGETS)
run_tests: test
@./test
gen_ft8: gen_ft8.o common/wave.o
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
test: test.o
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
decode_ft8: decode_ft8.o common/wave.o
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
clean:
rm -f *.o common/*.o $(TARGETS)
install:
$(INSTALL) -D decode_ft8 /usr/bin/decode_ft8

Wyświetl plik

@ -0,0 +1,12 @@
#pragma once
#include <stdio.h>
#define LOG_DEBUG 0
#define LOG_INFO 1
#define LOG_WARN 2
#define LOG_ERROR 3
#define LOG_FATAL 4
#define LOG(level, ...) if (level >= LOG_LEVEL) fprintf(stderr, __VA_ARGS__)

130
apps/common/wave.c 100644
Wyświetl plik

@ -0,0 +1,130 @@
#include "wave.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdint.h>
// 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)
{
char subChunk1ID[4] = {'f', 'm', 't', ' '};
uint32_t subChunk1Size = 16; // 16 for PCM
uint16_t audioFormat = 1; // PCM = 1
uint16_t numChannels = 1;
uint16_t bitsPerSample = 16;
uint32_t sampleRate = sample_rate;
uint16_t blockAlign = numChannels * bitsPerSample / 8;
uint32_t byteRate = sampleRate * blockAlign;
int i = 0;
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 (i = 0; i < num_samples; i++)
{
float x = signal[i];
if (x > 1.0)
x = 1.0;
else if (x < -1.0)
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);
fwrite(&numChannels, sizeof(numChannels), 1, f);
fwrite(&sampleRate, sizeof(sampleRate), 1, f);
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);
}
// 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)
{
char subChunk1ID[4]; // = {'f', 'm', 't', ' '};
uint32_t subChunk1Size; // = 16; // 16 for PCM
uint16_t audioFormat; // = 1; // PCM = 1
uint16_t numChannels; // = 1;
uint16_t bitsPerSample; // = 16;
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'};
int i = 0;
FILE *f = fopen(path, "rb");
// 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 (i = 0; i < *num_samples; i++)
{
signal[i] = raw_data[i] / 32768.0f;
}
free(raw_data);
fclose(f);
return 0;
}

10
apps/common/wave.h 100644
Wyświetl plik

@ -0,0 +1,10 @@
#ifndef _INCLUDE_WAVE_H_
#define _INCLUDE_WAVE_H_
// 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);
// 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);
#endif // _INCLUDE_WAVE_H_

271
apps/decode_ft8.c 100644
Wyświetl plik

@ -0,0 +1,271 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <stdbool.h>
#include "ft8_unpack.h"
#include "ft8_ldpc.h"
#include "ft8_decode.h"
#include "ft8_constants.h"
#include "ft8_encode.h"
#include "ft8_crc.h"
#include "wave.h"
#include "debug.h"
#include "kiss_fftr.h"
#define LOG_LEVEL LOG_INFO
const int kMin_score = 10; // Minimum sync score threshold for candidates
const int kMax_candidates = 120;
const int kLDPC_iterations = 20;
const int kMax_decoded_messages = 50;
const int kFreq_osr = 2;
const int kTime_osr = 2;
const float kFSK_dev = 6.25f; // tone deviation in Hz and symbol rate
void usage()
{
fprintf(stderr, "Decode a 15-second (or slighly shorter) WAV file.\n");
}
float hann_i(int i, int N)
{
float x = sinf((float)M_PI * i / N);
return x * x;
}
float hamming_i(int i, int N)
{
const float a0 = (float)25 / 46;
const float a1 = 1 - a0;
float x1 = cosf(2 * (float)M_PI * i / N);
return a0 - a1 * x1;
}
float blackman_i(int i, int N)
{
const float alpha = 0.16f; // or 2860/18608
const float a0 = (1 - alpha) / 2;
const float a1 = 1.0f / 2;
const float a2 = alpha / 2;
float x1 = cosf(2 * (float)M_PI * i / N);
float x2 = 2 * x1 * x1 - 1; // Use double angle formula
return a0 - a1 * x1 + a2 * x2;
}
static float max2(float a, float b)
{
return (a >= b) ? a : b;
}
// Compute FFT magnitudes (log power) for each timeslot in the signal
void extract_power(const float signal[], waterfall_t *power, int block_size)
{
const int subblock_size = block_size / power->time_osr;
const int nfft = block_size * power->freq_osr;
const float fft_norm = 2.0f / nfft;
const int len_window = 1.8f * block_size; // hand-picked and optimized
float window[nfft];
for (int i = 0; i < nfft; ++i)
{
// window[i] = 1;
// window[i] = hann_i(i, nfft);
// window[i] = blackman_i(i, nfft);
// window[i] = hamming_i(i, nfft);
window[i] = (i < len_window) ? hann_i(i, len_window) : 0;
}
size_t fft_work_size;
kiss_fftr_alloc(nfft, 0, 0, &fft_work_size);
LOG(LOG_INFO, "Block size = %d\n", block_size);
LOG(LOG_INFO, "Subblock size = %d\n", subblock_size);
LOG(LOG_INFO, "N_FFT = %d\n", nfft);
LOG(LOG_INFO, "FFT work area = %lu\n", fft_work_size);
void *fft_work = malloc(fft_work_size);
kiss_fftr_cfg fft_cfg = kiss_fftr_alloc(nfft, 0, fft_work, &fft_work_size);
int offset = 0;
float max_mag = -120.0f;
for (int idx_block = 0; idx_block < power->num_blocks; ++idx_block)
{
// Loop over two possible time offsets (0 and block_size/2)
for (int time_sub = 0; time_sub < power->time_osr; ++time_sub)
{
kiss_fft_scalar timedata[nfft];
kiss_fft_cpx freqdata[nfft / 2 + 1];
float mag_db[nfft / 2 + 1];
// Extract windowed signal block
for (int pos = 0; pos < nfft; ++pos)
{
timedata[pos] = window[pos] * signal[(idx_block * block_size) + (time_sub * subblock_size) + pos];
}
kiss_fftr(fft_cfg, timedata, freqdata);
// Compute log magnitude in decibels
for (int idx_bin = 0; idx_bin < nfft / 2 + 1; ++idx_bin)
{
float mag2 = (freqdata[idx_bin].i * freqdata[idx_bin].i) + (freqdata[idx_bin].r * freqdata[idx_bin].r);
mag_db[idx_bin] = 10.0f * log10f(1E-12f + mag2 * fft_norm * fft_norm);
}
// Loop over two possible frequency bin offsets (for averaging)
for (int freq_sub = 0; freq_sub < power->freq_osr; ++freq_sub)
{
for (int pos = 0; pos < power->num_bins; ++pos)
{
float db = mag_db[pos * power->freq_osr + freq_sub];
// Scale decibels to unsigned 8-bit range and clamp the value
// Range 0-240 covers -120..0 dB in 0.5 dB steps
int scaled = (int)(2 * db + 240);
power->mag[offset] = (scaled < 0) ? 0 : ((scaled > 255) ? 255 : scaled);
++offset;
if (db > max_mag)
max_mag = db;
}
}
}
}
LOG(LOG_INFO, "Max magnitude: %.1f dB\n", max_mag);
free(fft_work);
}
int main(int argc, char **argv)
{
// Expect one command-line argument
if (argc < 2)
{
usage();
return -1;
}
const char *wav_path = argv[1];
int sample_rate = 12000;
int num_samples = 15 * sample_rate;
float signal[num_samples];
int rc = load_wav(signal, &num_samples, &sample_rate, wav_path);
if (rc < 0)
{
return -1;
}
// 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 block_size = (int)(sample_rate / kFSK_dev); // samples corresponding to one FSK symbol
const int subblock_size = block_size / kTime_osr;
const int nfft = block_size * kFreq_osr;
const int num_blocks = (num_samples - nfft + subblock_size) / block_size;
LOG(LOG_INFO, "Sample rate %d Hz, %d blocks, %d bins\n", sample_rate, num_blocks, num_bins);
// Compute FFT over the whole signal and store it
uint8_t mag_power[num_blocks * kFreq_osr * kTime_osr * num_bins];
waterfall_t power = {
.num_blocks = num_blocks,
.num_bins = num_bins,
.time_osr = kTime_osr,
.freq_osr = kFreq_osr,
.mag = mag_power};
extract_power(signal, &power, block_size);
// Find top candidates by Costas sync score and localize them in time and frequency
candidate_t candidate_list[kMax_candidates];
int num_candidates = find_sync(&power, kMax_candidates, candidate_list, kMin_score);
// Hash table for decoded messages (to check for duplicates)
int num_decoded = 0;
message_t decoded[kMax_decoded_messages];
message_t *decoded_hashtable[kMax_decoded_messages];
// Initialize hash table pointers
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)
{
const candidate_t *cand = &candidate_list[idx];
if (cand->score < kMin_score)
continue;
float freq_hz = (cand->freq_offset + (float)cand->freq_sub / kFreq_osr) * kFSK_dev;
float time_sec = (cand->time_offset + (float)cand->time_sub / kTime_osr) / kFSK_dev;
message_t message;
decode_status_t status;
if (!decode(&power, cand, &message, kLDPC_iterations, &status))
{
if (status.ldpc_errors > 0)
{
LOG(LOG_DEBUG, "LDPC decode: %d errors\n", status.ldpc_errors);
}
else if (status.crc_calculated != status.crc_extracted)
{
LOG(LOG_DEBUG, "CRC mismatch!\n");
}
else if (status.unpack_status != 0)
{
LOG(LOG_DEBUG, "Error while unpacking!\n");
}
continue;
}
LOG(LOG_DEBUG, "Checking hash table for %4.1fs / %4.1fHz [%d]...\n", time_sec, freq_hz, cand->score);
int idx_hash = message.hash % kMax_decoded_messages;
bool found_empty_slot = false;
bool found_duplicate = false;
do
{
if (decoded_hashtable[idx_hash] == NULL)
{
LOG(LOG_DEBUG, "Found an empty slot\n");
found_empty_slot = true;
}
else if ((decoded_hashtable[idx_hash]->hash == message.hash) && (0 == strcmp(decoded_hashtable[idx_hash]->text, message.text)))
{
LOG(LOG_DEBUG, "Found a duplicate [%s]\n", message.text);
found_duplicate = true;
}
else
{
LOG(LOG_DEBUG, "Hash table clash!\n");
// 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)
{
// Fill the empty hashtable slot
memcpy(&decoded[idx_hash], &message, sizeof(message));
decoded_hashtable[idx_hash] = &decoded[idx_hash];
++num_decoded;
// Fake WSJT-X-like output for now
int snr = 0; // TODO: compute SNR
printf("000000 %3d %+4.2f %4.0f ~ %s\n", cand->score, time_sec, freq_hz, message.text);
}
}
LOG(LOG_INFO, "Decoded %d messages\n", num_decoded);
return 0;
}

210
apps/gen_ft8.c 100644
Wyświetl plik

@ -0,0 +1,210 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <stdbool.h>
#include "wave.h"
#include "debug.h"
#include "ft8_pack.h"
#include "ft8_encode.h"
#include "ft8_constants.h"
#define LOG_LEVEL LOG_INFO
#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_BT 2.0f // symbol smoothing filter bandwidth factor (BT)
#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_BT 1.0f // symbol smoothing filter bandwidth factor (BT)
#define GFSK_CONST_K 5.336446f // pi * sqrt(2 / log(2))
/// Computes a GFSK smoothing pulse.
/// 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] b 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)
{
int i = 0;
for (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);
pulse[i] = (erff(arg1) - erff(arg2)) / 2;
}
}
/// Synthesize waveform data using GFSK phase shaping.
/// The output waveform will contain n_sym symbols.
/// @param[in] symbols Array of symbols (tones) (0-7 for FT8)
/// @param[in] n_sym Number of symbols in the symbol array
/// @param[in] f0 Audio frequency in Hertz for the symbol 0 (base frequency)
/// @param[in] symbol_bt Symbol smoothing filter bandwidth (2 for FT8, 1 for FT4)
/// @param[in] symbol_rate Rate of symbols per second, 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)
///
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_wave = n_sym * n_spsym; // Number of output samples
float hmod = 1.0f;
int i = 0;
int j = 0;
int k = 0;
LOG(LOG_DEBUG, "n_spsym = %d\n", n_spsym);
// Compute the smoothed frequency waveform.
// Length = (nsym+2)*n_spsym samples, first and last symbols extended
float dphi_peak = 2 * M_PI * hmod / n_spsym;
float dphi[n_wave + 2 * n_spsym];
// Shift frequency up by f0
for (i = 0; i < n_wave + 2 * n_spsym; ++i)
{
dphi[i] = 2 * M_PI * f0 / signal_rate;
}
float pulse[3 * n_spsym];
gfsk_pulse(n_spsym, symbol_bt, pulse);
for (i = 0; i < n_sym; ++i)
{
int ib = i * n_spsym;
for (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 (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 (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 (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;
}
}
void usage()
{
printf("Generate a 15-second WAV file encoding a given message.\n");
printf("Usage:\n");
printf("\n");
printf("gen_ft8 MESSAGE WAV_FILE [FREQUENCY]\n");
printf("\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)
{
// Expect two command-line arguments
if (argc < 3)
{
usage();
return -1;
}
int j = 0;
int i = 0;
const char *message = argv[1];
const char *wav_path = argv[2];
float frequency = 1000.0;
if (argc > 3)
{
frequency = atof(argv[3]);
}
bool is_ft4 = (argc > 4) && (0 == strcmp(argv[4], "-ft4"));
// First, pack the text data into binary message
uint8_t packed[FT8_LDPC_K_BYTES];
int rc = pack77(message, packed);
if (rc < 0)
{
printf("Cannot parse message!\n");
printf("RC = %d\n", rc);
return -2;
}
printf("Packed data: ");
for (j = 0; j < 10; ++j)
{
printf("%02x ", packed[j]);
}
printf("\n");
if (is_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 (i = 0; i < 10; ++i)
{
packed[i] ^= kFT4_XOR_sequence[i];
}
}
int num_tones = (is_ft4) ? FT4_NN : FT8_NN;
float symbol_rate = (is_ft4) ? FT4_SYMBOL_RATE : FT8_SYMBOL_RATE;
float symbol_bt = (is_ft4) ? FT4_SYMBOL_BT : FT8_SYMBOL_BT;
float slot_time = (is_ft4) ? FT4_SLOT_TIME : FT8_SLOT_TIME;
// Second, encode the binary message as a sequence of FSK tones
uint8_t tones[num_tones]; // Array of 79 tones (symbols)
if (is_ft4)
{
genft4(packed, tones);
}
else
{
genft8(packed, tones);
}
printf("FSK tones: ");
for (j = 0; j < num_tones; ++j)
{
printf("%d", tones[j]);
}
printf("\n");
// Third, convert the FSK tones into an audio signal
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_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
float signal[num_total_samples];
for (i = 0; i < num_silence; i++)
{
signal[i] = 0;
signal[i + num_samples + num_silence] = 0;
}
// Synthesize waveform data (signal) and save it as WAV file
synth_gfsk(tones, num_tones, frequency, symbol_bt, symbol_rate, sample_rate, signal + num_silence);
save_wav(signal, num_total_samples, sample_rate, wav_path);
return 0;
}

158
apps/test.c 100644
Wyświetl plik

@ -0,0 +1,158 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <stdbool.h>
#include "ft8_text.h"
#include "ft8_pack.h"
#include "ft8_encode.h"
#include "ft8_constants.h"
#include "kiss_fftr.h"
#include "debug.h"
#define LOG_LEVEL LOG_INFO
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;
}
}
}
/*
bool test1() {
//const char *msg = "CQ DL7ACA JO40"; // 62, 32, 32, 49, 37, 27, 59, 2, 30, 19, 49, 16
const char *msg = "VA3UG F1HMR 73"; // 52, 54, 60, 12, 55, 54, 7, 19, 2, 23, 59, 16
//const char *msg = "RA3Y VE3NLS 73"; // 46, 6, 32, 22, 55, 20, 11, 32, 53, 23, 59, 16
uint8_t a72[9];
int rc = packmsg(msg, a72);
if (rc < 0) return false;
LOG(LOG_INFO, "8-bit packed: ");
for (int i = 0; i < 9; ++i) {
LOG(LOG_INFO, "%02x ", a72[i]);
}
LOG(LOG_INFO, "\n");
uint8_t a72_6bit[12];
convert_8bit_to_6bit(a72_6bit, a72, 72);
LOG(LOG_INFO, "6-bit packed: ");
for (int i = 0; i < 12; ++i) {
LOG(LOG_INFO, "%d ", a72_6bit[i]);
}
LOG(LOG_INFO, "\n");
char msg_out_raw[14];
unpack(a72, msg_out_raw);
char msg_out[14];
fmtmsg(msg_out, msg_out_raw);
LOG(LOG_INFO, "msg_out = [%s]\n", msg_out);
return true;
}
void test2() {
uint8_t test_in[11] = { 0xF1, 0x02, 0x03, 0x04, 0x05, 0x60, 0x70, 0x80, 0x90, 0xA0, 0xFF };
uint8_t test_out[22];
encode174(test_in, test_out);
for (int j = 0; j < 22; ++j) {
LOG(LOG_INFO, "%02x ", test_out[j]);
}
LOG(LOG_INFO, "\n");
}
void test3() {
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
LOG(LOG_INFO, "CRC: %04x\n", crc1); // should be 0x0708
}
*/
void test_tones(float *log174)
{
// Just a test case
for (int i = 0; i < FT8_ND; ++i)
{
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()
{
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;
}