diff --git a/Cargo.toml b/Cargo.toml index 3b0a26c..fc16cff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ [workspace] members = [ + "src/trx-ft8", "src/trx-server", "src/trx-server/trx-backend", "src/trx-server/trx-backend/trx-backend-ft817", diff --git a/external/ft8_lib/.clang-format b/external/ft8_lib/.clang-format new file mode 100644 index 0000000..9d0668c --- /dev/null +++ b/external/ft8_lib/.clang-format @@ -0,0 +1,33 @@ +BasedOnStyle: WebKit +# Cpp11BracedListStyle: false +# ColumnLimit: 120 +IndentCaseLabels: false +IndentExternBlock: false +IndentWidth: 4 +TabWidth: 8 +UseTab: Never +PointerAlignment: Left +SortIncludes: false +AlignConsecutiveMacros: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AllowShortFunctionsOnASingleLine: false +AlignTrailingComments: true +BreakConstructorInitializers: BeforeColon +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 0 +BreakBeforeBraces: Custom +BreakBeforeBinaryOperators: All +BraceWrapping: + AfterControlStatement: true + AfterClass: true + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeElse: true + BeforeCatch: true diff --git a/external/ft8_lib/.gitignore b/external/ft8_lib/.gitignore new file mode 100644 index 0000000..e80131a --- /dev/null +++ b/external/ft8_lib/.gitignore @@ -0,0 +1,9 @@ +gen_ft8 +decode_ft8 +test_ft8 +libft8.a +wsjtx2/ +.build/ +.DS_Store +.vscode/ +__pycache__/ \ No newline at end of file diff --git a/external/ft8_lib/LICENSE b/external/ft8_lib/LICENSE new file mode 100644 index 0000000..7fb6a38 --- /dev/null +++ b/external/ft8_lib/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Kārlis Goba + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/external/ft8_lib/Makefile b/external/ft8_lib/Makefile new file mode 100644 index 0000000..8626c6b --- /dev/null +++ b/external/ft8_lib/Makefile @@ -0,0 +1,58 @@ +BUILD_DIR = .build + +FT8_SRC = $(wildcard ft8/*.c) +FT8_OBJ = $(patsubst %.c,$(BUILD_DIR)/%.o,$(FT8_SRC)) + +COMMON_SRC = $(wildcard common/*.c) +COMMON_OBJ = $(patsubst %.c,$(BUILD_DIR)/%.o,$(COMMON_SRC)) + +FFT_SRC = $(wildcard fft/*.c) +FFT_OBJ = $(patsubst %.c,$(BUILD_DIR)/%.o,$(FFT_SRC)) + +TARGETS = libft8.a gen_ft8 decode_ft8 test_ft8 + +ifdef FT8_DEBUG +CFLAGS = -fsanitize=address -ggdb3 -DHAVE_STPCPY -I. -DFTX_DEBUG_PRINT +LDFLAGS = -fsanitize=address -lm +else +CFLAGS = -O3 -DHAVE_STPCPY -I. +LDFLAGS = -lm +endif + +# Optionally, use Portaudio for live audio input +# Portaudio is a C++ library, so then you need to set CC=clang++ or CC=g++ +ifdef PORTAUDIO_PREFIX +CFLAGS += -DUSE_PORTAUDIO -I$(PORTAUDIO_PREFIX)/include +LDFLAGS += -lportaudio -L$(PORTAUDIO_PREFIX)/lib +endif + +.PHONY: all clean run_tests install + +all: $(TARGETS) + +clean: + rm -rf $(BUILD_DIR) $(TARGETS) + +run_tests: test_ft8 + @./test_ft8 + +install: libft8.a + install libft8.a /usr/lib/libft8.a + +gen_ft8: $(BUILD_DIR)/demo/gen_ft8.o libft8.a + $(CC) $(CFLAGS) -o $@ .build/demo/gen_ft8.o -lft8 -L. -lm + +decode_ft8: $(BUILD_DIR)/demo/decode_ft8.o libft8.a $(FFT_OBJ) + $(CC) $(CFLAGS) -o $@ $(BUILD_DIR)/demo/decode_ft8.o $(FFT_OBJ) -lft8 -L. -lm + +test_ft8: $(BUILD_DIR)/test/test.o libft8.a + $(CC) $(CFLAGS) -o $@ .build/test/test.o -lft8 -L. -lm + +$(BUILD_DIR)/%.o: %.c + @mkdir -p $(dir $@) + $(CC) $(CFLAGS) -o $@ -c $^ + +lib: libft8.a + +libft8.a: $(FT8_OBJ) $(COMMON_OBJ) + $(AR) rc libft8.a $(FT8_OBJ) $(COMMON_OBJ) diff --git a/external/ft8_lib/README.md b/external/ft8_lib/README.md new file mode 100644 index 0000000..4ebe25d --- /dev/null +++ b/external/ft8_lib/README.md @@ -0,0 +1,54 @@ +# FT8 (and now FT4) library + +C implementation of a lightweight FT8/FT4 decoder and encoder, mostly intended for experimental use on microcontrollers. + +The intent of this library is to allow FT8/FT4 encoding and decoding in standalone environments (i.e. without a PC or RPi), e.g. automated beacons or SDR transceivers. It's also my learning process, optimization problem and source of fun. + +The encoding process is relatively light on resources, and an Arduino should be perfectly capable of running this code. + +The decoder is designed with memory and computing efficiency in mind, in order to be usable with a fast enough microcontroller. It is shown to be working on STM32F7 boards fast enough for real work, but the embedded application itself is beyond this repository. This repository provides an example decoder which can decode a 15-second WAV file on a desktop machine or SBC. The decoder needs to access the whole 15-second window in spectral magnitude representation (the window can be also shorter, and messages can have varying starting time within the window). The example FT8 decoder can work with slightly less than 200 KB of RAM. + +# Current state + +Currently the basic message set for establishing QSOs, as well as telemetry and free-text message modes are supported: +* CQ {call} {grid}, e.g. CQ CA0LL GG77 +* CQ {xy} {call} {grid}, e.g. CQ JA CA0LL GG77 +* {call} {call} {report}, e.g. CA0LL OT7ER R-07 +* {call} {call} 73/RRR/RR73, e.g. OT7ER CA0LL 73 +* Free-text messages (up to 13 characters from a limited alphabet) (decoding only, untested) +* Telemetry data (71 bits as 18 hex symbols) + +Encoding and decoding works for both FT8 and FT4. For encoding and decoding, there is a console application provided for each, which serves mostly as test code, and could be a starting point for your potential application on an MCU. The console apps should run perfectly well on a RPi or a PC/Mac. I don't provide a concrete example for a particular MCU hardware here, since it would be very specific. + +The code is not yet really a library, rather a collection of routines and example code. + +# Future ideas + +Incremental decoding (processing during the 15 second window) is something that I would like to explore, but haven't started. + +These features are low on my priority list: +* Contest modes +* Compound callsigns with country prefixes and special callsigns + +# What to do with it + +You can generate 15-second WAV files with your own messages as a proof of concept or for testing purposes. They can either be played back or opened directly from WSJT-X. To do that, run ```make```. Then run ```gen_ft8``` (run it without parameters to check what parameters are supported). Currently messages are modulated at 1000-1050 Hz. + +You can decode 15-second (or shorter) WAV files with ```decode_ft8```. This is only an example application and does not support live processing/recording. For that you could use third party code (PortAudio, for example). + +# References and credits + +Thanks goes out to: +* my contributors who have provided me with various improvements which have often been beyond my skill set. +* Robert Morris, AB1HL, whose Python code (https://github.com/rtmrtmrtmrtm/weakmon) inspired this and helped to test various parts of the code. +* Mark Borgerding for his FFT implementation (https://github.com/mborgerding/kissfft). I have included a portion of his code. +* WSJT-X authors, who developed a very interesting and novel communications protocol + +The details of FT4 and FT8 procotols and decoding/encoding are described here: https://physics.princeton.edu/pulsar/k1jt/FT4_FT8_QEX.pdf + +The public part of FT4/FT8 implementation is included in this repository under ft4_ft8_public. + +Of course in moments of frustration I have looked up the original WSJT-X code, which is mostly written in Fortran (http://physics.princeton.edu/pulsar/K1JT/wsjtx.html). However, this library contains my own original DSP routines and a different implementation of the decoder which is suitable for resource-constrained embedded environments. + +Karlis Goba, +YL3JG diff --git a/external/ft8_lib/common/audio.c b/external/ft8_lib/common/audio.c new file mode 100644 index 0000000..5d97fd2 --- /dev/null +++ b/external/ft8_lib/common/audio.c @@ -0,0 +1,191 @@ +#include "audio.h" + +#include +#include + +#ifdef USE_PORTAUDIO +#include + +typedef struct +{ + PaStream* instream; +} audio_context_t; + +static audio_context_t audio_context; + +static int audio_cb(void* inputBuffer, void* outputBuffer, unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void* userData) +{ + audio_context_t* context = (audio_context_t*)userData; + float* samples_in = (float*)inputBuffer; + + // PaTime time = data->startTime + timeInfo->inputBufferAdcTime; + printf("Callback with %ld samples\n", framesPerBuffer); + return 0; +} + +void audio_list(void) +{ + PaError pa_rc; + + pa_rc = Pa_Initialize(); // Initialize PortAudio + if (pa_rc != paNoError) + { + printf("Error initializing PortAudio.\n"); + printf("\tErrortext: %s\n\tNumber: %d\n", Pa_GetErrorText(pa_rc), pa_rc); + return; + } + + int numDevices; + numDevices = Pa_GetDeviceCount(); + if (numDevices < 0) + { + printf("ERROR: Pa_CountDevices returned 0x%x\n", numDevices); + return; + } + + printf("%d audio devices found:\n", numDevices); + for (int i = 0; i < numDevices; i++) + { + const PaDeviceInfo* deviceInfo = Pa_GetDeviceInfo(i); + + PaStreamParameters inputParameters = { + .device = i, + .channelCount = 1, // 1 = mono, 2 = stereo + .sampleFormat = paFloat32, + .suggestedLatency = 0.2, + .hostApiSpecificStreamInfo = NULL + }; + double sample_rate = 12000; // sample rate (frames per second) + pa_rc = Pa_IsFormatSupported(&inputParameters, NULL, sample_rate); + + printf("%d: [%s] [%s]\n", (i + 1), deviceInfo->name, (pa_rc == paNoError) ? "OK" : "NOT SUPPORTED"); + } +} + +int audio_init(void) +{ + PaError pa_rc; + + pa_rc = Pa_Initialize(); // Initialize PortAudio + if (pa_rc != paNoError) + { + printf("Error initializing PortAudio.\n"); + printf("\tErrortext: %s\n\tNumber: %d\n", Pa_GetErrorText(pa_rc), pa_rc); + Pa_Terminate(); // I don't think we need this but... + return -1; + } + return 0; +} + +int audio_open(const char* name) +{ + PaError pa_rc; + audio_context.instream = NULL; + + PaDeviceIndex ndevice_in = -1; + int numDevices = Pa_GetDeviceCount(); + for (int i = 0; i < numDevices; i++) + { + const PaDeviceInfo* deviceInfo = Pa_GetDeviceInfo(i); + if (0 == strcmp(deviceInfo->name, name)) + { + ndevice_in = i; + break; + } + } + + if (ndevice_in < 0) + { + printf("Could not find device [%s].\n", name); + audio_list(); + return -1; + } + + unsigned long nfpb = 1920 / 4; // frames per buffer + double sample_rate = 12000; // sample rate (frames per second) + + PaStreamParameters inputParameters = { + .device = ndevice_in, + .channelCount = 1, // 1 = mono, 2 = stereo + .sampleFormat = paFloat32, + .suggestedLatency = 0.2, + .hostApiSpecificStreamInfo = NULL + }; + + // Test if this configuration actually works, so we do not run into an ugly assertion + pa_rc = Pa_IsFormatSupported(&inputParameters, NULL, sample_rate); + if (pa_rc != paNoError) + { + printf("Error opening input audio stream.\n"); + printf("\tErrortext: %s\n\tNumber: %d\n", Pa_GetErrorText(pa_rc), pa_rc); + return -2; + } + + PaStream* instream; + pa_rc = Pa_OpenStream( + &instream, // address of stream + &inputParameters, + NULL, + sample_rate, // Sample rate + nfpb, // Frames per buffer + paNoFlag, + NULL /*(PaStreamCallback*)audio_cb*/, // Callback routine + NULL /*(void*)&audio_context*/); // address of data structure + if (pa_rc != paNoError) + { // We should have no error here usually + printf("Error opening input audio stream:\n"); + printf("\tErrortext: %s\n\tNumber: %d\n", Pa_GetErrorText(pa_rc), pa_rc); + return -3; + } + // printf("Successfully opened audio input.\n"); + + pa_rc = Pa_StartStream(instream); // Start input stream + if (pa_rc != paNoError) + { + printf("Error starting input audio stream!\n"); + printf("\tErrortext: %s\n\tNumber: %d\n", Pa_GetErrorText(pa_rc), pa_rc); + return -4; + } + + audio_context.instream = instream; + + // while (Pa_IsStreamActive(instream)) + // { + // Pa_Sleep(100); + // } + // Pa_AbortStream(instream); // Abort stream + // Pa_CloseStream(instream); // Close stream, we're done. + + return 0; +} + +int audio_read(float* buffer, int num_samples) +{ + PaError pa_rc; + pa_rc = Pa_ReadStream(audio_context.instream, (void*)buffer, num_samples); + return 0; +} + +#else + +int audio_init(void) +{ + return -1; +} + +void audio_list(void) +{ +} + +int audio_open(const char* name) +{ + return -1; +} + +int audio_read(float* buffer, int num_samples) +{ + return -1; +} + +#endif \ No newline at end of file diff --git a/external/ft8_lib/common/audio.h b/external/ft8_lib/common/audio.h new file mode 100644 index 0000000..213ca29 --- /dev/null +++ b/external/ft8_lib/common/audio.h @@ -0,0 +1,18 @@ +#ifndef _INCLUDE_AUDIO_H_ +#define _INCLUDE_AUDIO_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +int audio_init(void); +void audio_list(void); +int audio_open(const char* name); +int audio_read(float* buffer, int num_samples); + +#ifdef __cplusplus +} +#endif + +#endif // _INCLUDE_AUDIO_H_ \ No newline at end of file diff --git a/external/ft8_lib/common/common.h b/external/ft8_lib/common/common.h new file mode 100644 index 0000000..fc2a2f9 --- /dev/null +++ b/external/ft8_lib/common/common.h @@ -0,0 +1,3 @@ +#ifndef M_PI + #define M_PI 3.14159265358979323846 +#endif diff --git a/external/ft8_lib/common/monitor.c b/external/ft8_lib/common/monitor.c new file mode 100644 index 0000000..6bd228a --- /dev/null +++ b/external/ft8_lib/common/monitor.c @@ -0,0 +1,263 @@ +#include "monitor.h" +#include + +#define LOG_LEVEL LOG_INFO +#include + +#include + +static float hann_i(int i, int N) +{ + float x = sinf((float)M_PI * i / N); + return x * x; +} + +// static 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; +// } + +// static 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 void waterfall_init(ftx_waterfall_t* me, int max_blocks, int num_bins, int time_osr, int freq_osr) +{ + size_t mag_size = max_blocks * time_osr * freq_osr * num_bins * sizeof(me->mag[0]); + me->max_blocks = max_blocks; + me->num_blocks = 0; + me->num_bins = num_bins; + me->time_osr = time_osr; + me->freq_osr = freq_osr; + me->block_stride = (time_osr * freq_osr * num_bins); + me->mag = (WF_ELEM_T*)malloc(mag_size); + LOG(LOG_DEBUG, "Waterfall size = %zu\n", mag_size); +} + +static void waterfall_free(ftx_waterfall_t* me) +{ + free(me->mag); +} + +void monitor_init(monitor_t* me, const monitor_config_t* cfg) +{ + float slot_time = (cfg->protocol == FTX_PROTOCOL_FT4) ? FT4_SLOT_TIME : FT8_SLOT_TIME; + float symbol_period = (cfg->protocol == FTX_PROTOCOL_FT4) ? FT4_SYMBOL_PERIOD : FT8_SYMBOL_PERIOD; + // Compute DSP parameters that depend on the sample rate + me->block_size = (int)(cfg->sample_rate * symbol_period); // samples corresponding to one FSK symbol + me->subblock_size = me->block_size / cfg->time_osr; + me->nfft = me->block_size * cfg->freq_osr; + me->fft_norm = 2.0f / me->nfft; + // const int len_window = 1.8f * me->block_size; // hand-picked and optimized + + me->window = (float*)malloc(me->nfft * sizeof(me->window[0])); + for (int i = 0; i < me->nfft; ++i) + { + // window[i] = 1; + me->window[i] = me->fft_norm * hann_i(i, me->nfft); + // me->window[i] = blackman_i(i, me->nfft); + // me->window[i] = hamming_i(i, me->nfft); + // me->window[i] = (i < len_window) ? hann_i(i, len_window) : 0; + } + me->last_frame = (float*)calloc(me->nfft, sizeof(me->last_frame[0])); + + LOG(LOG_INFO, "Block size = %d\n", me->block_size); + LOG(LOG_INFO, "Subblock size = %d\n", me->subblock_size); + + size_t fft_work_size = 0; + kiss_fftr_alloc(me->nfft, 0, 0, &fft_work_size); + me->fft_work = malloc(fft_work_size); + me->fft_cfg = kiss_fftr_alloc(me->nfft, 0, me->fft_work, &fft_work_size); + + LOG(LOG_INFO, "N_FFT = %d\n", me->nfft); + LOG(LOG_DEBUG, "FFT work area = %zu\n", fft_work_size); + +#ifdef WATERFALL_USE_PHASE + me->nifft = 64; // Gives 200 Hz sample rate for FT8 (160ms symbol period) + + size_t ifft_work_size = 0; + kiss_fft_alloc(me->nifft, 1, 0, &ifft_work_size); + me->ifft_work = malloc(ifft_work_size); + me->ifft_cfg = kiss_fft_alloc(me->nifft, 1, me->ifft_work, &ifft_work_size); + + LOG(LOG_INFO, "N_iFFT = %d\n", me->nifft); + LOG(LOG_DEBUG, "iFFT work area = %zu\n", ifft_work_size); +#endif + + // Allocate enough blocks to fit the entire FT8/FT4 slot in memory + const int max_blocks = (int)(slot_time / symbol_period); + // Keep only FFT bins in the specified frequency range (f_min/f_max) + me->min_bin = (int)(cfg->f_min * symbol_period); + me->max_bin = (int)(cfg->f_max * symbol_period) + 1; + const int num_bins = me->max_bin - me->min_bin; + + waterfall_init(&me->wf, max_blocks, num_bins, cfg->time_osr, cfg->freq_osr); + me->wf.protocol = cfg->protocol; + + me->symbol_period = symbol_period; + + me->max_mag = -120.0f; +} + +void monitor_free(monitor_t* me) +{ + waterfall_free(&me->wf); + free(me->fft_work); + free(me->last_frame); + free(me->window); +} + +void monitor_reset(monitor_t* me) +{ + me->wf.num_blocks = 0; + me->max_mag = -120.0f; +} + +// Compute FFT magnitudes (log wf) for a frame in the signal and update waterfall data +void monitor_process(monitor_t* me, const float* frame) +{ + // Check if we can still store more waterfall data + if (me->wf.num_blocks >= me->wf.max_blocks) + return; + + int offset = me->wf.num_blocks * me->wf.block_stride; + int frame_pos = 0; + + // Loop over block subdivisions + 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) + { + me->last_frame[pos] = me->last_frame[pos + me->subblock_size]; + } + for (int pos = me->nfft - me->subblock_size; pos < me->nfft; ++pos) + { + me->last_frame[pos] = frame[frame_pos]; + ++frame_pos; + } + + // Do DFT of windowed analysis frame + for (int pos = 0; pos < me->nfft; ++pos) + { + timedata[pos] = me->window[pos] * me->last_frame[pos]; + } + kiss_fftr(me->fft_cfg, timedata, freqdata); + + // Loop over possible frequency OSR offsets + for (int freq_sub = 0; freq_sub < me->wf.freq_osr; ++freq_sub) + { + for (int bin = me->min_bin; bin < me->max_bin; ++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); + +#ifdef WATERFALL_USE_PHASE + // Save the magnitude in dB and phase in radians + float phase = atan2f(freqdata[src_bin].i, freqdata[src_bin].r); + me->wf.mag[offset].mag = db; + me->wf.mag[offset].phase = phase; +#else + // 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); + me->wf.mag[offset] = (scaled < 0) ? 0 : ((scaled > 255) ? 255 : scaled); +#endif + ++offset; + + if (db > me->max_mag) + me->max_mag = db; + } + } + } + + ++me->wf.num_blocks; +} + +#ifdef WATERFALL_USE_PHASE +void monitor_resynth(const monitor_t* me, const candidate_t* candidate, float* signal) +{ + const int num_ifft = me->nifft; + const int num_shift = num_ifft / 2; + const int taper_width = 4; + const int num_tones = 8; + + // Starting offset is 3 subblocks due to analysis buffer loading + int offset = 1; // candidate->time_offset; + offset = (offset * me->wf.time_osr) + 1; // + candidate->time_sub; + offset = (offset * me->wf.freq_osr); // + candidate->freq_sub; + offset = (offset * me->wf.num_bins); // + candidate->freq_offset; + + WF_ELEM_T* el = me->wf.mag + offset; + + // DFT frequency data - initialize to zero + kiss_fft_cpx freqdata[num_ifft]; + for (int i = 0; i < num_ifft; ++i) + { + freqdata[i].r = 0; + freqdata[i].i = 0; + } + + int pos = 0; + for (int num_block = 1; num_block < me->wf.num_blocks; ++num_block) + { + // Extract frequency data around the selected candidate only + for (int i = candidate->freq_offset - taper_width - 1; i < candidate->freq_offset + 8 + taper_width - 1; ++i) + { + if ((i >= 0) && (i < me->wf.num_bins)) + { + int tgt_bin = (me->wf.freq_osr * (i - candidate->freq_offset) + num_ifft) % num_ifft; + float weight = 1.0f; + if (i < candidate->freq_offset) + { + weight = ((i - candidate->freq_offset) + taper_width) / (float)taper_width; + } + else if (i > candidate->freq_offset + 7) + { + weight = ((candidate->freq_offset + 7 - i) + taper_width) / (float)taper_width; + } + + // Convert (dB magnitude, phase) to (real, imaginary) + float mag = powf(10.0f, el[i].mag / 20) / 2 * weight; + freqdata[tgt_bin].r = mag * cosf(el[i].phase); + freqdata[tgt_bin].i = mag * sinf(el[i].phase); + + int i2 = i + me->wf.num_bins; + tgt_bin = (tgt_bin + 1) % num_ifft; + float mag2 = powf(10.0f, el[i2].mag / 20) / 2 * weight; + freqdata[tgt_bin].r = mag2 * cosf(el[i2].phase); + freqdata[tgt_bin].i = mag2 * sinf(el[i2].phase); + } + } + + // Compute inverse DFT and overlap-add the waveform + kiss_fft_cpx timedata[num_ifft]; + kiss_fft(me->ifft_cfg, freqdata, timedata); + for (int i = 0; i < num_ifft; ++i) + { + signal[pos + i] += timedata[i].i; + } + + // Move to the next symbol + el += me->wf.block_stride; + pos += num_shift; + } +} +#endif diff --git a/external/ft8_lib/common/monitor.h b/external/ft8_lib/common/monitor.h new file mode 100644 index 0000000..dd70d8d --- /dev/null +++ b/external/ft8_lib/common/monitor.h @@ -0,0 +1,62 @@ +#ifndef _INCLUDE_MONITOR_H_ +#define _INCLUDE_MONITOR_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include +#include + +/// 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 + 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 +{ + float symbol_period; ///< FT4/FT8 symbol period in seconds + int min_bin; ///< First FFT bin in the frequency range (begin) + int max_bin; ///< First FFT bin outside the frequency range (end) + 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) + ftx_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 + kiss_fftr_cfg fft_cfg; ///< Kiss FFT housekeeping object +#ifdef WATERFALL_USE_PHASE + int nifft; ///< iFFT size + void* ifft_work; ///< Work area required by inverse Kiss FFT + kiss_fft_cfg ifft_cfg; ///< Inverse Kiss FFT housekeeping object +#endif +} monitor_t; + +void monitor_init(monitor_t* me, const monitor_config_t* cfg); +void monitor_reset(monitor_t* me); +void monitor_process(monitor_t* me, const float* frame); +void monitor_free(monitor_t* me); + +#ifdef WATERFALL_USE_PHASE +void monitor_resynth(const monitor_t* me, const candidate_t* candidate, float* signal); +#endif + +#ifdef __cplusplus +} +#endif + +#endif // _INCLUDE_MONITOR_H_ \ No newline at end of file diff --git a/external/ft8_lib/common/wave.c b/external/ft8_lib/common/wave.c new file mode 100644 index 0000000..9c0668a --- /dev/null +++ b/external/ft8_lib/common/wave.c @@ -0,0 +1,133 @@ +#include "wave.h" + +#include +#include +#include + +#include + +// Save signal in floating point format (-1 .. +1) as a WAVE file using 16-bit signed integers. +int 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; + + 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]; + 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"); + if (f == NULL) + return -1; + + // 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); + return 0; +} + +// 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'}; + + 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 -2; + + 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 -3; + + fread((void*)subChunk2ID, sizeof(subChunk2ID), 1, f); + fread((void*)&subChunk2Size, sizeof(subChunk2Size), 1, f); + + if (subChunk2Size / blockAlign > *num_samples) + return -4; + + *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; +} diff --git a/external/ft8_lib/common/wave.h b/external/ft8_lib/common/wave.h new file mode 100644 index 0000000..2d60725 --- /dev/null +++ b/external/ft8_lib/common/wave.h @@ -0,0 +1,19 @@ +#ifndef _INCLUDE_WAVE_H_ +#define _INCLUDE_WAVE_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +// Save signal in floating point format (-1 .. +1) as a WAVE file using 16-bit signed integers. +int 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); + +#ifdef __cplusplus +} +#endif + +#endif // _INCLUDE_WAVE_H_ diff --git a/external/ft8_lib/demo/decode_ft8.c b/external/ft8_lib/demo/decode_ft8.c new file mode 100644 index 0000000..dcb0299 --- /dev/null +++ b/external/ft8_lib/demo/decode_ft8.c @@ -0,0 +1,393 @@ +#define _POSIX_C_SOURCE 199309L +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#define LOG_LEVEL LOG_INFO +#include + +const int kMin_score = 10; // Minimum sync score threshold for candidates +const int kMax_candidates = 140; +const int kLDPC_iterations = 25; + +const int kMax_decoded_messages = 50; + +const int kFreq_osr = 2; // Frequency oversampling rate (bin subdivision) +const int kTime_osr = 2; // Time oversampling rate (symbol subdivision) + +void usage(const char* error_msg) +{ + if (error_msg != NULL) + { + fprintf(stderr, "ERROR: %s\n", error_msg); + } + fprintf(stderr, "Usage: decode_ft8 [-list|([-ft4] [INPUT|-dev DEVICE])]\n\n"); + fprintf(stderr, "Decode a 15-second (or slighly shorter) WAV file.\n"); +} + +#define CALLSIGN_HASHTABLE_SIZE 256 + +static struct +{ + char callsign[12]; ///> Up to 11 symbols of callsign + trailing zeros (always filled) + uint32_t hash; ///> 8 MSBs contain the age of callsign; 22 LSBs contain hash value +} callsign_hashtable[CALLSIGN_HASHTABLE_SIZE]; + +static int callsign_hashtable_size; + +void hashtable_init(void) +{ + callsign_hashtable_size = 0; + memset(callsign_hashtable, 0, sizeof(callsign_hashtable)); +} + +void hashtable_cleanup(uint8_t max_age) +{ + for (int idx_hash = 0; idx_hash < CALLSIGN_HASHTABLE_SIZE; ++idx_hash) + { + if (callsign_hashtable[idx_hash].callsign[0] != '\0') + { + uint8_t age = (uint8_t)(callsign_hashtable[idx_hash].hash >> 24); + if (age > max_age) + { + LOG(LOG_INFO, "Removing [%s] from hash table, age = %d\n", callsign_hashtable[idx_hash].callsign, age); + // free the hash entry + callsign_hashtable[idx_hash].callsign[0] = '\0'; + callsign_hashtable[idx_hash].hash = 0; + callsign_hashtable_size--; + } + else + { + // increase callsign age + callsign_hashtable[idx_hash].hash = (((uint32_t)age + 1u) << 24) | (callsign_hashtable[idx_hash].hash & 0x3FFFFFu); + } + } + } +} + +void hashtable_add(const char* callsign, uint32_t hash) +{ + uint16_t hash10 = (hash >> 12) & 0x3FFu; + int idx_hash = (hash10 * 23) % CALLSIGN_HASHTABLE_SIZE; + while (callsign_hashtable[idx_hash].callsign[0] != '\0') + { + if (((callsign_hashtable[idx_hash].hash & 0x3FFFFFu) == hash) && (0 == strcmp(callsign_hashtable[idx_hash].callsign, callsign))) + { + // reset age + callsign_hashtable[idx_hash].hash &= 0x3FFFFFu; + LOG(LOG_DEBUG, "Found a duplicate [%s]\n", callsign); + return; + } + else + { + LOG(LOG_DEBUG, "Hash table clash!\n"); + // Move on to check the next entry in hash table + idx_hash = (idx_hash + 1) % CALLSIGN_HASHTABLE_SIZE; + } + } + callsign_hashtable_size++; + strncpy(callsign_hashtable[idx_hash].callsign, callsign, 11); + callsign_hashtable[idx_hash].callsign[11] = '\0'; + callsign_hashtable[idx_hash].hash = hash; +} + +bool hashtable_lookup(ftx_callsign_hash_type_t hash_type, uint32_t hash, char* callsign) +{ + uint8_t hash_shift = (hash_type == FTX_CALLSIGN_HASH_10_BITS) ? 12 : (hash_type == FTX_CALLSIGN_HASH_12_BITS ? 10 : 0); + uint16_t hash10 = (hash >> (12 - hash_shift)) & 0x3FFu; + int idx_hash = (hash10 * 23) % CALLSIGN_HASHTABLE_SIZE; + while (callsign_hashtable[idx_hash].callsign[0] != '\0') + { + if (((callsign_hashtable[idx_hash].hash & 0x3FFFFFu) >> hash_shift) == hash) + { + strcpy(callsign, callsign_hashtable[idx_hash].callsign); + return true; + } + // Move on to check the next entry in hash table + idx_hash = (idx_hash + 1) % CALLSIGN_HASHTABLE_SIZE; + } + callsign[0] = '\0'; + return false; +} + +ftx_callsign_hash_interface_t hash_if = { + .lookup_hash = hashtable_lookup, + .save_hash = hashtable_add +}; + +void decode(const monitor_t* mon, struct tm* tm_slot_start) +{ + const ftx_waterfall_t* wf = &mon->wf; + // Find top candidates by Costas sync score and localize them in time and frequency + ftx_candidate_t candidate_list[kMax_candidates]; + int num_candidates = ftx_find_candidates(wf, kMax_candidates, candidate_list, kMin_score); + + // Hash table for decoded messages (to check for duplicates) + int num_decoded = 0; + ftx_message_t decoded[kMax_decoded_messages]; + ftx_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 ftx_candidate_t* cand = &candidate_list[idx]; + + float freq_hz = (mon->min_bin + cand->freq_offset + (float)cand->freq_sub / wf->freq_osr) / mon->symbol_period; + float time_sec = (cand->time_offset + (float)cand->time_sub / wf->time_osr) * mon->symbol_period; + +#ifdef WATERFALL_USE_PHASE + // int resynth_len = 12000 * 16; + // float resynth_signal[resynth_len]; + // for (int pos = 0; pos < resynth_len; ++pos) + // { + // resynth_signal[pos] = 0; + // } + // monitor_resynth(mon, cand, resynth_signal); + // char resynth_path[80]; + // sprintf(resynth_path, "resynth_%04f_%02.1f.wav", freq_hz, time_sec); + // save_wav(resynth_signal, resynth_len, 12000, resynth_path); +#endif + + ftx_message_t message; + ftx_decode_status_t status; + if (!ftx_decode_candidate(wf, cand, kLDPC_iterations, &message, &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"); + } + 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 == memcmp(decoded_hashtable[idx_hash]->payload, message.payload, sizeof(message.payload)))) + { + LOG(LOG_DEBUG, "Found a duplicate!\n"); + 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; + + char text[FTX_MAX_MESSAGE_LENGTH]; + ftx_message_offsets_t offsets; + ftx_message_rc_t unpack_status = ftx_message_decode(&message, &hash_if, text, &offsets); + if (unpack_status != FTX_MESSAGE_RC_OK) + { + snprintf(text, sizeof(text), "Error [%d] while unpacking!", (int)unpack_status); + } + + // Fake WSJT-X-like output for now + float snr = cand->score * 0.5f; // TODO: compute better approximation of SNR + printf("%02d%02d%02d %+05.1f %+4.2f %4.0f ~ %s\n", + tm_slot_start->tm_hour, tm_slot_start->tm_min, tm_slot_start->tm_sec, + snr, time_sec, freq_hz, text); + } + } + LOG(LOG_INFO, "Decoded %d messages, callsign hashtable size %d\n", num_decoded, callsign_hashtable_size); + hashtable_cleanup(10); +} + +int main(int argc, char** argv) +{ + // Accepted arguments + const char* wav_path = NULL; + const char* dev_name = NULL; + ftx_protocol_t protocol = FTX_PROTOCOL_FT8; + float time_shift = 0.8; + + // Parse arguments one by one + int arg_idx = 1; + while (arg_idx < argc) + { + // Check if the current argument is an option (-xxx) + if (argv[arg_idx][0] == '-') + { + // Check agaist valid options + if (0 == strcmp(argv[arg_idx], "-ft4")) + { + protocol = FTX_PROTOCOL_FT4; + } + else if (0 == strcmp(argv[arg_idx], "-list")) + { + audio_init(); + audio_list(); + return 0; + } + else if (0 == strcmp(argv[arg_idx], "-dev")) + { + if (arg_idx + 1 < argc) + { + ++arg_idx; + dev_name = argv[arg_idx]; + } + else + { + usage("Expected an audio device name after -dev"); + return -1; + } + } + else + { + usage("Unknown command line option"); + return -1; + } + } + else + { + if (wav_path == NULL) + { + wav_path = argv[arg_idx]; + } + else + { + usage("Multiple positional arguments"); + return -1; + } + } + ++arg_idx; + } + // Check if all mandatory arguments have been received + if (wav_path == NULL && dev_name == NULL) + { + usage("Expected either INPUT file path or DEVICE name"); + return -1; + } + + float slot_period = ((protocol == FTX_PROTOCOL_FT8) ? FT8_SLOT_TIME : FT4_SLOT_TIME); + int sample_rate = 12000; + int num_samples = slot_period * sample_rate; + float signal[num_samples]; + bool is_live = false; + + if (wav_path != NULL) + { + int rc = load_wav(signal, &num_samples, &sample_rate, wav_path); + if (rc < 0) + { + LOG(LOG_ERROR, "ERROR: cannot load wave file %s\n", wav_path); + return -1; + } + LOG(LOG_INFO, "Sample rate %d Hz, %d samples, %.3f seconds\n", sample_rate, num_samples, (double)num_samples / sample_rate); + } + else if (dev_name != NULL) + { + audio_init(); + audio_open(dev_name); + num_samples = (slot_period - 0.4f) * sample_rate; + is_live = true; + } + + // Compute FFT over the whole signal and store it + monitor_t mon; + monitor_config_t mon_cfg = { + .f_min = 200, + .f_max = 3000, + .sample_rate = sample_rate, + .time_osr = kTime_osr, + .freq_osr = kFreq_osr, + .protocol = protocol + }; + + hashtable_init(); + + monitor_init(&mon, &mon_cfg); + LOG(LOG_DEBUG, "Waterfall allocated %d symbols\n", mon.wf.max_blocks); + + do + { + struct tm tm_slot_start = { 0 }; + if (is_live) + { + // Wait for the start of time slot + while (true) + { + struct timespec spec; + clock_gettime(CLOCK_REALTIME, &spec); + double time = (double)spec.tv_sec + (spec.tv_nsec / 1e9); + double time_within_slot = fmod(time - time_shift, slot_period); + if (time_within_slot > slot_period / 4) + { + audio_read(signal, mon.block_size); + } + else + { + time_t time_slot_start = (time_t)(time - time_within_slot); + gmtime_r(&time_slot_start, &tm_slot_start); + LOG(LOG_INFO, "Time within slot %02d%02d%02d: %.3f s\n", tm_slot_start.tm_hour, + tm_slot_start.tm_min, tm_slot_start.tm_sec, time_within_slot); + break; + } + } + } + + // Process and accumulate audio data in a monitor/waterfall instance + for (int frame_pos = 0; frame_pos + mon.block_size <= num_samples; frame_pos += mon.block_size) + { + if (dev_name != NULL) + { + audio_read(signal + frame_pos, mon.block_size); + } + // LOG(LOG_DEBUG, "Frame pos: %.3fs\n", (float)(frame_pos + mon.block_size) / sample_rate); + fprintf(stderr, "#"); + // 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); + } + fprintf(stderr, "\n"); + LOG(LOG_DEBUG, "Waterfall accumulated %d symbols\n", mon.wf.num_blocks); + LOG(LOG_INFO, "Max magnitude: %.1f dB\n", mon.max_mag); + + // Decode accumulated data (containing slightly less than a full time slot) + decode(&mon, &tm_slot_start); + + // Reset internal variables for the next time slot + monitor_reset(&mon); + } while (is_live); + + monitor_free(&mon); + + return 0; +} diff --git a/external/ft8_lib/demo/gen_ft8.c b/external/ft8_lib/demo/gen_ft8.c new file mode 100644 index 0000000..0f56068 --- /dev/null +++ b/external/ft8_lib/demo/gen_ft8.c @@ -0,0 +1,189 @@ +#include +#include +#include +#include +#include + +#include "common/common.h" +#include "common/wave.h" +#include "ft8/message.h" +#include "ft8/encode.h" +#include "ft8/constants.h" + +#define LOG_LEVEL LOG_INFO +#include "ft8/debug.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) + +#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) +{ + 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); + 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_period Symbol period (duration), seconds +/// @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_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 + float hmod = 1.0f; + + 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 (int i = 0; i < n_wave + 2 * n_spsym; ++i) + { + dphi[i] = 2 * M_PI * f0 / signal_rate; + } + + float pulse[3 * n_spsym]; + gfsk_pulse(n_spsym, symbol_bt, pulse); + + for (int i = 0; i < n_sym; ++i) + { + int ib = i * n_spsym; + for (int j = 0; j < 3 * n_spsym; ++j) + { + dphi[j + ib] += dphi_peak * symbols[i] * pulse[j]; + } + } + + // Add dummy symbols at beginning and end with tone values equal to 1st and last symbol, respectively + for (int j = 0; j < 2 * n_spsym; ++j) + { + dphi[j] += dphi_peak * pulse[j + n_spsym] * symbols[0]; + dphi[j + n_sym * n_spsym] += dphi_peak * pulse[j] * symbols[n_sym - 1]; + } + + // Calculate and insert the audio waveform + float phi = 0; + 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) + { + 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; + } + + 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 + ftx_message_t msg; + ftx_message_rc_t rc = ftx_message_encode(&msg, NULL, message); + if (rc != FTX_MESSAGE_RC_OK) + { + printf("Cannot parse message!\n"); + printf("RC = %d\n", (int)rc); + return -2; + } + + printf("Packed data: "); + for (int j = 0; j < 10; ++j) + { + printf("%02x ", msg.payload[j]); + } + printf("\n"); + + int num_tones = (is_ft4) ? FT4_NN : FT8_NN; + float symbol_period = (is_ft4) ? FT4_SYMBOL_PERIOD : FT8_SYMBOL_PERIOD; + 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) + { + ft4_encode(msg.payload, tones); + } + else + { + ft8_encode(msg.payload, tones); + } + + printf("FSK tones: "); + for (int 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_period * 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 (int 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_period, sample_rate, signal + num_silence); + save_wav(signal, num_total_samples, sample_rate, wav_path); + + return 0; +} diff --git a/external/ft8_lib/fft/_kiss_fft_guts.h b/external/ft8_lib/fft/_kiss_fft_guts.h new file mode 100644 index 0000000..bf5d53c --- /dev/null +++ b/external/ft8_lib/fft/_kiss_fft_guts.h @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2003-2010, Mark Borgerding. All rights reserved. + * This file is part of KISS FFT - https://github.com/mborgerding/kissfft + * + * SPDX-License-Identifier: BSD-3-Clause + * See COPYING file for more information. + */ + +/* kiss_fft.h + defines kiss_fft_scalar as either short or a float type + and defines + typedef struct { kiss_fft_scalar r; kiss_fft_scalar i; }kiss_fft_cpx; */ +#include "kiss_fft.h" +#include + +#define MAXFACTORS 32 +/* e.g. an fft of length 128 has 4 factors + as far as kissfft is concerned + 4*4*4*2 + */ + +struct kiss_fft_state{ + int nfft; + int inverse; + int factors[2*MAXFACTORS]; + kiss_fft_cpx twiddles[1]; +}; + +/* + Explanation of macros dealing with complex math: + + C_MUL(m,a,b) : m = a*b + C_FIXDIV( c , div ) : if a fixed point impl., c /= div. noop otherwise + C_SUB( res, a,b) : res = a - b + C_SUBFROM( res , a) : res -= a + C_ADDTO( res , a) : res += a + * */ +#ifdef FIXED_POINT +#if (FIXED_POINT==32) +# define FRACBITS 31 +# define SAMPPROD int64_t +#define SAMP_MAX 2147483647 +#else +# define FRACBITS 15 +# define SAMPPROD int32_t +#define SAMP_MAX 32767 +#endif + +#define SAMP_MIN -SAMP_MAX + +#if defined(CHECK_OVERFLOW) +# define CHECK_OVERFLOW_OP(a,op,b) \ + if ( (SAMPPROD)(a) op (SAMPPROD)(b) > SAMP_MAX || (SAMPPROD)(a) op (SAMPPROD)(b) < SAMP_MIN ) { \ + fprintf(stderr,"WARNING:overflow @ " __FILE__ "(%d): (%d " #op" %d) = %ld\n",__LINE__,(a),(b),(SAMPPROD)(a) op (SAMPPROD)(b) ); } +#endif + + +# define smul(a,b) ( (SAMPPROD)(a)*(b) ) +# define sround( x ) (kiss_fft_scalar)( ( (x) + (1<<(FRACBITS-1)) ) >> FRACBITS ) + +# define S_MUL(a,b) sround( smul(a,b) ) + +# define C_MUL(m,a,b) \ + do{ (m).r = sround( smul((a).r,(b).r) - smul((a).i,(b).i) ); \ + (m).i = sround( smul((a).r,(b).i) + smul((a).i,(b).r) ); }while(0) + +# define DIVSCALAR(x,k) \ + (x) = sround( smul( x, SAMP_MAX/k ) ) + +# define C_FIXDIV(c,div) \ + do { DIVSCALAR( (c).r , div); \ + DIVSCALAR( (c).i , div); }while (0) + +# define C_MULBYSCALAR( c, s ) \ + do{ (c).r = sround( smul( (c).r , s ) ) ;\ + (c).i = sround( smul( (c).i , s ) ) ; }while(0) + +#else /* not FIXED_POINT*/ + +# define S_MUL(a,b) ( (a)*(b) ) +#define C_MUL(m,a,b) \ + do{ (m).r = (a).r*(b).r - (a).i*(b).i;\ + (m).i = (a).r*(b).i + (a).i*(b).r; }while(0) +# define C_FIXDIV(c,div) /* NOOP */ +# define C_MULBYSCALAR( c, s ) \ + do{ (c).r *= (s);\ + (c).i *= (s); }while(0) +#endif + +#ifndef CHECK_OVERFLOW_OP +# define CHECK_OVERFLOW_OP(a,op,b) /* noop */ +#endif + +#define C_ADD( res, a,b)\ + do { \ + CHECK_OVERFLOW_OP((a).r,+,(b).r)\ + CHECK_OVERFLOW_OP((a).i,+,(b).i)\ + (res).r=(a).r+(b).r; (res).i=(a).i+(b).i; \ + }while(0) +#define C_SUB( res, a,b)\ + do { \ + CHECK_OVERFLOW_OP((a).r,-,(b).r)\ + CHECK_OVERFLOW_OP((a).i,-,(b).i)\ + (res).r=(a).r-(b).r; (res).i=(a).i-(b).i; \ + }while(0) +#define C_ADDTO( res , a)\ + do { \ + CHECK_OVERFLOW_OP((res).r,+,(a).r)\ + CHECK_OVERFLOW_OP((res).i,+,(a).i)\ + (res).r += (a).r; (res).i += (a).i;\ + }while(0) + +#define C_SUBFROM( res , a)\ + do {\ + CHECK_OVERFLOW_OP((res).r,-,(a).r)\ + CHECK_OVERFLOW_OP((res).i,-,(a).i)\ + (res).r -= (a).r; (res).i -= (a).i; \ + }while(0) + + +#ifdef FIXED_POINT +# define KISS_FFT_COS(phase) floor(.5+SAMP_MAX * cos (phase)) +# define KISS_FFT_SIN(phase) floor(.5+SAMP_MAX * sin (phase)) +# define HALF_OF(x) ((x)>>1) +#elif defined(USE_SIMD) +# define KISS_FFT_COS(phase) _mm_set1_ps( cos(phase) ) +# define KISS_FFT_SIN(phase) _mm_set1_ps( sin(phase) ) +# define HALF_OF(x) ((x)*_mm_set1_ps(.5)) +#else +# define KISS_FFT_COS(phase) (kiss_fft_scalar) cos(phase) +# define KISS_FFT_SIN(phase) (kiss_fft_scalar) sin(phase) +# define HALF_OF(x) ((x)*.5) +#endif + +#define kf_cexp(x,phase) \ + do{ \ + (x)->r = KISS_FFT_COS(phase);\ + (x)->i = KISS_FFT_SIN(phase);\ + }while(0) + + +/* a debugging function */ +#define pcpx(c)\ + fprintf(stderr,"%g + %gi\n",(double)((c)->r),(double)((c)->i) ) + + +#ifdef KISS_FFT_USE_ALLOCA +// define this to allow use of alloca instead of malloc for temporary buffers +// Temporary buffers are used in two case: +// 1. FFT sizes that have "bad" factors. i.e. not 2,3 and 5 +// 2. "in-place" FFTs. Notice the quotes, since kissfft does not really do an in-place transform. +#include +#define KISS_FFT_TMP_ALLOC(nbytes) alloca(nbytes) +#define KISS_FFT_TMP_FREE(ptr) +#else +#define KISS_FFT_TMP_ALLOC(nbytes) KISS_FFT_MALLOC(nbytes) +#define KISS_FFT_TMP_FREE(ptr) KISS_FFT_FREE(ptr) +#endif diff --git a/external/ft8_lib/fft/kiss_fft.c b/external/ft8_lib/fft/kiss_fft.c new file mode 100644 index 0000000..af2f695 --- /dev/null +++ b/external/ft8_lib/fft/kiss_fft.c @@ -0,0 +1,402 @@ +/* + * Copyright (c) 2003-2010, Mark Borgerding. All rights reserved. + * This file is part of KISS FFT - https://github.com/mborgerding/kissfft + * + * SPDX-License-Identifier: BSD-3-Clause + * See COPYING file for more information. + */ + + +#include "_kiss_fft_guts.h" +/* The guts header contains all the multiplication and addition macros that are defined for + fixed or floating point complex numbers. It also delares the kf_ internal functions. + */ + +static void kf_bfly2( + kiss_fft_cpx * Fout, + const size_t fstride, + const kiss_fft_cfg st, + int m + ) +{ + kiss_fft_cpx * Fout2; + kiss_fft_cpx * tw1 = st->twiddles; + kiss_fft_cpx t; + Fout2 = Fout + m; + do{ + C_FIXDIV(*Fout,2); C_FIXDIV(*Fout2,2); + + C_MUL (t, *Fout2 , *tw1); + tw1 += fstride; + C_SUB( *Fout2 , *Fout , t ); + C_ADDTO( *Fout , t ); + ++Fout2; + ++Fout; + }while (--m); +} + +static void kf_bfly4( + kiss_fft_cpx * Fout, + const size_t fstride, + const kiss_fft_cfg st, + const size_t m + ) +{ + kiss_fft_cpx *tw1,*tw2,*tw3; + kiss_fft_cpx scratch[6]; + size_t k=m; + const size_t m2=2*m; + const size_t m3=3*m; + + + tw3 = tw2 = tw1 = st->twiddles; + + do { + C_FIXDIV(*Fout,4); C_FIXDIV(Fout[m],4); C_FIXDIV(Fout[m2],4); C_FIXDIV(Fout[m3],4); + + C_MUL(scratch[0],Fout[m] , *tw1 ); + C_MUL(scratch[1],Fout[m2] , *tw2 ); + C_MUL(scratch[2],Fout[m3] , *tw3 ); + + C_SUB( scratch[5] , *Fout, scratch[1] ); + C_ADDTO(*Fout, scratch[1]); + C_ADD( scratch[3] , scratch[0] , scratch[2] ); + C_SUB( scratch[4] , scratch[0] , scratch[2] ); + C_SUB( Fout[m2], *Fout, scratch[3] ); + tw1 += fstride; + tw2 += fstride*2; + tw3 += fstride*3; + C_ADDTO( *Fout , scratch[3] ); + + if(st->inverse) { + Fout[m].r = scratch[5].r - scratch[4].i; + Fout[m].i = scratch[5].i + scratch[4].r; + Fout[m3].r = scratch[5].r + scratch[4].i; + Fout[m3].i = scratch[5].i - scratch[4].r; + }else{ + Fout[m].r = scratch[5].r + scratch[4].i; + Fout[m].i = scratch[5].i - scratch[4].r; + Fout[m3].r = scratch[5].r - scratch[4].i; + Fout[m3].i = scratch[5].i + scratch[4].r; + } + ++Fout; + }while(--k); +} + +static void kf_bfly3( + kiss_fft_cpx * Fout, + const size_t fstride, + const kiss_fft_cfg st, + size_t m + ) +{ + size_t k=m; + const size_t m2 = 2*m; + kiss_fft_cpx *tw1,*tw2; + kiss_fft_cpx scratch[5]; + kiss_fft_cpx epi3; + epi3 = st->twiddles[fstride*m]; + + tw1=tw2=st->twiddles; + + do{ + C_FIXDIV(*Fout,3); C_FIXDIV(Fout[m],3); C_FIXDIV(Fout[m2],3); + + C_MUL(scratch[1],Fout[m] , *tw1); + C_MUL(scratch[2],Fout[m2] , *tw2); + + C_ADD(scratch[3],scratch[1],scratch[2]); + C_SUB(scratch[0],scratch[1],scratch[2]); + tw1 += fstride; + tw2 += fstride*2; + + Fout[m].r = Fout->r - HALF_OF(scratch[3].r); + Fout[m].i = Fout->i - HALF_OF(scratch[3].i); + + C_MULBYSCALAR( scratch[0] , epi3.i ); + + C_ADDTO(*Fout,scratch[3]); + + Fout[m2].r = Fout[m].r + scratch[0].i; + Fout[m2].i = Fout[m].i - scratch[0].r; + + Fout[m].r -= scratch[0].i; + Fout[m].i += scratch[0].r; + + ++Fout; + }while(--k); +} + +static void kf_bfly5( + kiss_fft_cpx * Fout, + const size_t fstride, + const kiss_fft_cfg st, + int m + ) +{ + kiss_fft_cpx *Fout0,*Fout1,*Fout2,*Fout3,*Fout4; + int u; + kiss_fft_cpx scratch[13]; + kiss_fft_cpx * twiddles = st->twiddles; + kiss_fft_cpx *tw; + kiss_fft_cpx ya,yb; + ya = twiddles[fstride*m]; + yb = twiddles[fstride*2*m]; + + Fout0=Fout; + Fout1=Fout0+m; + Fout2=Fout0+2*m; + Fout3=Fout0+3*m; + Fout4=Fout0+4*m; + + tw=st->twiddles; + for ( u=0; ur += scratch[7].r + scratch[8].r; + Fout0->i += scratch[7].i + scratch[8].i; + + scratch[5].r = scratch[0].r + S_MUL(scratch[7].r,ya.r) + S_MUL(scratch[8].r,yb.r); + scratch[5].i = scratch[0].i + S_MUL(scratch[7].i,ya.r) + S_MUL(scratch[8].i,yb.r); + + scratch[6].r = S_MUL(scratch[10].i,ya.i) + S_MUL(scratch[9].i,yb.i); + scratch[6].i = -S_MUL(scratch[10].r,ya.i) - S_MUL(scratch[9].r,yb.i); + + C_SUB(*Fout1,scratch[5],scratch[6]); + C_ADD(*Fout4,scratch[5],scratch[6]); + + scratch[11].r = scratch[0].r + S_MUL(scratch[7].r,yb.r) + S_MUL(scratch[8].r,ya.r); + scratch[11].i = scratch[0].i + S_MUL(scratch[7].i,yb.r) + S_MUL(scratch[8].i,ya.r); + scratch[12].r = - S_MUL(scratch[10].i,yb.i) + S_MUL(scratch[9].i,ya.i); + scratch[12].i = S_MUL(scratch[10].r,yb.i) - S_MUL(scratch[9].r,ya.i); + + C_ADD(*Fout2,scratch[11],scratch[12]); + C_SUB(*Fout3,scratch[11],scratch[12]); + + ++Fout0;++Fout1;++Fout2;++Fout3;++Fout4; + } +} + +/* perform the butterfly for one stage of a mixed radix FFT */ +static void kf_bfly_generic( + kiss_fft_cpx * Fout, + const size_t fstride, + const kiss_fft_cfg st, + int m, + int p + ) +{ + int u,k,q1,q; + kiss_fft_cpx * twiddles = st->twiddles; + kiss_fft_cpx t; + int Norig = st->nfft; + + kiss_fft_cpx * scratch = (kiss_fft_cpx*)KISS_FFT_TMP_ALLOC(sizeof(kiss_fft_cpx)*p); + + for ( u=0; u=Norig) twidx-=Norig; + C_MUL(t,scratch[q] , twiddles[twidx] ); + C_ADDTO( Fout[ k ] ,t); + } + k += m; + } + } + KISS_FFT_TMP_FREE(scratch); +} + +static +void kf_work( + kiss_fft_cpx * Fout, + const kiss_fft_cpx * f, + const size_t fstride, + int in_stride, + int * factors, + const kiss_fft_cfg st + ) +{ + kiss_fft_cpx * Fout_beg=Fout; + const int p=*factors++; /* the radix */ + const int m=*factors++; /* stage's fft length/p */ + const kiss_fft_cpx * Fout_end = Fout + p*m; + +#ifdef _OPENMP + // use openmp extensions at the + // top-level (not recursive) + if (fstride==1 && p<=5) + { + int k; + + // execute the p different work units in different threads +# pragma omp parallel for + for (k=0;k floor_sqrt) + p = n; /* no more factors, skip to end */ + } + n /= p; + *facbuf++ = p; + *facbuf++ = n; + } while (n > 1); +} + +/* + * + * User-callable function to allocate all necessary storage space for the fft. + * + * The return value is a contiguous block of memory, allocated with malloc. As such, + * It can be freed with free(), rather than a kiss_fft-specific function. + * */ +kiss_fft_cfg kiss_fft_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem ) +{ + kiss_fft_cfg st=NULL; + size_t memneeded = sizeof(struct kiss_fft_state) + + sizeof(kiss_fft_cpx)*(nfft-1); /* twiddle factors*/ + + if ( lenmem==NULL ) { + st = ( kiss_fft_cfg)KISS_FFT_MALLOC( memneeded ); + }else{ + if (mem != NULL && *lenmem >= memneeded) + st = (kiss_fft_cfg)mem; + *lenmem = memneeded; + } + if (st) { + int i; + st->nfft=nfft; + st->inverse = inverse_fft; + + for (i=0;iinverse) + phase *= -1; + kf_cexp(st->twiddles+i, phase ); + } + + kf_factor(nfft,st->factors); + } + return st; +} + + +void kiss_fft_stride(kiss_fft_cfg st,const kiss_fft_cpx *fin,kiss_fft_cpx *fout,int in_stride) +{ + if (fin == fout) { + //NOTE: this is not really an in-place FFT algorithm. + //It just performs an out-of-place FFT into a temp buffer + kiss_fft_cpx * tmpbuf = (kiss_fft_cpx*)KISS_FFT_TMP_ALLOC( sizeof(kiss_fft_cpx)*st->nfft); + kf_work(tmpbuf,fin,1,in_stride, st->factors,st); + memcpy(fout,tmpbuf,sizeof(kiss_fft_cpx)*st->nfft); + KISS_FFT_TMP_FREE(tmpbuf); + }else{ + kf_work( fout, fin, 1,in_stride, st->factors,st ); + } +} + +void kiss_fft(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout) +{ + kiss_fft_stride(cfg,fin,fout,1); +} + + +void kiss_fft_cleanup(void) +{ + // nothing needed any more +} + +int kiss_fft_next_fast_size(int n) +{ + while(1) { + int m=n; + while ( (m%2) == 0 ) m/=2; + while ( (m%3) == 0 ) m/=3; + while ( (m%5) == 0 ) m/=5; + if (m<=1) + break; /* n is completely factorable by twos, threes, and fives */ + n++; + } + return n; +} diff --git a/external/ft8_lib/fft/kiss_fft.h b/external/ft8_lib/fft/kiss_fft.h new file mode 100644 index 0000000..45c3a6a --- /dev/null +++ b/external/ft8_lib/fft/kiss_fft.h @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2003-2010, Mark Borgerding. All rights reserved. + * This file is part of KISS FFT - https://github.com/mborgerding/kissfft + * + * SPDX-License-Identifier: BSD-3-Clause + * See COPYING file for more information. + */ + +#ifndef KISS_FFT_H +#define KISS_FFT_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + ATTENTION! + If you would like a : + -- a utility that will handle the caching of fft objects + -- real-only (no imaginary time component ) FFT + -- a multi-dimensional FFT + -- a command-line utility to perform ffts + -- a command-line utility to perform fast-convolution filtering + + Then see kfc.h kiss_fftr.h kiss_fftnd.h fftutil.c kiss_fastfir.c + in the tools/ directory. +*/ + +#ifdef USE_SIMD +# include +# define kiss_fft_scalar __m128 +#define KISS_FFT_MALLOC(nbytes) _mm_malloc(nbytes,16) +#define KISS_FFT_FREE _mm_free +#else +#define KISS_FFT_MALLOC malloc +#define KISS_FFT_FREE free +#endif + + +#ifdef FIXED_POINT +#include +# if (FIXED_POINT == 32) +# define kiss_fft_scalar int32_t +# else +# define kiss_fft_scalar int16_t +# endif +#else +# ifndef kiss_fft_scalar +/* default is float */ +# define kiss_fft_scalar float +# endif +#endif + +typedef struct { + kiss_fft_scalar r; + kiss_fft_scalar i; +}kiss_fft_cpx; + +typedef struct kiss_fft_state* kiss_fft_cfg; + +/* + * kiss_fft_alloc + * + * Initialize a FFT (or IFFT) algorithm's cfg/state buffer. + * + * typical usage: kiss_fft_cfg mycfg=kiss_fft_alloc(1024,0,NULL,NULL); + * + * The return value from fft_alloc is a cfg buffer used internally + * by the fft routine or NULL. + * + * If lenmem is NULL, then kiss_fft_alloc will allocate a cfg buffer using malloc. + * The returned value should be free()d when done to avoid memory leaks. + * + * The state can be placed in a user supplied buffer 'mem': + * If lenmem is not NULL and mem is not NULL and *lenmem is large enough, + * then the function places the cfg in mem and the size used in *lenmem + * and returns mem. + * + * If lenmem is not NULL and ( mem is NULL or *lenmem is not large enough), + * then the function returns NULL and places the minimum cfg + * buffer size in *lenmem. + * */ + +kiss_fft_cfg kiss_fft_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem); + +/* + * kiss_fft(cfg,in_out_buf) + * + * Perform an FFT on a complex input buffer. + * for a forward FFT, + * fin should be f[0] , f[1] , ... ,f[nfft-1] + * fout will be F[0] , F[1] , ... ,F[nfft-1] + * Note that each element is complex and can be accessed like + f[k].r and f[k].i + * */ +void kiss_fft(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout); + +/* + A more generic version of the above function. It reads its input from every Nth sample. + * */ +void kiss_fft_stride(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout,int fin_stride); + +/* If kiss_fft_alloc allocated a buffer, it is one contiguous + buffer and can be simply free()d when no longer needed*/ +#define kiss_fft_free KISS_FFT_FREE + +/* + Cleans up some memory that gets managed internally. Not necessary to call, but it might clean up + your compiler output to call this before you exit. +*/ +void kiss_fft_cleanup(void); + + +/* + * Returns the smallest integer k, such that k>=n and k has only "fast" factors (2,3,5) + */ +int kiss_fft_next_fast_size(int n); + +/* for real ffts, we need an even size */ +#define kiss_fftr_next_fast_size_real(n) \ + (kiss_fft_next_fast_size( ((n)+1)>>1)<<1) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/external/ft8_lib/fft/kiss_fftr.c b/external/ft8_lib/fft/kiss_fftr.c new file mode 100644 index 0000000..8102132 --- /dev/null +++ b/external/ft8_lib/fft/kiss_fftr.c @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2003-2004, Mark Borgerding. All rights reserved. + * This file is part of KISS FFT - https://github.com/mborgerding/kissfft + * + * SPDX-License-Identifier: BSD-3-Clause + * See COPYING file for more information. + */ + +#include "kiss_fftr.h" +#include "_kiss_fft_guts.h" + +struct kiss_fftr_state{ + kiss_fft_cfg substate; + kiss_fft_cpx * tmpbuf; + kiss_fft_cpx * super_twiddles; +#ifdef USE_SIMD + void * pad; +#endif +}; + +kiss_fftr_cfg kiss_fftr_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem) +{ + int i; + kiss_fftr_cfg st = NULL; + size_t subsize = 0, memneeded; + + if (nfft & 1) { + fprintf(stderr,"Real FFT optimization must be even.\n"); + return NULL; + } + nfft >>= 1; + + kiss_fft_alloc (nfft, inverse_fft, NULL, &subsize); + memneeded = sizeof(struct kiss_fftr_state) + subsize + sizeof(kiss_fft_cpx) * ( nfft * 3 / 2); + + if (lenmem == NULL) { + st = (kiss_fftr_cfg) KISS_FFT_MALLOC (memneeded); + } else { + if (*lenmem >= memneeded) + st = (kiss_fftr_cfg) mem; + *lenmem = memneeded; + } + if (!st) + return NULL; + + st->substate = (kiss_fft_cfg) (st + 1); /*just beyond kiss_fftr_state struct */ + st->tmpbuf = (kiss_fft_cpx *) (((char *) st->substate) + subsize); + st->super_twiddles = st->tmpbuf + nfft; + kiss_fft_alloc(nfft, inverse_fft, st->substate, &subsize); + + for (i = 0; i < nfft/2; ++i) { + double phase = + -3.14159265358979323846264338327 * ((double) (i+1) / nfft + .5); + if (inverse_fft) + phase *= -1; + kf_cexp (st->super_twiddles+i,phase); + } + return st; +} + +void kiss_fftr(kiss_fftr_cfg st,const kiss_fft_scalar *timedata,kiss_fft_cpx *freqdata) +{ + /* input buffer timedata is stored row-wise */ + int k,ncfft; + kiss_fft_cpx fpnk,fpk,f1k,f2k,tw,tdc; + + if ( st->substate->inverse) { + fprintf(stderr,"kiss fft usage error: improper alloc\n"); + exit(1); + } + + ncfft = st->substate->nfft; + + /*perform the parallel fft of two real signals packed in real,imag*/ + kiss_fft( st->substate , (const kiss_fft_cpx*)timedata, st->tmpbuf ); + /* The real part of the DC element of the frequency spectrum in st->tmpbuf + * contains the sum of the even-numbered elements of the input time sequence + * The imag part is the sum of the odd-numbered elements + * + * The sum of tdc.r and tdc.i is the sum of the input time sequence. + * yielding DC of input time sequence + * The difference of tdc.r - tdc.i is the sum of the input (dot product) [1,-1,1,-1... + * yielding Nyquist bin of input time sequence + */ + + tdc.r = st->tmpbuf[0].r; + tdc.i = st->tmpbuf[0].i; + C_FIXDIV(tdc,2); + CHECK_OVERFLOW_OP(tdc.r ,+, tdc.i); + CHECK_OVERFLOW_OP(tdc.r ,-, tdc.i); + freqdata[0].r = tdc.r + tdc.i; + freqdata[ncfft].r = tdc.r - tdc.i; +#ifdef USE_SIMD + freqdata[ncfft].i = freqdata[0].i = _mm_set1_ps(0); +#else + freqdata[ncfft].i = freqdata[0].i = 0; +#endif + + for ( k=1;k <= ncfft/2 ; ++k ) { + fpk = st->tmpbuf[k]; + fpnk.r = st->tmpbuf[ncfft-k].r; + fpnk.i = - st->tmpbuf[ncfft-k].i; + C_FIXDIV(fpk,2); + C_FIXDIV(fpnk,2); + + C_ADD( f1k, fpk , fpnk ); + C_SUB( f2k, fpk , fpnk ); + C_MUL( tw , f2k , st->super_twiddles[k-1]); + + freqdata[k].r = HALF_OF(f1k.r + tw.r); + freqdata[k].i = HALF_OF(f1k.i + tw.i); + freqdata[ncfft-k].r = HALF_OF(f1k.r - tw.r); + freqdata[ncfft-k].i = HALF_OF(tw.i - f1k.i); + } +} + +void kiss_fftri(kiss_fftr_cfg st,const kiss_fft_cpx *freqdata,kiss_fft_scalar *timedata) +{ + /* input buffer timedata is stored row-wise */ + int k, ncfft; + + if (st->substate->inverse == 0) { + fprintf (stderr, "kiss fft usage error: improper alloc\n"); + exit (1); + } + + ncfft = st->substate->nfft; + + st->tmpbuf[0].r = freqdata[0].r + freqdata[ncfft].r; + st->tmpbuf[0].i = freqdata[0].r - freqdata[ncfft].r; + C_FIXDIV(st->tmpbuf[0],2); + + for (k = 1; k <= ncfft / 2; ++k) { + kiss_fft_cpx fk, fnkc, fek, fok, tmp; + fk = freqdata[k]; + fnkc.r = freqdata[ncfft - k].r; + fnkc.i = -freqdata[ncfft - k].i; + C_FIXDIV( fk , 2 ); + C_FIXDIV( fnkc , 2 ); + + C_ADD (fek, fk, fnkc); + C_SUB (tmp, fk, fnkc); + C_MUL (fok, tmp, st->super_twiddles[k-1]); + C_ADD (st->tmpbuf[k], fek, fok); + C_SUB (st->tmpbuf[ncfft - k], fek, fok); +#ifdef USE_SIMD + st->tmpbuf[ncfft - k].i *= _mm_set1_ps(-1.0); +#else + st->tmpbuf[ncfft - k].i *= -1; +#endif + } + kiss_fft (st->substate, st->tmpbuf, (kiss_fft_cpx *) timedata); +} diff --git a/external/ft8_lib/fft/kiss_fftr.h b/external/ft8_lib/fft/kiss_fftr.h new file mode 100644 index 0000000..588948d --- /dev/null +++ b/external/ft8_lib/fft/kiss_fftr.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2003-2004, Mark Borgerding. All rights reserved. + * This file is part of KISS FFT - https://github.com/mborgerding/kissfft + * + * SPDX-License-Identifier: BSD-3-Clause + * See COPYING file for more information. + */ + +#ifndef KISS_FTR_H +#define KISS_FTR_H + +#include "kiss_fft.h" +#ifdef __cplusplus +extern "C" { +#endif + + +/* + + Real optimized version can save about 45% cpu time vs. complex fft of a real seq. + + + + */ + +typedef struct kiss_fftr_state *kiss_fftr_cfg; + + +kiss_fftr_cfg kiss_fftr_alloc(int nfft,int inverse_fft,void * mem, size_t * lenmem); +/* + nfft must be even + + If you don't care to allocate space, use mem = lenmem = NULL +*/ + + +void kiss_fftr(kiss_fftr_cfg cfg,const kiss_fft_scalar *timedata,kiss_fft_cpx *freqdata); +/* + input timedata has nfft scalar points + output freqdata has nfft/2+1 complex points +*/ + +void kiss_fftri(kiss_fftr_cfg cfg,const kiss_fft_cpx *freqdata,kiss_fft_scalar *timedata); +/* + input freqdata has nfft/2+1 complex points + output timedata has nfft scalar points +*/ + +#define kiss_fftr_free KISS_FFT_FREE + +#ifdef __cplusplus +} +#endif +#endif diff --git a/external/ft8_lib/ft4_ft8_public/Makefile b/external/ft8_lib/ft4_ft8_public/Makefile new file mode 100644 index 0000000..3831acb --- /dev/null +++ b/external/ft8_lib/ft4_ft8_public/Makefile @@ -0,0 +1,54 @@ +# +# On MS Windows using Msys/MinGW gfortran invoke like this: +# +# FC=gfortran make +# +# On macOS using MacPorts gfortran invoke like this: +# +# FC=gfortran make +# +# or if the gfortran compiler is named gfortran-mp-8 or similar +# +# FC=gfortran-mp-8 make +# +# otherwise invoke like this: +# +# make +# + +ifeq ($(OS),Windows_NT) + EXE = .exe +endif + +EXES = hashcodes$(EXE) std_call_to_c28$(EXE) nonstd_to_c58$(EXE) \ + free_text_to_f71$(EXE) grid4_to_g15$(EXE) grid6_to_g25$(EXE) \ + gen_crc14$(EXE) + +%.o: %.f90 + $(FC) -c $(FFLAGS) -o $@ $< + +all: $(EXES) + +hashcodes$(EXE): hashcodes.o + ${FC} -o $@ $^ + +std_call_to_c28$(EXE): std_call_to_c28.o + ${FC} -o $@ $^ + +nonstd_to_c58$(EXE): nonstd_to_c58.o + ${FC} -o $@ $^ + +free_text_to_f71$(EXE): free_text_to_f71.o + ${FC} -o $@ $^ + +grid4_to_g15$(EXE): grid4_to_g15.o + ${FC} -o $@ $^ + +grid6_to_g25$(EXE): grid6_to_g25.o + ${FC} -o $@ $^ + +gen_crc14$(EXE): gen_crc14.o + ${FC} -o $@ $^ + +clean: + -rm $(EXES) *.o diff --git a/external/ft8_lib/ft4_ft8_public/arrl_rac_sections.txt b/external/ft8_lib/ft4_ft8_public/arrl_rac_sections.txt new file mode 100644 index 0000000..948a24a --- /dev/null +++ b/external/ft8_lib/ft4_ft8_public/arrl_rac_sections.txt @@ -0,0 +1,13 @@ +! Abbreviations for ARRL/RAC Sections as a Fortran 90 data statement: + + +data csec/ & + "AB ","AK ","AL ","AR ","AZ ","BC ","CO ","CT ","DE ","EB ", & + "EMA","ENY","EPA","EWA","GA ","GTA","IA ","ID ","IL ","IN ", & + "KS ","KY ","LA ","LAX","MAR","MB ","MDC","ME ","MI ","MN ", & + "MO ","MS ","MT ","NC ","ND ","NE ","NFL","NH ","NL ","NLI", & + "NM ","NNJ","NNY","NT ","NTX","NV ","OH ","OK ","ONE","ONN", & + "ONS","OR ","ORG","PAC","PR ","QC ","RI ","SB ","SC ","SCV", & + "SD ","SDG","SF ","SFL","SJV","SK ","SNJ","STX","SV ","TN ", & + "UT ","VA ","VI ","VT ","WCF","WI ","WMA","WNY","WPA","WTX", & + "WV ","WWA","WY ","DX "/ diff --git a/external/ft8_lib/ft4_ft8_public/free_text_to_f71.f90 b/external/ft8_lib/ft4_ft8_public/free_text_to_f71.f90 new file mode 100644 index 0000000..eb38ffe --- /dev/null +++ b/external/ft8_lib/ft4_ft8_public/free_text_to_f71.f90 @@ -0,0 +1,67 @@ +program free_text_to_f71 + + character*13 c13,w + character*71 f71 + character*42 c + character*1 qa(10),qb(10) + data c/' 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+-./?'/ + + nargs=iargc() + if(nargs.ne.1) then + print*,'Usage: free_text_to_f71 ""' + print*,'Example: free_text_to_f71 "TNX BOB 73 GL"' + go to 999 + endif + call getarg(1,c13) + call mp_short_init + qa=char(0) + w=adjustr(c13) + do i=1,13 + j=index(c,w(i:i))-1 + if(j.lt.0) j=0 + call mp_short_mult(qb,qa(2:10),9,42) !qb(1:9)=42*qa(2:9) + call mp_short_add(qa,qb(2:10),9,j) !qa(1:9)=qb(2:9)+j + enddo + write(f71,1000) qa(2:10) +1000 format(b7.7,8b8.8) + write(*,1010) c13,f71 +1010 format('Free text: ',a13/'f71: ',a71) + +999 end program free_text_to_f71 + +subroutine mp_short_ops(w,u) +! Multi-precision arithmetic with storage in character arrays. + character*1 w(*),u(*) + integer i,ireg,j,n,ir,iv,ii1,ii2 + character*1 creg(4) + save ii1,ii2 + equivalence (ireg,creg) + + entry mp_short_init + ireg=256*ichar('2')+ichar('1') + do j=1,4 + if (creg(j).eq.'1') ii1=j + if (creg(j).eq.'2') ii2=j + enddo + return + + entry mp_short_add(w,u,n,iv) + ireg=256*iv + do j=n,1,-1 + ireg=ichar(u(j))+ichar(creg(ii2)) + w(j+1)=creg(ii1) + enddo + w(1)=creg(ii2) + return + + entry mp_short_mult(w,u,n,iv) + ireg=0 + do j=n,1,-1 + ireg=ichar(u(j))*iv+ichar(creg(ii2)) + w(j+1)=creg(ii1) + enddo + w(1)=creg(ii2) + return + + return +end subroutine mp_short_ops diff --git a/external/ft8_lib/ft4_ft8_public/gen_crc14.f90 b/external/ft8_lib/ft4_ft8_public/gen_crc14.f90 new file mode 100644 index 0000000..e4ec122 --- /dev/null +++ b/external/ft8_lib/ft4_ft8_public/gen_crc14.f90 @@ -0,0 +1,36 @@ +program gen_crc14 + + character m77*77,c14*14 + + integer mc(96),r(15),p(15),ncrc +! polynomial for 14-bit CRC 0x6757 + data p/1,1,0,0,1,1,1,0,1,0,1,0,1,1,1/ + + nargs=iargc() + if(nargs.ne.1) then + print*,'Usage: gen_crc14 <77-bit message>' + print*,'Example: gen_crc14 "00000000000000000000000000100000010011011111110011011100100010100001010000001"' + go to 999 + endif + +! pad the 77bit message out to 96 bits + call getarg(1,m77) + read(m77,'(77i1)') mc(1:77) + mc(78:96)=0 + +! divide by polynomial + r=mc(1:15) + do i=0,81 + r(15)=mc(i+15) + r=mod(r+r(1)*p,2) + r=cshift(r,1) + enddo + +! the crc is in r(1:14) - print it in various ways: + write(c14,'(14b1)') r(1:14) + write(*,'(a40,1x,a14)') 'crc14 as a string: ',c14 + read(c14,'(b14.14)') ncrc + write(*,'(a40,i6)') 'crc14 as an integer: ',ncrc + write(*,'(a40,1x,b14.14)') 'binary representation of the integer: ',ncrc + +999 end program gen_crc14 diff --git a/external/ft8_lib/ft4_ft8_public/generator.dat b/external/ft8_lib/ft4_ft8_public/generator.dat new file mode 100644 index 0000000..235d350 --- /dev/null +++ b/external/ft8_lib/ft4_ft8_public/generator.dat @@ -0,0 +1,86 @@ +This file contains the generator matrix for the FT8/FT4 (174,91) LDPC code. +The matrix has 91 columns and 83 rows. + +1000001100101001110011100001000110111111001100011110101011110101000010011111001001111111110 +0111011000011100001001100100111000100101110000100101100100110011010101001001001100010011001 +1101110000100110010110010000001011111011001001110111110001100100000100001010000110111101110 +0001101100111111010000010111100001011000110011010010110111010011001111101100011111110110001 +0000100111111101101001001111111011100000010000011001010111111101000000110100011110000011101 +0000011101111100110011001100000100011011100010000111001111101101010111000011110101001000101 +0010100110110110001010101111111000111100101000000011011011110100111111100001101010011101101 +0110000001010100111110101111010111110011010111011001011011010011101100001100100011000011111 +1110001000000111100110001110010000110001000011101110110100100111100010000100101011101001000 +0111011101011100100111000000100011101000000011100010011011011101101011100101011000110001100 +1011000010111000000100010000001010001100001010111111100110010111001000010011010010000111110 +0001100010100000110010010010001100011111110001100000101011011111010111000101111010100011001 +0111011001000111000111101000001100000010101000000111001000011110000000011011000100101011100 +1111111110111100110010111000000011001010100000110100000111111010111110110100011110110010111 +0110011010100111001010100001010110001111100100110010010110100010101111110110011100010111000 +1100010000100100001101101000100111111110100001011011000111000101000100110110001110100001100 +0000110111111111011100111001010000010100110100011010000110110011010010110001110000100111000 +0001010110110100100010000011000001100011011011001000101110011001100010010100100101110010111 +0010100110101000100111000000110100111101111010000001110101100110010101001000100110110000111 +0100111100010010011011110011011111111010010100011100101111100110000110111101011010111001010 +1001100111000100011100100011100111010000110110010111110100111100100001001110000010010100000 +0001100100011001101101110101000100011001011101100101011000100001101110110100111100011110100 +0000100111011011000100101101011100110001111110101110111000001011100001101101111101101011100 +0100100010001111110000110011110111110100001111111011110111101110101001001110101011111011010 +1000001001110100001000111110111001000000101101100111010111110111010101101110101101011111111 +1010101111100001100101111100010010000100110010110111010001110101011100010100010010101001101 +0010101101010000000011100100101111000000111011000101101001101101001010111101101111011101000 +1100010001110100101010100101001111010111000000100001100001110110000101100110100100110110000 +1000111010111010000110100001001111011011001100111001000010111101011001110001100011001110110 +0111010100111000010001000110011100111010001001110111100000101100110001000010000000010010111 +0000011011111111100000111010000101000101110000110111000000110101101001011100000100100110100 +0011101100110111010000010111100001011000110011000010110111010011001111101100001111110110001 +1001101001001010010110100010100011101110000101111100101010011100001100100100100001000010110 +1011110000101001111101000110010100110000100111001001011101111110100010010110000100001010010 +0010011001100011101011100110110111011111100010110101110011100010101110110010100101001000100 +0100011011110010001100011110111111100100010101110000001101001100000110000001010001000001100 +0011111110110010110011101000010110101011111010011011000011000111001011100000011011111011111 +1101111010000111010010000001111100101000001011000001010100111001011100011010000010100010111 +1111110011010111110011001111001000111100011010011111101010011001101110111010000101000001001 +1111000000100110000101000100011111101001010010010000110010101000111001000111010011001110110 +0100010000010000000100010101100000011000000110010110111110010101110011011101011100000001001 +0000100010001111110000110001110111110100101111111011110111100010101001001110101011111011010 +1011100011111110111100011011011000110000011101110010100111111011000010100000011110001100000 +0101101011111110101001111010110011001100101101110111101110111100100111011001100110101001000 +0100100110100111000000010110101011000110010100111111011001011110110011011100100100000111011 +0001100101000100110100001000010110111110010011100111110110101000110101101100110001111101000 +0010010100011111011000101010110111000100000000110010111100001110111001110001010000000000001 +0101011001000111000111111000011100000010101000000111001000011110000000001011000100101011100 +0010101110001110010010010010001111110010110111010101000111100010110101010011011111111010000 +0110101101010101000010100100000010100110011011110100011101010101110111101001010111000010011 +1010000110001010110100101000110101001110001001111111111010010010101001001111011011001000010 +0001000011000010111001011000011000111000100011001011100000101010001111011000000001110101100 +1110111100110100101001000001100000010111111011100000001000010011001111011011001011101011000 +0111111010011100000011000101010000110010010110101001110000010101100000110110111000000000000 +0011011010010011111001010111001011010001111111011110010011001101111100000111100111101000011 +1011111110110010110011101100010110101011111000011011000011000111001011100000011111111011111 +0111111011100001100000100011000011000101100000111100110011001100010101111101010010110000100 +1010000001100110110010110010111111101101101011111100100111110101001001100110010000010010011 +1011101100100011011100100101101010111100010001111100110001011111010011001100010011001101001 +1101111011011001110110111010001110111110111001000000110001011001101101010110000010011011010 +1101100110100111000000010110101011000110010100111110011011011110110011011100100100000011011 +1001101011010100011010101110110101011111011100000111111100101000000010101011010111111100010 +1110010110010010000111000111011110000010001001011000011100110001011011010111110100111100001 +0100111100010100110110101000001001000010101010001011100001101101110010100111001100110101001 +1000101110001011010100000111101011010100011001111101010001000100000111011111011101110000111 +0010001010000011000111001001110011110001000101101001010001100111101011010000010010110110100 +0010000100111011100000111000111111100010101011100101010011000011100011101110011100011000000 +0101110110010010011010110110110111010111000111110000100001010001100000011010010011100001001 +0110011010101011011110011101010010110010100111101110011011100110100101010000100111100101011 +1001010110000001010010000110100000101101011101001000101000111000110111010110100010111010101 +1011100011001110000000100000110011110000011010011100001100101010011100100011101010110001010 +1111010000110011000111010110110101000110000101100000011111101001010101110101001001110100011 +0110110110100010001110111010010000100100101110010101100101100001001100111100111110011100100 +1010011000110110101111001011110001111011001100001100010111111011111010101110011001111111111 +0101110010110000110110000110101000000111110111110110010101001010100100001000100110100010000 +1111000100011111000100000110100001001000011110000000111111001001111011001101110110000000101 +0001111110111011010100110110010011111011100011010010110010011101011100110000110101011011101 +1111110010111000011010111100011100001010010100001100100111010000001010100101110100000011010 +1010010100110100010000110011000000101001111010101100000101011111001100100010111000110100110 +1100100110001001110110011100011111000011110100111011100011000101010111010111010100010011000 +0111101110110011100010110010111100000001100001101101010001100110010000111010111010010110001 +0010011001000100111010111010110111101011010001001011100101000110011111010001111101000010110 +0110000010001100110010000101011101011001010010111111101110110101010111010110100101100000000 diff --git a/external/ft8_lib/ft4_ft8_public/grid4_to_g15.f90 b/external/ft8_lib/ft4_ft8_public/grid4_to_g15.f90 new file mode 100644 index 0000000..e88bbc9 --- /dev/null +++ b/external/ft8_lib/ft4_ft8_public/grid4_to_g15.f90 @@ -0,0 +1,55 @@ +program grid4_to_g15 + + parameter (MAXGRID4=32400) + character*4 w,grid4 + character c1*1,c2*2 + logical is_grid4 + is_grid4(grid4)=len(trim(grid4)).eq.4 .and. & + grid4(1:1).ge.'A' .and. grid4(1:1).le.'R' .and. & + grid4(2:2).ge.'A' .and. grid4(2:2).le.'R' .and. & + grid4(3:3).ge.'0' .and. grid4(3:3).le.'9' .and. & + grid4(4:4).ge.'0' .and. grid4(4:4).le.'9' + + nargs=iargc() + if(nargs.ne.1) then + print*,'Convert a 4-character grid, signal report, etc., to a g15 value.' + print*,'Usage examples:' + print*,'grid4_to_g15 FN20' + print*,'grid4_to_g15 -11' + print*,'grid4_to_g15 +02' + print*,'grid4_to_g15 RRR' + print*,'grid4_to_g15 RR73' + print*,'grid4_to_g15 73' + print*,'grid4_to_g15 ""' + go to 999 + endif + call getarg(1,w) + if(is_grid4(w) .and. w.ne.'RR73') then + j1=(ichar(w(1:1))-ichar('A'))*18*10*10 + j2=(ichar(w(2:2))-ichar('A'))*10*10 + j3=(ichar(w(3:3))-ichar('0'))*10 + j4=(ichar(w(4:4))-ichar('0')) + igrid4=j1+j2+j3+j4 + else + c1=w(1:1) + if(c1.ne.'+' .and. c1.ne.'-'.and. trim(w).ne.'RRR' .and. w.ne.'RR73' & + .and. trim(w).ne.'73' .and. len(trim(w)).ne.0) go to 900 + if(c1.eq.'+' .or. c1.eq.'-') then + read(w,*,err=900) irpt + irpt=irpt+35 + endif + if(len(trim(w)).eq.0) irpt=1 + if(trim(w).eq.'RRR') irpt=2 + if(w.eq.'RR73') irpt=3 + if(trim(w).eq.'73') irpt=4 + igrid4=MAXGRID4 + irpt + endif + + write(*,1000) w,igrid4,igrid4 +1000 format('Encoded word: ',a4,' g15 in binary: ',b15.15,' decimal:',i6) + go to 999 + +900 write(*,1900) +1900 format('Invalid input') + +999 end program grid4_to_g15 diff --git a/external/ft8_lib/ft4_ft8_public/grid6_to_g25.f90 b/external/ft8_lib/ft4_ft8_public/grid6_to_g25.f90 new file mode 100644 index 0000000..47a7b63 --- /dev/null +++ b/external/ft8_lib/ft4_ft8_public/grid6_to_g25.f90 @@ -0,0 +1,41 @@ +program grid6_to_g25 + + parameter (MAXGRID4=32400) + character*6 w,grid6 + character c1*1,c2*2 + logical is_grid6 + + is_grid6(grid6)=len(trim(grid6)).eq.6 .and. & + grid6(1:1).ge.'A' .and. grid6(1:1).le.'R' .and. & + grid6(2:2).ge.'A' .and. grid6(2:2).le.'R' .and. & + grid6(3:3).ge.'0' .and. grid6(3:3).le.'9' .and. & + grid6(4:4).ge.'0' .and. grid6(4:4).le.'9' .and. & + grid6(5:5).ge.'A' .and. grid6(5:5).le.'X' .and. & + grid6(6:6).ge.'A' .and. grid6(6:6).le.'X' + + nargs=iargc() + if(nargs.ne.1) then + print*,'Convert a 6-character grid to a g25 value.' + print*,'Usage: grid6_to_g25 IO91NP' + go to 999 + endif + call getarg(1,w) + if(.not. is_grid6(w)) go to 900 + + j1=(ichar(w(1:1))-ichar('A'))*18*10*10*24*24 + j2=(ichar(w(2:2))-ichar('A'))*10*10*24*24 + j3=(ichar(w(3:3))-ichar('0'))*10*24*24 + j4=(ichar(w(4:4))-ichar('0'))*24*24 + j5=(ichar(w(5:5))-ichar('A'))*24 + j6=(ichar(w(6:6))-ichar('A')) + igrid6=j1+j2+j3+j4+j5+j6 + + write(*,1000) w,igrid6,igrid6 +1000 format('Encoded word: ',a6,' g25 in binary: ',b25.25/ & + 30x,'decimal:',i9) + go to 999 + +900 write(*,1900) +1900 format('Invalid input') + +999 end program grid6_to_g25 diff --git a/external/ft8_lib/ft4_ft8_public/hashcodes.f90 b/external/ft8_lib/ft4_ft8_public/hashcodes.f90 new file mode 100644 index 0000000..0c0628e --- /dev/null +++ b/external/ft8_lib/ft4_ft8_public/hashcodes.f90 @@ -0,0 +1,34 @@ +program hashcodes + + parameter (NTOKENS=2063592) + integer*8 nprime,n8(3) + integer nbits(3),ihash(3) + character*11 callsign + character*38 c + data c/' 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ/'/ + data nprime/47055833459_8/,nbits/10,12,22/ + + nargs=iargc() + if(nargs.ne.1) then + print*,'Usage: hashcodes ' + print*,'Examples: hashcodes PJ4/K1ABC' + print*,' hashcodes YW18FIFA' + go to 999 + endif + call getarg(1,callsign) + callsign=adjustl(callsign) + + do k=1,3 + n8(k)=0 + do i=1,11 + j=index(c,callsign(i:i)) - 1 + n8(k)=38*n8(k) + j + enddo + ihash(k)=ishft(nprime*n8(k),nbits(k)-64) + enddo + ih22_biased=ihash(3) + NTOKENS + write(*,1000) callsign,ihash,ih22_biased +1000 format('Callsign',9x,'h10',7x,'h12',7x,'h22'/41('-')/ & + a11,i9,2i10,/'Biased for storage in c28:',i14) + +999 end program hashcodes diff --git a/external/ft8_lib/ft4_ft8_public/nonstd_to_c58.f90 b/external/ft8_lib/ft4_ft8_public/nonstd_to_c58.f90 new file mode 100644 index 0000000..ffec7f0 --- /dev/null +++ b/external/ft8_lib/ft4_ft8_public/nonstd_to_c58.f90 @@ -0,0 +1,24 @@ +program nonstd_to_c58 + + integer*8 n58 + character*11 callsign + character*38 c + data c/' 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ/'/ + + nargs=iargc() + if(nargs.ne.1) then + print*,'Usage: nonstd_to_c58 ' + print*,'Examples: nonstd_to_c58 PJ4/K1ABC' + print*,' nonstd_to_c58 YW18FIFA' + go to 999 + endif + call getarg(1,callsign) + + n58=0 + do i=1,11 + n58=n58*38 + index(c,callsign(i:i)) - 1 + enddo + write(*,1000) callsign,n58,n58 +1000 format('Callsign: ',a11/'c58 (binary): ' b58.58/'c58 (decimal):',i20) + +999 end program nonstd_to_c58 diff --git a/external/ft8_lib/ft4_ft8_public/parity.dat b/external/ft8_lib/ft4_ft8_public/parity.dat new file mode 100644 index 0000000..ca6f76a --- /dev/null +++ b/external/ft8_lib/ft4_ft8_public/parity.dat @@ -0,0 +1,183 @@ +This file specifies the sparse 83x174 parity-check matrix for the +FT8/FT4 (174,91) LDPC code. Each of the 174 columns contains +exactly 3 ones. The rows contain either 6 or 7 ones. +The matrix is specified by the following list consisting of +174 lines, each of which includes 3 numbers. +Each line corresponds to a column of the parity check matrix. +The three numbers are indices of the rows that contain a one in +the corresponding column. The indices range from 1 through 83. + + 16 45 73 + 25 51 62 + 33 58 78 + 1 44 45 + 2 7 61 + 3 6 54 + 4 35 48 + 5 13 21 + 8 56 79 + 9 64 69 + 10 19 66 + 11 36 60 + 12 37 58 + 14 32 43 + 15 63 80 + 17 28 77 + 18 74 83 + 22 53 81 + 23 30 34 + 24 31 40 + 26 41 76 + 27 57 70 + 29 49 65 + 3 38 78 + 5 39 82 + 46 50 73 + 51 52 74 + 55 71 72 + 44 67 72 + 43 68 78 + 1 32 59 + 2 6 71 + 4 16 54 + 7 65 67 + 8 30 42 + 9 22 31 + 10 18 76 + 11 23 82 + 12 28 61 + 13 52 79 + 14 50 51 + 15 81 83 + 17 29 60 + 19 33 64 + 20 26 73 + 21 34 40 + 24 27 77 + 25 55 58 + 35 53 66 + 36 48 68 + 37 46 75 + 38 45 47 + 39 57 69 + 41 56 62 + 20 49 53 + 46 52 63 + 45 70 75 + 27 35 80 + 1 15 30 + 2 68 80 + 3 36 51 + 4 28 51 + 5 31 56 + 6 20 37 + 7 40 82 + 8 60 69 + 9 10 49 + 11 44 57 + 12 39 59 + 13 24 55 + 14 21 65 + 16 71 78 + 17 30 76 + 18 25 80 + 19 61 83 + 22 38 77 + 23 41 50 + 7 26 58 + 29 32 81 + 33 40 73 + 18 34 48 + 13 42 64 + 5 26 43 + 47 69 72 + 54 55 70 + 45 62 68 + 10 63 67 + 14 66 72 + 22 60 74 + 35 39 79 + 1 46 64 + 1 24 66 + 2 5 70 + 3 31 65 + 4 49 58 + 1 4 5 + 6 60 67 + 7 32 75 + 8 48 82 + 9 35 41 + 10 39 62 + 11 14 61 + 12 71 74 + 13 23 78 + 11 35 55 + 15 16 79 + 7 9 16 + 17 54 63 + 18 50 57 + 19 30 47 + 20 64 80 + 21 28 69 + 22 25 43 + 13 22 37 + 2 47 51 + 23 54 74 + 26 34 72 + 27 36 37 + 21 36 63 + 29 40 44 + 19 26 57 + 3 46 82 + 14 15 58 + 33 52 53 + 30 43 52 + 6 9 52 + 27 33 65 + 25 69 73 + 38 55 83 + 20 39 77 + 18 29 56 + 32 48 71 + 42 51 59 + 28 44 79 + 34 60 62 + 31 45 61 + 46 68 77 + 6 24 76 + 8 10 78 + 40 41 70 + 17 50 53 + 42 66 68 + 4 22 72 + 36 64 81 + 13 29 47 + 2 8 81 + 56 67 73 + 5 38 50 + 12 38 64 + 59 72 80 + 3 26 79 + 45 76 81 + 1 65 74 + 7 18 77 + 11 56 59 + 14 39 54 + 16 37 66 + 10 28 55 + 15 60 70 + 17 25 82 + 20 30 31 + 12 67 68 + 23 75 80 + 27 32 62 + 24 69 75 + 19 21 71 + 34 53 61 + 35 46 47 + 33 59 76 + 40 43 83 + 41 42 63 + 49 75 83 + 20 44 48 + 42 49 57 diff --git a/external/ft8_lib/ft4_ft8_public/states_provinces.txt b/external/ft8_lib/ft4_ft8_public/states_provinces.txt new file mode 100644 index 0000000..abc68d2 --- /dev/null +++ b/external/ft8_lib/ft4_ft8_public/states_provinces.txt @@ -0,0 +1,11 @@ +! Abbreviations for US States and Canadian Provinces as a Fortran 90 +! data statement: + + data cmult/ & + "AL ","AK ","AZ ","AR ","CA ","CO ","CT ","DE ","FL ","GA ", & + "HI ","ID ","IL ","IN ","IA ","KS ","KY ","LA ","ME ","MD ", & + "MA ","MI ","MN ","MS ","MO ","MT ","NE ","NV ","NH ","NJ ", & + "NM ","NY ","NC ","ND ","OH ","OK ","OR ","PA ","RI ","SC ", & + "SD ","TN ","TX ","UT ","VT ","VA ","WA ","WV ","WI ","WY ", & + "NB ","NS ","QC ","ON ","MB ","SK ","AB ","BC ","NWT","NF ", & + "LB ","NU ","YT ","PEI","DC "/ diff --git a/external/ft8_lib/ft4_ft8_public/std_call_to_c28.f90 b/external/ft8_lib/ft4_ft8_public/std_call_to_c28.f90 new file mode 100644 index 0000000..952134f --- /dev/null +++ b/external/ft8_lib/ft4_ft8_public/std_call_to_c28.f90 @@ -0,0 +1,31 @@ +program std_call_to_c28 + + parameter (NTOKENS=2063592,MAX22=4194304) + character*6 call_std + character a1*37,a2*36,a3*10,a4*27 + data a1/' 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'/ + data a2/'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'/ + data a3/'0123456789'/ + data a4/' ABCDEFGHIJKLMNOPQRSTUVWXYZ'/ + + nargs=iargc() + if(nargs.ne.1) then + print*,'Usage: std_call_to_c28 ' + print*,'Example: std_call_to_c28 K1ABC' + go to 999 + endif + call getarg(1,call_std) + call_std=adjustr(call_std) + i1=index(a1,call_std(1:1))-1 + i2=index(a2,call_std(2:2))-1 + i3=index(a3,call_std(3:3))-1 + i4=index(a4,call_std(4:4))-1 + i5=index(a4,call_std(5:5))-1 + i6=index(a4,call_std(6:6))-1 + n28=NTOKENS + MAX22 + 36*10*27*27*27*i1 + 10*27*27*27*i2 + & + 27*27*27*i3 + 27*27*i4 + 27*i5 + i6 + + write(*,1000) call_std,n28 +1000 format('Callsign: ',a6,2x,'c28 as decimal integer:',i10) + +999 end program std_call_to_c28 diff --git a/external/ft8_lib/ft8/constants.c b/external/ft8_lib/ft8/constants.c new file mode 100644 index 0000000..330342a --- /dev/null +++ b/external/ft8_lib/ft8/constants.c @@ -0,0 +1,392 @@ +#include "constants.h" + +// Costas sync tone pattern +const uint8_t kFT8_Costas_pattern[7] = { 3, 1, 4, 0, 6, 5, 2 }; +const uint8_t kFT4_Costas_pattern[4][4] = { + { 0, 1, 3, 2 }, + { 1, 0, 2, 3 }, + { 2, 3, 1, 0 }, + { 3, 2, 0, 1 } +}; + +// Gray code map (FTx bits -> channel symbols) +const uint8_t kFT8_Gray_map[8] = { 0, 1, 3, 2, 5, 6, 4, 7 }; +const uint8_t kFT4_Gray_map[4] = { 0, 1, 3, 2 }; + +const uint8_t kFT4_XOR_sequence[10] = { + 0x4Au, // 01001010 + 0x5Eu, // 01011110 + 0x89u, // 10001001 + 0xB4u, // 10110100 + 0xB0u, // 10110000 + 0x8Au, // 10001010 + 0x79u, // 01111001 + 0x55u, // 01010101 + 0xBEu, // 10111110 + 0x28u, // 00101 [000] +}; + +// Parity generator matrix for (174,91) LDPC code, stored in bitpacked format (MSB first) +const uint8_t kFTX_LDPC_generator[FTX_LDPC_M][FTX_LDPC_K_BYTES] = { + { 0x83, 0x29, 0xce, 0x11, 0xbf, 0x31, 0xea, 0xf5, 0x09, 0xf2, 0x7f, 0xc0 }, + { 0x76, 0x1c, 0x26, 0x4e, 0x25, 0xc2, 0x59, 0x33, 0x54, 0x93, 0x13, 0x20 }, + { 0xdc, 0x26, 0x59, 0x02, 0xfb, 0x27, 0x7c, 0x64, 0x10, 0xa1, 0xbd, 0xc0 }, + { 0x1b, 0x3f, 0x41, 0x78, 0x58, 0xcd, 0x2d, 0xd3, 0x3e, 0xc7, 0xf6, 0x20 }, + { 0x09, 0xfd, 0xa4, 0xfe, 0xe0, 0x41, 0x95, 0xfd, 0x03, 0x47, 0x83, 0xa0 }, + { 0x07, 0x7c, 0xcc, 0xc1, 0x1b, 0x88, 0x73, 0xed, 0x5c, 0x3d, 0x48, 0xa0 }, + { 0x29, 0xb6, 0x2a, 0xfe, 0x3c, 0xa0, 0x36, 0xf4, 0xfe, 0x1a, 0x9d, 0xa0 }, + { 0x60, 0x54, 0xfa, 0xf5, 0xf3, 0x5d, 0x96, 0xd3, 0xb0, 0xc8, 0xc3, 0xe0 }, + { 0xe2, 0x07, 0x98, 0xe4, 0x31, 0x0e, 0xed, 0x27, 0x88, 0x4a, 0xe9, 0x00 }, + { 0x77, 0x5c, 0x9c, 0x08, 0xe8, 0x0e, 0x26, 0xdd, 0xae, 0x56, 0x31, 0x80 }, + { 0xb0, 0xb8, 0x11, 0x02, 0x8c, 0x2b, 0xf9, 0x97, 0x21, 0x34, 0x87, 0xc0 }, + { 0x18, 0xa0, 0xc9, 0x23, 0x1f, 0xc6, 0x0a, 0xdf, 0x5c, 0x5e, 0xa3, 0x20 }, + { 0x76, 0x47, 0x1e, 0x83, 0x02, 0xa0, 0x72, 0x1e, 0x01, 0xb1, 0x2b, 0x80 }, + { 0xff, 0xbc, 0xcb, 0x80, 0xca, 0x83, 0x41, 0xfa, 0xfb, 0x47, 0xb2, 0xe0 }, + { 0x66, 0xa7, 0x2a, 0x15, 0x8f, 0x93, 0x25, 0xa2, 0xbf, 0x67, 0x17, 0x00 }, + { 0xc4, 0x24, 0x36, 0x89, 0xfe, 0x85, 0xb1, 0xc5, 0x13, 0x63, 0xa1, 0x80 }, + { 0x0d, 0xff, 0x73, 0x94, 0x14, 0xd1, 0xa1, 0xb3, 0x4b, 0x1c, 0x27, 0x00 }, + { 0x15, 0xb4, 0x88, 0x30, 0x63, 0x6c, 0x8b, 0x99, 0x89, 0x49, 0x72, 0xe0 }, + { 0x29, 0xa8, 0x9c, 0x0d, 0x3d, 0xe8, 0x1d, 0x66, 0x54, 0x89, 0xb0, 0xe0 }, + { 0x4f, 0x12, 0x6f, 0x37, 0xfa, 0x51, 0xcb, 0xe6, 0x1b, 0xd6, 0xb9, 0x40 }, + { 0x99, 0xc4, 0x72, 0x39, 0xd0, 0xd9, 0x7d, 0x3c, 0x84, 0xe0, 0x94, 0x00 }, + { 0x19, 0x19, 0xb7, 0x51, 0x19, 0x76, 0x56, 0x21, 0xbb, 0x4f, 0x1e, 0x80 }, + { 0x09, 0xdb, 0x12, 0xd7, 0x31, 0xfa, 0xee, 0x0b, 0x86, 0xdf, 0x6b, 0x80 }, + { 0x48, 0x8f, 0xc3, 0x3d, 0xf4, 0x3f, 0xbd, 0xee, 0xa4, 0xea, 0xfb, 0x40 }, + { 0x82, 0x74, 0x23, 0xee, 0x40, 0xb6, 0x75, 0xf7, 0x56, 0xeb, 0x5f, 0xe0 }, + { 0xab, 0xe1, 0x97, 0xc4, 0x84, 0xcb, 0x74, 0x75, 0x71, 0x44, 0xa9, 0xa0 }, + { 0x2b, 0x50, 0x0e, 0x4b, 0xc0, 0xec, 0x5a, 0x6d, 0x2b, 0xdb, 0xdd, 0x00 }, + { 0xc4, 0x74, 0xaa, 0x53, 0xd7, 0x02, 0x18, 0x76, 0x16, 0x69, 0x36, 0x00 }, + { 0x8e, 0xba, 0x1a, 0x13, 0xdb, 0x33, 0x90, 0xbd, 0x67, 0x18, 0xce, 0xc0 }, + { 0x75, 0x38, 0x44, 0x67, 0x3a, 0x27, 0x78, 0x2c, 0xc4, 0x20, 0x12, 0xe0 }, + { 0x06, 0xff, 0x83, 0xa1, 0x45, 0xc3, 0x70, 0x35, 0xa5, 0xc1, 0x26, 0x80 }, + { 0x3b, 0x37, 0x41, 0x78, 0x58, 0xcc, 0x2d, 0xd3, 0x3e, 0xc3, 0xf6, 0x20 }, + { 0x9a, 0x4a, 0x5a, 0x28, 0xee, 0x17, 0xca, 0x9c, 0x32, 0x48, 0x42, 0xc0 }, + { 0xbc, 0x29, 0xf4, 0x65, 0x30, 0x9c, 0x97, 0x7e, 0x89, 0x61, 0x0a, 0x40 }, + { 0x26, 0x63, 0xae, 0x6d, 0xdf, 0x8b, 0x5c, 0xe2, 0xbb, 0x29, 0x48, 0x80 }, + { 0x46, 0xf2, 0x31, 0xef, 0xe4, 0x57, 0x03, 0x4c, 0x18, 0x14, 0x41, 0x80 }, + { 0x3f, 0xb2, 0xce, 0x85, 0xab, 0xe9, 0xb0, 0xc7, 0x2e, 0x06, 0xfb, 0xe0 }, + { 0xde, 0x87, 0x48, 0x1f, 0x28, 0x2c, 0x15, 0x39, 0x71, 0xa0, 0xa2, 0xe0 }, + { 0xfc, 0xd7, 0xcc, 0xf2, 0x3c, 0x69, 0xfa, 0x99, 0xbb, 0xa1, 0x41, 0x20 }, + { 0xf0, 0x26, 0x14, 0x47, 0xe9, 0x49, 0x0c, 0xa8, 0xe4, 0x74, 0xce, 0xc0 }, + { 0x44, 0x10, 0x11, 0x58, 0x18, 0x19, 0x6f, 0x95, 0xcd, 0xd7, 0x01, 0x20 }, + { 0x08, 0x8f, 0xc3, 0x1d, 0xf4, 0xbf, 0xbd, 0xe2, 0xa4, 0xea, 0xfb, 0x40 }, + { 0xb8, 0xfe, 0xf1, 0xb6, 0x30, 0x77, 0x29, 0xfb, 0x0a, 0x07, 0x8c, 0x00 }, + { 0x5a, 0xfe, 0xa7, 0xac, 0xcc, 0xb7, 0x7b, 0xbc, 0x9d, 0x99, 0xa9, 0x00 }, + { 0x49, 0xa7, 0x01, 0x6a, 0xc6, 0x53, 0xf6, 0x5e, 0xcd, 0xc9, 0x07, 0x60 }, + { 0x19, 0x44, 0xd0, 0x85, 0xbe, 0x4e, 0x7d, 0xa8, 0xd6, 0xcc, 0x7d, 0x00 }, + { 0x25, 0x1f, 0x62, 0xad, 0xc4, 0x03, 0x2f, 0x0e, 0xe7, 0x14, 0x00, 0x20 }, + { 0x56, 0x47, 0x1f, 0x87, 0x02, 0xa0, 0x72, 0x1e, 0x00, 0xb1, 0x2b, 0x80 }, + { 0x2b, 0x8e, 0x49, 0x23, 0xf2, 0xdd, 0x51, 0xe2, 0xd5, 0x37, 0xfa, 0x00 }, + { 0x6b, 0x55, 0x0a, 0x40, 0xa6, 0x6f, 0x47, 0x55, 0xde, 0x95, 0xc2, 0x60 }, + { 0xa1, 0x8a, 0xd2, 0x8d, 0x4e, 0x27, 0xfe, 0x92, 0xa4, 0xf6, 0xc8, 0x40 }, + { 0x10, 0xc2, 0xe5, 0x86, 0x38, 0x8c, 0xb8, 0x2a, 0x3d, 0x80, 0x75, 0x80 }, + { 0xef, 0x34, 0xa4, 0x18, 0x17, 0xee, 0x02, 0x13, 0x3d, 0xb2, 0xeb, 0x00 }, + { 0x7e, 0x9c, 0x0c, 0x54, 0x32, 0x5a, 0x9c, 0x15, 0x83, 0x6e, 0x00, 0x00 }, + { 0x36, 0x93, 0xe5, 0x72, 0xd1, 0xfd, 0xe4, 0xcd, 0xf0, 0x79, 0xe8, 0x60 }, + { 0xbf, 0xb2, 0xce, 0xc5, 0xab, 0xe1, 0xb0, 0xc7, 0x2e, 0x07, 0xfb, 0xe0 }, + { 0x7e, 0xe1, 0x82, 0x30, 0xc5, 0x83, 0xcc, 0xcc, 0x57, 0xd4, 0xb0, 0x80 }, + { 0xa0, 0x66, 0xcb, 0x2f, 0xed, 0xaf, 0xc9, 0xf5, 0x26, 0x64, 0x12, 0x60 }, + { 0xbb, 0x23, 0x72, 0x5a, 0xbc, 0x47, 0xcc, 0x5f, 0x4c, 0xc4, 0xcd, 0x20 }, + { 0xde, 0xd9, 0xdb, 0xa3, 0xbe, 0xe4, 0x0c, 0x59, 0xb5, 0x60, 0x9b, 0x40 }, + { 0xd9, 0xa7, 0x01, 0x6a, 0xc6, 0x53, 0xe6, 0xde, 0xcd, 0xc9, 0x03, 0x60 }, + { 0x9a, 0xd4, 0x6a, 0xed, 0x5f, 0x70, 0x7f, 0x28, 0x0a, 0xb5, 0xfc, 0x40 }, + { 0xe5, 0x92, 0x1c, 0x77, 0x82, 0x25, 0x87, 0x31, 0x6d, 0x7d, 0x3c, 0x20 }, + { 0x4f, 0x14, 0xda, 0x82, 0x42, 0xa8, 0xb8, 0x6d, 0xca, 0x73, 0x35, 0x20 }, + { 0x8b, 0x8b, 0x50, 0x7a, 0xd4, 0x67, 0xd4, 0x44, 0x1d, 0xf7, 0x70, 0xe0 }, + { 0x22, 0x83, 0x1c, 0x9c, 0xf1, 0x16, 0x94, 0x67, 0xad, 0x04, 0xb6, 0x80 }, + { 0x21, 0x3b, 0x83, 0x8f, 0xe2, 0xae, 0x54, 0xc3, 0x8e, 0xe7, 0x18, 0x00 }, + { 0x5d, 0x92, 0x6b, 0x6d, 0xd7, 0x1f, 0x08, 0x51, 0x81, 0xa4, 0xe1, 0x20 }, + { 0x66, 0xab, 0x79, 0xd4, 0xb2, 0x9e, 0xe6, 0xe6, 0x95, 0x09, 0xe5, 0x60 }, + { 0x95, 0x81, 0x48, 0x68, 0x2d, 0x74, 0x8a, 0x38, 0xdd, 0x68, 0xba, 0xa0 }, + { 0xb8, 0xce, 0x02, 0x0c, 0xf0, 0x69, 0xc3, 0x2a, 0x72, 0x3a, 0xb1, 0x40 }, + { 0xf4, 0x33, 0x1d, 0x6d, 0x46, 0x16, 0x07, 0xe9, 0x57, 0x52, 0x74, 0x60 }, + { 0x6d, 0xa2, 0x3b, 0xa4, 0x24, 0xb9, 0x59, 0x61, 0x33, 0xcf, 0x9c, 0x80 }, + { 0xa6, 0x36, 0xbc, 0xbc, 0x7b, 0x30, 0xc5, 0xfb, 0xea, 0xe6, 0x7f, 0xe0 }, + { 0x5c, 0xb0, 0xd8, 0x6a, 0x07, 0xdf, 0x65, 0x4a, 0x90, 0x89, 0xa2, 0x00 }, + { 0xf1, 0x1f, 0x10, 0x68, 0x48, 0x78, 0x0f, 0xc9, 0xec, 0xdd, 0x80, 0xa0 }, + { 0x1f, 0xbb, 0x53, 0x64, 0xfb, 0x8d, 0x2c, 0x9d, 0x73, 0x0d, 0x5b, 0xa0 }, + { 0xfc, 0xb8, 0x6b, 0xc7, 0x0a, 0x50, 0xc9, 0xd0, 0x2a, 0x5d, 0x03, 0x40 }, + { 0xa5, 0x34, 0x43, 0x30, 0x29, 0xea, 0xc1, 0x5f, 0x32, 0x2e, 0x34, 0xc0 }, + { 0xc9, 0x89, 0xd9, 0xc7, 0xc3, 0xd3, 0xb8, 0xc5, 0x5d, 0x75, 0x13, 0x00 }, + { 0x7b, 0xb3, 0x8b, 0x2f, 0x01, 0x86, 0xd4, 0x66, 0x43, 0xae, 0x96, 0x20 }, + { 0x26, 0x44, 0xeb, 0xad, 0xeb, 0x44, 0xb9, 0x46, 0x7d, 0x1f, 0x42, 0xc0 }, + { 0x60, 0x8c, 0xc8, 0x57, 0x59, 0x4b, 0xfb, 0xb5, 0x5d, 0x69, 0x60, 0x00 } +}; + +// Each row describes one LDPC parity check. +// Each number is an index into the codeword (1-origin). +// The codeword bits mentioned in each row must XOR to zero. +const uint8_t kFTX_LDPC_Nm[FTX_LDPC_M][7] = { + { 4, 31, 59, 91, 92, 96, 153 }, + { 5, 32, 60, 93, 115, 146, 0 }, + { 6, 24, 61, 94, 122, 151, 0 }, + { 7, 33, 62, 95, 96, 143, 0 }, + { 8, 25, 63, 83, 93, 96, 148 }, + { 6, 32, 64, 97, 126, 138, 0 }, + { 5, 34, 65, 78, 98, 107, 154 }, + { 9, 35, 66, 99, 139, 146, 0 }, + { 10, 36, 67, 100, 107, 126, 0 }, + { 11, 37, 67, 87, 101, 139, 158 }, + { 12, 38, 68, 102, 105, 155, 0 }, + { 13, 39, 69, 103, 149, 162, 0 }, + { 8, 40, 70, 82, 104, 114, 145 }, + { 14, 41, 71, 88, 102, 123, 156 }, + { 15, 42, 59, 106, 123, 159, 0 }, + { 1, 33, 72, 106, 107, 157, 0 }, + { 16, 43, 73, 108, 141, 160, 0 }, + { 17, 37, 74, 81, 109, 131, 154 }, + { 11, 44, 75, 110, 121, 166, 0 }, + { 45, 55, 64, 111, 130, 161, 173 }, + { 8, 46, 71, 112, 119, 166, 0 }, + { 18, 36, 76, 89, 113, 114, 143 }, + { 19, 38, 77, 104, 116, 163, 0 }, + { 20, 47, 70, 92, 138, 165, 0 }, + { 2, 48, 74, 113, 128, 160, 0 }, + { 21, 45, 78, 83, 117, 121, 151 }, + { 22, 47, 58, 118, 127, 164, 0 }, + { 16, 39, 62, 112, 134, 158, 0 }, + { 23, 43, 79, 120, 131, 145, 0 }, + { 19, 35, 59, 73, 110, 125, 161 }, + { 20, 36, 63, 94, 136, 161, 0 }, + { 14, 31, 79, 98, 132, 164, 0 }, + { 3, 44, 80, 124, 127, 169, 0 }, + { 19, 46, 81, 117, 135, 167, 0 }, + { 7, 49, 58, 90, 100, 105, 168 }, + { 12, 50, 61, 118, 119, 144, 0 }, + { 13, 51, 64, 114, 118, 157, 0 }, + { 24, 52, 76, 129, 148, 149, 0 }, + { 25, 53, 69, 90, 101, 130, 156 }, + { 20, 46, 65, 80, 120, 140, 170 }, + { 21, 54, 77, 100, 140, 171, 0 }, + { 35, 82, 133, 142, 171, 174, 0 }, + { 14, 30, 83, 113, 125, 170, 0 }, + { 4, 29, 68, 120, 134, 173, 0 }, + { 1, 4, 52, 57, 86, 136, 152 }, + { 26, 51, 56, 91, 122, 137, 168 }, + { 52, 84, 110, 115, 145, 168, 0 }, + { 7, 50, 81, 99, 132, 173, 0 }, + { 23, 55, 67, 95, 172, 174, 0 }, + { 26, 41, 77, 109, 141, 148, 0 }, + { 2, 27, 41, 61, 62, 115, 133 }, + { 27, 40, 56, 124, 125, 126, 0 }, + { 18, 49, 55, 124, 141, 167, 0 }, + { 6, 33, 85, 108, 116, 156, 0 }, + { 28, 48, 70, 85, 105, 129, 158 }, + { 9, 54, 63, 131, 147, 155, 0 }, + { 22, 53, 68, 109, 121, 174, 0 }, + { 3, 13, 48, 78, 95, 123, 0 }, + { 31, 69, 133, 150, 155, 169, 0 }, + { 12, 43, 66, 89, 97, 135, 159 }, + { 5, 39, 75, 102, 136, 167, 0 }, + { 2, 54, 86, 101, 135, 164, 0 }, + { 15, 56, 87, 108, 119, 171, 0 }, + { 10, 44, 82, 91, 111, 144, 149 }, + { 23, 34, 71, 94, 127, 153, 0 }, + { 11, 49, 88, 92, 142, 157, 0 }, + { 29, 34, 87, 97, 147, 162, 0 }, + { 30, 50, 60, 86, 137, 142, 162 }, + { 10, 53, 66, 84, 112, 128, 165 }, + { 22, 57, 85, 93, 140, 159, 0 }, + { 28, 32, 72, 103, 132, 166, 0 }, + { 28, 29, 84, 88, 117, 143, 150 }, + { 1, 26, 45, 80, 128, 147, 0 }, + { 17, 27, 89, 103, 116, 153, 0 }, + { 51, 57, 98, 163, 165, 172, 0 }, + { 21, 37, 73, 138, 152, 169, 0 }, + { 16, 47, 76, 130, 137, 154, 0 }, + { 3, 24, 30, 72, 104, 139, 0 }, + { 9, 40, 90, 106, 134, 151, 0 }, + { 15, 58, 60, 74, 111, 150, 163 }, + { 18, 42, 79, 144, 146, 152, 0 }, + { 25, 38, 65, 99, 122, 160, 0 }, + { 17, 42, 75, 129, 170, 172, 0 } +}; + +// Each row corresponds to a codeword bit. +// The numbers indicate which three LDPC parity checks (rows in Nm) refer to the codeword bit. +// 1-origin. +const uint8_t kFTX_LDPC_Mn[FTX_LDPC_N][3] = { + { 16, 45, 73 }, + { 25, 51, 62 }, + { 33, 58, 78 }, + { 1, 44, 45 }, + { 2, 7, 61 }, + { 3, 6, 54 }, + { 4, 35, 48 }, + { 5, 13, 21 }, + { 8, 56, 79 }, + { 9, 64, 69 }, + { 10, 19, 66 }, + { 11, 36, 60 }, + { 12, 37, 58 }, + { 14, 32, 43 }, + { 15, 63, 80 }, + { 17, 28, 77 }, + { 18, 74, 83 }, + { 22, 53, 81 }, + { 23, 30, 34 }, + { 24, 31, 40 }, + { 26, 41, 76 }, + { 27, 57, 70 }, + { 29, 49, 65 }, + { 3, 38, 78 }, + { 5, 39, 82 }, + { 46, 50, 73 }, + { 51, 52, 74 }, + { 55, 71, 72 }, + { 44, 67, 72 }, + { 43, 68, 78 }, + { 1, 32, 59 }, + { 2, 6, 71 }, + { 4, 16, 54 }, + { 7, 65, 67 }, + { 8, 30, 42 }, + { 9, 22, 31 }, + { 10, 18, 76 }, + { 11, 23, 82 }, + { 12, 28, 61 }, + { 13, 52, 79 }, + { 14, 50, 51 }, + { 15, 81, 83 }, + { 17, 29, 60 }, + { 19, 33, 64 }, + { 20, 26, 73 }, + { 21, 34, 40 }, + { 24, 27, 77 }, + { 25, 55, 58 }, + { 35, 53, 66 }, + { 36, 48, 68 }, + { 37, 46, 75 }, + { 38, 45, 47 }, + { 39, 57, 69 }, + { 41, 56, 62 }, + { 20, 49, 53 }, + { 46, 52, 63 }, + { 45, 70, 75 }, + { 27, 35, 80 }, + { 1, 15, 30 }, + { 2, 68, 80 }, + { 3, 36, 51 }, + { 4, 28, 51 }, + { 5, 31, 56 }, + { 6, 20, 37 }, + { 7, 40, 82 }, + { 8, 60, 69 }, + { 9, 10, 49 }, + { 11, 44, 57 }, + { 12, 39, 59 }, + { 13, 24, 55 }, + { 14, 21, 65 }, + { 16, 71, 78 }, + { 17, 30, 76 }, + { 18, 25, 80 }, + { 19, 61, 83 }, + { 22, 38, 77 }, + { 23, 41, 50 }, + { 7, 26, 58 }, + { 29, 32, 81 }, + { 33, 40, 73 }, + { 18, 34, 48 }, + { 13, 42, 64 }, + { 5, 26, 43 }, + { 47, 69, 72 }, + { 54, 55, 70 }, + { 45, 62, 68 }, + { 10, 63, 67 }, + { 14, 66, 72 }, + { 22, 60, 74 }, + { 35, 39, 79 }, + { 1, 46, 64 }, + { 1, 24, 66 }, + { 2, 5, 70 }, + { 3, 31, 65 }, + { 4, 49, 58 }, + { 1, 4, 5 }, + { 6, 60, 67 }, + { 7, 32, 75 }, + { 8, 48, 82 }, + { 9, 35, 41 }, + { 10, 39, 62 }, + { 11, 14, 61 }, + { 12, 71, 74 }, + { 13, 23, 78 }, + { 11, 35, 55 }, + { 15, 16, 79 }, + { 7, 9, 16 }, + { 17, 54, 63 }, + { 18, 50, 57 }, + { 19, 30, 47 }, + { 20, 64, 80 }, + { 21, 28, 69 }, + { 22, 25, 43 }, + { 13, 22, 37 }, + { 2, 47, 51 }, + { 23, 54, 74 }, + { 26, 34, 72 }, + { 27, 36, 37 }, + { 21, 36, 63 }, + { 29, 40, 44 }, + { 19, 26, 57 }, + { 3, 46, 82 }, + { 14, 15, 58 }, + { 33, 52, 53 }, + { 30, 43, 52 }, + { 6, 9, 52 }, + { 27, 33, 65 }, + { 25, 69, 73 }, + { 38, 55, 83 }, + { 20, 39, 77 }, + { 18, 29, 56 }, + { 32, 48, 71 }, + { 42, 51, 59 }, + { 28, 44, 79 }, + { 34, 60, 62 }, + { 31, 45, 61 }, + { 46, 68, 77 }, + { 6, 24, 76 }, + { 8, 10, 78 }, + { 40, 41, 70 }, + { 17, 50, 53 }, + { 42, 66, 68 }, + { 4, 22, 72 }, + { 36, 64, 81 }, + { 13, 29, 47 }, + { 2, 8, 81 }, + { 56, 67, 73 }, + { 5, 38, 50 }, + { 12, 38, 64 }, + { 59, 72, 80 }, + { 3, 26, 79 }, + { 45, 76, 81 }, + { 1, 65, 74 }, + { 7, 18, 77 }, + { 11, 56, 59 }, + { 14, 39, 54 }, + { 16, 37, 66 }, + { 10, 28, 55 }, + { 15, 60, 70 }, + { 17, 25, 82 }, + { 20, 30, 31 }, + { 12, 67, 68 }, + { 23, 75, 80 }, + { 27, 32, 62 }, + { 24, 69, 75 }, + { 19, 21, 71 }, + { 34, 53, 61 }, + { 35, 46, 47 }, + { 33, 59, 76 }, + { 40, 43, 83 }, + { 41, 42, 63 }, + { 49, 75, 83 }, + { 20, 44, 48 }, + { 42, 49, 57 } +}; + +const uint8_t kFTX_LDPC_Num_rows[FTX_LDPC_M] = { + 7, 6, 6, 6, 7, 6, 7, 6, 6, 7, 6, 6, 7, 7, 6, 6, + 6, 7, 6, 7, 6, 7, 6, 6, 6, 7, 6, 6, 6, 7, 6, 6, + 6, 6, 7, 6, 6, 6, 7, 7, 6, 6, 6, 6, 7, 7, 6, 6, + 6, 6, 7, 6, 6, 6, 7, 6, 6, 6, 6, 7, 6, 6, 6, 7, + 6, 6, 6, 7, 7, 6, 6, 7, 6, 6, 6, 6, 6, 6, 6, 7, + 6, 6, 6 +}; \ No newline at end of file diff --git a/external/ft8_lib/ft8/constants.h b/external/ft8_lib/ft8/constants.h new file mode 100644 index 0000000..eb50fca --- /dev/null +++ b/external/ft8_lib/ft8/constants.h @@ -0,0 +1,90 @@ +#ifndef _INCLUDE_CONSTANTS_H_ +#define _INCLUDE_CONSTANTS_H_ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +#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 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 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_SYNC_OFFSET (36) ///< Offset between sync groups + +// Define FT4 symbol counts +// FT4 message structure: +// R Sa D1 Sb D2 Sc D3 Sd R +// 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 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_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) + +typedef enum +{ + FTX_PROTOCOL_FT4, + FTX_PROTOCOL_FT8 +} ftx_protocol_t; + +/// Costas 7x7 tone pattern for synchronization +extern const uint8_t kFT8_Costas_pattern[7]; +extern const uint8_t kFT4_Costas_pattern[4][4]; + +/// Gray code map to encode 8 symbols (tones) +extern const uint8_t kFT8_Gray_map[8]; +extern const uint8_t kFT4_Gray_map[4]; + +extern const uint8_t kFT4_XOR_sequence[10]; + +/// Parity generator matrix for (174,91) LDPC code, stored in bitpacked format (MSB first) +extern const uint8_t kFTX_LDPC_generator[FTX_LDPC_M][FTX_LDPC_K_BYTES]; + +/// LDPC(174,91) parity check matrix, containing 83 rows, +/// each row describes one parity check, +/// each number is an index into the codeword (1-origin). +/// The codeword bits mentioned in each row must xor to zero. +/// From WSJT-X's ldpc_174_91_c_reordered_parity.f90. +extern const uint8_t kFTX_LDPC_Nm[FTX_LDPC_M][7]; + +/// Mn from WSJT-X's bpdecode174.f90. Each row corresponds to a codeword bit. +/// The numbers indicate which three parity checks (rows in Nm) refer to the codeword bit. +/// The numbers use 1 as the origin (first entry). +extern const uint8_t kFTX_LDPC_Mn[FTX_LDPC_N][3]; + +/// Number of rows (columns in C/C++) in the array Nm. +extern const uint8_t kFTX_LDPC_Num_rows[FTX_LDPC_M]; + +#ifdef __cplusplus +} +#endif + +#endif // _INCLUDE_CONSTANTS_H_ diff --git a/external/ft8_lib/ft8/crc.c b/external/ft8_lib/ft8/crc.c new file mode 100644 index 0000000..72a4c89 --- /dev/null +++ b/external/ft8_lib/ft8/crc.c @@ -0,0 +1,63 @@ +#include "crc.h" +#include "constants.h" + +#define TOPBIT (1u << (FT8_CRC_WIDTH - 1)) + +// Compute 14-bit CRC for a sequence of given number of bits +// Adapted from https://barrgroup.com/Embedded-Systems/How-To/CRC-Calculation-C-Code +// [IN] message - byte sequence (MSB first) +// [IN] num_bits - number of bits in the sequence +uint16_t ftx_compute_crc(const uint8_t message[], int num_bits) +{ + uint16_t remainder = 0; + int idx_byte = 0; + + // Perform modulo-2 division, a bit at a time. + for (int idx_bit = 0; idx_bit < num_bits; ++idx_bit) + { + if (idx_bit % 8 == 0) + { + // 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) + { + remainder = (remainder << 1) ^ FT8_CRC_POLYNOMIAL; + } + else + { + remainder = (remainder << 1); + } + } + + return remainder & ((TOPBIT << 1) - 1u); +} + +uint16_t ftx_extract_crc(const uint8_t a91[]) +{ + uint16_t chksum = ((a91[9] & 0x07) << 11) | (a91[10] << 3) | (a91[11] >> 5); + return chksum; +} + +void ftx_add_crc(const uint8_t payload[], uint8_t a91[]) +{ + // Copy 77 bits of payload data + for (int i = 0; i < 10; i++) + a91[i] = payload[i]; + + // Clear 3 bits after the payload to make 82 bits + a91[9] &= 0xF8u; + a91[10] = 0; + + // Calculate CRC of 82 bits (77 + 5 zeros) + // 'The CRC is calculated on the source-encoded message, zero-extended from 77 to 82 bits' + uint16_t checksum = ftx_compute_crc(a91, 96 - 14); + + // Store the CRC at the end of 77 bit message + a91[9] |= (uint8_t)(checksum >> 11); + a91[10] = (uint8_t)(checksum >> 3); + a91[11] = (uint8_t)(checksum << 5); +} diff --git a/external/ft8_lib/ft8/crc.h b/external/ft8_lib/ft8/crc.h new file mode 100644 index 0000000..f9bc7e0 --- /dev/null +++ b/external/ft8_lib/ft8/crc.h @@ -0,0 +1,31 @@ +#ifndef _INCLUDE_CRC_H_ +#define _INCLUDE_CRC_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +// Compute 14-bit CRC for a sequence of given number of bits using FT8/FT4 CRC polynomial +// [IN] message - byte sequence (MSB first) +// [IN] num_bits - number of bits in the sequence +uint16_t ftx_compute_crc(const uint8_t message[], int num_bits); + +/// Extract the FT8/FT4 CRC of a packed message (during decoding) +/// @param[in] a91 77 bits of payload data + CRC +/// @return Extracted CRC +uint16_t ftx_extract_crc(const uint8_t a91[]); + +/// Add FT8/FT4 CRC to a packed message (during encoding) +/// @param[in] payload 77 bits of payload data +/// @param[out] a91 91 bits of payload data + CRC +void ftx_add_crc(const uint8_t payload[], uint8_t a91[]); + +#ifdef __cplusplus +} +#endif + +#endif // _INCLUDE_CRC_H_ \ No newline at end of file diff --git a/external/ft8_lib/ft8/debug.h b/external/ft8_lib/ft8/debug.h new file mode 100644 index 0000000..4922665 --- /dev/null +++ b/external/ft8_lib/ft8/debug.h @@ -0,0 +1,22 @@ +#ifndef _DEBUG_H_INCLUDED_ +#define _DEBUG_H_INCLUDED_ + +#define LOG_DEBUG 0 +#define LOG_INFO 1 +#define LOG_WARN 2 +#define LOG_ERROR 3 +#define LOG_FATAL 4 + +#ifdef LOG_LEVEL +#ifndef LOG_PRINTF +#include +#define LOG_PRINTF(...) fprintf(stderr, __VA_ARGS__) +#endif +#define LOG(level, ...) \ + if (level >= LOG_LEVEL) \ + LOG_PRINTF(__VA_ARGS__) +#else // ifdef LOG_LEVEL +#define LOG(level, ...) +#endif + +#endif // _DEBUG_H_INCLUDED_ \ No newline at end of file diff --git a/external/ft8_lib/ft8/decode.c b/external/ft8_lib/ft8/decode.c new file mode 100644 index 0000000..efb1718 --- /dev/null +++ b/external/ft8_lib/ft8/decode.c @@ -0,0 +1,592 @@ +#include "decode.h" +#include "constants.h" +#include "crc.h" +#include "ldpc.h" + +#include +#include + +// #define LOG_LEVEL LOG_DEBUG +// #include "debug.h" + +// Lookup table for y = 10*log10(1 + 10^(x/10)), where +// y - increase in signal level dB when adding a weaker independent signal +// x - specific relative strength of the weaker signal in dB +// Table index corresponds to x in dB (index 0: 0 dB, index 1: -1 dB etc) +static const float db_power_sum[40] = { + 3.01029995663981f, 2.53901891043867f, 2.1244260279434f, 1.76434862436485f, 1.45540463109294f, + 1.19331048066095f, 0.973227937086954f, 0.790097496525665f, 0.638920341433796f, 0.514969420252302f, + 0.413926851582251f, 0.331956199884278f, 0.265723755961025f, 0.212384019142551f, 0.16954289279533f, + 0.135209221080382f, 0.10774225511957f, 0.085799992300358f, 0.06829128312453f, 0.054333142200458f, + 0.043213737826426f, 0.034360947517284f, 0.027316043349389f, 0.021711921641451f, 0.017255250287928f, + 0.013711928326833f, 0.010895305999614f, 0.008656680827934f, 0.006877654943187f, 0.005464004928574f, + 0.004340774793186f, 0.003448354310253f, 0.002739348814965f, 0.002176083232619f, 0.001728613409904f, + 0.001373142636584f, 0.001090761428665f, 0.000866444976964f, 0.000688255828734f, 0.000546709946839f +}; + +/// Compute log likelihood log(p(1) / p(0)) of 174 message bits for later use in soft-decision LDPC decoding +/// @param[in] wf Waterfall data collected during message slot +/// @param[in] cand Candidate to extract the message from +/// @param[in] code_map Symbol encoding map +/// @param[out] log174 Output of decoded log likelihoods for each of the 174 message bits +static void ft4_extract_likelihood(const ftx_waterfall_t* wf, const ftx_candidate_t* cand, float* log174); +static void ft8_extract_likelihood(const ftx_waterfall_t* wf, const ftx_candidate_t* cand, float* log174); + +/// Packs a string of bits each represented as a zero/non-zero byte in bit_array[], +/// as a string of packed bits starting from the MSB of the first byte of packed[] +/// @param[in] plain Array of bits (0 and nonzero values) with num_bits entires +/// @param[in] num_bits Number of bits (entries) passed in bit_array +/// @param[out] packed Byte-packed bits representing the data in bit_array +static void pack_bits(const uint8_t bit_array[], int num_bits, uint8_t packed[]); + +static float max2(float a, float b); +static float max4(float a, float b, float c, float d); +static void heapify_down(ftx_candidate_t heap[], int heap_size); +static void heapify_up(ftx_candidate_t heap[], int heap_size); + +static void ftx_normalize_logl(float* log174); +static void ft4_extract_symbol(const WF_ELEM_T* wf, float* logl); +static void ft8_extract_symbol(const WF_ELEM_T* wf, float* logl); +static void ft8_decode_multi_symbols(const WF_ELEM_T* wf, int num_bins, int n_syms, int bit_idx, float* log174); + +static const WF_ELEM_T* get_cand_mag(const ftx_waterfall_t* wf, const ftx_candidate_t* candidate) +{ + int offset = candidate->time_offset; + offset = (offset * wf->time_osr) + candidate->time_sub; + offset = (offset * wf->freq_osr) + candidate->freq_sub; + offset = (offset * wf->num_bins) + candidate->freq_offset; + return wf->mag + offset; +} + +static int ft8_sync_score(const ftx_waterfall_t* wf, const ftx_candidate_t* candidate) +{ + int score = 0; + int num_average = 0; + + // Get the pointer to symbol 0 of the candidate + const WF_ELEM_T* mag_cand = get_cand_mag(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 + int block_abs = candidate->time_offset + block; // relative to the captured signal + // Check for time boundaries + if (block_abs < 0) + continue; + if (block_abs >= wf->num_blocks) + break; + + // Get the pointer to symbol 'block' of the candidate + const WF_ELEM_T* p8 = mag_cand + (block * wf->block_stride); + + // Weighted difference between the expected and all other symbols + // Does not work as well as the alternative score below + // score += 8 * p8[kFT8_Costas_pattern[k]] - + // p8[0] - p8[1] - p8[2] - p8[3] - + // p8[4] - p8[5] - p8[6] - p8[7]; + // ++num_average; + + // Check only the neighbors of the expected symbol frequency- and time-wise + int sm = kFT8_Costas_pattern[k]; // Index of the expected bin + if (sm > 0) + { + // look at one frequency bin lower + score += WF_ELEM_MAG_INT(p8[sm]) - WF_ELEM_MAG_INT(p8[sm - 1]); + ++num_average; + } + if (sm < 7) + { + // look at one frequency bin higher + score += WF_ELEM_MAG_INT(p8[sm]) - WF_ELEM_MAG_INT(p8[sm + 1]); + ++num_average; + } + if ((k > 0) && (block_abs > 0)) + { + // look one symbol back in time + score += WF_ELEM_MAG_INT(p8[sm]) - WF_ELEM_MAG_INT(p8[sm - wf->block_stride]); + ++num_average; + } + if (((k + 1) < FT8_LENGTH_SYNC) && ((block_abs + 1) < wf->num_blocks)) + { + // look one symbol forward in time + score += WF_ELEM_MAG_INT(p8[sm]) - WF_ELEM_MAG_INT(p8[sm + wf->block_stride]); + ++num_average; + } + } + } + + if (num_average > 0) + score /= num_average; + + return score; +} + +static int ft4_sync_score(const ftx_waterfall_t* wf, const ftx_candidate_t* candidate) +{ + int score = 0; + int num_average = 0; + + // Get the pointer to symbol 0 of the candidate + const WF_ELEM_T* mag_cand = get_cand_mag(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) + { + int block = 1 + (FT4_SYNC_OFFSET * m) + k; + int block_abs = candidate->time_offset + block; + // Check for time boundaries + if (block_abs < 0) + continue; + if (block_abs >= wf->num_blocks) + break; + + // Get the pointer to symbol 'block' of the candidate + const WF_ELEM_T* p4 = mag_cand + (block * wf->block_stride); + + int sm = kFT4_Costas_pattern[m][k]; // Index of the expected bin + + // score += (4 * p4[sm]) - p4[0] - p4[1] - p4[2] - p4[3]; + // num_average += 4; + + // Check only the neighbors of the expected symbol frequency- and time-wise + if (sm > 0) + { + // look at one frequency bin lower + score += WF_ELEM_MAG_INT(p4[sm]) - WF_ELEM_MAG_INT(p4[sm - 1]); + ++num_average; + } + if (sm < 3) + { + // look at one frequency bin higher + score += WF_ELEM_MAG_INT(p4[sm]) - WF_ELEM_MAG_INT(p4[sm + 1]); + ++num_average; + } + if ((k > 0) && (block_abs > 0)) + { + // look one symbol back in time + score += WF_ELEM_MAG_INT(p4[sm]) - WF_ELEM_MAG_INT(p4[sm - wf->block_stride]); + ++num_average; + } + if (((k + 1) < FT4_LENGTH_SYNC) && ((block_abs + 1) < wf->num_blocks)) + { + // look one symbol forward in time + score += WF_ELEM_MAG_INT(p4[sm]) - WF_ELEM_MAG_INT(p4[sm + wf->block_stride]); + ++num_average; + } + } + } + + if (num_average > 0) + score /= num_average; + + return score; +} + +int ftx_find_candidates(const ftx_waterfall_t* wf, int num_candidates, ftx_candidate_t heap[], int min_score) +{ + int (*sync_fun)(const ftx_waterfall_t*, const ftx_candidate_t*) = (wf->protocol == FTX_PROTOCOL_FT4) ? ft4_sync_score : ft8_sync_score; + int num_tones = (wf->protocol == FTX_PROTOCOL_FT4) ? 4 : 8; + + int heap_size = 0; + ftx_candidate_t candidate; + + // 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 = -10; candidate.time_offset < 20; ++candidate.time_offset) + { + for (candidate.freq_offset = 0; (candidate.freq_offset + num_tones - 1) < wf->num_bins; ++candidate.freq_offset) + { + candidate.score = sync_fun(wf, &candidate); + + if (candidate.score < min_score) + continue; + + // 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)) + { + --heap_size; + heap[0] = heap[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) + { + heap[heap_size] = candidate; + ++heap_size; + heapify_up(heap, heap_size); + } + } + } + } + } + + // Sort the candidates by sync strength - here we benefit from the heap structure + int len_unsorted = heap_size; + while (len_unsorted > 1) + { + // Take the top (index 0) element which is guaranteed to have the smallest score, + // exchange it with the last element in the heap, and decrease the heap size. + // Then restore the heap property in the new, smaller heap. + // At the end the elements will be sorted in descending order. + ftx_candidate_t tmp = heap[len_unsorted - 1]; + heap[len_unsorted - 1] = heap[0]; + heap[0] = tmp; + len_unsorted--; + heapify_down(heap, len_unsorted); + } + + return heap_size; +} + +static void ft4_extract_likelihood(const ftx_waterfall_t* wf, const ftx_candidate_t* cand, float* log174) +{ + const WF_ELEM_T* mag = get_cand_mag(wf, cand); // Pointer to 4 magnitude bins of the first symbol + + // Go over FSK tones and skip Costas sync symbols + 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)); + int bit_idx = 2 * k; + + // Check for time boundaries + int block = cand->time_offset + sym_idx; + if ((block < 0) || (block >= wf->num_blocks)) + { + log174[bit_idx + 0] = 0; + log174[bit_idx + 1] = 0; + } + else + { + ft4_extract_symbol(mag + (sym_idx * wf->block_stride), log174 + bit_idx); + } + } +} + +static void ft8_extract_likelihood(const ftx_waterfall_t* wf, const ftx_candidate_t* cand, float* log174) +{ + const WF_ELEM_T* mag = get_cand_mag(wf, cand); // Pointer to 8 magnitude bins of the first symbol + + // Go over FSK tones and skip Costas sync symbols + 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); + int bit_idx = 3 * k; + + // Check for time boundaries + int block = cand->time_offset + sym_idx; + if ((block < 0) || (block >= wf->num_blocks)) + { + log174[bit_idx + 0] = 0; + log174[bit_idx + 1] = 0; + log174[bit_idx + 2] = 0; + } + else + { + ft8_extract_symbol(mag + (sym_idx * wf->block_stride), log174 + bit_idx); + } + } +} + +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) + { + sum += log174[i]; + sum2 += log174[i] * log174[i]; + } + float inv_n = 1.0f / FTX_LDPC_N; + float variance = (sum2 - (sum * sum * inv_n)) * inv_n; + + // 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) + { + log174[i] *= norm_factor; + } +} + +bool ftx_decode_candidate(const ftx_waterfall_t* wf, const ftx_candidate_t* cand, int max_iterations, ftx_message_t* message, ftx_decode_status_t* status) +{ + float log174[FTX_LDPC_N]; // message bits encoded as likelihood + if (wf->protocol == FTX_PROTOCOL_FT4) + { + ft4_extract_likelihood(wf, cand, log174); + } + else + { + ft8_extract_likelihood(wf, cand, log174); + } + + ftx_normalize_logl(log174); + + uint8_t plain174[FTX_LDPC_N]; // message bits (0/1) + bp_decode(log174, max_iterations, plain174, &status->ldpc_errors); + // ldpc_decode(log174, max_iterations, plain174, &status->ldpc_errors); + + if (status->ldpc_errors > 0) + { + return false; + } + + // Extract payload + CRC (first FTX_LDPC_K bits) packed into a byte array + uint8_t a91[FTX_LDPC_K_BYTES]; + pack_bits(plain174, FTX_LDPC_K, a91); + + // Extract CRC and check it + status->crc_extracted = ftx_extract_crc(a91); + // [1]: 'The CRC is calculated on the source-encoded message, zero-extended from 77 to 82 bits.' + a91[9] &= 0xF8; + a91[10] &= 0x00; + status->crc_calculated = ftx_compute_crc(a91, 96 - 14); + + if (status->crc_extracted != status->crc_calculated) + { + return false; + } + + // Reuse CRC value as a hash for the message (TODO: 14 bits only, should perhaps use full 16 or 32 bits?) + message->hash = status->crc_calculated; + + if (wf->protocol == FTX_PROTOCOL_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-OR’ed with [a] pseudorandom sequence before computing the CRC and FEC parity bits' + for (int i = 0; i < 10; ++i) + { + message->payload[i] = a91[i] ^ kFT4_XOR_sequence[i]; + } + } + else + { + for (int i = 0; i < 10; ++i) + { + message->payload[i] = a91[i]; + } + } + + // LOG(LOG_DEBUG, "Decoded message (CRC %04x), trying to unpack...\n", status->crc_extracted); + return true; +} + +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 void heapify_down(ftx_candidate_t heap[], int heap_size) +{ + // heapify from the root down + int current = 0; // root node + while (true) + { + int left = 2 * current + 1; + int right = left + 1; + + // Find the smallest value of (parent, left child, right child) + int smallest = current; + if ((left < heap_size) && (heap[left].score < heap[smallest].score)) + { + smallest = left; + } + if ((right < heap_size) && (heap[right].score < heap[smallest].score)) + { + smallest = right; + } + + if (smallest == current) + { + break; + } + + // Exchange the current node with the smallest child and move down to it + ftx_candidate_t tmp = heap[smallest]; + heap[smallest] = heap[current]; + heap[current] = tmp; + current = smallest; + } +} + +static void heapify_up(ftx_candidate_t heap[], int heap_size) +{ + // heapify from the last node up + int current = heap_size - 1; + while (current > 0) + { + int parent = (current - 1) / 2; + if (!(heap[current].score < heap[parent].score)) + { + break; + } + + // Exchange the current node with its parent and move up + ftx_candidate_t tmp = heap[parent]; + heap[parent] = heap[current]; + heap[current] = tmp; + current = parent; + } +} + +// Compute unnormalized log likelihood log(p(1) / p(0)) of 2 message bits (1 FSK symbol) +static void ft4_extract_symbol(const WF_ELEM_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) + { + s2[j] = WF_ELEM_MAG(wf[kFT4_Gray_map[j]]); + } + + logl[0] = max2(s2[2], s2[3]) - max2(s2[0], s2[1]); + logl[1] = max2(s2[1], s2[3]) - max2(s2[0], s2[2]); +} + +// Compute unnormalized log likelihood log(p(1) / p(0)) of 3 message bits (1 FSK symbol) +static void ft8_extract_symbol(const WF_ELEM_T* wf, float* logl) +{ + // Cleaned up code for the simple case of n_syms==1 +#if 1 + float s2[8]; + + for (int j = 0; j < 8; ++j) + { + s2[j] = WF_ELEM_MAG(wf[kFT8_Gray_map[j]]); + } + + logl[0] = max4(s2[4], s2[5], s2[6], s2[7]) - max4(s2[0], s2[1], s2[2], s2[3]); + logl[1] = max4(s2[2], s2[3], s2[6], s2[7]) - max4(s2[0], s2[1], s2[4], s2[5]); + logl[2] = max4(s2[1], s2[3], s2[5], s2[7]) - max4(s2[0], s2[2], s2[4], s2[6]); +#else + float a[7] = { + // (float)wf[7] - (float)wf[0], // 0: p(111) / p(000) + (float)wf[5] - (float)wf[2], // 0: p(100) / p(011) + (float)wf[3] - (float)wf[0], // 1: p(010) / p(000) + (float)wf[6] - (float)wf[3], // 2: p(101) / p(010) + (float)wf[6] - (float)wf[2], // 3: p(101) / p(011) + (float)wf[7] - (float)wf[4], // 4: p(111) / p(110) + (float)wf[4] - (float)wf[1], // 5: p(110) / p(001) + (float)wf[5] - (float)wf[1] // 6: p(100) / p(001) + }; + float k = 1.0f; + + // logl[0] = k * (a[0] + a[2] + a[3] + a[5] + a[6]) / 5; + // logl[1] = k * (a[0] / 4 + (a[1] - a[3]) * 5 / 24 + (a[5] - a[2]) / 6 + (a[4] - a[6]) / 24); + // logl[2] = k * (a[0] / 4 + (a[1] - a[3]) / 24 + (a[2] - a[5]) / 6 + (a[4] - a[6]) * 5 / 24); + logl[0] = k * (a[1] / 6 + a[2] / 3 + a[3] / 6 + a[4] / 6 + a[5] / 3 + a[6] / 6); + logl[1] = k * (-a[0] / 4 + a[1] * 7 / 24 + (a[4] - a[3]) / 8 + a[5] / 3 + a[6] / 24); + logl[2] = k * (-a[0] / 4 + (a[1] - a[6]) / 8 + a[2] / 3 + a[3] / 24 + a[4] * 7 / 24 - a[5] * 5 / 18); +#endif + // for (int i = 0; i < 8; ++i) + // printf("%d ", WF_ELEM_MAG_INT(wf[i])); + // for (int i = 0; i < 3; ++i) + // printf("%.1f ", logl[i]); + // printf("\n"); +} + +// Compute unnormalized log likelihood log(p(1) / p(0)) of bits corresponding to several FSK symbols at once +static void ft8_decode_multi_symbols(const WF_ELEM_T* wf, int num_bins, int n_syms, int bit_idx, float* log174) +{ + const int n_bits = 3 * n_syms; + const int n_tones = (1 << n_bits); + + float s2[n_tones]; + + for (int j = 0; j < n_tones; ++j) + { + int j1 = j & 0x07; + if (n_syms == 1) + { + s2[j] = WF_ELEM_MAG(wf[kFT8_Gray_map[j1]]); + continue; + } + int j2 = (j >> 3) & 0x07; + if (n_syms == 2) + { + s2[j] = WF_ELEM_MAG(wf[kFT8_Gray_map[j2]]); + s2[j] += WF_ELEM_MAG(wf[kFT8_Gray_map[j1] + 4 * num_bins]); + continue; + } + int j3 = (j >> 6) & 0x07; + s2[j] = WF_ELEM_MAG(wf[kFT8_Gray_map[j3]]); + s2[j] += WF_ELEM_MAG(wf[kFT8_Gray_map[j2] + 4 * num_bins]); + s2[j] += WF_ELEM_MAG(wf[kFT8_Gray_map[j1] + 8 * num_bins]); + } + + // Extract bit significance (and convert them to float) + // 8 FSK tones = 3 bits + for (int i = 0; i < n_bits; ++i) + { + if (bit_idx + i >= FTX_LDPC_N) + { + // Respect array size + break; + } + + uint16_t mask = (n_tones >> (i + 1)); + float max_zero = -1000, max_one = -1000; + for (int n = 0; n < n_tones; ++n) + { + if (n & mask) + { + max_one = max2(max_one, s2[n]); + } + else + { + max_zero = max2(max_zero, s2[n]); + } + } + + log174[bit_idx + i] = max_one - max_zero; + } +} + +// Packs a string of bits each represented as a zero/non-zero byte in plain[], +// as a string of packed bits starting from the MSB of the first byte of packed[] +static void pack_bits(const uint8_t bit_array[], int num_bits, uint8_t packed[]) +{ + int num_bytes = (num_bits + 7) / 8; + for (int i = 0; i < num_bytes; ++i) + { + packed[i] = 0; + } + + uint8_t mask = 0x80; + int byte_idx = 0; + for (int i = 0; i < num_bits; ++i) + { + if (bit_array[i]) + { + packed[byte_idx] |= mask; + } + mask >>= 1; + if (!mask) + { + mask = 0x80; + ++byte_idx; + } + } +} \ No newline at end of file diff --git a/external/ft8_lib/ft8/decode.h b/external/ft8_lib/ft8/decode.h new file mode 100644 index 0000000..43cd376 --- /dev/null +++ b/external/ft8_lib/ft8/decode.h @@ -0,0 +1,96 @@ +#ifndef _INCLUDE_DECODE_H_ +#define _INCLUDE_DECODE_H_ + +#include +#include + +#include "constants.h" +#include "message.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef struct +{ + float mag; + float phase; +} waterfall_cpx_t; + +// #define WATERFALL_USE_PHASE + +#ifdef WATERFALL_USE_PHASE +#define WF_ELEM_T waterfall_cpx_t +#define WF_ELEM_MAG(x) ((x).mag) +#define WF_ELEM_MAG_INT(x) (int)(2 * ((x).mag + 120.0f)) +#else +#define WF_ELEM_T uint8_t +#define WF_ELEM_MAG(x) ((float)(x)*0.5f - 120.0f) +#define WF_ELEM_MAG_INT(x) (int)(x) +#endif + +/// Input structure to ftx_find_sync() function. This structure describes stored waterfall data over the whole message slot. +/// Fields time_osr and freq_osr specify additional oversampling rate for time and frequency resolution. +/// If time_osr=1, FFT magnitude data is collected once for every symbol transmitted, i.e. every 1/6.25 = 0.16 seconds. +/// Values time_osr > 1 mean each symbol is further subdivided in time. +/// 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 + WF_ELEM_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 +} ftx_waterfall_t; + +/// Output structure of ftx_find_sync() and input structure of ftx_decode(). +/// Holds the position of potential start of a message in time and frequency. +typedef struct +{ + int16_t score; ///< Candidate score (non-negative number; higher score means higher likelihood) + int16_t time_offset; ///< Index of the time block + int16_t freq_offset; ///< Index of the frequency bin + uint8_t time_sub; ///< Index of the time subdivision used + uint8_t freq_sub; ///< Index of the frequency subdivision used +} ftx_candidate_t; + +/// Structure that contains the status of various steps during decoding of a message +typedef struct +{ + float freq; + float time; + 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 +} ftx_decode_status_t; + +/// Localize top N candidates in frequency and time according to their sync strength (looking at Costas symbols) +/// We treat and organize the candidate list as a min-heap (empty initially). +/// @param[in] power Waterfall data collected during message slot +/// @param[in] sync_pattern Synchronization pattern +/// @param[in] num_candidates Number of maximum candidates (size of heap array) +/// @param[in,out] heap Array of ftx_candidate_t type entries (with num_candidates allocated entries) +/// @param[in] min_score Minimal score allowed for pruning unlikely candidates (can be zero for no effect) +/// @return Number of candidates filled in the heap +int ftx_find_candidates(const ftx_waterfall_t* power, int num_candidates, ftx_candidate_t heap[], int min_score); + +/// Attempt to decode a message candidate. Extracts the bit probabilities, runs LDPC decoder, checks CRC and unpacks the message in plain text. +/// @param[in] power Waterfall data collected during message slot +/// @param[in] cand Candidate to decode +/// @param[in] max_iterations Maximum allowed LDPC iterations (lower number means faster decode, but less precise) +/// @param[out] message ftx_message_t structure that will receive the decoded message +/// @param[out] status ftx_decode_status_t structure that will be filled with the status of various decoding steps +/// @return True if the decoding was successful, false otherwise (check status for details) +bool ftx_decode_candidate(const ftx_waterfall_t* power, const ftx_candidate_t* cand, int max_iterations, ftx_message_t* message, ftx_decode_status_t* status); + +#ifdef __cplusplus +} +#endif + +#endif // _INCLUDE_DECODE_H_ diff --git a/external/ft8_lib/ft8/encode.c b/external/ft8_lib/ft8/encode.c new file mode 100644 index 0000000..33c249c --- /dev/null +++ b/external/ft8_lib/ft8/encode.c @@ -0,0 +1,195 @@ +#include "encode.h" +#include "constants.h" +#include "crc.h" + +#include + +// 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 + return x % 2; // modulo 2 +} + +// Encode via LDPC a 91-bit message and return a 174-bit codeword. +// The generator matrix has dimensions (87,87). +// The code is a (174,91) regular LDPC code with column weight 3. +// Arguments: +// [IN] message - array of 91 bits stored as 12 bytes (MSB first) +// [OUT] codeword - array of 174 bits stored as 22 bytes (MSB first) +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) + { + 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 + + // Compute the LDPC checksum bits and store them in codeword + 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) + { + uint8_t bits = message[j] & kFTX_LDPC_generator[i][j]; // bitwise AND (bitwise multiplication) + nsum ^= parity8(bits); // bitwise XOR (addition modulo 2) + } + + // Set the current checksum bit in codeword if nsum is odd + 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) + { + col_mask = 0x80u; + ++col_idx; + } + } +} + +void ft8_encode(const uint8_t* payload, uint8_t* tones) +{ + uint8_t a91[FTX_LDPC_K_BYTES]; // Store 77 bits of payload + 14 bits CRC + + // Compute and add CRC at the end of the message + // a91 contains 77 bits of payload + 14 bits of CRC + ftx_add_crc(payload, a91); + + uint8_t codeword[FTX_LDPC_N_BYTES]; + encode174(a91, codeword); + + // Message structure: S7 D29 S7 D29 S7 + // 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)) + { + tones[i_tone] = kFT8_Costas_pattern[i_tone]; + } + 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)) + { + tones[i_tone] = kFT8_Costas_pattern[i_tone - 72]; + } + 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)) + { + mask = 0x80u; + i_byte++; + } + if (codeword[i_byte] & mask) + bits3 |= 2; + if (0 == (mask >>= 1)) + { + mask = 0x80u; + i_byte++; + } + if (codeword[i_byte] & mask) + bits3 |= 1; + if (0 == (mask >>= 1)) + { + mask = 0x80u; + i_byte++; + } + + tones[i_tone] = kFT8_Gray_map[bits3]; + } + } +} + +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 + + // '[..] 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-OR’ed with [a] pseudorandom sequence before computing the CRC and FEC parity bits' + for (int i = 0; i < 10; ++i) + { + payload_xor[i] = payload[i] ^ kFT4_XOR_sequence[i]; + } + + // Compute and add CRC at the end of the message + // a91 contains 77 bits of payload + 14 bits of CRC + ftx_add_crc(payload_xor, a91); + + uint8_t codeword[FTX_LDPC_N_BYTES]; + encode174(a91, codeword); // 91 bits -> 174 bits + + // Message structure: R S4_1 D29 S4_2 D29 S4_3 D29 S4_4 R + // 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)) + { + tones[i_tone] = 0; // R (ramp) symbol + } + 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)) + { + tones[i_tone] = kFT4_Costas_pattern[1][i_tone - 34]; + } + 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)) + { + tones[i_tone] = kFT4_Costas_pattern[3][i_tone - 100]; + } + 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)) + { + mask = 0x80u; + i_byte++; + } + if (codeword[i_byte] & mask) + bits2 |= 1; + if (0 == (mask >>= 1)) + { + mask = 0x80u; + i_byte++; + } + tones[i_tone] = kFT4_Gray_map[bits2]; + } + } +} diff --git a/external/ft8_lib/ft8/encode.h b/external/ft8_lib/ft8/encode.h new file mode 100644 index 0000000..dd3712f --- /dev/null +++ b/external/ft8_lib/ft8/encode.h @@ -0,0 +1,41 @@ +#ifndef _INCLUDE_ENCODE_H_ +#define _INCLUDE_ENCODE_H_ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +// typedef struct +// { +// uint8_t tones[FT8_NN]; +// // for waveform readout: +// int n_spsym; // Number of waveform samples per symbol +// float *pulse; // [3 * n_spsym] +// int idx_symbol; // Index of the current symbol +// float f0; // Base frequency, Hertz +// float signal_rate; // Waveform sample rate, Hertz +// } encoder_t; + +// void encoder_init(float signal_rate, float *pulse_buffer); +// void encoder_set_f0(float f0); +// void encoder_process(const message_t *message); // in: message +// void encoder_generate(float *block); // out: block of waveforms + +/// Generate FT8 tone sequence from payload data +/// @param[in] payload - 10 byte array consisting of 77 bit payload +/// @param[out] tones - array of FT8_NN (79) bytes to store the generated tones (encoded as 0..7) +void ft8_encode(const uint8_t* payload, uint8_t* tones); + +/// Generate FT4 tone sequence from payload data +/// @param[in] payload - 10 byte array consisting of 77 bit payload +/// @param[out] tones - array of FT4_NN (105) bytes to store the generated tones (encoded as 0..3) +void ft4_encode(const uint8_t* payload, uint8_t* tones); + +#ifdef __cplusplus +} +#endif + +#endif // _INCLUDE_ENCODE_H_ diff --git a/external/ft8_lib/ft8/ldpc.c b/external/ft8_lib/ft8/ldpc.c new file mode 100644 index 0000000..539a1df --- /dev/null +++ b/external/ft8_lib/ft8/ldpc.c @@ -0,0 +1,251 @@ +// +// LDPC decoder for FT8. +// +// given a 174-bit codeword as an array of log-likelihood of zero, +// return a 174-bit corrected codeword, or zero-length array. +// last 87 bits are the (systematic) plain-text. +// this is an implementation of the sum-product algorithm +// from Sarah Johnson's Iterative Error Correction book. +// codeword[i] = log ( P(x=0) / P(x=1) ) +// + +#include "ldpc.h" +#include "constants.h" + +#include +#include +#include +#include + +static int ldpc_check(uint8_t codeword[]); +static float fast_tanh(float x); +static float fast_atanh(float x); + +// codeword is 174 log-likelihoods. +// plain is a return value, 174 ints, to be 0 or 1. +// max_iters is how hard to try. +// ok == 87 means success. +void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int* ok) +{ + float m[FTX_LDPC_M][FTX_LDPC_N]; // ~60 kB + 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++) + { + 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++) + { + int i1 = kFTX_LDPC_Nm[j][ii1] - 1; + float a = 1.0f; + for (int ii2 = 0; ii2 < kFTX_LDPC_Num_rows[j]; ii2++) + { + int i2 = kFTX_LDPC_Nm[j][ii2] - 1; + if (i2 != i1) + { + a *= fast_tanh(-m[j][i2] / 2.0f); + } + } + e[j][i1] = -2.0f * fast_atanh(a); + } + } + + 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]; + plain[i] = (l > 0) ? 1 : 0; + } + + int errors = ldpc_check(plain); + + if (errors < min_errors) + { + // Update the current best result + min_errors = errors; + + if (errors == 0) + { + break; // Found a perfect answer + } + } + + 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) + { + int j2 = kFTX_LDPC_Mn[i][ji2] - 1; + l += e[j2][i]; + } + } + m[j1][i] = l; + } + } + } + + *ok = min_errors; +} + +// +// does a 174-bit codeword pass the FT8's LDPC parity checks? +// returns the number of parity errors. +// 0 means total success. +// +static int ldpc_check(uint8_t codeword[]) +{ + int errors = 0; + + for (int m = 0; m < FTX_LDPC_M; ++m) + { + uint8_t x = 0; + for (int i = 0; i < kFTX_LDPC_Num_rows[m]; ++i) + { + x ^= codeword[kFTX_LDPC_Nm[m][i] - 1]; + } + if (x != 0) + { + ++errors; + } + } + return errors; +} + +void bp_decode(float codeword[], int max_iters, uint8_t plain[], int* ok) +{ + float tov[FTX_LDPC_N][3]; + float toc[FTX_LDPC_M][7]; + + int min_errors = FTX_LDPC_M; + + // initialize message data + 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) + { + // Do a hard decision guess (tov=0 in iter 0) + int plain_sum = 0; + 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) + { + // message converged to all-zeros, which is prohibited + break; + } + + // Check to see if we have a codeword (check before we do any iter) + int errors = ldpc_check(plain); + + if (errors < min_errors) + { + // we have a better guess - update the result + min_errors = errors; + + 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) + { + 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) + { + Tnm += tov[n][m_idx]; + } + } + toc[m][n_idx] = fast_tanh(-Tnm / 2); + } + } + + // 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) + { + 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) + { + Tmn *= toc[m][n_idx]; + } + } + tov[n][m_idx] = -2 * fast_atanh(Tmn); + } + } + } + + *ok = min_errors; +} + +// Ideas for approximating tanh/atanh: +// * https://varietyofsound.wordpress.com/2011/02/14/efficient-tanh-computation-using-lamberts-continued-fraction/ +// * http://functions.wolfram.com/ElementaryFunctions/ArcTanh/10/0001/ +// * https://mathr.co.uk/blog/2017-09-06_approximating_hyperbolic_tangent.html +// * https://math.stackexchange.com/a/446411 + +static float fast_tanh(float x) +{ + if (x < -4.97f) + { + return -1.0f; + } + if (x > 4.97f) + { + return 1.0f; + } + float x2 = x * x; + // float a = x * (135135.0f + x2 * (17325.0f + x2 * (378.0f + x2))); + // float b = 135135.0f + x2 * (62370.0f + x2 * (3150.0f + x2 * 28.0f)); + // float a = x * (10395.0f + x2 * (1260.0f + x2 * 21.0f)); + // float b = 10395.0f + x2 * (4725.0f + x2 * (210.0f + x2)); + float a = x * (945.0f + x2 * (105.0f + x2)); + float b = 945.0f + x2 * (420.0f + x2 * 15.0f); + return a / b; +} + +static float fast_atanh(float x) +{ + float x2 = x * x; + // float a = x * (-15015.0f + x2 * (19250.0f + x2 * (-5943.0f + x2 * 256.0f))); + // float b = (-15015.0f + x2 * (24255.0f + x2 * (-11025.0f + x2 * 1225.0f))); + // float a = x * (-1155.0f + x2 * (1190.0f + x2 * -231.0f)); + // float b = (-1155.0f + x2 * (1575.0f + x2 * (-525.0f + x2 * 25.0f))); + float a = x * (945.0f + x2 * (-735.0f + x2 * 64.0f)); + float b = (945.0f + x2 * (-1050.0f + x2 * 225.0f)); + return a / b; +} diff --git a/external/ft8_lib/ft8/ldpc.h b/external/ft8_lib/ft8/ldpc.h new file mode 100644 index 0000000..02f4a99 --- /dev/null +++ b/external/ft8_lib/ft8/ldpc.h @@ -0,0 +1,23 @@ +#ifndef _INCLUDE_LDPC_H_ +#define _INCLUDE_LDPC_H_ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +// codeword is 174 log-likelihoods. +// plain is a return value, 174 ints, to be 0 or 1. +// iters is how hard to try. +// ok == 87 means success. +void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int* ok); + +void bp_decode(float codeword[], int max_iters, uint8_t plain[], int* ok); + +#ifdef __cplusplus +} +#endif + +#endif // _INCLUDE_LDPC_H_ diff --git a/external/ft8_lib/ft8/message.c b/external/ft8_lib/ft8/message.c new file mode 100644 index 0000000..5975636 --- /dev/null +++ b/external/ft8_lib/ft8/message.c @@ -0,0 +1,1155 @@ +#include "message.h" +#include "text.h" +#include +#include + +#define LOG_LEVEL LOG_WARN +#include "debug.h" + +#define MAX22 ((uint32_t)4194304ul) +#define NTOKENS ((uint32_t)2063592ul) +#define MAXGRID4 ((uint16_t)32400ul) + +////////////////////////////////////////////////////// Static function prototypes ////////////////////////////////////////////////////////////// + +static void add_brackets(char* result, const char* original, int length); + +/// Compute hash value for a callsign and save it in a hash table via the provided callsign hash interface. +/// @param[in] hash_if Callsign hash interface +/// @param[in] callsign Callsign (up to 11 characters, trimmed) +/// @param[out] n22_out Pointer to store 22-bit hash value (can be NULL) +/// @param[out] n12_out Pointer to store 12-bit hash value (can be NULL) +/// @param[out] n10_out Pointer to store 10-bit hash value (can be NULL) +/// @return True on success +static bool save_callsign(const ftx_callsign_hash_interface_t* hash_if, const char* callsign, uint32_t* n22_out, uint16_t* n12_out, uint16_t* n10_out); +static bool lookup_callsign(const ftx_callsign_hash_interface_t* hash_if, ftx_callsign_hash_type_t hash_type, uint32_t hash, char* callsign); + +/// returns the numeric value if it matches "CQ nnn" or "CQ a[bcd]", otherwise -1 +static int parse_cq_modifier(const char* string); + +/// Pack a special token, a 22-bit hash code, or a valid base call into a 29-bit integer. +static int32_t pack28(const char* callsign, const ftx_callsign_hash_interface_t* hash_if, uint8_t* ip); + +/// Unpack a callsign from 28+1 bit field in the payload of the standard message (type 1 or type 2). +/// @param[in] n29 29-bit integer, e.g. n29a or n29b, containing encoded callsign, plus suffix flag (1 bit) as LSB +/// @param[in] i3 Payload type (3 bits), 1 or 2 +/// @param[in] hash_if Callsign hash table interface (can be NULL) +/// @param[out] result Unpacked callsign (max size: 13 characters including the terminating \0) +static int unpack28(uint32_t n28, uint8_t ip, uint8_t i3, const ftx_callsign_hash_interface_t* hash_if, char* result, ftx_field_t* field_type); + +/// Pack a non-standard base call into a 28-bit integer. +static bool pack58(const ftx_callsign_hash_interface_t* hash_if, const char* callsign, uint64_t* n58); + +/// Unpack a non-standard base call from a 58-bit integer. +static bool unpack58(uint64_t n58, const ftx_callsign_hash_interface_t* hash_if, char* callsign); + +static uint16_t packgrid(const char* grid4); +static int unpackgrid(uint16_t igrid4, uint8_t ir, char* extra, ftx_field_t* extra_field_type); + +/////////////////////////////////////////////////////////// Exported functions ///////////////////////////////////////////////////////////////// + +void ftx_message_init(ftx_message_t* msg) +{ + memset((void*)msg, 0, sizeof(ftx_message_t)); +} + +uint8_t ftx_message_get_i3(const ftx_message_t* msg) +{ + // Extract i3 (bits 74..76) + uint8_t i3 = (msg->payload[9] >> 3) & 0x07u; + return i3; +} + +uint8_t ftx_message_get_n3(const ftx_message_t* msg) +{ + // Extract n3 (bits 71..73) + uint8_t n3 = ((msg->payload[8] << 2) & 0x04u) | ((msg->payload[9] >> 6) & 0x03u); + return n3; +} + +ftx_message_type_t ftx_message_get_type(const ftx_message_t* msg) +{ + // Extract i3 (bits 74..76) + uint8_t i3 = (msg->payload[9] >> 3) & 0x07u; + + switch (i3) + { + case 0: { + // Extract n3 (bits 71..73) + uint8_t n3 = ((msg->payload[8] << 2) & 0x04u) | ((msg->payload[9] >> 6) & 0x03u); + + switch (n3) + { + case 0: + return FTX_MESSAGE_TYPE_FREE_TEXT; + case 1: + return FTX_MESSAGE_TYPE_DXPEDITION; + case 2: + return FTX_MESSAGE_TYPE_EU_VHF; + case 3: + case 4: + return FTX_MESSAGE_TYPE_ARRL_FD; + case 5: + return FTX_MESSAGE_TYPE_TELEMETRY; + default: + return FTX_MESSAGE_TYPE_UNKNOWN; + } + break; + } + case 1: + case 2: + return FTX_MESSAGE_TYPE_STANDARD; + break; + case 3: + return FTX_MESSAGE_TYPE_ARRL_RTTY; + break; + case 4: + return FTX_MESSAGE_TYPE_NONSTD_CALL; + break; + case 5: + return FTX_MESSAGE_TYPE_WWROF; + default: + return FTX_MESSAGE_TYPE_UNKNOWN; + } +} + +ftx_message_rc_t ftx_message_encode(ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if, const char* message_text) +{ + char call_to[12]; + char call_de[12]; + char extra[20]; + + const char* parse_position = message_text; + const bool is_cq = starts_with(message_text, "CQ "); + if (is_cq) { + parse_position += 3; + memset(call_to, 0, sizeof(call_to)); + + // copy the next token temporarily (for the debug message) + copy_token(call_de, 12, parse_position); + LOG(LOG_DEBUG, "next token after CQ: '%s' in '%s'\n", call_de, message_text); + + // see if the word after CQ matches the a[bcd] or nnn pattern, and append to call_to + int cq_modifier_v = parse_cq_modifier(message_text); + if (cq_modifier_v >= 0) { + // treat "CQ nnn" or "CQ a[bcd]" as a single token: + // copy the CQ and then the next token to call_to + memcpy(call_to, "CQ \0", 4); + parse_position = copy_token(call_to + 3, sizeof(call_to) - 3, parse_position); + LOG(LOG_DEBUG, "CQ modifier encoding %d; parse_pos after CQ: %s in %s\n", cq_modifier_v, parse_position, message_text); + } else { + memcpy(call_to, "CQ\0", 3); + // the next token should be call_de, which we will get below + } + } else { + // else it's not a CQ: expect first token to be the "to" callsign + parse_position = copy_token(call_to, sizeof(call_to), parse_position); + } + // now we are fairly sure the next word should be the "de" callsign + parse_position = copy_token(call_de, sizeof(call_de), parse_position); + // and the word after that may be a grid or signal report + parse_position = copy_token(extra, sizeof(extra), parse_position); + + LOG(LOG_DEBUG, "ftx_message_encode: parsed '%s' '%s' '%s'; remaining chars '%s'\n", call_to, call_de, extra, parse_position); + + if (call_to[sizeof(call_to) - 1] != '\0') + { + // token too long + return FTX_MESSAGE_RC_ERROR_CALLSIGN1; + } + if (call_de[sizeof(call_de) - 1] != '\0') + { + // token too long + return FTX_MESSAGE_RC_ERROR_CALLSIGN2; + } + if (extra[sizeof(extra) - 1] != '\0') + { + // token too long + return FTX_MESSAGE_RC_ERROR_GRID; + } + + ftx_message_rc_t rc; + if (!parse_position[0]) { + // up to 3 tokens with no leftovers + rc = ftx_message_encode_std(msg, hash_if, call_to, call_de, extra); + if (rc == FTX_MESSAGE_RC_OK) + return rc; + LOG(LOG_DEBUG, " ftx_message_encode_std failed: %d\n", rc); + rc = ftx_message_encode_nonstd(msg, hash_if, call_to, call_de, extra); + if (rc == FTX_MESSAGE_RC_OK) + return rc; + LOG(LOG_DEBUG, " ftx_message_encode_nonstd failed: %d\n", rc); + } + rc = ftx_message_encode_free(msg, message_text); + if (rc == FTX_MESSAGE_RC_OK) + return rc; + LOG(LOG_DEBUG, " ftx_message_encode_free failed: %d\n", rc); + + return rc; +} + +ftx_message_rc_t ftx_message_encode_std(ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if, const char* call_to, const char* call_de, const char* extra) +{ + uint8_t ipa, ipb; + + LOG(LOG_DEBUG, "ftx_message_encode_std '%s' '%s' '%s'\n", call_to, call_de, extra); + + int32_t n28a = pack28(call_to, hash_if, &ipa); + int32_t n28b = pack28(call_de, hash_if, &ipb); + LOG(LOG_DEBUG, " n29a = %d, n29b = %d\n", n28a, n28b); + + if (n28a < 0) + return FTX_MESSAGE_RC_ERROR_CALLSIGN1; + if (n28b < 0) + return FTX_MESSAGE_RC_ERROR_CALLSIGN2; + + uint8_t i3 = 1; // No suffix or /R + if (ends_with(call_to, "/P") || ends_with(call_de, "/P")) + { + i3 = 2; // Suffix /P for EU VHF contest + if (ends_with(call_to, "/R") || ends_with(call_de, "/R")) + { + return FTX_MESSAGE_RC_ERROR_SUFFIX; + } + } + + char* slash_de = strchr(call_de, '/'); + uint8_t icq = (uint8_t)equals(call_to, "CQ") || starts_with(call_to, "CQ "); + if (slash_de && (slash_de - call_de >= 2) && icq && !(equals(slash_de, "/P") || equals(slash_de, "/R"))) + { + return FTX_MESSAGE_RC_ERROR_CALLSIGN2; // nonstandard call: need a type 4 message + } + + uint16_t igrid4 = packgrid(extra); + LOG(LOG_DEBUG, "igrid4 = %d\n", igrid4); + + // Shift in ipa and ipb bits into n28a and n28b + uint32_t n29a = ((uint32_t)n28a << 1) | ipa; + uint32_t n29b = ((uint32_t)n28b << 1) | ipb; + + // TODO: check for suffixes + if (ends_with(call_to, "/R")) + n29a |= 1; // ipa = 1 + else if (ends_with(call_to, "/P")) + { + n29a |= 1; // ipa = 1 + i3 = 2; + } + + // Pack into (28 + 1) + (28 + 1) + (1 + 15) + 3 bits + msg->payload[0] = (uint8_t)(n29a >> 21); + msg->payload[1] = (uint8_t)(n29a >> 13); + msg->payload[2] = (uint8_t)(n29a >> 5); + msg->payload[3] = (uint8_t)(n29a << 3) | (uint8_t)(n29b >> 26); + msg->payload[4] = (uint8_t)(n29b >> 18); + msg->payload[5] = (uint8_t)(n29b >> 10); + msg->payload[6] = (uint8_t)(n29b >> 2); + msg->payload[7] = (uint8_t)(n29b << 6) | (uint8_t)(igrid4 >> 10); + msg->payload[8] = (uint8_t)(igrid4 >> 2); + msg->payload[9] = (uint8_t)(igrid4 << 6) | (uint8_t)(i3 << 3); + + return FTX_MESSAGE_RC_OK; +} + +ftx_message_rc_t ftx_message_encode_nonstd(ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if, const char* call_to, const char* call_de, const char* extra) +{ + uint8_t i3 = 4; + + LOG(LOG_DEBUG, "ftx_message_encode_nonstd '%s' '%s' '%s'\n", call_to, call_de, extra); + + uint8_t icq = (uint8_t)equals(call_to, "CQ") || starts_with(call_to, "CQ "); + int len_call_to = strlen(call_to); + int len_call_de = strlen(call_de); + + if ((icq == 0) && ((len_call_to < 3))) + return FTX_MESSAGE_RC_ERROR_CALLSIGN1; + if ((len_call_de < 3)) + return FTX_MESSAGE_RC_ERROR_CALLSIGN2; + + uint8_t iflip; + uint16_t n12; + uint64_t n58; + uint8_t nrpt; + + const char* call58; + + if (icq == 0) + { + // choose which of the callsigns to encode as plain-text (58 bits) or hash (12 bits) + iflip = 0; // call_de will be sent plain-text + if (call_de[0] == '<' && call_de[len_call_to - 1] == '>') + { + iflip = 1; + } + + const char* call12; + call12 = (iflip == 0) ? call_to : call_de; + call58 = (iflip == 0) ? call_de : call_to; + if (!save_callsign(hash_if, call12, NULL, &n12, NULL)) + { + return FTX_MESSAGE_RC_ERROR_CALLSIGN1; + } + } + else + { + iflip = 0; + n12 = 0; + call58 = call_de; + LOG(LOG_DEBUG, "CQ: 58-bit '%s'; omitting grid '%s'\n", call58, extra); + } + + if (!pack58(hash_if, call58, &n58)) + { + return FTX_MESSAGE_RC_ERROR_CALLSIGN2; + } + + if (icq != 0) + nrpt = 0; + else if (equals(extra, "RRR")) + nrpt = 1; + else if (equals(extra, "RR73")) + nrpt = 2; + else if (equals(extra, "73")) + nrpt = 3; + else + nrpt = 0; + + // Pack into 12 + 58 + 1 + 2 + 1 + 3 == 77 bits + // write(c77,1010) n12,n58,iflip,nrpt,icq,i3 + // format(b12.12,b58.58,b1,b2.2,b1,b3.3) + msg->payload[0] = (uint8_t)(n12 >> 4); + msg->payload[1] = (uint8_t)(n12 << 4) | (uint8_t)(n58 >> 54); + msg->payload[2] = (uint8_t)(n58 >> 46); + msg->payload[3] = (uint8_t)(n58 >> 38); + msg->payload[4] = (uint8_t)(n58 >> 30); + msg->payload[5] = (uint8_t)(n58 >> 22); + msg->payload[6] = (uint8_t)(n58 >> 14); + msg->payload[7] = (uint8_t)(n58 >> 6); + msg->payload[8] = (uint8_t)(n58 << 2) | (uint8_t)(iflip << 1) | (uint8_t)(nrpt >> 1); + msg->payload[9] = (uint8_t)(nrpt << 7) | (uint8_t)(icq << 6) | (uint8_t)(i3 << 3); + + return FTX_MESSAGE_RC_OK; +} + +ftx_message_rc_t ftx_message_encode_free(ftx_message_t* msg, const char* text) +{ + uint8_t str_len = strlen(text); + if (str_len > 13) + { + // Too long text + return FTX_MESSAGE_RC_ERROR_TYPE; + } + + uint8_t b71[9]; + memset(b71, 0, 9); + int8_t cid; + char c; + + for (int idx = 0; idx < 13; idx++) + { + if (idx < str_len) + { + c = text[idx]; + } + else + { + c = ' '; + } + cid = nchar(c, FT8_CHAR_TABLE_FULL); + if (cid == -1) + { + return FTX_MESSAGE_RC_ERROR_TYPE; + } + uint16_t rem = cid; + for (int i = 8; i >= 0; i--) + { + rem += b71[i] * 42; + b71[i] = rem & 0xff; + rem = rem >> 8; + } + } + ftx_message_rc_t ret = ftx_message_encode_telemetry(msg, b71); + msg->payload[9] = 0; // i3.n3 = 0.0; etc. + return ret; +} + +// TODO set byte 9? or must the caller do it? +ftx_message_rc_t ftx_message_encode_telemetry(ftx_message_t* msg, const uint8_t* telemetry) +{ + // Shift bits in telemetry left by 1 bit to right-align the data + uint8_t carry = 0; + for (int i = 8; i >= 0; --i) + { + msg->payload[i] = (telemetry[i] << 1) | (carry >> 7); + carry = telemetry[i] & 0x80; + } + return FTX_MESSAGE_RC_OK; +} + +ftx_message_rc_t ftx_message_decode(const ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if, char* message, ftx_message_offsets_t* offsets) +{ + ftx_message_rc_t rc; + + char buf[35]; // 13 + 13 + 6 (std/nonstd) / 14 (free text) / 19 (telemetry) + char* field1 = buf; + char* field2 = buf + 14; + char* field3 = buf + 14 + 14; + + message[0] = '\0'; + for (int i = 0; i < FTX_MAX_MESSAGE_FIELDS; ++i) + { + offsets->types[i] = FTX_FIELD_UNKNOWN; + offsets->offsets[i] = -1; + } + + ftx_message_type_t msg_type = ftx_message_get_type(msg); + switch (msg_type) + { + case FTX_MESSAGE_TYPE_STANDARD: + rc = ftx_message_decode_std(msg, hash_if, field1, field2, field3, offsets->types); + break; + case FTX_MESSAGE_TYPE_NONSTD_CALL: + rc = ftx_message_decode_nonstd(msg, hash_if, field1, field2, field3, offsets->types); + break; + case FTX_MESSAGE_TYPE_FREE_TEXT: + ftx_message_decode_free(msg, field1); + field2 = NULL; + field3 = NULL; + rc = FTX_MESSAGE_RC_OK; + break; + case FTX_MESSAGE_TYPE_TELEMETRY: + ftx_message_decode_telemetry_hex(msg, field1); + field2 = NULL; + field3 = NULL; + rc = FTX_MESSAGE_RC_OK; + break; + default: + // not handled yet + field1 = NULL; + rc = FTX_MESSAGE_RC_ERROR_TYPE; + break; + } + + if (field1 != NULL) + { + // TODO join fields via whitespace + const char* message_start = message; + message = append_string(message, field1); + offsets->offsets[0] = 0; + if (field2 != NULL) + { + message = append_string(message, " "); + offsets->offsets[1] = message - message_start; + message = append_string(message, field2); + if ((field3 != NULL) && (field3[0] != 0)) + { + message = append_string(message, " "); + offsets->offsets[2] = message - message_start; + message = append_string(message, field3); + } + } + } + + return rc; +} + +ftx_message_rc_t ftx_message_decode_std(const ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if, + char* call_to, char* call_de, char* extra, ftx_field_t field_types[FTX_MAX_MESSAGE_FIELDS]) +{ + uint32_t n29a, n29b; + uint16_t igrid4; + uint8_t ir; + + // Extract packed fields + n29a = (msg->payload[0] << 21); + n29a |= (msg->payload[1] << 13); + n29a |= (msg->payload[2] << 5); + n29a |= (msg->payload[3] >> 3); + n29b = ((msg->payload[3] & 0x07u) << 26); + n29b |= (msg->payload[4] << 18); + n29b |= (msg->payload[5] << 10); + n29b |= (msg->payload[6] << 2); + n29b |= (msg->payload[7] >> 6); + ir = ((msg->payload[7] & 0x20u) >> 5); + igrid4 = ((msg->payload[7] & 0x1Fu) << 10); + igrid4 |= (msg->payload[8] << 2); + igrid4 |= (msg->payload[9] >> 6); + + // Extract i3 (bits 74..76) + uint8_t i3 = (msg->payload[9] >> 3) & 0x07u; + LOG(LOG_DEBUG, "decode_std() n28a=%d ipa=%d n28b=%d ipb=%d ir=%d igrid4=%d i3=%d\n", n29a >> 1, n29a & 1u, n29b >> 1, n29b & 1u, ir, igrid4, i3); + + call_to[0] = call_de[0] = extra[0] = '\0'; + + // Unpack both callsigns + if (unpack28(n29a >> 1, n29a & 1u, i3, hash_if, call_to, &field_types[0]) < 0) + { + return FTX_MESSAGE_RC_ERROR_CALLSIGN1; + } + if (unpack28(n29b >> 1, n29b & 1u, i3, hash_if, call_de, &field_types[1]) < 0) + { + return FTX_MESSAGE_RC_ERROR_CALLSIGN2; + } + if (unpackgrid(igrid4, ir, extra, &field_types[2]) < 0) + { + return FTX_MESSAGE_RC_ERROR_GRID; + } + + LOG(LOG_INFO, "Decoded standard (type %d) message [%s] [%s] [%s]\n", i3, call_to, call_de, extra); + return FTX_MESSAGE_RC_OK; +} + +// non-standard messages, code originally by KD8CEC +ftx_message_rc_t ftx_message_decode_nonstd(const ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if, + char* call_to, char* call_de, char* extra, ftx_field_t field_types[FTX_MAX_MESSAGE_FIELDS]) +{ + uint16_t n12, iflip, nrpt, icq; + uint64_t n58; + n12 = (msg->payload[0] << 4); // 11 ~ 4 : 8 + n12 |= (msg->payload[1] >> 4); // 3 ~ 0 : 12 + + n58 = ((uint64_t)(msg->payload[1] & 0x0Fu) << 54); // 57 ~ 54 : 4 + n58 |= ((uint64_t)msg->payload[2] << 46); // 53 ~ 46 : 12 + n58 |= ((uint64_t)msg->payload[3] << 38); // 45 ~ 38 : 12 + n58 |= ((uint64_t)msg->payload[4] << 30); // 37 ~ 30 : 12 + n58 |= ((uint64_t)msg->payload[5] << 22); // 29 ~ 22 : 12 + n58 |= ((uint64_t)msg->payload[6] << 14); // 21 ~ 14 : 12 + n58 |= ((uint64_t)msg->payload[7] << 6); // 13 ~ 6 : 12 + n58 |= ((uint64_t)msg->payload[8] >> 2); // 5 ~ 0 : 765432 10 + + iflip = (msg->payload[8] >> 1) & 0x01u; // 76543210 + nrpt = ((msg->payload[8] & 0x01u) << 1); + nrpt |= (msg->payload[9] >> 7); // 76543210 + icq = ((msg->payload[9] >> 6) & 0x01u); + + // Extract i3 (bits 74..76) + uint8_t i3 = (msg->payload[9] >> 3) & 0x07u; + LOG(LOG_DEBUG, "decode_nonstd() n12=%04x n58=%08llx iflip=%d nrpt=%d icq=%d i3=%d\n", n12, n58, iflip, nrpt, icq, i3); + + // Decode one of the calls from 58 bit encoded string + char call_decoded[14]; + unpack58(n58, hash_if, call_decoded); + + // Decode the other call from hash lookup table + char call_3[14]; + lookup_callsign(hash_if, FTX_CALLSIGN_HASH_12_BITS, n12, call_3); + + // Possibly flip them around + char* call_1 = (iflip) ? call_decoded : call_3; + char* call_2 = (iflip) ? call_3 : call_decoded; + + if (icq == 0) + { + strcpy(call_to, call_1); + field_types[0] = FTX_FIELD_CALL; + if (nrpt == 1) + { + strcpy(extra, "RRR"); + field_types[2] = FTX_FIELD_TOKEN; + } + else if (nrpt == 2) + { + strcpy(extra, "RR73"); + field_types[2] = FTX_FIELD_TOKEN; + } + else if (nrpt == 3) + { + strcpy(extra, "73"); + field_types[2] = FTX_FIELD_TOKEN; + } + else + { + extra[0] = '\0'; + field_types[2] = FTX_FIELD_NONE; + } + } + else + { + strcpy(call_to, "CQ"); + extra[0] = '\0'; + field_types[0] = FTX_FIELD_TOKEN; + field_types[2] = FTX_FIELD_NONE; + } + strcpy(call_de, call_2); + field_types[1] = FTX_FIELD_CALL; + LOG(LOG_INFO, "Decoded non-standard (type %d) message [%s] [%s] [%s]\n", i3, call_to, call_de, extra); + return FTX_MESSAGE_RC_OK; +} + +void ftx_message_decode_free(const ftx_message_t* msg, char* text) +{ + uint8_t b71[9]; + + ftx_message_decode_telemetry(msg, b71); + + char c14[14]; + c14[13] = 0; + for (int idx = 12; idx >= 0; --idx) + { + // Divide the long integer in b71 by 42 + uint16_t rem = 0; + for (int i = 0; i < 9; ++i) + { + rem = (rem << 8) | b71[i]; + b71[i] = rem / 42; + rem = rem % 42; + } + c14[idx] = charn(rem, FT8_CHAR_TABLE_FULL); + } + + strcpy(text, trim(c14)); +} + +void ftx_message_decode_telemetry_hex(const ftx_message_t* msg, char* telemetry_hex) +{ + uint8_t b71[9]; + + ftx_message_decode_telemetry(msg, b71); + + // Convert b71 to hexadecimal string + for (int i = 0; i < 9; ++i) + { + uint8_t nibble1 = (b71[i] >> 4); + uint8_t nibble2 = (b71[i] & 0x0Fu); + char c1 = (nibble1 > 9) ? (nibble1 - 10 + 'A') : nibble1 + '0'; + char c2 = (nibble2 > 9) ? (nibble2 - 10 + 'A') : nibble2 + '0'; + telemetry_hex[i * 2] = c1; + telemetry_hex[i * 2 + 1] = c2; + } + + telemetry_hex[18] = '\0'; +} + +void ftx_message_decode_telemetry(const ftx_message_t* msg, uint8_t* telemetry) +{ + // Shift bits in payload right by 1 bit to right-align the data + uint8_t carry = 0; + for (int i = 0; i < 9; ++i) + { + telemetry[i] = (carry << 7) | (msg->payload[i] >> 1); + carry = (msg->payload[i] & 0x01u); + } +} + +#ifdef FTX_DEBUG_PRINT +#include + +void ftx_message_print(ftx_message_t* msg) +{ + printf("["); + for (int i = 0; i < FTX_PAYLOAD_LENGTH_BYTES; ++i) + { + if (i > 0) + printf(" "); + printf("%02x", msg->payload[i]); + } + printf("]"); +} +#endif + +/////////////////////////////////////////////////////////// Static functions ///////////////////////////////////////////////////////////////// + +static void add_brackets(char* result, const char* original, int length) +{ + result[0] = '<'; + memcpy(result + 1, original, length); + result[length + 1] = '>'; + result[length + 2] = '\0'; +} + +static bool save_callsign(const ftx_callsign_hash_interface_t* hash_if, const char* callsign, uint32_t* n22_out, uint16_t* n12_out, uint16_t* n10_out) +{ + uint64_t n58 = 0; + int i = 0; + while (callsign[i] != '\0' && i < 11) + { + int j = nchar(callsign[i], FT8_CHAR_TABLE_ALPHANUM_SPACE_SLASH); + if (j < 0) + return false; // hash error (wrong character set) + n58 = (38 * n58) + j; + i++; + } + // pretend to have trailing whitespace (with j=0, index of ' ') + while (i < 11) + { + n58 = (38 * n58); + i++; + } + + uint32_t n22 = ((47055833459ull * n58) >> (64 - 22)) & (0x3FFFFFul); + uint32_t n12 = n22 >> 10; + uint32_t n10 = n22 >> 12; + LOG(LOG_DEBUG, "save_callsign('%s') = [n22=%d, n12=%d, n10=%d]\n", callsign, n22, n12, n10); + + if (n22_out != NULL) + *n22_out = n22; + if (n12_out != NULL) + *n12_out = n12; + if (n10_out != NULL) + *n10_out = n10; + + if (hash_if != NULL) + hash_if->save_hash(callsign, n22); + + return true; +} + +static bool lookup_callsign(const ftx_callsign_hash_interface_t* hash_if, ftx_callsign_hash_type_t hash_type, uint32_t hash, char* callsign) +{ + char c11[12]; + + bool found; + if (hash_if != NULL) + found = hash_if->lookup_hash(hash_type, hash, c11); + else + found = false; + + if (!found) + { + strcpy(callsign, "<...>"); + } + else + { + add_brackets(callsign, c11, strlen(c11)); + } + LOG(LOG_DEBUG, "lookup_callsign(n%s=%d) = '%s'\n", (hash_type == FTX_CALLSIGN_HASH_22_BITS ? "22" : (hash_type == FTX_CALLSIGN_HASH_12_BITS ? "12" : "10")), hash, callsign); + return found; +} + +int32_t pack_basecall(const char* callsign, int length) +{ + if (length > 2) + { + // Attempt to pack a standard callsign, if fail, revert to hashed callsign + char c6[6] = { ' ', ' ', ' ', ' ', ' ', ' ' }; + + // Copy callsign to 6 character buffer + if (starts_with(callsign, "3DA0") && (length > 4) && (length <= 7)) + { + // Work-around for Swaziland prefix: 3DA0XYZ -> 3D0XYZ + memcpy(c6, "3D0", 3); + memcpy(c6 + 3, callsign + 4, length - 4); + } + else if (starts_with(callsign, "3X") && is_letter(callsign[2]) && length <= 7) + { + // Work-around for Guinea prefixes: 3XA0XYZ -> QA0XYZ + memcpy(c6, "Q", 1); + memcpy(c6 + 1, callsign + 2, length - 2); + } + else + { + // Check the position of callsign digit and make a right-aligned copy into c6 + if (is_digit(callsign[2]) && length <= 6) + { + // AB0XYZ + memcpy(c6, callsign, length); + } + else if (is_digit(callsign[1]) && length <= 5) + { + // A0XYZ -> " A0XYZ" + memcpy(c6 + 1, callsign, length); + } + } + + // Check for standard callsign + int i0 = nchar(c6[0], FT8_CHAR_TABLE_ALPHANUM_SPACE); + int i1 = nchar(c6[1], FT8_CHAR_TABLE_ALPHANUM); + int i2 = nchar(c6[2], FT8_CHAR_TABLE_NUMERIC); + int i3 = nchar(c6[3], FT8_CHAR_TABLE_LETTERS_SPACE); + int i4 = nchar(c6[4], FT8_CHAR_TABLE_LETTERS_SPACE); + int i5 = nchar(c6[5], FT8_CHAR_TABLE_LETTERS_SPACE); + if ((i0 >= 0) && (i1 >= 0) && (i2 >= 0) && (i3 >= 0) && (i4 >= 0) && (i5 >= 0)) + { + // This is a standard callsign + LOG(LOG_DEBUG, "Encoding basecall [%.6s]\n", c6); + int32_t n = i0; + n = n * 36 + i1; + n = n * 10 + i2; + n = n * 27 + i3; + n = n * 27 + i4; + n = n * 27 + i5; + + return n; // Standard callsign + } + } + return -1; +} + +// returns the numeric value if it matches CQ_nnn or CQ_a[bcd], otherwise -1 +static int parse_cq_modifier(const char* string) +{ + int nnum = 0, nlet = 0; + + // encode CQ_nnn or CQ_a[bcd] + int m = 0; + for (int i = 3; i < 8; ++i) { + if (!string[i] || is_space(string[i])) + break; + else if (is_digit(string[i])) + ++nnum; + else if (is_letter(string[i])) { + ++nlet; + m = 27 * m + (string[i] - 'A' + 1); + } else { + // non-digit non-letter characters (such as '/') are not allowed + return -1; + } + } + LOG(LOG_DEBUG, "CQ_nnn/CQ_a[bcd] '%s' %d/%d\n", string, nnum, nlet); + if (nnum == 3 && nlet == 0) { + LOG(LOG_DEBUG, "CQ_nnn detected: %d\n", atoi(string + 3)); + return atoi(string + 3); + } + else if (nnum == 0 && nlet <= 4) { + LOG(LOG_DEBUG, "CQ_a[bcd] detected: m %d\n", m); + return 1000 + m; + } + return -1; // not a special CQ +} + +static int32_t pack28(const char* callsign, const ftx_callsign_hash_interface_t* hash_if, uint8_t* ip) +{ + LOG(LOG_DEBUG, "pack28() callsign [%s]\n", callsign); + *ip = 0; + + // Check for special tokens first + if (equals(callsign, "DE")) + return 0; + if (equals(callsign, "QRZ")) + return 1; + if (equals(callsign, "CQ")) + return 2; + + int length = strlen(callsign); + LOG(LOG_DEBUG, "Callsign length = %d\n", length); + + if (starts_with(callsign, "CQ ") && length < 8) + { + int v = parse_cq_modifier(callsign); + if (v < 0) { + LOG(LOG_WARN, "CQ_nnn/CQ_a[bcd] '%s' not allowed\n", callsign); + return -1; + } + return 3 + v; + } + + // Detect /R and /P suffix for basecall check + int length_base = length; + if (ends_with(callsign, "/P") || ends_with(callsign, "/R")) + { + LOG(LOG_DEBUG, "Suffix /P or /R detected\n"); + *ip = 1; + length_base = length - 2; + } + + int32_t n28 = pack_basecall(callsign, length_base); + if (n28 >= 0) + { + // Callsign can be encoded as a standard basecall with optional /P or /R suffix + if (!save_callsign(hash_if, callsign, NULL, NULL, NULL)) + return -1; // Error (some problem with callsign contents) + return NTOKENS + MAX22 + (uint32_t)n28; // Standard callsign + } + + if ((length >= 3) && (length <= 11)) + { + // Treat this as a nonstandard callsign: compute its 22-bit hash + LOG(LOG_DEBUG, "Encoding '%s' as non-standard callsign\n", callsign); + uint32_t n22; + if (!save_callsign(hash_if, callsign, &n22, NULL, NULL)) { + LOG(LOG_DEBUG, " Failed to save '%s'\n", callsign); + return -1; // Error (some problem with callsign contents) + } + *ip = 0; + return NTOKENS + n22; // 22-bit hashed callsign + } + + return -1; // Error +} + +static int unpack28(uint32_t n28, uint8_t ip, uint8_t i3, const ftx_callsign_hash_interface_t* hash_if, char* result, ftx_field_t* field_type) +{ + LOG(LOG_DEBUG, "unpack28() n28=%d i3=%d\n", n28, i3); + // Check for special tokens DE, QRZ, CQ, CQ nnn, CQ a[bcd] + if (n28 < NTOKENS) + { + if (n28 <= 2u) + { + if (n28 == 0) + { + strcpy(result, "DE"); + *field_type = FTX_FIELD_TOKEN; + } + else if (n28 == 1) + { + strcpy(result, "QRZ"); + *field_type = FTX_FIELD_TOKEN; + } + else + { /* if (n28 == 2) */ + strcpy(result, "CQ"); + *field_type = FTX_FIELD_TOKEN; + } + return 0; // Success + } + if (n28 <= 1002u) + { + // CQ nnn with 3 digits + strcpy(result, "CQ "); + int_to_dd(result + 3, n28 - 3, 3, false); + *field_type = FTX_FIELD_TOKEN_WITH_ARG; + return 0; // Success + } + if (n28 <= 532443ul) + { + // CQ ABCD with 4 alphanumeric symbols + uint32_t n = n28 - 1003u; + char aaaa[5]; + + aaaa[4] = '\0'; + for (int i = 3; /* no condition */; --i) + { + aaaa[i] = charn(n % 27u, FT8_CHAR_TABLE_LETTERS_SPACE); + if (i == 0) + break; + n /= 27u; + } + + strcpy(result, "CQ "); + strcat(result, trim_front(aaaa, ' ')); + *field_type = FTX_FIELD_TOKEN_WITH_ARG; + return 0; // Success + } + // unspecified + return -1; + } + + n28 = n28 - NTOKENS; + if (n28 < MAX22) + { + // This is a 22-bit hash of a result + lookup_callsign(hash_if, FTX_CALLSIGN_HASH_22_BITS, n28, result); + *field_type = FTX_FIELD_CALL; + return 0; // Success + } + + // Standard callsign + uint32_t n = n28 - MAX22; + + char callsign[7]; + callsign[6] = '\0'; + callsign[5] = charn(n % 27, FT8_CHAR_TABLE_LETTERS_SPACE); + n /= 27; + callsign[4] = charn(n % 27, FT8_CHAR_TABLE_LETTERS_SPACE); + n /= 27; + callsign[3] = charn(n % 27, FT8_CHAR_TABLE_LETTERS_SPACE); + n /= 27; + callsign[2] = charn(n % 10, FT8_CHAR_TABLE_NUMERIC); + n /= 10; + callsign[1] = charn(n % 36, FT8_CHAR_TABLE_ALPHANUM); + n /= 36; + callsign[0] = charn(n % 37, FT8_CHAR_TABLE_ALPHANUM_SPACE); + + // Copy callsign to 6 character buffer + if (starts_with(callsign, "3D0") && !is_space(callsign[3])) + { + // Work-around for Swaziland prefix: 3D0XYZ -> 3DA0XYZ + memcpy(result, "3DA0", 4); + trim_copy(result + 4, callsign + 3); + } + else if ((callsign[0] == 'Q') && is_letter(callsign[1])) + { + // Work-around for Guinea prefixes: QA0XYZ -> 3XA0XYZ + memcpy(result, "3X", 2); + trim_copy(result + 2, callsign + 1); + } + else + { + // Skip trailing and leading whitespace in case of a short callsign + trim_copy(result, callsign); + } + + int length = strlen(result); + if (length < 3) + return -1; // Callsign too short + + // Check if we should append /R or /P suffix + if (ip != 0) + { + if (i3 == 1) + strcat(result, "/R"); + else if (i3 == 2) + strcat(result, "/P"); + else + return -2; + } + + // Save the result to hash table + save_callsign(hash_if, result, NULL, NULL, NULL); + + *field_type = FTX_FIELD_CALL; + return 0; // Success +} + +static bool pack58(const ftx_callsign_hash_interface_t* hash_if, const char* callsign, uint64_t* n58) +{ + // Decode one of the calls from 58 bit encoded string + const char* src = callsign; + if (*src == '<') + src++; + int length = 0; + uint64_t result = 0; + char c11[12]; + while (*src != '\0' && *src != '<' && (length < 11)) + { + c11[length] = *src; + int j = nchar(*src, FT8_CHAR_TABLE_ALPHANUM_SPACE_SLASH); + if (j < 0) + return false; + result = (result * 38) + j; + src++; + length++; + } + c11[length] = '\0'; + + if (!save_callsign(hash_if, c11, NULL, NULL, NULL)) + return false; + + *n58 = result; + LOG(LOG_DEBUG, "pack58('%s')=%016llx\n", callsign, *n58); + return true; +} + +static bool unpack58(uint64_t n58, const ftx_callsign_hash_interface_t* hash_if, char* callsign) +{ + // Decode one of the calls from 58 bit encoded string + char c11[12]; + c11[11] = '\0'; + uint64_t n58_backup = n58; + for (int i = 10; /* no condition */; --i) + { + c11[i] = charn(n58 % 38, FT8_CHAR_TABLE_ALPHANUM_SPACE_SLASH); + if (i == 0) + break; + n58 /= 38; + } + // The decoded string will be right-aligned, so trim all whitespace (also from back just in case) + trim_copy(callsign, c11); + + LOG(LOG_DEBUG, "unpack58(%016llx)=%s\n", n58_backup, callsign); + + // Save the decoded call in a hash table for later + if (strlen(callsign) >= 3) + return save_callsign(hash_if, callsign, NULL, NULL, NULL); + + return false; +} + +static uint16_t packgrid(const char* grid4) +{ + if (grid4 == 0 || grid4[0] == '\0') + { + // Two callsigns only, no report/grid + return MAXGRID4 + 1; + } + + // Take care of special cases + if (equals(grid4, "RRR")) + return MAXGRID4 + 2; + if (equals(grid4, "RR73")) + return MAXGRID4 + 3; + if (equals(grid4, "73")) + return MAXGRID4 + 4; + + // TODO: Check for "R " prefix before a 4 letter grid + + // Check for standard 4 letter grid + if (in_range(grid4[0], 'A', 'R') && in_range(grid4[1], 'A', 'R') && is_digit(grid4[2]) && is_digit(grid4[3])) + { + uint16_t igrid4 = (grid4[0] - 'A'); + igrid4 = igrid4 * 18 + (grid4[1] - 'A'); + igrid4 = igrid4 * 10 + (grid4[2] - '0'); + igrid4 = igrid4 * 10 + (grid4[3] - '0'); + return igrid4; + } + + // Parse report: +dd / -dd / R+dd / R-dd + // TODO: check the range of dd + if (grid4[0] == 'R') + { + int dd = dd_to_int(grid4 + 1, 3); + uint16_t irpt = 35 + dd; + return (MAXGRID4 + irpt) | 0x8000; // ir = 1 + } + else + { + int dd = dd_to_int(grid4, 3); + uint16_t irpt = 35 + dd; + return (MAXGRID4 + irpt); // ir = 0 + } + + return MAXGRID4 + 1; +} + +static int unpackgrid(uint16_t igrid4, uint8_t ir, char* extra, ftx_field_t* extra_field_type) +{ + char* dst = extra; + + if (igrid4 <= MAXGRID4) + { + // Extract 4 symbol grid locator + if (ir > 0) + { + // In case of ir=1 add an "R " before grid + dst = stpcpy(dst, "R "); + } + + uint16_t n = igrid4; + dst[4] = '\0'; + dst[3] = '0' + (n % 10); // 0..9 + n /= 10; + dst[2] = '0' + (n % 10); // 0..9 + n /= 10; + dst[1] = 'A' + (n % 18); // A..R + n /= 18; + dst[0] = 'A' + (n % 18); // A..R + // if (ir > 0 && strncmp(call_to, "CQ", 2) == 0) return -1; + *extra_field_type = FTX_FIELD_GRID; + } + else + { + // Extract report + int irpt = igrid4 - MAXGRID4; + + // Check special cases first (irpt > 0 always) + switch (irpt) + { + case 1: + dst[0] = '\0'; + *extra_field_type = FTX_FIELD_NONE; + break; + case 2: + strcpy(dst, "RRR"); + *extra_field_type = FTX_FIELD_TOKEN; + break; + case 3: + strcpy(dst, "RR73"); + *extra_field_type = FTX_FIELD_TOKEN; + break; + case 4: + strcpy(dst, "73"); + *extra_field_type = FTX_FIELD_TOKEN; + break; + default: + // Extract signal report as a two digit number with a + or - sign + if (ir > 0) + *dst++ = 'R'; // Add "R" before report + int_to_dd(dst, irpt - 35, 2, true); + *extra_field_type = FTX_FIELD_RST; + break; + } + // if (irpt >= 2 && strncmp(call_to, "CQ", 2) == 0) return -1; + } + + return 0; +} diff --git a/external/ft8_lib/ft8/message.h b/external/ft8_lib/ft8/message.h new file mode 100644 index 0000000..a88a9c8 --- /dev/null +++ b/external/ft8_lib/ft8/message.h @@ -0,0 +1,160 @@ +#ifndef _INCLUDE_MESSAGE_H_ +#define _INCLUDE_MESSAGE_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define FTX_PAYLOAD_LENGTH_BYTES 10 ///< number of bytes to hold 77 bits of FTx payload data +#define FTX_MAX_MESSAGE_LENGTH 35 ///< max message length = callsign[13] + space + callsign[13] + space + report[6] + terminator +#define FTX_MAX_MESSAGE_FIELDS 3 // may need to get longer for multi-part messages (DXpedition, contest etc.) + +/// Structure that holds the decoded message +typedef struct +{ + uint8_t payload[FTX_PAYLOAD_LENGTH_BYTES]; + uint16_t hash; ///< Hash value to be used in hash table and quick checking for duplicates +} ftx_message_t; + +// ---------------------------------------------------------------------------------- +// i3.n3 Example message Bits Total Purpose +// ---------------------------------------------------------------------------------- +// 0.0 FREE TEXT MSG 71 71 Free text +// 0.1 K1ABC RR73; W9XYZ -12 28 28 10 5 71 DXpedition Mode +// 0.2 PA3XYZ/P R 590003 IO91NP 28 1 1 3 12 25 70 EU VHF contest +// 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 +// 0.5 123456789ABCDEF012 71 71 Telemetry (18 hex) +// 0.6 K1ABC RR73; CQ W9XYZ EN37 28 28 15 71 Contesting +// 0.7 ... tbd +// 1 WA9XYZ/R KA1ABC/R R FN42 28 1 28 1 1 15 74 Standard msg +// 2 PA3XYZ/P GM4ABC/P R JO22 28 1 28 1 1 15 74 EU VHF contest +// 3 TU; W9XYZ K1ABC R 579 MA 1 28 28 1 3 13 74 ARRL RTTY Roundup +// 4 PJ4/KA1ABC RR73 12 58 1 2 1 74 Nonstandard calls +// 5 TU; W9XYZ K1ABC R-07 FN 1 28 28 1 7 9 74 WWROF contest ? + +typedef enum +{ + FTX_MESSAGE_TYPE_FREE_TEXT, // 0.0 FREE TEXT MSG 71 71 Free text + FTX_MESSAGE_TYPE_DXPEDITION, // 0.1 K1ABC RR73; W9XYZ -12 28 28 10 5 71 DXpedition Mode + FTX_MESSAGE_TYPE_EU_VHF, // 0.2 PA3XYZ/P R 590003 IO91NP 28 1 1 3 12 25 70 EU VHF contest + FTX_MESSAGE_TYPE_ARRL_FD, // 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 + FTX_MESSAGE_TYPE_TELEMETRY, // 0.5 0123456789abcdef01 71 71 Telemetry (18 hex) + FTX_MESSAGE_TYPE_CONTESTING, // 0.6 K1ABC RR73; CQ W9XYZ EN37 28 28 15 71 Contesting + FTX_MESSAGE_TYPE_STANDARD, // 1 WA9XYZ/R KA1ABC/R R FN42 28 1 28 1 1 15 74 Standard msg + // 2 PA3XYZ/P GM4ABC/P R JO22 28 1 28 1 1 15 74 EU VHF contest + FTX_MESSAGE_TYPE_ARRL_RTTY, // 3 TU; W9XYZ K1ABC R 579 MA 1 28 28 1 3 13 74 ARRL RTTY Roundup + FTX_MESSAGE_TYPE_NONSTD_CALL, // 4 PJ4/KA1ABC RR73 12 58 1 2 1 74 Nonstandard calls + FTX_MESSAGE_TYPE_WWROF, // 5 TU; W9XYZ K1ABC R-07 FN 1 28 28 1 7 9 74 WWROF contest ? + FTX_MESSAGE_TYPE_UNKNOWN // Unknown or invalid type +} ftx_message_type_t; + +typedef enum +{ + FTX_CALLSIGN_HASH_22_BITS, + FTX_CALLSIGN_HASH_12_BITS, + FTX_CALLSIGN_HASH_10_BITS +} ftx_callsign_hash_type_t; + +typedef struct +{ + /// Called when a callsign is looked up by its 22/12/10 bit hash code + bool (*lookup_hash)(ftx_callsign_hash_type_t hash_type, uint32_t hash, char* callsign); + /// Called when a callsign should hashed and stored (by its 22, 12 and 10 bit hash codes) + void (*save_hash)(const char* callsign, uint32_t n22); +} ftx_callsign_hash_interface_t; + +typedef enum +{ + FTX_MESSAGE_RC_OK, + FTX_MESSAGE_RC_ERROR_CALLSIGN1, + FTX_MESSAGE_RC_ERROR_CALLSIGN2, + FTX_MESSAGE_RC_ERROR_SUFFIX, + FTX_MESSAGE_RC_ERROR_GRID, + FTX_MESSAGE_RC_ERROR_TYPE +} ftx_message_rc_t; + +typedef enum +{ + FTX_FIELD_UNKNOWN, + FTX_FIELD_NONE, + FTX_FIELD_TOKEN, // RRR, RR73, 73, DE, QRZ, CQ, ... + FTX_FIELD_TOKEN_WITH_ARG, // CQ nnn, CQ abcd + FTX_FIELD_CALL, + FTX_FIELD_GRID, + FTX_FIELD_RST +} ftx_field_t; + +typedef struct +{ + // parallel arrays: + // e.g. "CQ POTA W9XYZ AB12" generates + // types { FTX_FIELD_TOKEN_WITH_ARG, FTX_FIELD_CALL, FTX_FIELD_CALL_GRID" } + // offsets { 0, 8, 14 } + // Both arrays end where offsets[i] < 0 + ftx_field_t types[FTX_MAX_MESSAGE_FIELDS]; + int16_t offsets[FTX_MAX_MESSAGE_FIELDS]; +} ftx_message_offsets_t; + +// Callsign types and sizes: +// * Std. call (basecall) - 1-2 letter/digit prefix (at least one letter), 1 digit area code, 1-3 letter suffix, +// total 3-6 chars (exception: 7 character calls with prefixes 3DA0- and 3XA..3XZ-) +// * Ext. std. call - basecall followed by /R or /P +// * Nonstd. call - all the rest, limited to 3-11 characters either alphanumeric or stroke (/) +// In case a call is looked up from its hash value, the call is enclosed in angular brackets (). + +void ftx_message_init(ftx_message_t* msg); + +uint8_t ftx_message_get_i3(const ftx_message_t* msg); +uint8_t ftx_message_get_n3(const ftx_message_t* msg); +ftx_message_type_t ftx_message_get_type(const ftx_message_t* msg); + +// bool ftx_message_check_recipient(const ftx_message_t* msg, const char* callsign); + +/// Pack (encode) a callsign in the standard way, and return the numeric representation. +/// Returns -1 if \a callsign cannot be encoded in the standard way. +/// This function can be used to decide whether to call ftx_message_encode_std() or ftx_message_decode_nonstd(). +/// Alternatively, ftx_message_encode_std() itself fails when one of the callsigns cannot be packed this way. +int32_t pack_basecall(const char* callsign, int length); + +/// Pack (encode) a text message, guessing which message type to use and falling back on failure: +/// if there are 3 or fewer tokens, try ftx_message_encode_std first, +/// then ftx_message_encode_nonstd if that fails because of a non-standard callsign; +/// otherwise fall back to ftx_message_encode_free. +/// If you already know which type to use, you can call one of those functions directly. +ftx_message_rc_t ftx_message_encode(ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if, const char* message_text); + +/// Pack Type 1 (Standard 77-bit message) or Type 2 (ditto, with a "/P" call) message +/// Rules of callsign validity: +/// - call_to can be 'DE', 'CQ', 'QRZ', 'CQ_nnn' (three digits), or 'CQ_abcd' (four letters) +/// - nonstandard calls within <> brackets are allowed, if they don't contain '/' +ftx_message_rc_t ftx_message_encode_std(ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if, const char* call_to, const char* call_de, const char* extra); + +/// Pack Type 4 (One nonstandard call and one hashed call) message +ftx_message_rc_t ftx_message_encode_nonstd(ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if, const char* call_to, const char* call_de, const char* extra); + +/// Pack plain text, up to 13 characters +ftx_message_rc_t ftx_message_encode_free(ftx_message_t* msg, const char* text); +ftx_message_rc_t ftx_message_encode_telemetry(ftx_message_t* msg, const uint8_t* telemetry); + +ftx_message_rc_t ftx_message_decode(const ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if, char* message, ftx_message_offsets_t* offsets); +ftx_message_rc_t ftx_message_decode_std(const ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if, char* call_to, char* call_de, char* extra, ftx_field_t field_types[FTX_MAX_MESSAGE_FIELDS]); +ftx_message_rc_t ftx_message_decode_nonstd(const ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if, char* call_to, char* call_de, char* extra, ftx_field_t field_types[FTX_MAX_MESSAGE_FIELDS]); +void ftx_message_decode_free(const ftx_message_t* msg, char* text); +void ftx_message_decode_telemetry_hex(const ftx_message_t* msg, char* telemetry_hex); +void ftx_message_decode_telemetry(const ftx_message_t* msg, uint8_t* telemetry); + +#ifdef FTX_DEBUG_PRINT +void ftx_message_print(ftx_message_t* msg); +#endif + +#ifdef __cplusplus +} +#endif + +#endif // _INCLUDE_MESSAGE_H_ diff --git a/external/ft8_lib/ft8/text.c b/external/ft8_lib/ft8/text.c new file mode 100644 index 0000000..e8b6101 --- /dev/null +++ b/external/ft8_lib/ft8/text.c @@ -0,0 +1,303 @@ +#include "text.h" + +#include + +const char* trim_front(const char* str, char to_trim) +{ + // Skip leading to_trim characters + while (*str == to_trim) + { + str++; + } + return str; +} + +void trim_back(char* str, char to_trim) +{ + // Skip trailing to_trim characters by replacing them with '\0' characters + int idx = strlen(str) - 1; + while (idx >= 0 && str[idx] == to_trim) + { + str[idx--] = '\0'; + } +} + +char* trim(char* str) +{ + str = (char*)trim_front(str, ' '); + trim_back(str, ' '); + // return a pointer to the first non-whitespace character + return str; +} + +char* trim_brackets(char* str) +{ + str = (char*)trim_front(str, '<'); + trim_back(str, '>'); + // return a pointer to the first non-whitespace character + return str; +} + +void trim_copy(char* trimmed, const char* str) +{ + str = (char*)trim_front(str, ' '); + int len = strlen(str) - 1; + while (len >= 0 && str[len] == ' ') + { + len--; + } + strncpy(trimmed, str, len + 1); + trimmed[len + 1] = '\0'; +} + +char to_upper(char c) +{ + return (c >= 'a' && c <= 'z') ? (c - 'a' + 'A') : c; +} + +bool is_digit(char c) +{ + return (c >= '0') && (c <= '9'); +} + +bool is_letter(char c) +{ + return ((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')); +} + +bool is_space(char c) +{ + return (c == ' '); +} + +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 ends_with(const char* string, const char* suffix) +{ + int pos = strlen(string) - strlen(suffix); + if (pos >= 0) + { + return 0 == memcmp(string + pos, suffix, strlen(suffix)); + } + return false; +} + +bool equals(const char* string1, const char* string2) +{ + return 0 == strcmp(string1, string2); +} + +// Text message formatting: +// - replaces lowercase letters with uppercase +// - merges consecutive spaces into single space +void fmtmsg(char* msg_out, const char* msg_in) +{ + char c; + char last_out = 0; + while ((c = *msg_in)) + { + if (c != ' ' || last_out != ' ') + { + last_out = to_upper(c); + *msg_out = last_out; + ++msg_out; + } + ++msg_in; + } + *msg_out = 0; // Add zero termination +} + +// Returns pointer to the null terminator within the given string (destination) +char* append_string(char* string, const char* token) +{ + while (*token != '\0') + { + *string = *token; + string++; + token++; + } + *string = '\0'; + return string; +} + +const char* copy_token(char* token, int length, const char* string) +{ + // Copy characters until a whitespace character or the end of string + while (*string != ' ' && *string != '\0') + { + if (length > 1) + { + *token = *string; + token++; + length--; + } + string++; + } + // Fill up the rest of token with \0 terminators + while (length > 0) + { + *token = '\0'; + token++; + length--; + } + // Skip whitespace characters + while (*string == ' ') + { + string++; + } + return string; +} + +// Parse a 2 digit integer from string +int dd_to_int(const char* str, int length) +{ + int result = 0; + bool negative; + int i; + if (str[0] == '-') + { + negative = true; + i = 1; // Consume the - sign + } + else + { + negative = false; + i = (str[0] == '+') ? 1 : 0; // Consume a + sign if found + } + + while (i < length) + { + if (str[i] == 0) + break; + if (!is_digit(str[i])) + break; + result *= 10; + result += (str[i] - '0'); + ++i; + } + + return negative ? -result : result; +} + +// Convert a 2 digit integer to string +void int_to_dd(char* str, int value, int width, bool full_sign) +{ + if (value < 0) + { + *str = '-'; + ++str; + value = -value; + } + else if (full_sign) + { + *str = '+'; + ++str; + } + + int divisor = 1; + for (int i = 0; i < width - 1; ++i) + { + divisor *= 10; + } + + while (divisor >= 1) + { + int digit = value / divisor; + + *str = '0' + digit; + ++str; + + value -= digit * divisor; + divisor /= 10; + } + *str = 0; // Add zero terminator +} + +char charn(int c, ft8_char_table_e table) +{ + if ((table != FT8_CHAR_TABLE_ALPHANUM) && (table != FT8_CHAR_TABLE_NUMERIC)) + { + if (c == 0) + return ' '; + c -= 1; + } + if (table != FT8_CHAR_TABLE_LETTERS_SPACE) + { + if (c < 10) + return '0' + c; + c -= 10; + } + if (table != FT8_CHAR_TABLE_NUMERIC) + { + if (c < 26) + return 'A' + c; + c -= 26; + } + + if (table == FT8_CHAR_TABLE_FULL) + { + if (c < 5) + return "+-./?"[c]; + } + else if (table == FT8_CHAR_TABLE_ALPHANUM_SPACE_SLASH) + { + if (c == 0) + return '/'; + } + + return '_'; // unknown character, should never get here +} + +// Convert character to its index (charn in reverse) according to a table +int nchar(char c, ft8_char_table_e table) +{ + int n = 0; + if ((table != FT8_CHAR_TABLE_ALPHANUM) && (table != FT8_CHAR_TABLE_NUMERIC)) + { + if (c == ' ') + return n + 0; + n += 1; + } + if (table != FT8_CHAR_TABLE_LETTERS_SPACE) + { + if (c >= '0' && c <= '9') + return n + (c - '0'); + n += 10; + } + if (table != FT8_CHAR_TABLE_NUMERIC) + { + if (c >= 'A' && c <= 'Z') + return n + (c - 'A'); + n += 26; + } + + if (table == FT8_CHAR_TABLE_FULL) + { + if (c == '+') + return n + 0; + if (c == '-') + return n + 1; + if (c == '.') + return n + 2; + if (c == '/') + return n + 3; + if (c == '?') + return n + 4; + } + else if (table == FT8_CHAR_TABLE_ALPHANUM_SPACE_SLASH) + { + if (c == '/') + return n + 0; + } + + // Character not found + return -1; +} diff --git a/external/ft8_lib/ft8/text.h b/external/ft8_lib/ft8/text.h new file mode 100644 index 0000000..d395d69 --- /dev/null +++ b/external/ft8_lib/ft8/text.h @@ -0,0 +1,82 @@ +#ifndef _INCLUDE_TEXT_H_ +#define _INCLUDE_TEXT_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +// Utility functions for characters and strings + +const char* trim_front(const char* str, char to_trim); +void trim_back(char* str, char to_trim); + +/// In-place whitespace trim from front and back: +/// 1) trims the back by changing whitespaces to '\0' +/// 2) trims the front by skipping whitespaces +/// @return trimmed string (pointer to first non-whitespace character) +char* trim(char* str); + +/// Trim whitespace from start and end of string +void trim_copy(char* trimmed, const char* str); + +/// In-place trim of <> characters from front and back: +/// 1) trims the back by changing > to '\0' +/// 2) trims the front by skipping < +/// @return trimmed string (pointer to first non-whitespace character) +char* trim_brackets(char* str); + +char to_upper(char c); +bool is_digit(char c); +bool is_letter(char c); +bool is_space(char c); +bool in_range(char c, char min, char max); +bool starts_with(const char* string, const char* prefix); +bool ends_with(const char* string, const char* suffix); +bool equals(const char* string1, const char* string2); + +// Text message formatting: +// - replaces lowercase letters with uppercase +// - merges consecutive spaces into single space +void fmtmsg(char* msg_out, const char* msg_in); + +/// Extract and copy a space-delimited token from a string. +/// When the last token has been extracted, the return value points to the terminating zero character. +/// @param[out] token Buffer to receive the extracted token +/// @param[in] length Length of the token buffer (number of characters) +/// @param[in] string Pointer to the string +/// @return Pointer to the next token (can be passed to copy_token to extract the next token) +const char* copy_token(char* token, int length, const char* string); + +char* append_string(char* string, const char* token); + +// Parse a 2 digit integer from string +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); + +typedef enum +{ + FT8_CHAR_TABLE_FULL, // table[42] " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+-./?" + FT8_CHAR_TABLE_ALPHANUM_SPACE_SLASH, // table[38] " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ/" + FT8_CHAR_TABLE_ALPHANUM_SPACE, // table[37] " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + FT8_CHAR_TABLE_LETTERS_SPACE, // table[27] " ABCDEFGHIJKLMNOPQRSTUVWXYZ" + FT8_CHAR_TABLE_ALPHANUM, // table[36] "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + FT8_CHAR_TABLE_NUMERIC, // table[10] "0123456789" +} ft8_char_table_e; + +/// Convert integer index to ASCII character according to one of character tables +char charn(int c, ft8_char_table_e table); + +/// Look up the index of an ASCII character in one of character tables +int nchar(char c, ft8_char_table_e table); + +#ifdef __cplusplus +} +#endif + +#endif // _INCLUDE_TEXT_H_ diff --git a/external/ft8_lib/test/test.c b/external/ft8_lib/test/test.c new file mode 100644 index 0000000..b94cda0 --- /dev/null +++ b/external/ft8_lib/test/test.c @@ -0,0 +1,286 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include + +#include "ft8/text.h" +#include "ft8/encode.h" +#include "ft8/constants.h" + +#include "fft/kiss_fftr.h" +#include "common/common.h" +#include "ft8/message.h" + +#define LOG_LEVEL LOG_INFO +#include "ft8/debug.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; +// } +// } +// } + +/* +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 = ftx_compute_crc(test_in2, 76); // Calculate CRC of 76 bits only + LOG(LOG_INFO, "CRC: %04x\n", crc1); // should be 0x0708 +} +*/ + +#define CHECK(condition) \ + if (!(condition)) \ + { \ + printf("FAIL! Condition \'" #condition "' failed\n\n"); \ + return; \ + } + +#define CHECK_EQ_VAL(this, that) \ + if ((this) != (that)) \ + { \ + printf("FAIL! Expected " #this " (%d) == " #that " (%d)\n\n", \ + (this), (that)); \ + return; \ + } + +#define TEST_END printf("Test OK\n\n") + +#define CALLSIGN_HASHTABLE_SIZE 256 + +struct +{ + char callsign[12]; + uint32_t hash; +} callsign_hashtable[CALLSIGN_HASHTABLE_SIZE]; + +void hashtable_init(void) +{ + // for (int idx = 0; idx < CALLSIGN_HASHTABLE_SIZE; ++idx) + // { + // callsign_hashtable[idx]->callsign[0] = '\0'; + // } + memset(callsign_hashtable, 0, sizeof(callsign_hashtable)); +} + +void hashtable_add(const char* callsign, uint32_t hash) +{ + int idx_hash = (hash * 23) % CALLSIGN_HASHTABLE_SIZE; + while (callsign_hashtable[idx_hash].callsign[0] != '\0') + { + if ((callsign_hashtable[idx_hash].hash == hash) && (0 == strcmp(callsign_hashtable[idx_hash].callsign, callsign))) + { + LOG(LOG_DEBUG, "Found a duplicate [%s]\n", callsign); + return; + } + else + { + LOG(LOG_DEBUG, "Hash table clash!\n"); + // Move on to check the next entry in hash table + idx_hash = (idx_hash + 1) % CALLSIGN_HASHTABLE_SIZE; + } + } + strncpy(callsign_hashtable[idx_hash].callsign, callsign, 11); + callsign_hashtable[idx_hash].callsign[11] = '\0'; + callsign_hashtable[idx_hash].hash = hash; +} + +bool hashtable_lookup(ftx_callsign_hash_type_t hash_type, uint32_t hash, char* callsign) +{ + uint32_t hash_mask = (hash_type == FTX_CALLSIGN_HASH_10_BITS) ? 0x3FFu : (hash_type == FTX_CALLSIGN_HASH_12_BITS ? 0xFFFu : 0x3FFFFFu); + int idx_hash = (hash * 23) % CALLSIGN_HASHTABLE_SIZE; + while (callsign_hashtable[idx_hash].callsign[0] != '\0') + { + if ((callsign_hashtable[idx_hash].hash & hash_mask) == hash) + { + strcpy(callsign, callsign_hashtable[idx_hash].callsign); + return true; + } + // Move on to check the next entry in hash table + idx_hash = (idx_hash + 1) % CALLSIGN_HASHTABLE_SIZE; + } + callsign[0] = '\0'; + return false; +} + +ftx_callsign_hash_interface_t hash_if = { + .lookup_hash = hashtable_lookup, + .save_hash = hashtable_add +}; + +void test_std_msg(const char* call_to_tx, ftx_field_t to_field, const char* call_de_tx, ftx_field_t de_field, const char* extra_tx, ftx_field_t extra_field) +{ + ftx_message_t msg; + ftx_message_init(&msg); + + ftx_message_rc_t rc_encode = ftx_message_encode_std(&msg, &hash_if, call_to_tx, call_de_tx, extra_tx); + printf("Encoded [%s] [%s] [%s]\n", call_to_tx, call_de_tx, extra_tx); + CHECK_EQ_VAL(rc_encode, FTX_MESSAGE_RC_OK); + + char call_to_arr[14]; + char call_de_arr[14]; + char extra[14]; + char *call_to = call_to_arr; + char *call_de = call_de_arr; + ftx_field_t types[FTX_MAX_MESSAGE_FIELDS]; + ftx_message_rc_t rc_decode = ftx_message_decode_std(&msg, &hash_if, call_to, call_de, extra, types); + CHECK_EQ_VAL(rc_decode, FTX_MESSAGE_RC_OK); + printf("Decoded [%s] [%s] [%s]\n", call_to, call_de, extra); + call_to = trim_brackets(call_to); + call_de = trim_brackets(call_de); + CHECK_EQ_VAL(0, strcmp(call_to, call_to_tx)); + CHECK_EQ_VAL(0, strcmp(call_de, call_de_tx)); + CHECK_EQ_VAL(0, strcmp(extra, extra_tx)); + CHECK_EQ_VAL(to_field, types[0]); + CHECK_EQ_VAL(de_field, types[1]); + CHECK_EQ_VAL(extra_field, types[2]); + TEST_END; +} + +void test_msg(const char* message_text, ftx_message_type_t expected_type, const char* expected, ftx_callsign_hash_interface_t* hash_if) +{ + printf("Testing [%s]\n", message_text); + + ftx_message_t msg; + ftx_message_init(&msg); + + ftx_message_rc_t rc_encode = ftx_message_encode(&msg, hash_if, message_text); + CHECK_EQ_VAL(rc_encode, FTX_MESSAGE_RC_OK); + CHECK_EQ_VAL(expected_type, ftx_message_get_type(&msg)); + + char message_decoded[12 + 12 + 20]; + ftx_message_offsets_t offsets; + ftx_message_rc_t rc_decode = ftx_message_decode(&msg, hash_if, message_decoded, &offsets); + CHECK_EQ_VAL(rc_decode, FTX_MESSAGE_RC_OK); + printf("Decoded [%s]; offsets %d:%d %d:%d %d:%d\n", message_decoded, + offsets.offsets[0], offsets.types[0], offsets.offsets[1], offsets.types[1], offsets.offsets[2], offsets.types[2]); + CHECK_EQ_VAL(0, strcmp(expected, message_decoded)); + // TODO check offsets + TEST_END; +} + +#define SIZEOF_ARRAY(x) (sizeof(x) / sizeof((x)[0])) + +int main() +{ + // test1(); + // test4(); + const char* callsigns[] = { "YL3JG", "W1A", "W1A/R", "W5AB", "W8ABC", "DE6ABC", "DE6ABC/R", "DE7AB", "DE9A", "3DA0X", "3DA0XYZ", "3DA0XYZ/R", "3XZ0AB", "3XZ0A", "CQ1CQ" }; + const char* tokens[] = { "CQ", "QRZ", "CQ 123", "CQ 000", "CQ POTA", "CQ SA", "CQ O", "CQ ASD" }; + const ftx_field_t token_types[] = { FTX_FIELD_TOKEN, FTX_FIELD_TOKEN, FTX_FIELD_TOKEN_WITH_ARG, FTX_FIELD_TOKEN_WITH_ARG, FTX_FIELD_TOKEN_WITH_ARG, FTX_FIELD_TOKEN_WITH_ARG, FTX_FIELD_TOKEN_WITH_ARG, FTX_FIELD_TOKEN_WITH_ARG }; + const char* grids[] = { "KO26", "RR99", "AA00", "RR09", "AA01", "RRR", "RR73", "73", "R+10", "R+05", "R-12", "R-02", "+10", "+05", "-02", "-02", "" }; + const ftx_field_t grid_types[] = { FTX_FIELD_GRID, FTX_FIELD_GRID, FTX_FIELD_GRID, FTX_FIELD_GRID, FTX_FIELD_GRID, FTX_FIELD_TOKEN, FTX_FIELD_TOKEN, FTX_FIELD_TOKEN, FTX_FIELD_RST, FTX_FIELD_RST, FTX_FIELD_RST, FTX_FIELD_RST, FTX_FIELD_RST, FTX_FIELD_RST, FTX_FIELD_RST, FTX_FIELD_RST, FTX_FIELD_NONE }; + + for (int idx_grid = 0; idx_grid < SIZEOF_ARRAY(grids); ++idx_grid) + { + for (int idx_callsign = 0; idx_callsign < SIZEOF_ARRAY(callsigns); ++idx_callsign) + { + for (int idx_callsign2 = 0; idx_callsign2 < SIZEOF_ARRAY(callsigns); ++idx_callsign2) + { + test_std_msg(callsigns[idx_callsign], FTX_FIELD_CALL, callsigns[idx_callsign2], FTX_FIELD_CALL, grids[idx_grid], grid_types[idx_grid]); + } + } + for (int idx_token = 0; idx_token < SIZEOF_ARRAY(tokens); ++idx_token) + { + for (int idx_callsign2 = 0; idx_callsign2 < SIZEOF_ARRAY(callsigns); ++idx_callsign2) + { + test_std_msg(tokens[idx_token], token_types[idx_token], callsigns[idx_callsign2], FTX_FIELD_CALL, grids[idx_grid], grid_types[idx_grid]); + } + } + } + test_msg("CQ K7IHZ DM43", FTX_MESSAGE_TYPE_STANDARD, + "CQ K7IHZ DM43", &hash_if); + test_msg("CQ EA8/G5LSI", FTX_MESSAGE_TYPE_NONSTD_CALL, + "CQ EA8/G5LSI", &hash_if); + test_msg("EA8/G5LSI R2RFE RR73", FTX_MESSAGE_TYPE_STANDARD, + " R2RFE RR73", &hash_if); + test_msg("R2RFE/P EA8/G5LSI R+12", FTX_MESSAGE_TYPE_STANDARD, + "R2RFE/P R+12", &hash_if); + test_msg("TNX BOB 73 GL", FTX_MESSAGE_TYPE_FREE_TEXT, + "TNX BOB 73 GL", &hash_if); // message with 4 tokens must be free text + test_msg("TNX BOB 73", FTX_MESSAGE_TYPE_STANDARD, + " 73", &hash_if); // can't distinguish special callsigns from other tokens + test_msg("CQ YL/LB2JK KO16sw", FTX_MESSAGE_TYPE_NONSTD_CALL, + "CQ YL/LB2JK", &hash_if); // grid not allowed with nonstandard call + test_msg("CQ POTA YL/LB2JK KO16sw", FTX_MESSAGE_TYPE_NONSTD_CALL, + "CQ YL/LB2JK", &hash_if); // CQ modifier not allowed with nonstandard call + test_msg("CQ JA LB2JK JO59", FTX_MESSAGE_TYPE_STANDARD, + "CQ JA LB2JK JO59", &hash_if); + test_msg("CQ 123 LB2JK JO59", FTX_MESSAGE_TYPE_STANDARD, + "CQ 123 LB2JK JO59", &hash_if); + + return 0; +} diff --git a/external/ft8_lib/test/wav/191111_110115.txt b/external/ft8_lib/test/wav/191111_110115.txt new file mode 100644 index 0000000..db85685 --- /dev/null +++ b/external/ft8_lib/test/wav/191111_110115.txt @@ -0,0 +1 @@ +110115 6 0.9 1234 ~ GJ0KYZ RK9AX MO05 diff --git a/external/ft8_lib/test/wav/191111_110115.wav b/external/ft8_lib/test/wav/191111_110115.wav new file mode 100644 index 0000000..b34b358 Binary files /dev/null and b/external/ft8_lib/test/wav/191111_110115.wav differ diff --git a/external/ft8_lib/test/wav/191111_110130.txt b/external/ft8_lib/test/wav/191111_110130.txt new file mode 100644 index 0000000..98956c2 --- /dev/null +++ b/external/ft8_lib/test/wav/191111_110130.txt @@ -0,0 +1,5 @@ +110130 -6 0.7 683 ~ CQ TA6CQ KN70 AS Turkey +110130 -16 1.0 989 ~ OH3NIV ZS6S -03 +110130 -6 0.9 1291 ~ CQ R7IW LN35 EU Russia +110130 -4 0.9 2096 ~ CQ DX R6WA LN32 EU Russia +110130 -14 1.2 2479 ~ TK4LS YC1MRF 73 diff --git a/external/ft8_lib/test/wav/191111_110130.wav b/external/ft8_lib/test/wav/191111_110130.wav new file mode 100644 index 0000000..5703908 Binary files /dev/null and b/external/ft8_lib/test/wav/191111_110130.wav differ diff --git a/external/ft8_lib/test/wav/191111_110145.txt b/external/ft8_lib/test/wav/191111_110145.txt new file mode 100644 index 0000000..6f2589d --- /dev/null +++ b/external/ft8_lib/test/wav/191111_110145.txt @@ -0,0 +1,2 @@ +110145 -4 1.0 322 ~ <...> RY8CAA +110145 7 1.0 1234 ~ GJ0KYZ RK9AX MO05 diff --git a/external/ft8_lib/test/wav/191111_110145.wav b/external/ft8_lib/test/wav/191111_110145.wav new file mode 100644 index 0000000..5bb0abf Binary files /dev/null and b/external/ft8_lib/test/wav/191111_110145.wav differ diff --git a/external/ft8_lib/test/wav/191111_110200.txt b/external/ft8_lib/test/wav/191111_110200.txt new file mode 100644 index 0000000..8d1684c --- /dev/null +++ b/external/ft8_lib/test/wav/191111_110200.txt @@ -0,0 +1,5 @@ +110200 -4 0.7 683 ~ CQ TA6CQ KN70 AS Turkey +110200 -16 1.0 990 ~ OH3NIV ZS6S RR73 +110200 -17 0.6 1031 ~ CQ LZ1JZ KN22 Bulgaria +110200 -12 0.9 1292 ~ CQ R7IW LN35 EU Russia +110200 -7 0.9 2097 ~ CQ DX R6WA LN32 EU Russia diff --git a/external/ft8_lib/test/wav/191111_110200.wav b/external/ft8_lib/test/wav/191111_110200.wav new file mode 100644 index 0000000..d9149f5 Binary files /dev/null and b/external/ft8_lib/test/wav/191111_110200.wav differ diff --git a/external/ft8_lib/test/wav/191111_110215.txt b/external/ft8_lib/test/wav/191111_110215.txt new file mode 100644 index 0000000..96f0560 --- /dev/null +++ b/external/ft8_lib/test/wav/191111_110215.txt @@ -0,0 +1,4 @@ +110215 3 1.0 323 ~ <...> RY8CAA R-10 +110215 -12 0.1 996 ~ GJ0KYZ UA6HI -15 +110215 2 0.9 1235 ~ GJ0KYZ RK9AX MO05 +110215 -16 0.9 2059 ~ CQ DX Z33Z KN11 N. Macedonia diff --git a/external/ft8_lib/test/wav/191111_110215.wav b/external/ft8_lib/test/wav/191111_110215.wav new file mode 100644 index 0000000..b7d5682 Binary files /dev/null and b/external/ft8_lib/test/wav/191111_110215.wav differ diff --git a/external/ft8_lib/test/wav/191111_110615.txt b/external/ft8_lib/test/wav/191111_110615.txt new file mode 100644 index 0000000..1ffa3a5 --- /dev/null +++ b/external/ft8_lib/test/wav/191111_110615.txt @@ -0,0 +1,22 @@ +110615 -2 1.0 431 ~ VK4BLE OH8JK R-17 +110615 -14 0.9 539 ~ RK6AH JH1AJT -05 +110615 -18 0.9 656 ~ PA3EPP SP8NFO KN09 +110615 -10 1.8 700 ~ RV6K RU3XL -13 +110615 -16 0.9 756 ~ PA3EPP SP8NFO KN09 +110615 -11 1.3 810 ~ SQ8OHR UA9LL MO27 +110615 15 0.9 906 ~ PA3EPP SP8NFO KN09 +110615 10 0.9 1196 ~ ET3RFG/R IN3ADG -23 +110615 3 0.9 1284 ~ CQ F4FSY JN25 France +110615 -8 0.9 1349 ~ JR5MJS OH8NW 73 +110615 -12 1.0 1404 ~ SV1GN RK6AUV LN05 +110615 -24 0.9 1617 ~ PB5DX EI3CTB IO63 +110615 10 1.5 2191 ~ CQ IZ1ANK JN33 Italy +110615 0 0.9 2281 ~ NT6Q OH8GDU -17 +110615 -7 0.9 2447 ~ CQ DL1UDO JO31 Germany +110615 4 0.8 2576 ~ VK4BLE OH1EDK -20 +110615 8 1.0 2656 ~ CQ JA OH1LWZ KP11 Finland +110615 -11 1.0 297 ~ <...> ON7EE JO10 +110615 -17 0.8 594 ~ CQ DG0OFT JO50 Germany +110615 -16 0.8 1049 ~ CQ UB3AQS KO85 EU Russia +110615 -3 1.0 1201 ~ G1XJM HA7JIV JN97 +110615 -16 1.4 2727 ~ SP7XIF JA2GQT -15 diff --git a/external/ft8_lib/test/wav/191111_110615.wav b/external/ft8_lib/test/wav/191111_110615.wav new file mode 100644 index 0000000..09e052c Binary files /dev/null and b/external/ft8_lib/test/wav/191111_110615.wav differ diff --git a/external/ft8_lib/test/wav/191111_110630.txt b/external/ft8_lib/test/wav/191111_110630.txt new file mode 100644 index 0000000..7a955ba --- /dev/null +++ b/external/ft8_lib/test/wav/191111_110630.txt @@ -0,0 +1,15 @@ +110630 -20 1.1 518 ~ CQ PC2J JO22 +110630 4 1.2 809 ~ UA9LL SQ8OHR -10 +110630 15 -0.5 973 ~ JA2GQT SP7XIF JO91 +110630 -3 0.8 1034 ~ CQ EA3UV JN01 +110630 -5 1.4 1405 ~ RK6AUV SV1GN -18 +110630 -15 1.0 1485 ~ SP8NFO PA3EPP +04 +110630 -6 0.9 1670 ~ CQ PB5DX JO22 +110630 -9 0.9 1722 ~ CQ SM7HZK JO76 +110630 5 0.8 1954 ~ JH1AJT RK6AH R+07 +110630 -2 0.9 2030 ~ JL1TZQ R3BV R-18 +110630 -16 0.9 2110 ~ <...> DF1XG JO53 +110630 19 1.3 2728 ~ CQ DX IK0YVV JN62 +110630 -10 0.9 840 ~ CQ OR18RSX +110630 -24 0.3 1114 ~ CQ JR5MJS PM74 +110630 -21 1.0 1695 ~ JA2GQT F8NHF -10 diff --git a/external/ft8_lib/test/wav/191111_110630.wav b/external/ft8_lib/test/wav/191111_110630.wav new file mode 100644 index 0000000..b84cfd9 Binary files /dev/null and b/external/ft8_lib/test/wav/191111_110630.wav differ diff --git a/external/ft8_lib/test/wav/191111_110645.txt b/external/ft8_lib/test/wav/191111_110645.txt new file mode 100644 index 0000000..cddc764 --- /dev/null +++ b/external/ft8_lib/test/wav/191111_110645.txt @@ -0,0 +1,20 @@ +110645 0 0.9 430 ~ VK4BLE OH8JK R-17 +110645 -18 1.8 699 ~ CQ RU3XL KO84 +110645 -23 0.7 756 ~ PA3EPP SP8NFO R+01 +110645 9 0.7 906 ~ PA3EPP SP8NFO R+01 +110645 -20 0.9 1049 ~ CQ UB3AQS KO85 +110645 10 0.9 1196 ~ ET3RFG/R IN3ADG -23 +110645 -1 0.9 1283 ~ CQ F4FSY JN25 +110645 -16 1.0 1404 ~ SV1GN RK6AUV R-03 +110645 -24 0.9 1617 ~ PB5DX EI3CTB IO63 +110645 -10 1.0 2111 ~ CQ OR18TRA +110645 3 1.5 2191 ~ PC2J IZ1ANK +01 +110645 -4 0.9 2281 ~ CQ OH8GDU KP24 +110645 -10 0.9 2447 ~ CQ DL1UDO JO31 +110645 6 0.7 2576 ~ VK4BLE OH1EDK -20 +110645 5 1.0 2656 ~ CQ JA OH1LWZ KP11 +110645 -21 0.8 594 ~ CQ DG0OFT JO50 +110645 -21 0.7 1114 ~ <...> DA0FONTANE +110645 -6 1.0 1201 ~ G1XJM HA7JIV JN97 +110645 -21 0.9 2092 ~ WB2QJ ES3AT KO18 +110645 -15 1.4 2726 ~ SP7XIF JA2GQT -13 diff --git a/external/ft8_lib/test/wav/191111_110645.wav b/external/ft8_lib/test/wav/191111_110645.wav new file mode 100644 index 0000000..8d4d590 Binary files /dev/null and b/external/ft8_lib/test/wav/191111_110645.wav differ diff --git a/external/ft8_lib/test/wav/191111_110700.txt b/external/ft8_lib/test/wav/191111_110700.txt new file mode 100644 index 0000000..c2c9fc5 --- /dev/null +++ b/external/ft8_lib/test/wav/191111_110700.txt @@ -0,0 +1,16 @@ +110700 -17 1.1 518 ~ IZ1ANK PC2J R+10 +110700 -15 0.9 840 ~ CQ OR18RSX +110700 13 -0.5 973 ~ JA2GQT SP7XIF JO91 +110700 -2 0.8 1034 ~ CQ EA3UV JN01 +110700 -22 0.3 1115 ~ CQ JR5MJS PM74 +110700 -21 0.9 1244 ~ DG0OFT W4FGA EM83 +110700 -8 1.6 1405 ~ RK6AUV SV1GN RR73 +110700 -21 1.0 1485 ~ SP8NFO PA3EPP +04 +110700 -12 0.9 1670 ~ CQ PB5DX JO22 +110700 15 0.9 1726 ~ JH1AJT SP8BJU -04 +110700 4 1.0 1954 ~ JH1AJT RK6AH 73 +110700 -10 0.9 2030 ~ JL1TZQ R3BV R-12 +110700 -15 0.9 2111 ~ <...> IT9AAI JM67 +110700 -6 1.1 2358 ~ LA2GCA F5MXH JN07 +110700 18 1.3 2728 ~ CQ DX IK0YVV JN62 +110700 -24 0.9 1578 ~ CQ M0NPT IO92 diff --git a/external/ft8_lib/test/wav/191111_110700.wav b/external/ft8_lib/test/wav/191111_110700.wav new file mode 100644 index 0000000..b0e4004 Binary files /dev/null and b/external/ft8_lib/test/wav/191111_110700.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_01.txt b/external/ft8_lib/test/wav/20m_busy/test_01.txt new file mode 100644 index 0000000..3f388c7 --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_01.txt @@ -0,0 +1,24 @@ +000000 -7 0.6 955 ~ CQ IU8DMZ JN70 +000000 -16 0.8 338 ~ JO1COV PE1OYB JO21 +000000 13 0.9 708 ~ CQ IK4LZH JN54 +000000 -3 1.9 771 ~ JA1FWS OK2BV JN89 +000000 -1 0.9 824 ~ LY2EW DL1KDA RR73 +000000 9 0.8 892 ~ SA5QED IQ5PJ 73 +000000 12 0.8 1124 ~ CQ HB9CUZ JN47 +000000 -5 1.0 1292 ~ EA9ACD HA5LGO -13 +000000 -3 0.8 1369 ~ CQ OK6LZ JN99 +000000 -24 1.7 1450 ~ CQ RX3ASQ KO95 +000000 -2 0.8 1513 ~ JO1COV DL4SBF 73 +000000 -12 1.0 1564 ~ JI1TYA DH1NAS 73 +000000 0 0.8 2138 ~ LZ365BM <...> 73 +000000 7 1.2 2279 ~ PY2DPM ON6UF RR73 +000000 -1 0.8 2327 ~ CQ R8AU MO05 +000000 12 -1.1 2378 ~ R1CBP SP9LKP RR73 +000000 8 1.7 2390 ~ CQ E75C JN93 +000000 -5 1.9 719 ~ <...> SQ9JJR JO90 +000000 -1 1.0 773 ~ JA1FWS HA7CH JN97 +000000 4 0.8 1158 ~ CQ HA1BF JN86 +000000 -7 0.1 1285 ~ MM0IMC 4U1A -06 +000000 -7 0.1 1345 ~ CQ 4U1A JN88 +000000 -12 0.8 2104 ~ F1BHB SP4TXI 73 +000000 -7 0.7 2692 ~ CQ OE8GMQ JN66 diff --git a/external/ft8_lib/test/wav/20m_busy/test_01.wav b/external/ft8_lib/test/wav/20m_busy/test_01.wav new file mode 100644 index 0000000..8209726 Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_01.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_02.txt b/external/ft8_lib/test/wav/20m_busy/test_02.txt new file mode 100644 index 0000000..d5fa2ae --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_02.txt @@ -0,0 +1,24 @@ +000000 8 1.0 397 ~ JH7DFZ S51SG JN76 +000000 16 0.8 447 ~ CQ DG0OFT JO50 +000000 -1 0.8 998 ~ JH7DFZ PD7RF RR73 +000000 -4 0.8 1061 ~ DJ4TM EA5OL RR73 +000000 -9 1.6 1268 ~ DH3JF OR7EG RR73 +000000 -10 1.3 1505 ~ IZ5ILK TA3AHJ RR73 +000000 -7 0.8 1686 ~ CQ MM0IMC IO75 +000000 -8 0.7 1868 ~ JI1TYA I2XYI JN45 +000000 19 0.8 2046 ~ CQ 9A9A JN75 +000000 -2 1.5 2102 ~ SP4TXI F1BHB 73 +000000 -13 2.4 2202 ~ BD8NBG UY7IV R-19 +000000 8 1.3 2518 ~ CQ F5CCX JN18 +000000 -2 0.7 2724 ~ CQ R4HM LO43 +000000 -21 1.4 265 ~ CT3IQ EI8GVB IO63 +000000 -13 0.9 338 ~ SM6CWP JO1COV -10 +000000 -5 0.8 393 ~ CQ RV6AFG KN95 +000000 -9 0.8 941 ~ JR1MVA DL4GBA JN47 +000000 -24 2.5 1313 ~ ES3AT OE3MLC -15 +000000 -19 0.9 1368 ~ E75C F4VTS JN33 +000000 -24 1.0 1453 ~ OK2BJ JG1SRO -15 +000000 -18 0.8 1561 ~ CQ 7Z1AL LL56 +000000 -4 1.2 2137 ~ CQ LZ365BM +000000 -8 0.8 2578 ~ <...> DL8RCH JN68 +000000 -12 0.9 2632 ~ <...> OM7OM JN98 diff --git a/external/ft8_lib/test/wav/20m_busy/test_02.wav b/external/ft8_lib/test/wav/20m_busy/test_02.wav new file mode 100644 index 0000000..e85cb7a Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_02.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_03.txt b/external/ft8_lib/test/wav/20m_busy/test_03.txt new file mode 100644 index 0000000..9964fe9 --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_03.txt @@ -0,0 +1,19 @@ +000000 -8 0.6 955 ~ CQ IU8DMZ JN70 +000000 -7 1.0 394 ~ RV6AFG M0XMX IO92 +000000 11 0.8 708 ~ CQ IK4LZH JN54 +000000 0 1.9 771 ~ JA1FWS OK2BV JN89 +000000 -3 0.9 823 ~ CQ DL1KDA JO30 +000000 15 0.8 1123 ~ CQ HB9CUZ JN47 +000000 -6 0.8 1369 ~ CQ OK6LZ JN99 +000000 7 1.1 2279 ~ PY2DPM ON6UF 73 +000000 7 1.7 2390 ~ CQ E75C JN93 +000000 -11 0.8 2519 ~ F5CCX SP4TXI KO03 +000000 14 0.8 2632 ~ CQ OR18OSB +000000 -24 1.8 837 ~ CT3HF YO7IUN KN24 +000000 -16 0.8 947 ~ <...> E77VM R-11 +000000 4 0.9 1088 ~ EA2DIC R7NO -25 +000000 -4 0.8 1157 ~ CQ HA1BF JN86 +000000 -8 0.1 1345 ~ CQ 4U1A JN88 +000000 -1 0.8 2327 ~ CQ R8AU MO05 +000000 -4 0.8 2677 ~ CQ OE8GMQ JN66 +000000 -3 0.8 1059 ~ EA5OL DJ4TM 73 diff --git a/external/ft8_lib/test/wav/20m_busy/test_03.wav b/external/ft8_lib/test/wav/20m_busy/test_03.wav new file mode 100644 index 0000000..242f69b Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_03.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_04.txt b/external/ft8_lib/test/wav/20m_busy/test_04.txt new file mode 100644 index 0000000..6935e74 --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_04.txt @@ -0,0 +1,20 @@ +000000 -24 1.4 265 ~ CT3IQ EI8GVB IO63 +000000 -11 0.9 338 ~ SM6CWP JO1COV RR73 +000000 -10 0.8 393 ~ M0XMX RV6AFG -22 +000000 11 0.8 447 ~ CQ DG0OFT JO50 +000000 10 1.0 763 ~ UR7HN HB9BIN R+01 +000000 -4 0.8 941 ~ JR1MVA DL4GBA JN47 +000000 -8 0.8 998 ~ CQ PD7RF JO22 +000000 -7 0.8 1061 ~ CQ EA5OL IM99 +000000 -24 1.6 1268 ~ CQ OR7EG JO11 +000000 -24 2.4 1313 ~ ES3AT OE3MLC -15 +000000 -12 0.7 1868 ~ JI1TYA I2XYI JN45 +000000 20 0.8 2046 ~ CQ 9A9A JN75 +000000 -14 2.2 2202 ~ BD8NBG UY7IV R-19 +000000 -1 1.3 2519 ~ SP4TXI F5CCX +05 +000000 -17 0.9 2632 ~ <...> OM7OM JN98 +000000 -7 0.7 2724 ~ CQ R4HM LO43 +000000 -19 0.8 987 ~ CQ TA1NGE KN41 +000000 -24 1.0 1453 ~ OK2BJ JG1SRO -15 +000000 -9 1.1 2137 ~ CQ LZ365BM +000000 -11 0.8 2578 ~ <...> DL8RCH JN68 diff --git a/external/ft8_lib/test/wav/20m_busy/test_04.wav b/external/ft8_lib/test/wav/20m_busy/test_04.wav new file mode 100644 index 0000000..143f216 Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_04.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_05.txt b/external/ft8_lib/test/wav/20m_busy/test_05.txt new file mode 100644 index 0000000..8c0965c --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_05.txt @@ -0,0 +1,32 @@ +000000 -24 2.0 339 ~ JO1COV YO7IUN KN24 +000000 -8 1.0 394 ~ RV6AFG M0XMX R+03 +000000 11 0.9 708 ~ CQ IK4LZH JN54 +000000 -9 0.9 823 ~ R3FO DL1KDA -13 +000000 7 0.8 892 ~ CQ IQ5PJ JN53 +000000 9 -0.7 987 ~ TA1NGE RA3TPE LO25 +000000 10 0.8 1123 ~ CQ HB9CUZ JN47 +000000 -10 0.7 1215 ~ HB9BIN UR7HN RR73 +000000 -3 0.9 1264 ~ CQ SV2BRA KN10 +000000 -14 0.1 1345 ~ LY2EW 4U1A -05 +000000 2 -0.1 1565 ~ JI1TYA DF2FE JO51 +000000 -4 0.8 1830 ~ CQ F6HUK JN06 +000000 -6 2.3 1927 ~ UA3NFG RW6PA -09 +000000 -17 1.1 2045 ~ 9A9A DH1NAS JO50 +000000 2 0.8 2235 ~ PY2DPM DL1DV JN39 +000000 4 1.1 2279 ~ CQ ON6UF JO10 +000000 -7 0.8 2327 ~ CQ R8AU MO05 +000000 16 1.7 2389 ~ CQ E75C JN93 +000000 -11 0.8 2519 ~ F5CCX SP4TXI R+10 +000000 9 0.8 2632 ~ CQ OR18OSB +000000 2 0.6 955 ~ CQ IU8DMZ JN70 +000000 -11 0.9 558 ~ CQ G3ZQQ IO82 +000000 -5 1.9 718 ~ <...> SQ9JJR JO90 +000000 -14 1.0 793 ~ ZL2OK F8BBL IN94 +000000 4 0.9 1088 ~ EA2DIC R7NO -25 +000000 3 0.9 1158 ~ CQ HA1BF JN86 +000000 -10 0.3 1404 ~ R8JA CT3IQ RR73 +000000 1 1.9 1561 ~ 7Z1AL OK2BV JN89 +000000 0 0.8 1862 ~ CQ IZ5ILK JN63 +000000 7 -1.1 2378 ~ CQ SP9LKP JO90 +000000 -5 0.7 2677 ~ CQ OE8GMQ JN66 +000000 -13 0.9 1053 ~ <9A9A> F6DEO/QRP diff --git a/external/ft8_lib/test/wav/20m_busy/test_05.wav b/external/ft8_lib/test/wav/20m_busy/test_05.wav new file mode 100644 index 0000000..eba69cf Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_05.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_06.txt b/external/ft8_lib/test/wav/20m_busy/test_06.txt new file mode 100644 index 0000000..52a9fef --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_06.txt @@ -0,0 +1,27 @@ +000000 13 0.8 393 ~ M0XMX RV6AFG RRR +000000 10 0.8 448 ~ CQ DG0OFT JO50 +000000 12 1.0 763 ~ UR7HN HB9BIN R+01 +000000 -7 0.8 941 ~ JR1MVA DL4GBA JN47 +000000 -5 0.8 1062 ~ CQ EA5OL IM99 +000000 -24 1.6 1267 ~ CQ OR7EG JO11 +000000 -24 2.5 1313 ~ ES3AT OE3MLC RR73 +000000 -8 1.3 1505 ~ RX3ASQ TA3AHJ -08 +000000 -22 0.8 1561 ~ CQ 7Z1AL LL56 +000000 -12 1.7 1608 ~ OZ5VO IT9HVZ JM78 +000000 -2 0.8 1687 ~ CQ MM0IMC IO75 +000000 18 0.8 2046 ~ CQ 9A9A JN75 +000000 7 1.3 2519 ~ SP4TXI F5CCX RR73 +000000 -9 0.8 2632 ~ <...> PH0WAW JO32 +000000 -1 0.7 2725 ~ CQ R4HM LO43 +000000 -9 1.4 265 ~ CT3IQ EI8GVB IO63 +000000 -8 0.9 338 ~ CQ JO1COV PM95 +000000 9 1.0 456 ~ CQ ON2RK JO20 +000000 -6 0.9 770 ~ CQ DM100ZM +000000 -24 0.9 1368 ~ SP9LKP F4VTS JN33 +000000 -24 0.8 1575 ~ R7NO EA2DIC R-11 +000000 -18 1.3 1927 ~ RW6PA UA3NFG R-06 +000000 -22 0.8 2326 ~ R8AU DK3EL JO31 +000000 -7 0.8 2578 ~ <...> DL8RCH JN68 +000000 -7 0.9 2632 ~ <...> OM7OM JN98 +000000 3 0.9 493 ~ CQ 2E0LDW IO70 +000000 -18 0.9 835 ~ YO7IUN CT3HF -18 diff --git a/external/ft8_lib/test/wav/20m_busy/test_06.wav b/external/ft8_lib/test/wav/20m_busy/test_06.wav new file mode 100644 index 0000000..1aa3b7c Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_06.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_07.txt b/external/ft8_lib/test/wav/20m_busy/test_07.txt new file mode 100644 index 0000000..377c4fe --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_07.txt @@ -0,0 +1,31 @@ +000000 -6 1.0 394 ~ RV6AFG M0XMX 73 +000000 -16 0.8 494 ~ 2E0LDW OK6LZ JN99 +000000 -20 0.9 557 ~ CQ G3ZQQ IO82 +000000 8 0.9 708 ~ CQ IK4LZH JN54 +000000 -10 0.9 823 ~ R3FO DL1KDA RR73 +000000 9 0.8 892 ~ CQ IQ5PJ JN53 +000000 11 -0.7 987 ~ TA1NGE RA3TPE R-15 +000000 10 0.9 1123 ~ DG1BQC HB9CUZ -17 +000000 -5 0.7 1215 ~ HB9BIN UR7HN R+00 +000000 0 0.9 1265 ~ CQ SV2BRA KN10 +000000 -18 0.1 1345 ~ LY2EW 4U1A RR73 +000000 1 -0.1 1565 ~ JI1TYA DF2FE JO51 +000000 -1 0.8 1830 ~ CQ F6HUK JN06 +000000 -4 0.8 1862 ~ CQ IZ5ILK JN63 +000000 -6 2.1 1927 ~ UA3NFG RW6PA RR73 +000000 9 1.1 2279 ~ CQ ON6UF JO10 +000000 -7 0.8 2326 ~ DK3EL R8AU -16 +000000 17 1.7 2389 ~ CQ E75C JN93 +000000 -11 0.8 2519 ~ F5CCX SP4TXI 73 +000000 6 0.8 2631 ~ ES1KK <...> -08 +000000 -7 1.9 718 ~ <...> SQ9JJR JO90 +000000 -4 1.0 793 ~ ZL2OK F8BBL IN94 +000000 2 0.6 955 ~ CQ IU8DMZ JN70 +000000 12 0.9 1088 ~ R3FO R7NO -16 +000000 2 0.8 1158 ~ CQ HA1BF JN86 +000000 -15 0.8 1410 ~ JO1COV PA0CAH JO21 +000000 -18 1.8 1450 ~ CQ RX3ASQ KO95 +000000 3 1.9 1561 ~ 7Z1AL OK2BV JN89 +000000 -11 0.9 1968 ~ MM0IMC SQ6PZL JO80 +000000 11 -1.1 2378 ~ F4VTS SP9LKP -20 +000000 -4 0.8 2677 ~ CQ OE8GMQ JN66 diff --git a/external/ft8_lib/test/wav/20m_busy/test_07.wav b/external/ft8_lib/test/wav/20m_busy/test_07.wav new file mode 100644 index 0000000..03316f4 Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_07.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_08.txt b/external/ft8_lib/test/wav/20m_busy/test_08.txt new file mode 100644 index 0000000..323187b --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_08.txt @@ -0,0 +1,19 @@ +000000 -23 1.4 265 ~ CT3IQ EI8GVB IO63 +000000 0 0.8 394 ~ M0XMX RV6AFG 73 +000000 -2 1.0 456 ~ CQ ON2RK JO20 +000000 10 1.0 763 ~ UR7HN HB9BIN RR73 +000000 -16 0.8 1062 ~ CQ EA5OL IM99 +000000 -19 0.9 1264 ~ SV2BRA I4WQH JN54 +000000 -24 1.3 1505 ~ RX3ASQ TA3AHJ -08 +000000 -24 1.7 1608 ~ OZ5VO IT9HVZ JM78 +000000 -14 0.8 1688 ~ SQ6PZL MM0IMC -06 +000000 20 0.8 2046 ~ CQ 9A9A JN75 +000000 -6 1.3 2519 ~ CQ F5CCX JN18 +000000 -8 0.7 2724 ~ CQ R4HM LO43 +000000 -18 0.9 336 ~ CQ JO1COV PM95 +000000 -4 0.9 492 ~ OK6LZ 2E0LDW +06 +000000 -12 0.9 774 ~ CQ DM100ZM +000000 -24 0.8 987 ~ RA3TPE TA1NGE RR73 +000000 -24 1.6 1267 ~ CQ OR7EG JO11 +000000 -24 0.9 1368 ~ SP9LKP F4VTS R-12 +000000 -22 0.8 1561 ~ CQ 7Z1AL LL56 diff --git a/external/ft8_lib/test/wav/20m_busy/test_08.wav b/external/ft8_lib/test/wav/20m_busy/test_08.wav new file mode 100644 index 0000000..ef043e7 Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_08.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_09.txt b/external/ft8_lib/test/wav/20m_busy/test_09.txt new file mode 100644 index 0000000..a144788 --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_09.txt @@ -0,0 +1,27 @@ +000000 -6 0.6 338 ~ JO1COV IZ7NLM -11 +000000 -12 0.8 491 ~ 2E0LDW OK6LZ R-09 +000000 -24 0.8 556 ~ CQ G3ZQQ IO82 +000000 11 0.9 709 ~ CQ IK4LZH JN54 +000000 -9 0.9 823 ~ R3FO DL1KDA RR73 +000000 3 0.8 891 ~ CQ IQ5PJ JN53 +000000 9 -0.7 988 ~ TA1NGE RA3TPE 73 +000000 3 0.9 1124 ~ DG1BQC HB9CUZ -17 +000000 -4 0.7 1214 ~ HB9BIN UR7HN 73 +000000 -6 0.9 1265 ~ I4WQH SV2BRA -06 +000000 -20 0.3 1402 ~ CQ CT3IQ IM12 +000000 -24 1.8 1450 ~ CQ RX3ASQ KO95 +000000 -3 0.8 1830 ~ CQ F6HUK JN06 +000000 3 0.9 2046 ~ 9A9A DJ4TM JN47 +000000 -1 1.1 2279 ~ CQ ON6UF JO10 +000000 0 0.9 2326 ~ DK3EL R8AU -16 +000000 11 0.8 2632 ~ <...> OR18OSB RR73 +000000 -6 0.8 456 ~ ON2RK SP4TXI KO03 +000000 -2 1.1 793 ~ ZL2OK F8BBL R-10 +000000 -1 0.6 955 ~ CQ IU8DMZ JN70 +000000 5 0.9 1087 ~ R3FO R7NO -16 +000000 -1 0.8 1158 ~ CQ HA1BF JN86 +000000 -12 0.1 1345 ~ CQ 4U1A JN88 +000000 -22 0.8 1410 ~ JO1COV PA0CAH JO21 +000000 -6 0.8 1862 ~ CQ IZ5ILK JN63 +000000 -9 1.9 2242 ~ 9A9A HA5LGO -07 +000000 -11 0.9 1054 ~ <9A9A> F6DEO/QRP diff --git a/external/ft8_lib/test/wav/20m_busy/test_09.wav b/external/ft8_lib/test/wav/20m_busy/test_09.wav new file mode 100644 index 0000000..33cc8d6 Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_09.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_10.txt b/external/ft8_lib/test/wav/20m_busy/test_10.txt new file mode 100644 index 0000000..75dc9a2 --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_10.txt @@ -0,0 +1,20 @@ +000000 -16 1.4 265 ~ CT3IQ EI8GVB IO63 +000000 9 0.8 393 ~ CQ RV6AFG KN95 +000000 6 1.0 456 ~ CQ ON2RK JO20 +000000 3 0.9 490 ~ OK6LZ 2E0LDW +06 +000000 -8 1.0 775 ~ CQ DM100ZM +000000 -12 0.8 1062 ~ CQ EA5OL IM99 +000000 -10 1.0 1264 ~ SV2BRA I4WQH R-09 +000000 -12 1.3 1505 ~ RX3ASQ TA3AHJ -08 +000000 -18 1.7 1608 ~ OZ5VO IT9HVZ R-04 +000000 -14 0.8 1686 ~ SQ6PZL MM0IMC RR73 +000000 19 0.8 2046 ~ CQ 9A9A JN75 +000000 1 1.3 2519 ~ CQ F5CCX JN18 +000000 -7 0.7 2723 ~ ES3AT R4HM -06 +000000 -6 0.9 338 ~ DH1NAS JO1COV -05 +000000 -24 1.6 1267 ~ CQ OR7EG JO11 +000000 -24 1.1 1509 ~ CQ ZY50Y +000000 -21 0.8 1562 ~ CQ 7Z1AL LL56 +000000 -10 0.9 2390 ~ E75C PA3GAE JO21 +000000 -12 0.8 2578 ~ <...> DL8RCH JN68 +000000 -16 0.9 2632 ~ <...> OM7OM JN98 diff --git a/external/ft8_lib/test/wav/20m_busy/test_10.wav b/external/ft8_lib/test/wav/20m_busy/test_10.wav new file mode 100644 index 0000000..74b7c45 Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_10.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_11.txt b/external/ft8_lib/test/wav/20m_busy/test_11.txt new file mode 100644 index 0000000..6093ccb --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_11.txt @@ -0,0 +1,31 @@ +000000 -1 1.0 335 ~ JO1COV DH1NAS R+02 +000000 -5 0.8 490 ~ 2E0LDW OK6LZ R-04 +000000 12 0.9 708 ~ CQ IK4LZH JN54 +000000 -8 0.9 823 ~ CQ DL1KDA JO30 +000000 4 0.8 891 ~ CQ IQ5PJ JN53 +000000 -8 0.6 955 ~ CQ IU8DMZ JN70 +000000 5 0.9 1124 ~ DG1BQC HB9CUZ RRR +000000 -3 0.7 1214 ~ CQ UR7HN KN79 +000000 -5 0.9 1265 ~ I4WQH SV2BRA RR73 +000000 -10 0.2 1402 ~ CQ CT3IQ IM12 +000000 1 0.8 1830 ~ CQ F6HUK JN06 +000000 -23 0.9 1969 ~ MM0IMC SQ6PZL 73 +000000 -2 0.9 2046 ~ 9A9A DJ4TM JN47 +000000 3 1.1 2279 ~ CQ ON6UF JO10 +000000 -1 0.9 2326 ~ DK3EL R8AU RR73 +000000 4 1.7 2389 ~ PA3GAE E75C +02 +000000 -2 1.1 2457 ~ BA7IO EA3ZD JN01 +000000 -12 0.7 2547 ~ CQ OE8GMQ JN66 +000000 12 0.8 2632 ~ <...> OR18OSB +000000 -6 0.6 337 ~ JO1COV IZ7NLM -11 +000000 -7 0.8 456 ~ ON2RK SP4TXI KO03 +000000 -14 0.9 556 ~ CQ G3ZQQ IO82 +000000 -9 1.9 718 ~ <...> SQ9JJR JO90 +000000 -7 1.0 793 ~ ZL2OK F8BBL 73 +000000 4 0.9 1087 ~ CQ R7NO KN98 +000000 -7 0.9 1158 ~ CQ HA1BF JN86 +000000 0 0.1 1285 ~ CQ 4U1A JN88 +000000 -24 1.8 1450 ~ CQ RX3ASQ KO95 +000000 -10 0.8 1862 ~ R1CBP IZ5ILK -13 +000000 -7 1.1 2242 ~ 9A9A HA5LGO -07 +000000 -24 0.7 1411 ~ JO1COV PA0CAH JO21 diff --git a/external/ft8_lib/test/wav/20m_busy/test_11.wav b/external/ft8_lib/test/wav/20m_busy/test_11.wav new file mode 100644 index 0000000..b18f393 Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_11.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_12.txt b/external/ft8_lib/test/wav/20m_busy/test_12.txt new file mode 100644 index 0000000..2e5fa16 --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_12.txt @@ -0,0 +1,18 @@ +000000 -15 1.4 265 ~ CT3IQ EI8GVB IO63 +000000 12 0.8 393 ~ CQ RV6AFG KN95 +000000 8 1.0 456 ~ SP4TXI ON2RK +06 +000000 -7 0.8 776 ~ YO9IAB <...> +06 +000000 -12 0.8 1062 ~ CQ EA5OL IM99 +000000 -10 0.9 1402 ~ CT3IQ RV6ARS KN84 +000000 -11 1.3 1505 ~ IQ5PJ TA3AHJ -07 +000000 -12 1.7 1608 ~ OZ5VO IT9HVZ 73 +000000 -10 0.8 1686 ~ 4U1A MM0IMC R-03 +000000 18 0.8 2046 ~ RA9UJP 9A9A -12 +000000 -12 1.1 2134 ~ CQ LZ365BM +000000 -16 0.9 2390 ~ E75C PA3GAE R+01 +000000 3 1.3 2519 ~ R2GCT F5CCX -05 +000000 -19 0.9 2578 ~ <...> DL8RCH JN68 +000000 1 0.7 2724 ~ ES3AT R4HM -06 +000000 -10 0.9 338 ~ DH1NAS JO1COV RR73 +000000 -1 2.3 489 ~ OK6LZ 2E0LDW +06 +000000 -13 0.9 2633 ~ <...> OM7OM JN98 diff --git a/external/ft8_lib/test/wav/20m_busy/test_12.wav b/external/ft8_lib/test/wav/20m_busy/test_12.wav new file mode 100644 index 0000000..a3224aa Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_12.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_13.txt b/external/ft8_lib/test/wav/20m_busy/test_13.txt new file mode 100644 index 0000000..0d4147a --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_13.txt @@ -0,0 +1,26 @@ +000000 1 1.0 397 ~ <...> S51SG JN76 +000000 -3 0.8 489 ~ 2E0LDW OK6LZ R-08 +000000 12 0.9 709 ~ CQ IK4LZH JN54 +000000 -6 0.9 823 ~ CQ DL1KDA JO30 +000000 10 0.8 891 ~ RG0S IQ5PJ -12 +000000 -9 0.6 955 ~ CQ IU8DMZ JN70 +000000 5 0.9 1124 ~ DG1BQC HB9CUZ RRR +000000 -4 0.7 1193 ~ CQ UR7HN KN79 +000000 -13 0.1 1285 ~ MM0IMC 4U1A RR73 +000000 -13 0.1 1345 ~ CQ 4U1A JN88 +000000 4 0.6 1544 ~ <...> YO9IAB R-11 +000000 -4 0.8 1680 ~ DM2DLG F6HUK -13 +000000 -9 0.8 1862 ~ R1CBP IZ5ILK RR73 +000000 -24 0.2 2045 ~ 9A9A RA9UJP R+04 +000000 7 1.1 2279 ~ CQ ON6UF JO10 +000000 5 1.7 2389 ~ PA3GAE E75C +02 +000000 -6 1.1 2457 ~ BA7IO EA3ZD JN01 +000000 7 0.8 2632 ~ <...> OR18OSB +000000 -7 0.8 456 ~ ON2RK SP4TXI R+14 +000000 -14 0.9 555 ~ CQ G3ZQQ IO82 +000000 -4 1.9 717 ~ <...> SQ9JJR JO90 +000000 8 0.9 1087 ~ CQ R7NO KN98 +000000 -8 1.8 1509 ~ <...> G3WAG R-15 +000000 -23 0.9 1969 ~ CQ SQ6PZL JO80 +000000 -7 0.9 2326 ~ CQ R8AU MO05 +000000 -6 0.9 1054 ~ <9A9A> F6DEO/QRP diff --git a/external/ft8_lib/test/wav/20m_busy/test_13.wav b/external/ft8_lib/test/wav/20m_busy/test_13.wav new file mode 100644 index 0000000..5428b0b Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_13.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_14.txt b/external/ft8_lib/test/wav/20m_busy/test_14.txt new file mode 100644 index 0000000..4babdbb --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_14.txt @@ -0,0 +1,17 @@ +000000 -24 1.4 265 ~ CT3IQ EI8GVB IO63 +000000 4 0.8 394 ~ CQ RV6AFG KN95 +000000 1 1.0 456 ~ SP4TXI ON2RK RR73 +000000 -5 0.8 777 ~ <...> DM100ZM RR73 +000000 -11 0.8 1062 ~ CQ EA5OL IM99 +000000 -24 1.6 1267 ~ CQ OR7EG JO11 +000000 -16 0.8 1686 ~ 4U1A MM0IMC 73 +000000 -14 0.8 1826 ~ CQ R4WZ LO67 +000000 19 0.8 2046 ~ RA9UJP 9A9A RR73 +000000 -1 1.3 2518 ~ R2GCT F5CCX RR73 +000000 -3 0.7 2723 ~ ES3AT R4HM RR73 +000000 -16 0.9 338 ~ CQ JO1COV PM95 +000000 -7 0.9 1402 ~ CT3IQ RV6ARS KN84 +000000 -15 1.2 2133 ~ CQ LZ365BM +000000 -13 0.9 2390 ~ E75C PA3GAE R+02 +000000 -20 0.8 2578 ~ <...> DL8RCH JN68 +000000 -24 0.8 1562 ~ CQ 7Z1AL LL56 diff --git a/external/ft8_lib/test/wav/20m_busy/test_14.wav b/external/ft8_lib/test/wav/20m_busy/test_14.wav new file mode 100644 index 0000000..a7ba3e7 Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_14.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_15.txt b/external/ft8_lib/test/wav/20m_busy/test_15.txt new file mode 100644 index 0000000..9430bab --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_15.txt @@ -0,0 +1,28 @@ +000000 -5 0.6 337 ~ JO1COV IZ7NLM -11 +000000 -1 0.8 488 ~ 2E0LDW OK6LZ 73 +000000 7 1.0 543 ~ <...> S51SG JN76 +000000 12 0.9 708 ~ CQ IK4LZH JN54 +000000 -8 0.9 823 ~ CQ DL1KDA JO30 +000000 0 0.8 890 ~ RG0S IQ5PJ RRR +000000 -6 0.7 955 ~ CQ IU8DMZ JN70 +000000 2 0.9 1087 ~ ES1KK R7NO -07 +000000 -1 0.7 1193 ~ CQ UR7HN KN79 +000000 -5 0.1 1285 ~ CQ 4U1A JN88 +000000 -12 0.3 1402 ~ PH0WAW CT3IQ RR73 +000000 8 0.6 1544 ~ DM100ZM <...> 73 +000000 -1 0.8 1680 ~ DM2DLG F6HUK -13 +000000 -15 0.8 1862 ~ R1CBP IZ5ILK 73 +000000 -9 0.7 1939 ~ PD0CIF/PHOTO +000000 -18 -0.4 2046 ~ 9A9A RA9UJP 73 +000000 -2 0.9 2135 ~ <...> DJ4TM JN47 +000000 6 1.1 2279 ~ CQ ON6UF JO10 +000000 -1 0.9 2326 ~ CQ R8AU MO05 +000000 12 1.7 2389 ~ PA3GAE E75C RR73 +000000 -3 1.1 2457 ~ BA7IO EA3ZD JN01 +000000 5 0.8 2632 ~ CQ OR18OSB +000000 -4 0.8 456 ~ ON2RK SP4TXI 73 +000000 -4 1.9 718 ~ UY7IV SQ9JJR JO90 +000000 -10 0.9 1054 ~ <9A9A> F6DEO/QRP +000000 10 0.9 1124 ~ DG1BQC HB9CUZ RRR +000000 -3 1.8 1509 ~ ZY50Y <...> 73 +000000 -3 0.8 1158 ~ CQ HA1BF JN86 diff --git a/external/ft8_lib/test/wav/20m_busy/test_15.wav b/external/ft8_lib/test/wav/20m_busy/test_15.wav new file mode 100644 index 0000000..3e77983 Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_15.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_16.txt b/external/ft8_lib/test/wav/20m_busy/test_16.txt new file mode 100644 index 0000000..e543b14 --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_16.txt @@ -0,0 +1,16 @@ +000000 -24 1.4 265 ~ CT3IQ EI8GVB IO63 +000000 -2 0.8 394 ~ CQ RV6AFG KN95 +000000 -18 0.8 1062 ~ CQ EA5OL IM99 +000000 -24 1.3 1505 ~ SQ6PZL TA3AHJ -04 +000000 -18 0.8 1686 ~ CQ MM0IMC IO75 +000000 -19 0.8 1750 ~ JA7GFI DK3BT JO40 +000000 -21 1.0 1826 ~ CQ R4WZ LO67 +000000 -15 1.3 1984 ~ JI1QNP F5CCX -15 +000000 17 0.8 2046 ~ LU5HA 9A9A -12 +000000 -22 0.9 2631 ~ <...> ON3ONX JO20 +000000 -4 0.7 2724 ~ CQ R4HM LO43 +000000 -21 0.9 338 ~ PA0CAH JO1COV -18 +000000 -24 1.2 823 ~ DL1KDA UA3YPL KO73 +000000 -24 0.8 1562 ~ CQ 7Z1AL LL56 +000000 -13 0.8 2132 ~ DJ4TM <...> -07 +000000 -24 0.9 2390 ~ E75C PA3GAE 73 diff --git a/external/ft8_lib/test/wav/20m_busy/test_16.wav b/external/ft8_lib/test/wav/20m_busy/test_16.wav new file mode 100644 index 0000000..8462b44 Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_16.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_17.txt b/external/ft8_lib/test/wav/20m_busy/test_17.txt new file mode 100644 index 0000000..15f7704 --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_17.txt @@ -0,0 +1,26 @@ +000000 -12 0.6 337 ~ YC6RMT IZ7NLM -22 +000000 11 0.9 709 ~ CQ IK4LZH JN54 +000000 -7 0.9 823 ~ UA3YPL DL1KDA -06 +000000 3 0.8 890 ~ RG0S IQ5PJ 73 +000000 0 0.8 992 ~ YC6RMT IK3JLT JN65 +000000 -2 0.9 1087 ~ ES1KK R7NO RR73 +000000 0 0.9 1123 ~ DG1BQC HB9CUZ RRR +000000 -2 0.7 1192 ~ CQ UR7HN KN79 +000000 -13 0.1 1285 ~ R8JA 4U1A -23 +000000 -14 0.1 1345 ~ CQ 4U1A JN88 +000000 6 -0.7 1560 ~ 7Z1AL RA3TPE LO25 +000000 -5 0.8 1679 ~ DM2DLG F6HUK RR73 +000000 -6 1.0 1932 ~ CQ DH1NAS JO50 +000000 4 0.9 2089 ~ <...> IV3KVC JN65 +000000 4 0.9 2134 ~ <...> DJ4TM R-06 +000000 11 1.1 2279 ~ CQ ON6UF JO10 +000000 1 0.8 2326 ~ CQ R8AU MO05 +000000 15 1.7 2389 ~ CQ E75C JN93 +000000 -5 1.1 2456 ~ BA7IO EA3ZD JN01 +000000 -11 0.8 2547 ~ CQ OE8GMQ JN66 +000000 9 0.8 2631 ~ UX0KR <...> -11 +000000 -9 2.0 717 ~ UY7IV SQ9JJR JO90 +000000 -3 0.7 955 ~ CQ IU8DMZ JN70 +000000 -14 0.3 1402 ~ CQ CT3IQ IM12 +000000 -4 0.6 1652 ~ CQ RX6DA KN85 +000000 -9 0.9 1669 ~ YO8CQM I4WQH JN54 diff --git a/external/ft8_lib/test/wav/20m_busy/test_17.wav b/external/ft8_lib/test/wav/20m_busy/test_17.wav new file mode 100644 index 0000000..0afe610 Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_17.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_18.txt b/external/ft8_lib/test/wav/20m_busy/test_18.txt new file mode 100644 index 0000000..064c8dd --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_18.txt @@ -0,0 +1,20 @@ +000000 -24 1.4 265 ~ CT3IQ EI8GVB IO63 +000000 2 0.8 394 ~ CQ RV6AFG KN95 +000000 -11 1.0 779 ~ CQ DM100ZM +000000 -14 0.8 1062 ~ CQ EA5OL IM99 +000000 -24 1.7 1266 ~ CQ OR7EG JO11 +000000 -24 1.3 1505 ~ SQ6PZL TA3AHJ RR73 +000000 -22 1.8 1607 ~ <...> IT9HVZ JM78 +000000 -15 0.8 1686 ~ CQ MM0IMC IO75 +000000 -23 1.1 1826 ~ CQ R4WZ LO67 +000000 -7 1.3 1984 ~ JI1QNP F5CCX RR73 +000000 17 0.8 2046 ~ LU5HA 9A9A RR73 +000000 -23 2.4 2325 ~ R8AU F4AGZ JN38 +000000 -16 0.9 2631 ~ <...> ON3ONX JO20 +000000 -19 0.9 338 ~ PA0CAH JO1COV RR73 +000000 -24 0.9 891 ~ IQ5PJ RG0S 73 +000000 -24 0.9 1009 ~ CQ EA5AMC IM98 +000000 -15 0.9 1401 ~ CT3IQ RV6ARS KN84 +000000 -24 0.6 1669 ~ I4WQH YO8CQM -06 +000000 -17 0.9 2132 ~ <...> LZ365BM RR73 +000000 -24 1.8 2326 ~ R8AU EA3YE JN11 diff --git a/external/ft8_lib/test/wav/20m_busy/test_18.wav b/external/ft8_lib/test/wav/20m_busy/test_18.wav new file mode 100644 index 0000000..5720e36 Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_18.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_19.txt b/external/ft8_lib/test/wav/20m_busy/test_19.txt new file mode 100644 index 0000000..026da69 --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_19.txt @@ -0,0 +1,30 @@ +000000 -6 0.8 561 ~ CQ F5UOU JN06 +000000 11 1.0 709 ~ CQ IK4LZH JN54 +000000 -5 0.9 823 ~ UA3YPL DL1KDA RR73 +000000 0 0.8 890 ~ CQ IQ5PJ JN53 +000000 -9 0.7 955 ~ CQ IU8DMZ JN70 +000000 -5 0.8 992 ~ YC6RMT IK3JLT JN65 +000000 1 0.9 1089 ~ CQ R7NO KN98 +000000 1 0.9 1124 ~ DG1BQC HB9CUZ RRR +000000 -8 0.7 1192 ~ DM2DLG UR7HN -16 +000000 -10 0.1 1285 ~ R8JA 4U1A -23 +000000 -10 0.1 1345 ~ CQ 4U1A JN88 +000000 -14 0.3 1402 ~ RV6ARS CT3IQ -04 +000000 10 0.6 1543 ~ CQ YO9IAB KN25 +000000 -4 0.5 1652 ~ CQ RX6DA KN85 +000000 -4 0.8 1679 ~ CQ F6HUK JN06 +000000 -15 1.0 1931 ~ CQ DH1NAS JO50 +000000 3 0.9 2089 ~ <...> IV3KVC JN65 +000000 -1 0.9 2135 ~ LZ365BM <...> 73 +000000 6 1.1 2279 ~ CQ ON6UF JO10 +000000 -3 0.8 2326 ~ CQ R8AU MO05 +000000 15 1.7 2389 ~ CQ E75C JN93 +000000 -2 1.1 2457 ~ BA7IO EA3ZD JN01 +000000 -7 0.8 2548 ~ CQ OE8GMQ JN66 +000000 6 0.8 2631 ~ <...> OR18OSB RR73 +000000 -7 1.8 569 ~ CQ G3WAG IO82 +000000 -13 1.9 717 ~ UY7IV SQ9JJR JO90 +000000 -15 0.9 1054 ~ <...> F6DEO/QRP +000000 1 1.0 1510 ~ <...> M0XMX IO92 +000000 11 -0.7 1561 ~ 7Z1AL RA3TPE LO25 +000000 -7 1.0 1669 ~ YO8CQM I4WQH R-24 diff --git a/external/ft8_lib/test/wav/20m_busy/test_19.wav b/external/ft8_lib/test/wav/20m_busy/test_19.wav new file mode 100644 index 0000000..3695cf5 Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_19.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_20.txt b/external/ft8_lib/test/wav/20m_busy/test_20.txt new file mode 100644 index 0000000..186b492 --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_20.txt @@ -0,0 +1,20 @@ +000000 -15 1.4 265 ~ CT3IQ EI8GVB IO63 +000000 5 0.8 394 ~ CQ RV6AFG KN95 +000000 1 1.0 543 ~ <...> S51SG JN76 +000000 -15 1.0 780 ~ CQ DM100ZM +000000 -4 1.9 944 ~ <...> IK2ZDT JN45 +000000 -14 0.8 1062 ~ CQ EA5OL IM99 +000000 -11 0.9 1401 ~ CT3IQ RV6ARS R-15 +000000 -24 0.8 1562 ~ OH3BY DJ1DM R-18 +000000 -20 1.7 1607 ~ <...> IT9HVZ JM78 +000000 -8 0.8 1686 ~ DL1GHB MM0IMC -06 +000000 -11 1.4 1826 ~ ES3AT R4WZ -19 +000000 16 0.8 2046 ~ CQ 9A9A JN75 +000000 -21 2.4 2324 ~ R8AU F4AGZ JN38 +000000 -18 0.9 2631 ~ <...> ON3ONX JO20 +000000 -12 0.9 338 ~ CQ JO1COV PM95 +000000 -24 1.2 823 ~ DL1KDA UA3YPL 73 +000000 -24 0.7 1670 ~ I4WQH YO8CQM RR73 +000000 -16 1.0 2132 ~ CQ LZ365BM +000000 -24 1.8 2327 ~ R8AU EA3YE JN11 +000000 -20 0.8 2725 ~ <...> DL8RCH JN68 diff --git a/external/ft8_lib/test/wav/20m_busy/test_20.wav b/external/ft8_lib/test/wav/20m_busy/test_20.wav new file mode 100644 index 0000000..3631fcc Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_20.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_21.txt b/external/ft8_lib/test/wav/20m_busy/test_21.txt new file mode 100644 index 0000000..c140515 --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_21.txt @@ -0,0 +1,34 @@ +000000 -20 -0.3 338 ~ JO1COV RA9UJP NO25 +000000 -2 0.8 560 ~ CQ F5UOU JN06 +000000 6 0.8 637 ~ <...> OE9KFV JN47 +000000 14 0.9 708 ~ CQ IK4LZH JN54 +000000 -6 0.9 823 ~ BI8DHZ DL1KDA -17 +000000 0 0.8 890 ~ CQ IQ5PJ JN53 +000000 2 0.8 992 ~ YC6RMT IK3JLT JN65 +000000 3 0.9 1089 ~ CQ R7NO KN98 +000000 5 0.9 1124 ~ DG1BQC HB9CUZ RRR +000000 -7 0.7 1192 ~ DM2DLG UR7HN -13 +000000 -5 0.1 1285 ~ R8JA 4U1A -23 +000000 -5 0.1 1345 ~ BI8DHZ 4U1A -16 +000000 -11 0.3 1402 ~ RV6ARS CT3IQ RR73 +000000 -12 0.9 1509 ~ <...> OM7OM R+00 +000000 -1 -0.1 1560 ~ 7Z1AL DF2FE JO51 +000000 1 0.8 1679 ~ CQ F6HUK JN06 +000000 -13 1.0 1930 ~ CQ DH1NAS JO50 +000000 4 0.9 2089 ~ <...> IV3KVC JN65 +000000 5 1.1 2133 ~ <...> ON6UF JO10 +000000 4 0.8 2326 ~ EA3YE R8AU -16 +000000 18 1.7 2389 ~ CQ E75C JN93 +000000 2 1.1 2456 ~ BA7IO EA3ZD JN01 +000000 -22 1.0 337 ~ JO1COV PD0WH -13 +000000 -5 1.8 569 ~ EA5INF G3WAG -04 +000000 -6 1.9 717 ~ UY7IV SQ9JJR JO90 +000000 0 0.6 990 ~ YC6RMT IZ7NLM -22 +000000 -13 2.4 1190 ~ JA1FWS RU3OX LO00 +000000 -17 1.8 1267 ~ OR7EG RX3ASQ KO95 +000000 -4 1.9 1561 ~ JA1FWS OK2BV R-13 +000000 1 0.5 1652 ~ CQ RX6DA KN85 +000000 -23 2.0 1969 ~ CQ SQ6PZL JO80 +000000 13 -0.8 2378 ~ CQ SP9LKP JO90 +000000 -11 0.9 1008 ~ EA5AMC PA3GAE JO21 +000000 -8 0.9 1669 ~ YO8CQM I4WQH 73 diff --git a/external/ft8_lib/test/wav/20m_busy/test_21.wav b/external/ft8_lib/test/wav/20m_busy/test_21.wav new file mode 100644 index 0000000..a91c4c1 Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_21.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_22.txt b/external/ft8_lib/test/wav/20m_busy/test_22.txt new file mode 100644 index 0000000..bd237a5 --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_22.txt @@ -0,0 +1,23 @@ +000000 -24 1.4 265 ~ CT3IQ EI8GVB IO63 +000000 -19 0.9 338 ~ RA9UJP JO1COV -11 +000000 2 0.8 394 ~ CQ RV6AFG KN95 +000000 5 1.0 517 ~ <...> S51SG JN76 +000000 -17 1.0 780 ~ YO7IUN <...> +04 +000000 0 2.3 891 ~ IQ5PJ R3KCW KO90 +000000 -4 1.2 944 ~ <...> IK2ZDT JN45 +000000 -17 0.8 1062 ~ CQ EA5OL IM99 +000000 -24 1.6 1266 ~ RX3ASQ OR7EG -02 +000000 -21 0.9 1347 ~ CQ G0OSK IO91 +000000 -24 1.3 1505 ~ OZ5VO TA3AHJ -10 +000000 -17 1.7 1607 ~ <...> IT9HVZ JM78 +000000 -14 0.8 1686 ~ DL1GHB MM0IMC -06 +000000 -19 1.1 1826 ~ ES3AT R4WZ RR73 +000000 17 0.8 2046 ~ LU5HA 9A9A RR73 +000000 -24 1.8 2327 ~ R8AU EA3YE R-13 +000000 -4 1.3 2518 ~ CQ F5CCX JN18 +000000 -21 0.9 2631 ~ <...> ON3ONX JO20 +000000 -17 1.0 482 ~ CQ 2E0LDW IO70 +000000 -24 0.8 1563 ~ OH3BY DJ1DM 73 +000000 -16 1.2 2132 ~ CQ LZ365BM +000000 -2 0.8 2548 ~ CQ OE8GMQ JN66 +000000 -21 0.9 2725 ~ <...> DL8RCH JN68 diff --git a/external/ft8_lib/test/wav/20m_busy/test_22.wav b/external/ft8_lib/test/wav/20m_busy/test_22.wav new file mode 100644 index 0000000..665d9ad Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_22.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_23.txt b/external/ft8_lib/test/wav/20m_busy/test_23.txt new file mode 100644 index 0000000..341686e --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_23.txt @@ -0,0 +1,26 @@ +000000 -17 -0.2 338 ~ JO1COV RA9UJP R+01 +000000 -5 1.8 569 ~ EA5INF G3WAG RR73 +000000 12 0.9 637 ~ <...> OE9KFV JN47 +000000 12 0.9 708 ~ CQ IK4LZH JN54 +000000 -2 0.9 823 ~ BI8DHZ DL1KDA -17 +000000 13 0.8 890 ~ R3KCW IQ5PJ +03 +000000 2 0.9 1124 ~ DG1BQC HB9CUZ RRR +000000 -7 0.7 1191 ~ DM2DLG UR7HN -13 +000000 -2 0.1 1345 ~ BI8DHZ 4U1A -16 +000000 -10 0.3 1402 ~ CQ CT3IQ IM12 +000000 -8 0.9 1509 ~ ZY50Y <...> 73 +000000 1 0.8 1679 ~ CQ F6HUK JN06 +000000 -9 1.0 1929 ~ CQ DH1NAS JO50 +000000 6 0.9 2089 ~ IV3KVC JN65 +000000 11 1.1 2133 ~ <...> ON6UF JO10 +000000 -3 0.8 2326 ~ EA3YE R8AU RR73 +000000 15 1.7 2389 ~ CQ E75C JN93 +000000 5 1.1 2457 ~ BA7IO EA3ZD JN01 +000000 2 0.8 2632 ~ R1OAY <...> R-11 +000000 -1 0.8 560 ~ CQ F5UOU JN06 +000000 -13 1.9 717 ~ UY7IV SQ9JJR JO90 +000000 -3 1.1 793 ~ JA1FWS F8BBL R-15 +000000 -8 2.3 1190 ~ JA1FWS RU3OX LO00 +000000 -15 0.9 1960 ~ JO1COV PD0MNO JO22 +000000 1 -0.9 2378 ~ CQ SP9LKP JO90 +000000 -10 0.9 553 ~ CQ G3ZQQ IO82 diff --git a/external/ft8_lib/test/wav/20m_busy/test_23.wav b/external/ft8_lib/test/wav/20m_busy/test_23.wav new file mode 100644 index 0000000..dee405b Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_23.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_24.txt b/external/ft8_lib/test/wav/20m_busy/test_24.txt new file mode 100644 index 0000000..a5af66f --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_24.txt @@ -0,0 +1,22 @@ +000000 -12 1.5 265 ~ CT3IQ EI8GVB IO63 +000000 8 0.8 394 ~ CQ RV6AFG KN95 +000000 -11 1.0 482 ~ CQ 2E0LDW IO70 +000000 -8 0.9 781 ~ <...> DM100ZM RR73 +000000 8 2.3 890 ~ IQ5PJ R3KCW R+01 +000000 8 1.3 944 ~ <...> IK2ZDT JN45 +000000 -7 0.8 1062 ~ CQ EA5OL IM99 +000000 -13 1.6 1266 ~ RX3ASQ OR7EG -02 +000000 -11 2.4 1402 ~ CT3IQ UY7IV KN97 +000000 -10 1.7 1606 ~ <...> IT9HVZ JM78 +000000 -4 0.8 1685 ~ DL1GHB MM0IMC RR73 +000000 -6 1.0 1826 ~ CQ R4WZ LO67 +000000 18 0.8 2046 ~ CQ 9A9A JN75 +000000 -24 1.8 2327 ~ R8AU EA3YE 73 +000000 3 1.3 2518 ~ CQ F5CCX JN18 +000000 1 0.8 2631 ~ <...> ON8SS JO10 +000000 -12 0.9 338 ~ RA9UJP JO1COV RR73 +000000 -13 0.6 1670 ~ CQ YO8CQM KN37 +000000 -16 1.3 1929 ~ DH1NAS RZ9WA LO74 +000000 -10 0.8 2131 ~ F4AGZ <...> -03 +000000 -6 0.8 2548 ~ CQ OE8GMQ JN66 +000000 -17 0.8 2725 ~ <...> DL8RCH JN68 diff --git a/external/ft8_lib/test/wav/20m_busy/test_24.wav b/external/ft8_lib/test/wav/20m_busy/test_24.wav new file mode 100644 index 0000000..5c251f7 Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_24.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_25.txt b/external/ft8_lib/test/wav/20m_busy/test_25.txt new file mode 100644 index 0000000..93be28a --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_25.txt @@ -0,0 +1,28 @@ +000000 -19 -0.6 338 ~ JO1COV RA9UJP 73 +000000 9 0.8 664 ~ <...> US5IQI KN87 +000000 13 0.9 709 ~ CQ IK4LZH JN54 +000000 0 0.9 823 ~ BI8DHZ DL1KDA -17 +000000 9 0.8 890 ~ R3KCW IQ5PJ RRR +000000 4 0.8 992 ~ YC6RMT IK3JLT JN65 +000000 4 0.9 1123 ~ DG1BQC HB9CUZ RRR +000000 -10 2.5 1190 ~ JA1FWS RU3OX LO00 +000000 -14 0.1 1285 ~ R8JA 4U1A -23 +000000 -15 0.1 1345 ~ BI8DHZ 4U1A -16 +000000 -11 0.3 1402 ~ UY7IV CT3IQ -07 +000000 2 -0.1 1560 ~ 7Z1AL DF2FE JO51 +000000 1 0.8 1637 ~ SA0BYP F6HUK +01 +000000 -8 1.0 1928 ~ RZ9WA DH1NAS -13 +000000 -1 0.9 2089 ~ <...> IV3KVC R-21 +000000 -14 1.1 2201 ~ CQ BD8NBG OL36 +000000 3 0.9 2326 ~ CQ R8AU MO05 +000000 14 1.7 2388 ~ CQ E75C JN93 +000000 -1 1.1 2457 ~ BA7IO EA3ZD JN01 +000000 9 0.8 2631 ~ <...> OR18OSB 73 +000000 1 1.1 793 ~ JA1FWS F8BBL R-15 +000000 -8 0.6 990 ~ YC6RMT IZ7NLM -22 +000000 -22 1.8 1266 ~ OR7EG RX3ASQ R-08 +000000 -1 1.9 1562 ~ JA1FWS OK2BV R-13 +000000 -9 0.7 1939 ~ PD0CIF/PHOTO +000000 -2 2.4 2133 ~ <...> F4AGZ JN38 +000000 4 -1.0 2378 ~ CQ SP9LKP JO90 +000000 -14 2.0 777 ~ DM100ZM <...> 73 diff --git a/external/ft8_lib/test/wav/20m_busy/test_25.wav b/external/ft8_lib/test/wav/20m_busy/test_25.wav new file mode 100644 index 0000000..370655c Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_25.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_26.txt b/external/ft8_lib/test/wav/20m_busy/test_26.txt new file mode 100644 index 0000000..31638d7 --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_26.txt @@ -0,0 +1,23 @@ +000000 -11 1.5 265 ~ CT3IQ EI8GVB IO63 +000000 10 0.8 394 ~ F5UOU RV6AFG R-21 +000000 7 1.0 517 ~ BD8NBG S51SG JN76 +000000 7 2.3 891 ~ IQ5PJ R3KCW 73 +000000 4 1.2 944 ~ <...> IK2ZDT JN45 +000000 -9 0.8 1062 ~ CQ EA5OL IM99 +000000 1 0.9 1191 ~ RU3OX DL4GBA JN47 +000000 -24 1.6 1265 ~ RX3ASQ OR7EG RR73 +000000 -2 1.3 1344 ~ CQ F1BHB JN09 +000000 -10 1.7 1606 ~ <...> IT9HVZ JM78 +000000 -3 0.8 1686 ~ ES3AT MM0IMC -03 +000000 -5 1.1 1826 ~ CQ R4WZ LO67 +000000 14 0.8 2046 ~ LY3BES 9A9A +07 +000000 -11 0.9 2201 ~ BD8NBG OM7OM JN98 +000000 3 0.9 2389 ~ E75C RV6ARS KN84 +000000 11 1.3 2518 ~ RU0LL F5CCX -10 +000000 -17 0.8 2724 ~ <...> DL8RCH JN68 +000000 -7 0.9 481 ~ CQ 2E0LDW IO70 +000000 -8 0.8 1347 ~ CQ G0OSK IO91 +000000 -24 1.1 1509 ~ <...> ZY50Y RR73 +000000 -21 1.3 1928 ~ DH1NAS UA3NFG LO28 +000000 -9 1.0 2131 ~ F4AGZ <...> -02 +000000 -2 0.8 2547 ~ CQ OE8GMQ JN66 diff --git a/external/ft8_lib/test/wav/20m_busy/test_26.wav b/external/ft8_lib/test/wav/20m_busy/test_26.wav new file mode 100644 index 0000000..eedbc5a Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_26.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_27.txt b/external/ft8_lib/test/wav/20m_busy/test_27.txt new file mode 100644 index 0000000..224d7a2 --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_27.txt @@ -0,0 +1,29 @@ +000000 -4 0.8 560 ~ RV6AFG F5UOU RR73 +000000 13 0.9 709 ~ CQ IK4LZH JN54 +000000 -9 0.9 824 ~ BI8DHZ DL1KDA -17 +000000 2 0.8 890 ~ R3KCW IQ5PJ 73 +000000 1 0.8 992 ~ YC6RMT IK3JLT JN65 +000000 1 0.9 1124 ~ CQ HB9CUZ JN47 +000000 -10 0.9 1265 ~ CQ SV2BRA KN10 +000000 -6 0.1 1285 ~ BI8DHZ 4U1A RR73 +000000 -7 0.1 1345 ~ SM3MXR 4U1A R-08 +000000 12 0.6 1444 ~ ON2RK YO9IAB KN25 +000000 -6 -0.1 1560 ~ 7Z1AL DF2FE JO51 +000000 -6 0.8 1637 ~ SA0BYP F6HUK RR73 +000000 7 0.1 1826 ~ R4WZ UV5IW KN88 +000000 -9 1.0 1927 ~ RZ9WA DH1NAS -13 +000000 -16 0.9 1997 ~ R4WZ PA3GAE JO21 +000000 1 0.9 2089 ~ ZY50Y <...> 73 +000000 -11 2.4 2132 ~ <...> F4AGZ R-05 +000000 -21 1.5 2201 ~ CQ BD8NBG OL36 +000000 -3 0.8 2326 ~ CQ R8AU MO05 +000000 1 1.1 2456 ~ BA7IO EA3ZD JN01 +000000 0 0.8 2631 ~ CQ OR18OSB +000000 -7 1.9 569 ~ CQ G3WAG IO82 +000000 12 0.8 664 ~ <...> US5IQI KN87 +000000 -7 1.0 793 ~ JA1FWS F8BBL R-15 +000000 -2 0.7 955 ~ CQ IU8DMZ JN70 +000000 -4 0.6 990 ~ YC6RMT IZ7NLM -22 +000000 -7 1.9 1562 ~ JA1FWS OK2BV R-13 +000000 2 1.1 1826 ~ R4WZ ON6UF JO10 +000000 -9 0.9 553 ~ CQ G3ZQQ IO82 diff --git a/external/ft8_lib/test/wav/20m_busy/test_27.wav b/external/ft8_lib/test/wav/20m_busy/test_27.wav new file mode 100644 index 0000000..5a6fc2d Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_27.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_28.txt b/external/ft8_lib/test/wav/20m_busy/test_28.txt new file mode 100644 index 0000000..2f9b649 --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_28.txt @@ -0,0 +1,25 @@ +000000 -19 1.5 265 ~ CT3IQ EI8GVB IO63 +000000 9 0.8 394 ~ F5UOU RV6AFG R-21 +000000 15 1.0 517 ~ BD8NBG S51SG JN76 +000000 -6 0.9 782 ~ CQ DM100ZM +000000 -4 0.8 1062 ~ CQ EA5OL IM99 +000000 -19 0.9 1265 ~ SV2BRA DJ1DM JN58 +000000 -7 1.3 1345 ~ CQ F1BHB JN09 +000000 -16 2.4 1402 ~ CT3IQ UY7IV R-10 +000000 -15 1.0 1561 ~ OH3BY IZ6MPZ JN63 +000000 -9 0.8 1686 ~ ES3AT MM0IMC RR73 +000000 -11 1.1 1826 ~ CQ R4WZ LO67 +000000 19 0.8 2046 ~ LY3BES 9A9A RR73 +000000 9 -0.7 2198 ~ BD8NBG RA3TPE LO25 +000000 -14 0.9 2388 ~ E75C RV6ARS R+03 +000000 -10 1.3 2518 ~ RU0LL F5CCX RR73 +000000 -4 2.3 2632 ~ <...> R3KCW KO90 +000000 -6 1.0 480 ~ CQ 2E0LDW IO70 +000000 -7 0.9 1347 ~ CQ G0OSK IO91 +000000 -24 0.9 1509 ~ CQ ZY50Y +000000 2 0.9 1740 ~ BD8NBG DL4SBF JN48 +000000 -14 1.1 2130 ~ <...> LZ365BM RR73 +000000 -3 0.8 2548 ~ CQ OE8GMQ JN66 +000000 1 0.8 2631 ~ <...> ON8SS JO10 +000000 -17 0.9 2724 ~ <...> DL8RCH JN68 +000000 -9 1.0 456 ~ CQ ON2RK JO20 diff --git a/external/ft8_lib/test/wav/20m_busy/test_28.wav b/external/ft8_lib/test/wav/20m_busy/test_28.wav new file mode 100644 index 0000000..d5a9af7 Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_28.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_29.txt b/external/ft8_lib/test/wav/20m_busy/test_29.txt new file mode 100644 index 0000000..b6ae20e --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_29.txt @@ -0,0 +1,23 @@ +000000 6 0.8 663 ~ <...> US5IQI KN87 +000000 14 0.9 709 ~ CQ IK4LZH JN54 +000000 7 0.8 890 ~ CQ IQ5PJ JN53 +000000 -2 0.8 992 ~ YC6RMT IK3JLT JN65 +000000 6 0.9 1124 ~ CQ HB9CUZ JN47 +000000 -3 0.9 1265 ~ DJ1DM SV2BRA +03 +000000 -11 0.1 1345 ~ <...> 4U1A -20 +000000 0 0.6 1444 ~ <...> YO9IAB KN25 +000000 -4 1.9 1561 ~ JA1FWS OK2BV R-13 +000000 10 0.0 1826 ~ R4WZ UV5IW KN88 +000000 -8 1.0 1926 ~ RZ9WA DH1NAS -13 +000000 -15 0.9 1997 ~ R4WZ PA3GAE JO21 +000000 -8 2.4 2132 ~ LZ365BM <...> 73 +000000 -13 0.9 2201 ~ RA3TPE BD8NBG -18 +000000 -1 0.9 2326 ~ CQ R8AU MO05 +000000 13 1.7 2388 ~ RV6ARS E75C RR73 +000000 2 0.8 2632 ~ CQ OR18OSB +000000 0 0.7 955 ~ CQ IU8DMZ JN70 +000000 -2 0.1 1285 ~ SM3MXR 4U1A R-08 +000000 -5 0.3 1402 ~ UY7IV CT3IQ RR73 +000000 2 -0.1 1560 ~ 7Z1AL DF2FE JO51 +000000 0 -1.1 2378 ~ ES1KK SP9LKP -13 +000000 -4 0.6 990 ~ YC6RMT IZ7NLM -22 diff --git a/external/ft8_lib/test/wav/20m_busy/test_29.wav b/external/ft8_lib/test/wav/20m_busy/test_29.wav new file mode 100644 index 0000000..54b834c Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_29.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_30.txt b/external/ft8_lib/test/wav/20m_busy/test_30.txt new file mode 100644 index 0000000..0ac73af --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_30.txt @@ -0,0 +1,27 @@ +000000 -9 1.5 265 ~ CT3IQ EI8GVB IO63 +000000 16 0.8 394 ~ F5UOU RV6AFG R-21 +000000 -5 1.0 478 ~ CQ 2E0LDW IO70 +000000 -5 0.9 571 ~ G3WAG SP4TXI KO03 +000000 -1 1.0 782 ~ CQ DM100ZM +000000 -2 0.8 1063 ~ CQ EA5OL IM99 +000000 0 0.9 1191 ~ RU3OX DL4GBA JN47 +000000 -5 1.0 1239 ~ <...> 9A3KG JN83 +000000 1 1.3 1344 ~ CQ F1BHB JN09 +000000 -6 2.1 1402 ~ CT3IQ UY7IV 73 +000000 -1 0.8 1562 ~ OH3BY IZ6MPZ R-07 +000000 -13 1.7 1606 ~ CT3IQ IT9HVZ JM78 +000000 -11 1.0 1826 ~ CQ R4WZ LO67 +000000 16 0.8 2046 ~ CQ 9A9A JN75 +000000 10 -0.7 2199 ~ BD8NBG RA3TPE LO25 +000000 -6 0.9 2388 ~ E75C RV6ARS 73 +000000 7 1.3 2518 ~ CQ F5CCX JN18 +000000 9 2.3 2632 ~ <...> R3KCW KO90 +000000 -4 1.1 456 ~ CQ ON2RK JO20 +000000 -7 0.8 578 ~ CQ PA33EUDXF +000000 -1 0.8 1264 ~ SV2BRA DJ1DM R-08 +000000 -13 0.9 1508 ~ ES1KK <...> -16 +000000 -5 1.3 2130 ~ US5IQI <...> -08 +000000 5 1.0 2202 ~ BD8NBG DJ2BW -15 +000000 -3 0.8 2548 ~ CQ OE8GMQ JN66 +000000 -12 0.8 544 ~ LU5HA RA0ASM -13 +000000 -12 0.8 1562 ~ CQ 7Z1AL LL56 diff --git a/external/ft8_lib/test/wav/20m_busy/test_30.wav b/external/ft8_lib/test/wav/20m_busy/test_30.wav new file mode 100644 index 0000000..0ab8379 Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_30.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_31.txt b/external/ft8_lib/test/wav/20m_busy/test_31.txt new file mode 100644 index 0000000..3c774d3 --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_31.txt @@ -0,0 +1,24 @@ +000000 -6 1.8 569 ~ SP4TXI G3WAG +13 +000000 12 0.9 708 ~ CQ IK4LZH JN54 +000000 -10 1.0 793 ~ JA1FWS F8BBL 73 +000000 9 0.8 890 ~ CQ IQ5PJ JN53 +000000 -1 0.8 992 ~ YC6RMT IK3JLT JN65 +000000 3 0.9 1123 ~ CQ HB9CUZ JN47 +000000 -3 0.9 1265 ~ DJ1DM SV2BRA RR73 +000000 10 0.6 1444 ~ <...> YO9IAB KN25 +000000 -8 0.6 1508 ~ <...> IZ7NLM -16 +000000 -2 -0.1 1560 ~ 7Z1AL DF2FE JO51 +000000 -4 0.9 1826 ~ R4WZ DJ4TM JN47 +000000 -4 1.0 1925 ~ CQ DH1NAS JO50 +000000 -19 0.9 1997 ~ R4WZ PA3GAE JO21 +000000 -3 0.9 2326 ~ CQ R8AU MO05 +000000 17 1.7 2388 ~ CQ E75C JN93 +000000 -3 0.8 579 ~ <...> OK6LZ JN99 +000000 7 0.8 664 ~ <...> US5IQI R-06 +000000 -4 0.9 888 ~ ZL2OK DL1KDA JO30 +000000 -2 0.9 947 ~ <...> E77VM R-11 +000000 -5 0.9 1158 ~ CQ HA1BF JN86 +000000 -17 1.6 1960 ~ JO1COV PD0MNO JO22 +000000 -24 0.7 2141 ~ CQ PY5JO GG54 +000000 1 -1.1 2378 ~ ES1KK SP9LKP -13 +000000 -6 0.7 955 ~ CQ IU8DMZ JN70 diff --git a/external/ft8_lib/test/wav/20m_busy/test_31.wav b/external/ft8_lib/test/wav/20m_busy/test_31.wav new file mode 100644 index 0000000..6e0e71d Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_31.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_32.txt b/external/ft8_lib/test/wav/20m_busy/test_32.txt new file mode 100644 index 0000000..08638f0 --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_32.txt @@ -0,0 +1,25 @@ +000000 -10 1.5 265 ~ CT3IQ EI8GVB IO63 +000000 13 0.8 394 ~ F5UOU RV6AFG R-21 +000000 -4 1.0 478 ~ CQ 2E0LDW IO70 +000000 0 0.9 577 ~ CQ PA33EUDXF +000000 -3 1.0 783 ~ CQ DM100ZM +000000 -4 0.8 1063 ~ CQ EA5OL IM99 +000000 3 0.9 1191 ~ RU3OX DL4GBA JN47 +000000 -4 0.8 1265 ~ SV2BRA DJ1DM 73 +000000 5 1.3 1344 ~ CQ F1BHB JN09 +000000 -1 0.9 1561 ~ OH3BY IZ6MPZ 73 +000000 -11 1.7 1605 ~ CT3IQ IT9HVZ JM78 +000000 -7 0.8 1685 ~ RM3T MM0IMC -18 +000000 -3 0.8 1751 ~ JA7GFI DK3BT JO40 +000000 -11 1.2 1826 ~ LY3BES R4WZ -07 +000000 16 0.8 2046 ~ CQ 9A9A JN75 +000000 7 -0.7 2200 ~ BD8NBG RA3TPE LO25 +000000 9 1.3 2520 ~ F4AGZ F5CCX -06 +000000 -10 0.9 2631 ~ <...> ON8SS JO10 +000000 -6 1.1 456 ~ EA5INF ON2RK -10 +000000 -9 1.0 645 ~ <...> DH3JF JO31 +000000 -6 0.9 1239 ~ <...> 9A3KG JN83 +000000 -15 0.8 1561 ~ CQ 7Z1AL LL56 +000000 -2 0.8 1740 ~ CQ DL4SBF JN48 +000000 -8 1.2 2130 ~ <...> LZ365BM RR73 +000000 4 1.0 2201 ~ BD8NBG DJ2BW -15 diff --git a/external/ft8_lib/test/wav/20m_busy/test_32.wav b/external/ft8_lib/test/wav/20m_busy/test_32.wav new file mode 100644 index 0000000..dd4920a Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_32.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_33.txt b/external/ft8_lib/test/wav/20m_busy/test_33.txt new file mode 100644 index 0000000..7032ed3 --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_33.txt @@ -0,0 +1,28 @@ +000000 0 0.9 495 ~ CQ R7NO KN98 +000000 -10 1.9 569 ~ SP4TXI G3WAG RR73 +000000 6 0.8 663 ~ LZ365BM <...> 73 +000000 5 0.9 708 ~ CQ IK4LZH JN54 +000000 16 1.0 837 ~ CQ DX G0PQO IO92 +000000 4 0.8 891 ~ CQ IQ5PJ JN53 +000000 -5 0.7 955 ~ CQ IU8DMZ JN70 +000000 -2 0.8 992 ~ YC6RMT IK3JLT JN65 +000000 5 0.9 1123 ~ CQ HB9CUZ JN47 +000000 -2 0.1 1285 ~ SM3MXR 4U1A R-08 +000000 -2 0.1 1345 ~ <...> 4U1A -20 +000000 -12 0.3 1402 ~ IT9HVZ CT3IQ -02 +000000 -7 0.6 1508 ~ <...> IZ7NLM -16 +000000 -17 0.8 1899 ~ PP5AM DM7GBW JO71 +000000 -12 1.0 2201 ~ RA3TPE BD8NBG -17 +000000 5 0.9 2326 ~ CQ R8AU MO05 +000000 14 1.7 2388 ~ CQ E75C JN93 +000000 1 2.4 2520 ~ F5CCX F4AGZ JN38 +000000 2 0.8 2631 ~ R3KCW <...> -11 +000000 -7 1.1 500 ~ <...> RD4AN LN19 +000000 -6 0.8 577 ~ <...> OK6LZ JN99 +000000 -2 0.8 737 ~ SP5QAC F5UOU -11 +000000 -2 0.9 888 ~ ZL2OK DL1KDA R-24 +000000 -10 0.8 947 ~ <...> E77VM R-11 +000000 -6 0.8 1158 ~ CQ HA1BF JN86 +000000 -23 0.8 2141 ~ CQ PY5JO GG54 +000000 -17 1.1 2236 ~ F1BHB BA7IO -21 +000000 -10 0.9 551 ~ CQ G3ZQQ IO82 diff --git a/external/ft8_lib/test/wav/20m_busy/test_33.wav b/external/ft8_lib/test/wav/20m_busy/test_33.wav new file mode 100644 index 0000000..62b4c77 Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_33.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_34.txt b/external/ft8_lib/test/wav/20m_busy/test_34.txt new file mode 100644 index 0000000..f41fda9 --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_34.txt @@ -0,0 +1,25 @@ +000000 -18 1.4 265 ~ CT3IQ EI8GVB IO63 +000000 1 0.8 394 ~ F5UOU RV6AFG R-21 +000000 -8 0.8 576 ~ UX0KR <...> -04 +000000 -8 1.0 783 ~ R2DP <...> +00 +000000 -7 0.9 890 ~ IQ5PJ IZ6MPZ JN63 +000000 -12 0.8 1063 ~ CQ EA5OL IM99 +000000 -6 0.9 1191 ~ RU3OX DL4GBA JN47 +000000 -19 0.9 1239 ~ <...> 9A3KG JN83 +000000 -9 1.3 1344 ~ JG2PQN F1BHB -24 +000000 -21 1.7 1605 ~ CT3IQ IT9HVZ R-03 +000000 -13 0.8 1751 ~ JA7GFI DK3BT JO40 +000000 -22 1.3 1826 ~ LY3BES R4WZ RR73 +000000 19 0.8 2046 ~ <...> 9A9A -22 +000000 8 -0.5 2200 ~ BD8NBG RA3TPE R-17 +000000 -13 0.9 2388 ~ ES3AT RV6ARS -13 +000000 -5 1.3 2520 ~ F4AGZ F5CCX -04 +000000 -24 0.9 991 ~ CQ YC6RMT NJ81 +000000 -9 -0.1 1372 ~ IQ5PJ RA9H NO26 +000000 -18 0.8 1561 ~ CQ 7Z1AL LL56 +000000 -4 0.8 1685 ~ RM3T MM0IMC -18 +000000 -10 0.9 1740 ~ CQ DL4SBF JN48 +000000 -13 1.2 2130 ~ CQ LZ365BM +000000 -5 1.0 2201 ~ BD8NBG DJ2BW -15 +000000 -4 0.9 2363 ~ SV2BRA DJ1DM 73 +000000 -20 0.3 2626 ~ <...> DK3WL JO50 diff --git a/external/ft8_lib/test/wav/20m_busy/test_34.wav b/external/ft8_lib/test/wav/20m_busy/test_34.wav new file mode 100644 index 0000000..63add00 Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_34.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_35.txt b/external/ft8_lib/test/wav/20m_busy/test_35.txt new file mode 100644 index 0000000..111ccdd --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_35.txt @@ -0,0 +1,32 @@ +000000 6 0.9 495 ~ CQ R7NO KN98 +000000 -7 0.8 577 ~ <...> OK6LZ JN99 +000000 11 1.0 709 ~ CQ IK4LZH JN54 +000000 14 1.0 837 ~ CQ DX G0PQO IO92 +000000 0 0.8 891 ~ R1AV IQ5PJ -21 +000000 1 0.8 992 ~ YC6RMT IK3JLT JN65 +000000 3 0.9 1123 ~ CQ HB9CUZ JN47 +000000 0 0.9 1265 ~ CQ SV2BRA KN10 +000000 -13 0.1 1345 ~ R8JA 4U1A RR73 +000000 -5 0.3 1402 ~ IT9HVZ CT3IQ RR73 +000000 -4 0.7 1508 ~ ZY50Y <...> RRR +000000 0 0.9 2047 ~ 9A9A DJ4TM JN47 +000000 -17 0.9 2201 ~ RA3TPE BD8NBG RR73 +000000 12 1.7 2388 ~ CQ E75C JN93 +000000 17 -0.1 2484 ~ CQ UV5IW KN88 +000000 2 0.8 2631 ~ R3KCW <...> -11 +000000 1 0.9 2721 ~ SP5QAC R8AU MO05 +000000 -13 0.8 339 ~ JO1COV PE1OYB JO21 +000000 -7 1.1 500 ~ <...> RD4AN LN19 +000000 -4 1.9 569 ~ CQ G3WAG IO82 +000000 -2 0.8 737 ~ SP5QAC F5UOU -11 +000000 -1 0.9 888 ~ ZL2OK DL1KDA R-24 +000000 1 0.7 955 ~ CQ IU8DMZ JN70 +000000 -5 0.9 1158 ~ CQ HA1BF JN86 +000000 -10 1.0 1233 ~ PP5AM DH1NAS JO50 +000000 -7 0.1 1285 ~ <...> 4U1A -20 +000000 -22 1.5 1968 ~ CQ SQ6PZL JO80 +000000 -3 -1.1 2378 ~ 9A9A SP9LKP JO90 +000000 5 2.4 2520 ~ F5CCX F4AGZ JN38 +000000 -7 0.9 550 ~ CQ G3ZQQ IO82 +000000 -7 0.9 947 ~ E77VM R-11 +000000 -8 0.8 2547 ~ CQ OE8GMQ JN66 diff --git a/external/ft8_lib/test/wav/20m_busy/test_35.wav b/external/ft8_lib/test/wav/20m_busy/test_35.wav new file mode 100644 index 0000000..2901c44 Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_35.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_36.txt b/external/ft8_lib/test/wav/20m_busy/test_36.txt new file mode 100644 index 0000000..fc0f5ca --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_36.txt @@ -0,0 +1,24 @@ +000000 -19 1.4 265 ~ CT3IQ EI8GVB IO63 +000000 5 0.9 394 ~ F5UOU RV6AFG R-21 +000000 -12 0.8 576 ~ <...> PA33EUDXF RR73 +000000 -2 1.0 783 ~ <...> DM100ZM RR73 +000000 -4 0.9 1078 ~ R8AU R6LIG KN97 +000000 -13 0.9 1191 ~ RU3OX DL4GBA JN47 +000000 -10 0.9 1239 ~ <...> 9A3KG JN83 +000000 -4 1.3 1343 ~ JG2PQN F1BHB -24 +000000 -16 1.7 1605 ~ CT3IQ IT9HVZ 73 +000000 -8 0.8 1685 ~ RM3T MM0IMC -18 +000000 -14 0.8 1751 ~ JA7GFI DK3BT JO40 +000000 -16 1.0 1826 ~ CQ R4WZ LO67 +000000 19 0.8 2046 ~ <...> 9A9A -22 +000000 10 -0.7 2200 ~ BD8NBG RA3TPE 73 +000000 -16 0.9 2388 ~ ES3AT RV6ARS RR73 +000000 -12 1.1 2457 ~ BA7IO EA3ZD JN01 +000000 -2 1.3 2519 ~ F4AGZ F5CCX -17 +000000 -22 1.0 645 ~ PY5JO DH3JF JO31 +000000 -4 0.8 1063 ~ CQ EA5OL IM99 +000000 -9 0.8 1372 ~ IQ5PJ RA9H NO26 +000000 -18 0.8 1561 ~ CQ 7Z1AL LL56 +000000 -20 1.3 1926 ~ DH1NAS RZ9WA R-16 +000000 -17 1.2 2130 ~ CQ LZ365BM +000000 3 1.0 2201 ~ BD8NBG DJ2BW -15 diff --git a/external/ft8_lib/test/wav/20m_busy/test_36.wav b/external/ft8_lib/test/wav/20m_busy/test_36.wav new file mode 100644 index 0000000..1d1e817 Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_36.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_37.txt b/external/ft8_lib/test/wav/20m_busy/test_37.txt new file mode 100644 index 0000000..24d14f0 --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_37.txt @@ -0,0 +1,24 @@ +000000 -20 0.8 339 ~ JO1COV PE1OYB JO21 +000000 3 0.9 494 ~ PA3GAE R7NO -03 +000000 -4 1.9 569 ~ CQ G3WAG IO82 +000000 13 0.9 708 ~ CQ IK4LZH JN54 +000000 5 1.0 837 ~ CQ DX G0PQO IO92 +000000 11 0.8 891 ~ R1AV IQ5PJ -15 +000000 -2 0.8 992 ~ YC6RMT IK3JLT JN65 +000000 -4 0.9 1078 ~ R6LIG R8AU -11 +000000 4 0.9 1123 ~ CQ HB9CUZ JN47 +000000 -9 0.7 1188 ~ DM2DLG UR7HN RR73 +000000 -8 0.1 1345 ~ CQ 4U1A JN88 +000000 -7 0.6 1507 ~ ZY50Y <...> RRR +000000 -1 0.9 2130 ~ <...> I4WQH JN54 +000000 18 1.7 2388 ~ CQ E75C JN93 +000000 -2 0.8 2632 ~ CQ OR18OSB +000000 -6 1.1 500 ~ <...> RD4AN LN19 +000000 -5 0.8 577 ~ <...> OK6LZ JN99 +000000 10 0.7 663 ~ <...> US5IQI KN87 +000000 -4 0.9 947 ~ E77VM R-11 +000000 -4 0.9 1158 ~ CQ HA1BF JN86 +000000 -18 1.4 1739 ~ CQ DX PD0MNO JO22 +000000 -10 0.8 2281 ~ PP5AM DM7GBW JO71 +000000 -2 -1.1 2378 ~ 9A9A SP9LKP JO90 +000000 -7 0.9 550 ~ R4WZ G3ZQQ IO82 diff --git a/external/ft8_lib/test/wav/20m_busy/test_37.wav b/external/ft8_lib/test/wav/20m_busy/test_37.wav new file mode 100644 index 0000000..c800a83 Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_37.wav differ diff --git a/external/ft8_lib/test/wav/20m_busy/test_38.txt b/external/ft8_lib/test/wav/20m_busy/test_38.txt new file mode 100644 index 0000000..816946e --- /dev/null +++ b/external/ft8_lib/test/wav/20m_busy/test_38.txt @@ -0,0 +1,19 @@ +000000 -12 1.4 265 ~ CT3IQ EI8GVB IO63 +000000 3 0.9 395 ~ F5UOU RV6AFG R-21 +000000 -24 1.0 575 ~ CQ 2E0LDW IO70 +000000 -12 1.2 782 ~ R2DP IK2ZDT JN45 +000000 -12 0.8 1063 ~ CQ EA5OL IM99 +000000 -10 0.9 1191 ~ RU3OX DL4GBA JN47 +000000 -9 1.2 1343 ~ JG2PQN F1BHB -24 +000000 -24 1.7 1604 ~ UV5IW IT9HVZ JM78 +000000 -12 0.8 1685 ~ RM3T MM0IMC -18 +000000 19 0.8 2046 ~ <...> 9A9A -22 +000000 -17 1.0 2201 ~ BD8NBG DJ2BW -15 +000000 -22 1.2 2456 ~ BA7IO EA3ZD JN01 +000000 -11 1.3 2519 ~ F4AGZ F5CCX -17 +000000 -6 2.3 2632 ~ <...> R3KCW KO90 +000000 -24 1.0 645 ~ PY5JO DH3JF JO31 +000000 -19 1.0 1239 ~ <...> 9A3KG JN83 +000000 -19 1.0 1264 ~ SV2BRA R4IG LO43 +000000 -20 0.8 1454 ~ CQ RA9H NO26 +000000 -12 0.8 1560 ~ G0PQO 7Z1AL LL56 diff --git a/external/ft8_lib/test/wav/20m_busy/test_38.wav b/external/ft8_lib/test/wav/20m_busy/test_38.wav new file mode 100644 index 0000000..e3b6264 Binary files /dev/null and b/external/ft8_lib/test/wav/20m_busy/test_38.wav differ diff --git a/external/ft8_lib/test/wav/websdr_test1.txt b/external/ft8_lib/test/wav/websdr_test1.txt new file mode 100644 index 0000000..586bcbd --- /dev/null +++ b/external/ft8_lib/test/wav/websdr_test1.txt @@ -0,0 +1,18 @@ +000000 -17 -0.6 309 ~ G4CUS SP4FCA +10 +000000 4 2.2 587 ~ LZ1LZ G4UJS IO83 +000000 -11 0.6 691 ~ YO6OGJ F4IAG R-09 +000000 -5 1.1 809 ~ SQ5FBI G3NDC IO91 +000000 16 1.1 1109 ~ CQ IK4LZH JN54 Italy +000000 8 2.4 1517 ~ GM0LIR UA9SIX -09 +000000 4 1.1 1909 ~ R2EA IZ4OUL R-08 +000000 -2 0.9 2049 ~ CQ MM1AWV IO75 Scotland +000000 5 -0.4 2091 ~ ES5GI DD3SF 73 +000000 -1 1.0 2267 ~ CQ EA1ABT IN73 Spain +000000 9 0.6 2315 ~ 2M0OGG RA6ABO KN96 +000000 7 1.0 2535 ~ CQ IZ3XJM JN55 Italy +000000 -11 1.0 528 ~ VK3EVE SQ3MZM -24 +000000 -14 1.2 598 ~ LZ1CWK DC8VA RR73 +000000 -24 1.2 706 ~ CQ EA1HTF IN52 Spain +000000 -15 1.1 793 ~ YO7CGS A41ZZ -11 +000000 -6 1.1 1506 ~ R2ATW IZ0VLL -16 +000000 -1 1.1 2229 ~ CQ DX Z33Z KN11 Macedonia diff --git a/external/ft8_lib/test/wav/websdr_test1.wav b/external/ft8_lib/test/wav/websdr_test1.wav new file mode 100644 index 0000000..b4299f3 Binary files /dev/null and b/external/ft8_lib/test/wav/websdr_test1.wav differ diff --git a/external/ft8_lib/test/wav/websdr_test10.txt b/external/ft8_lib/test/wav/websdr_test10.txt new file mode 100644 index 0000000..a0d1897 --- /dev/null +++ b/external/ft8_lib/test/wav/websdr_test10.txt @@ -0,0 +1,15 @@ +000000 -4 1.3 335 ~ AE0XI R7CA -15 +000000 -6 0.1 534 ~ NU2Q OE4RWD R-02 +000000 -14 1.0 725 ~ IW9CTR PY5HT R-15 +000000 -14 0.2 988 ~ LU3DW EA8BEV IL17 +000000 -8 0.1 1080 ~ W1OP WA1TGN FN42 +000000 -10 0.2 1166 ~ OE5WRO SV2BRT KN10 +000000 -2 0.1 1432 ~ CQ F4DFQ JN05 France +000000 -11 0.1 1737 ~ CQ PY5EJ GG54 Brazil +000000 -20 0.1 1835 ~ SA6SKA KN4PHS EM64 +000000 -8 -0.9 2053 ~ CQ R7EL LN04 EU Russia +000000 5 0.2 2105 ~ K8JDC LZ3CQ -25 +000000 -10 0.1 2301 ~ YO3IAI WB2REM -18 +000000 -19 1.0 2434 ~ DK7ZT NU1T -07 +000000 -18 0.2 2578 ~ CT7AIX WG5D EM62 +000000 -8 -1.7 333 ~ K1GUY NA4RR EM61 diff --git a/external/ft8_lib/test/wav/websdr_test10.wav b/external/ft8_lib/test/wav/websdr_test10.wav new file mode 100644 index 0000000..972d8f7 Binary files /dev/null and b/external/ft8_lib/test/wav/websdr_test10.wav differ diff --git a/external/ft8_lib/test/wav/websdr_test11.txt b/external/ft8_lib/test/wav/websdr_test11.txt new file mode 100644 index 0000000..59e30ea --- /dev/null +++ b/external/ft8_lib/test/wav/websdr_test11.txt @@ -0,0 +1,23 @@ +000000 -11 -1.5 310 ~ EA8BEV LU3DW -13 +000000 -22 0.2 609 ~ CQ N2BJ EN61 U.S.A. +000000 -4 -0.2 734 ~ OE4RWD NU2Q RR73 +000000 9 0.1 903 ~ CQ IK4LZH JN54 Italy +000000 -12 0.1 1125 ~ CQ SV2FPI KN10 Greece +000000 -10 0.2 1320 ~ R7EL VE9FI FN75 +000000 -12 0.0 1432 ~ CQ 9A7DA JN86 Croatia +000000 -8 -0.0 1544 ~ PD3JO IZ2ODN JN55 +000000 11 0.1 1653 ~ CQ HA1RB JN86 Hungary +000000 3 -0.0 1881 ~ CQ PD1ECA JO32 Netherlands +000000 -9 -0.0 1955 ~ CQ PY1SX GG87 Brazil +000000 -19 -2.2 2080 ~ K4VBM HA8EK RR73 +000000 -18 -2.2 2130 ~ K4VBM HA8EK RR73 +000000 4 1.5 2198 ~ F4DFQ F5LOW IN95 +000000 7 -2.2 2230 ~ K4VBM HA8EK RR73 +000000 -3 -1.2 2601 ~ K2DSW IU8LLZ R-16 +000000 10 0.3 2830 ~ CQ F8IJV/P IN97 France +000000 -20 0.1 1219 ~ KC8MUE V51MA RRR +000000 -9 -0.8 1619 ~ G3PXT HA5MG R+01 +000000 -7 0.2 1687 ~ OK1AW G3JFS R+02 +000000 -11 -1.1 2036 ~ M0LMR IW1AYD 73 +000000 -2 0.1 2218 ~ K3ZK IK2ZDT RR73 +000000 -18 0.1 2406 ~ CQ 2E0PKK IO90 England diff --git a/external/ft8_lib/test/wav/websdr_test11.wav b/external/ft8_lib/test/wav/websdr_test11.wav new file mode 100644 index 0000000..e01b73e Binary files /dev/null and b/external/ft8_lib/test/wav/websdr_test11.wav differ diff --git a/external/ft8_lib/test/wav/websdr_test12.txt b/external/ft8_lib/test/wav/websdr_test12.txt new file mode 100644 index 0000000..dbac4ab --- /dev/null +++ b/external/ft8_lib/test/wav/websdr_test12.txt @@ -0,0 +1,14 @@ +000000 -13 0.7 724 ~ IW9CTR PY5HT 73 +000000 -19 0.1 988 ~ LU3DW EA8BEV R-03 +000000 -9 0.1 1080 ~ W1OP WA1TGN FN42 +000000 -7 0.1 1177 ~ CQ G0RQL IO70 England +000000 -3 0.0 1285 ~ DH0KAI IZ0MQN -20 +000000 -15 0.1 1737 ~ CQ PY5EJ GG54 Brazil +000000 -10 -0.9 2052 ~ VE9FI R7EL -12 +000000 4 0.1 2104 ~ IZ2ODN LZ3CQ +03 +000000 -17 0.5 2218 ~ IK2ZDT K3ZK R-14 +000000 -12 0.1 2794 ~ YO9HP WA6JRZ CM97 +000000 -20 0.1 1124 ~ SV2FPI KA5M EM32 +000000 -14 0.2 1165 ~ OE5WRO SV2BRT KN10 +000000 -14 0.3 2019 ~ YO9HP K6DRY CM98 +000000 -7 0.1 2136 ~ CQ M0SAS IO82 England diff --git a/external/ft8_lib/test/wav/websdr_test12.wav b/external/ft8_lib/test/wav/websdr_test12.wav new file mode 100644 index 0000000..b5ce453 Binary files /dev/null and b/external/ft8_lib/test/wav/websdr_test12.wav differ diff --git a/external/ft8_lib/test/wav/websdr_test13.txt b/external/ft8_lib/test/wav/websdr_test13.txt new file mode 100644 index 0000000..dc50f4d --- /dev/null +++ b/external/ft8_lib/test/wav/websdr_test13.txt @@ -0,0 +1,13 @@ +000000 -4 -1.5 309 ~ EA8BEV LU3DW RR73 +000000 5 0.1 903 ~ CQ IK4LZH JN54 Italy +000000 -11 0.3 1125 ~ KA5M SV2FPI -13 +000000 -8 0.2 1320 ~ R7EL VE9FI R-07 +000000 -8 0.1 1431 ~ CQ 9A7DA JN86 Croatia +000000 -6 -0.0 1543 ~ PD3JO IZ2ODN R-13 +000000 8 0.1 1653 ~ CQ HA1RB JN86 Hungary +000000 5 -0.0 1881 ~ CQ PD1ECA JO32 Netherlands +000000 3 1.5 2198 ~ F4DFQ F5LOW IN95 +000000 3 -0.3 2218 ~ K3ZK IK2ZDT RR73 +000000 1 -1.2 2600 ~ K2DSW IU8LLZ R-16 +000000 3 0.3 2830 ~ CQ F8IJV/P IN97 France +000000 1 -0.8 1618 ~ HNY 2019 73 diff --git a/external/ft8_lib/test/wav/websdr_test13.wav b/external/ft8_lib/test/wav/websdr_test13.wav new file mode 100644 index 0000000..a8a396b Binary files /dev/null and b/external/ft8_lib/test/wav/websdr_test13.wav differ diff --git a/external/ft8_lib/test/wav/websdr_test14.wav b/external/ft8_lib/test/wav/websdr_test14.wav new file mode 100644 index 0000000..24cabd3 Binary files /dev/null and b/external/ft8_lib/test/wav/websdr_test14.wav differ diff --git a/external/ft8_lib/test/wav/websdr_test14_12k.wav b/external/ft8_lib/test/wav/websdr_test14_12k.wav new file mode 100644 index 0000000..a1c40a6 Binary files /dev/null and b/external/ft8_lib/test/wav/websdr_test14_12k.wav differ diff --git a/external/ft8_lib/test/wav/websdr_test15.wav b/external/ft8_lib/test/wav/websdr_test15.wav new file mode 100644 index 0000000..c5d22b4 Binary files /dev/null and b/external/ft8_lib/test/wav/websdr_test15.wav differ diff --git a/external/ft8_lib/test/wav/websdr_test15_12k.wav b/external/ft8_lib/test/wav/websdr_test15_12k.wav new file mode 100644 index 0000000..d6ac30a Binary files /dev/null and b/external/ft8_lib/test/wav/websdr_test15_12k.wav differ diff --git a/external/ft8_lib/test/wav/websdr_test16.wav b/external/ft8_lib/test/wav/websdr_test16.wav new file mode 100644 index 0000000..23a210e Binary files /dev/null and b/external/ft8_lib/test/wav/websdr_test16.wav differ diff --git a/external/ft8_lib/test/wav/websdr_test17.wav b/external/ft8_lib/test/wav/websdr_test17.wav new file mode 100644 index 0000000..e7253f1 Binary files /dev/null and b/external/ft8_lib/test/wav/websdr_test17.wav differ diff --git a/external/ft8_lib/test/wav/websdr_test18.wav b/external/ft8_lib/test/wav/websdr_test18.wav new file mode 100644 index 0000000..29238a3 Binary files /dev/null and b/external/ft8_lib/test/wav/websdr_test18.wav differ diff --git a/external/ft8_lib/test/wav/websdr_test19.wav b/external/ft8_lib/test/wav/websdr_test19.wav new file mode 100644 index 0000000..63a0f27 Binary files /dev/null and b/external/ft8_lib/test/wav/websdr_test19.wav differ diff --git a/external/ft8_lib/test/wav/websdr_test2.txt b/external/ft8_lib/test/wav/websdr_test2.txt new file mode 100644 index 0000000..3f71387 --- /dev/null +++ b/external/ft8_lib/test/wav/websdr_test2.txt @@ -0,0 +1,21 @@ +000000 12 1.1 309 ~ SP4FCA G4CUS R+13 +000000 -8 1.1 389 ~ S9CT 9A4ZM -04 +000000 -7 1.8 458 ~ S9CT F4HPY JN28 +000000 1 1.2 598 ~ DC8VA LZ1CWK R-09 +000000 -3 1.1 638 ~ 4F3OM F6GGA JN37 +000000 -13 1.8 895 ~ CQ SV1GN KM17 Greece +000000 -3 0.4 1109 ~ IK4LZH 9A9TT JN76 +000000 -6 1.1 1188 ~ UR4MSF E75C R+10 +000000 11 1.1 1430 ~ LA9XBA F6CAM JO10 +000000 -1 1.4 1495 ~ CQ IT9PQO JM78 Sicily +000000 -13 1.4 1642 ~ OH3KAV 2M0OGG RR73 +000000 1 1.7 1706 ~ CQ LZ2II KN22 Bulgaria +000000 -8 1.1 2016 ~ A41ZZ YO7CGS R-18 +000000 3 1.2 2267 ~ EA1ABT I8LWL JN70 +000000 -24 1.6 2344 ~ S9CT EW8KT KO42 +000000 -23 1.1 2393 ~ HA8RC R4OF 73 +000000 -8 1.0 2597 ~ UA9CJM ON8BB -20 +000000 -6 2.1 2672 ~ CQ 2E0VDS JO02 England +000000 -1 1.0 1707 ~ CQ M0OIC IO92 England +000000 -19 1.3 2026 ~ DM8PV GM7VFR RR73 +000000 -20 1.1 2158 ~ YO8TVD M0JBF IO91 diff --git a/external/ft8_lib/test/wav/websdr_test2.wav b/external/ft8_lib/test/wav/websdr_test2.wav new file mode 100644 index 0000000..86cf0e3 Binary files /dev/null and b/external/ft8_lib/test/wav/websdr_test2.wav differ diff --git a/external/ft8_lib/test/wav/websdr_test20.wav b/external/ft8_lib/test/wav/websdr_test20.wav new file mode 100644 index 0000000..d4715a3 Binary files /dev/null and b/external/ft8_lib/test/wav/websdr_test20.wav differ diff --git a/external/ft8_lib/test/wav/websdr_test3.txt b/external/ft8_lib/test/wav/websdr_test3.txt new file mode 100644 index 0000000..6c9b142 --- /dev/null +++ b/external/ft8_lib/test/wav/websdr_test3.txt @@ -0,0 +1,11 @@ +000000 -19 -0.5 309 ~ G4CUS SP4FCA RRR +000000 -16 1.0 528 ~ VK3EVE SQ3MZM RR73 +000000 3 2.0 587 ~ LZ1LZ G4UJS IO83 +000000 -9 1.1 809 ~ SQ5FBI G3NDC R-04 +000000 18 1.1 1110 ~ 9A9TT IK4LZH -10 +000000 -14 1.1 1813 ~ CQ EA5OL IM99 Spain +000000 4 1.1 1909 ~ R2EA IZ4OUL 73 +000000 9 0.6 2315 ~ 2M0OGG RA6ABO KN96 +000000 6 1.0 2535 ~ CQ IZ3XJM JN55 Italy +000000 -9 1.2 587 ~ LZ1LZ EA3FHP RR73 +000000 -21 1.2 793 ~ YO7CGS A41ZZ -11 diff --git a/external/ft8_lib/test/wav/websdr_test3.wav b/external/ft8_lib/test/wav/websdr_test3.wav new file mode 100644 index 0000000..9168cf0 Binary files /dev/null and b/external/ft8_lib/test/wav/websdr_test3.wav differ diff --git a/external/ft8_lib/test/wav/websdr_test4.txt b/external/ft8_lib/test/wav/websdr_test4.txt new file mode 100644 index 0000000..e91d569 --- /dev/null +++ b/external/ft8_lib/test/wav/websdr_test4.txt @@ -0,0 +1,23 @@ +000000 -8 1.9 272 ~ CQ DL8ALH JN58 Germany +000000 -3 0.8 570 ~ 4X5MZ RA6FSD R-05 +000000 -6 1.7 915 ~ CQ UY5AX KO70 Ukraine +000000 -10 1.0 968 ~ PE0TS LZ2KV -25 +000000 -4 0.4 1113 ~ CQ OE3UKW JN88 Austria +000000 -14 1.7 1172 ~ UA3IBD SV9FBN -19 +000000 -4 0.2 1256 ~ CQ DM1YS JO30 Germany +000000 -7 -1.2 1315 ~ SP6DXH SP6ZJB 73 +000000 4 -0.1 1386 ~ RA1CP OM7JG JN98 +000000 -5 0.1 1502 ~ DO1RPK DL8NCU 73 +000000 -23 0.3 1616 ~ SM2EKA UT7IS KN98 +000000 -6 0.2 1667 ~ CQ DL7ACN JN49 Germany +000000 6 0.3 1716 ~ SM2EKA UT7IS KN98 +000000 -11 -0.6 1891 ~ CQ EA8PP IL18 Canary Is. +000000 1 0.3 1992 ~ RW6FY OM7ZM RR73 +000000 -6 0.1 2132 ~ ON4FG UT8UU R-11 +000000 -4 1.8 2187 ~ JH1AJT EA1RT -10 +000000 8 -0.1 2244 ~ CQ SQ7MRR JO91 Poland +000000 11 0.2 2324 ~ CQ DK7LE JO54 Germany +000000 -1 0.2 2746 ~ DL6ZNG ON8GE RR73 +000000 -2 0.1 859 ~ R2DQA IK2YCW -20 +000000 -16 0.2 1141 ~ CQ DK2TS JO31 Germany +000000 2 0.5 2256 ~ CQ DO8OL JO33 Germany diff --git a/external/ft8_lib/test/wav/websdr_test4.wav b/external/ft8_lib/test/wav/websdr_test4.wav new file mode 100644 index 0000000..8d3adb5 Binary files /dev/null and b/external/ft8_lib/test/wav/websdr_test4.wav differ diff --git a/external/ft8_lib/test/wav/websdr_test5.txt b/external/ft8_lib/test/wav/websdr_test5.txt new file mode 100644 index 0000000..c535739 --- /dev/null +++ b/external/ft8_lib/test/wav/websdr_test5.txt @@ -0,0 +1,27 @@ +000000 -9 0.2 349 ~ LZ2KV GW1YQM IO82 +000000 -11 0.3 570 ~ RA6FSD 4X5MZ RR73 +000000 -1 0.2 692 ~ UT9LB RZ3OA KO91 +000000 -7 0.2 787 ~ SB7W DL6CHF JO52 +000000 1 0.5 1054 ~ CQ DD2XJ JO53 Germany +000000 -18 0.5 1108 ~ EA8PP UN7IT LO80 +000000 3 0.2 1172 ~ R2ZBK UA3IBD -15 +000000 4 0.2 1397 ~ SV8EUB OM7AZA JN98 +000000 -3 0.2 1494 ~ CQ F5RRS JN36 France +000000 -14 0.2 1624 ~ CQ RA3QUE KO91 EU Russia +000000 13 0.2 1801 ~ OZ1KNX OZ5D -11 +000000 -6 0.6 1991 ~ OM7ZM RW6FY 73 +000000 -4 -0.0 2040 ~ ON8GE DL6ZNG 73 +000000 -3 0.2 2105 ~ EA2AA HA1BL JN87 +000000 -14 0.3 2184 ~ EA8PP DL5OBC JO52 +000000 -3 0.2 2392 ~ CQ DJ0AH JN57 Germany +000000 2 0.2 2746 ~ SP2EWQ DL8TG R+07 +000000 -24 0.2 735 ~ CQ ON7PM JO20 Belgium +000000 -9 0.4 1028 ~ LZ2KV SV8LMQ 73 +000000 -16 0.2 1218 ~ OZ0JD SM5NAS R-08 +000000 -10 0.1 1503 ~ CQ DO1RPK JO32 Germany +000000 3 0.3 1766 ~ ON6OM DL8FBD 73 +000000 0 0.2 1822 ~ DB4BU DK5OK RR73 +000000 2 -1.7 1874 ~ OH1WR RA4UDC RR73 +000000 -12 0.3 2047 ~ EA2AA S56ECR JN65 +000000 -3 0.2 2133 ~ UT8UU ON4FG RR73 +000000 -6 -1.3 2409 ~ CQ UA3YFS KO73 EU Russia diff --git a/external/ft8_lib/test/wav/websdr_test5.wav b/external/ft8_lib/test/wav/websdr_test5.wav new file mode 100644 index 0000000..97977ce Binary files /dev/null and b/external/ft8_lib/test/wav/websdr_test5.wav differ diff --git a/external/ft8_lib/test/wav/websdr_test6.txt b/external/ft8_lib/test/wav/websdr_test6.txt new file mode 100644 index 0000000..d937024 --- /dev/null +++ b/external/ft8_lib/test/wav/websdr_test6.txt @@ -0,0 +1,30 @@ +000000 -7 0.9 272 ~ CQ DL8ALH JN58 +000000 14 0.2 457 ~ CQ HF19NY +000000 -1 0.6 570 ~ 4X5MZ RA6FSD 73 +000000 -4 0.2 696 ~ EA8TH F8DBF R-04 +000000 -4 0.3 859 ~ CQ IK2YCW JN55 +000000 -2 1.9 915 ~ CQ UY5AX KO70 +000000 -2 0.2 1012 ~ CQ CU2DX HM77 +000000 0 0.4 1113 ~ CQ OE3UKW JN88 +000000 8 0.2 1256 ~ CQ DM1YS JO30 +000000 1 -1.4 1316 ~ CQ SP6ZJB JO80 +000000 -20 0.3 1616 ~ SM2EKA UT7IS -06 +000000 -8 0.2 1667 ~ CQ DL7ACN JN49 +000000 6 0.3 1716 ~ SM2EKA UT7IS -06 +000000 -10 0.3 1822 ~ DK5OK DB4BU 73 +000000 -10 0.2 1891 ~ JA6VQA EA8PP R-24 +000000 0 0.1 1992 ~ CQ OM7ZM JN98 +000000 2 0.4 2105 ~ HA1BL EA2AA -09 +000000 -3 0.5 2187 ~ JH1AJT EA1RT -10 +000000 10 -0.1 2244 ~ CQ SQ7MRR JO91 +000000 9 0.2 2324 ~ CQ DK7LE JO54 +000000 -2 0.2 2392 ~ DJ0AH DL6WAB JO41 +000000 4 0.2 2746 ~ CQ ON8GE JO20 +000000 -18 0.4 348 ~ OM7AZA SV8EUB -11 +000000 -8 0.5 586 ~ CQ DX DO4TP JO31 +000000 -9 0.6 690 ~ CQ UT9LB KN89 +000000 -3 0.3 922 ~ CQ E74BYZ JN84 +000000 -5 1.0 968 ~ PE0TS LZ2KV -25 +000000 -13 0.2 1140 ~ CQ DK2TS JO31 +000000 -4 1.7 1715 ~ SM2EKA SV9FBN KM25 +000000 -4 0.1 2132 ~ ON4FG UT8UU 73 diff --git a/external/ft8_lib/test/wav/websdr_test6.wav b/external/ft8_lib/test/wav/websdr_test6.wav new file mode 100644 index 0000000..272d0fc Binary files /dev/null and b/external/ft8_lib/test/wav/websdr_test6.wav differ diff --git a/external/ft8_lib/test/wav/websdr_test7.txt b/external/ft8_lib/test/wav/websdr_test7.txt new file mode 100644 index 0000000..7c051e5 --- /dev/null +++ b/external/ft8_lib/test/wav/websdr_test7.txt @@ -0,0 +1,27 @@ +000000 -18 0.5 350 ~ DM1YS GW1YQM IO82 +000000 0 0.2 457 ~ <...> SO5WD +04 +000000 10 0.2 570 ~ RA6FSD SP2EWQ -07 +000000 -4 -0.1 692 ~ UT9LB RZ3OA KO91 +000000 -8 0.2 787 ~ SB7W DL6CHF JO52 +000000 -10 0.2 968 ~ LZ2KV PE0TS 73 +000000 -6 0.4 1054 ~ CQ DD2XJ JO53 +000000 -14 0.5 1108 ~ OM7ZM UN7IT LO80 +000000 3 0.2 1172 ~ R2ZBK UA3IBD -15 +000000 6 0.2 1397 ~ SV8EUB OM7AZA JN98 +000000 -2 0.2 1494 ~ CQ F5RRS JN36 +000000 -1 0.7 1562 ~ CU2DX DO1KHW JO30 +000000 -7 0.2 1624 ~ CQ RA3QUE KO91 +000000 12 0.2 1801 ~ OZ1KNX OZ5D -03 +000000 -2 0.2 1884 ~ CU2DX SP9DLY JO90 +000000 -4 0.2 2133 ~ CQ ON4FG JO20 +000000 -5 0.3 2183 ~ EA8PP DL5OBC JO52 +000000 -1 0.2 2392 ~ DL6WAB DJ0AH +00 +000000 -10 0.3 2479 ~ DO8OL S56ECR JN65 +000000 3 0.2 2745 ~ SP2EWQ DL8TG R+07 +000000 -9 0.0 457 ~ <...> PA0PIW +000000 -3 0.0 527 ~ CU2DX SP6DXH -19 +000000 -10 0.4 757 ~ OE3UKW R7IW LN35 +000000 -21 -0.6 940 ~ EA8PP JH0INP PM96 +000000 -6 0.6 1481 ~ CQ DO6AZ JO50 +000000 0 0.2 1765 ~ CQ DL8FBD JO40 +000000 -12 0.1 2324 ~ DK7LE DO5HOK JO42 diff --git a/external/ft8_lib/test/wav/websdr_test7.wav b/external/ft8_lib/test/wav/websdr_test7.wav new file mode 100644 index 0000000..a8ca25c Binary files /dev/null and b/external/ft8_lib/test/wav/websdr_test7.wav differ diff --git a/external/ft8_lib/test/wav/websdr_test8.txt b/external/ft8_lib/test/wav/websdr_test8.txt new file mode 100644 index 0000000..b348161 --- /dev/null +++ b/external/ft8_lib/test/wav/websdr_test8.txt @@ -0,0 +1,26 @@ +000000 -13 0.5 587 ~ CQ DX DO4TP JO31 Germany +000000 -7 0.2 696 ~ EA8TH F8DBF 73 +000000 -9 0.1 859 ~ CQ IK2YCW JN55 Italy +000000 -4 1.6 914 ~ CQ UY5AX KO70 Ukraine +000000 -4 0.2 1011 ~ SP9DLY CU2DX -11 +000000 0 -0.9 1058 ~ DD2XJ SP3OTL JO81 +000000 -1 0.4 1113 ~ CQ OE3UKW JN88 Austria +000000 2 0.8 1219 ~ SM5NAS OZ0JD RR73 +000000 -1 0.2 1256 ~ CQ DM1YS JO30 Germany +000000 -6 -1.4 1316 ~ CQ SP6ZJB JO80 Poland +000000 -7 0.2 1667 ~ CQ DL7ACN JN49 Germany +000000 8 0.3 1762 ~ SM2EKA UT7IS R-15 +000000 -11 0.2 1891 ~ JA6VQA EA8PP 73 +000000 1 0.1 1992 ~ UN7IT OM7ZM -20 +000000 -7 0.6 2187 ~ JH1AJT EA1RT -10 +000000 -8 0.5 2256 ~ S56ECR DO8OL -11 +000000 12 0.2 2324 ~ CQ DK7LE JO54 Germany +000000 -8 0.2 2392 ~ DJ0AH DL6WAB R+01 +000000 -1 0.2 2746 ~ CQ ON8GE JO20 Belgium +000000 -20 0.8 348 ~ OM7AZA SV8EUB -09 +000000 -15 2.2 690 ~ RZ3OA UT9LB -06 +000000 -4 0.3 922 ~ CQ E74BYZ JN84 Bosnia-Herzegovina +000000 -9 1.0 968 ~ PE0TS LZ2KV -25 +000000 -10 1.0 1028 ~ DL8FBD LZ2KV -16 +000000 -14 0.2 1141 ~ CQ DK2TS JO31 Germany +000000 -5 1.7 1715 ~ SM2EKA SV9FBN KM25 diff --git a/external/ft8_lib/test/wav/websdr_test8.wav b/external/ft8_lib/test/wav/websdr_test8.wav new file mode 100644 index 0000000..c282e61 Binary files /dev/null and b/external/ft8_lib/test/wav/websdr_test8.wav differ diff --git a/external/ft8_lib/test/wav/websdr_test9.txt b/external/ft8_lib/test/wav/websdr_test9.txt new file mode 100644 index 0000000..ae52953 --- /dev/null +++ b/external/ft8_lib/test/wav/websdr_test9.txt @@ -0,0 +1,24 @@ +000000 -10 -1.5 312 ~ EA8BEV LU3DW -14 +000000 -19 0.1 610 ~ CQ N2BJ EN61 U.S.A. +000000 -5 -0.1 734 ~ OE4RWD NU2Q -05 +000000 9 0.1 904 ~ CQ IK4LZH JN54 Italy +000000 -12 0.3 1125 ~ CQ SV2FPI KN10 Greece +000000 -9 0.1 1375 ~ W4DOW EA5LV 73 +000000 -9 0.1 1431 ~ CQ 9A7DA JN86 Croatia +000000 -11 -0.8 1618 ~ CQ HA5MG JN97 Hungary +000000 -8 0.2 1686 ~ OK1AW G3JFS R+02 +000000 5 -0.0 1881 ~ CQ PD1ECA JO32 Netherlands +000000 -10 -0.0 1955 ~ CQ PY1SX GG87 Brazil +000000 -16 -1.1 2035 ~ M0LMR IW1AYD R-08 +000000 -18 -2.2 2079 ~ K4VBM HA8EK -15 +000000 -17 -2.2 2129 ~ K4VBM HA8EK -15 +000000 8 -2.2 2229 ~ K4VBM HA8EK -15 +000000 -3 -1.2 2601 ~ K2DSW IU8LLZ JN70 +000000 15 0.3 2830 ~ GI7KMC F8IJV/P 73 +000000 -15 0.1 954 ~ W9WI SV3AQM R-13 +000000 -8 0.1 1454 ~ S57NCP PY2GIG RR73 +000000 5 1.5 2199 ~ F4DFQ F5LOW IN95 +000000 -15 -0.2 2303 ~ CQ OE4ATS JN87 Austria +000000 -17 0.1 2406 ~ CQ 2E0PKK IO90 England +000000 -21 0.1 2577 ~ CQ CT7AIX IM59 Portugal +000000 -6 -0.4 2218 ~ K3ZK IK2ZDT RR73 diff --git a/external/ft8_lib/test/wav/websdr_test9.wav b/external/ft8_lib/test/wav/websdr_test9.wav new file mode 100644 index 0000000..66fe2a6 Binary files /dev/null and b/external/ft8_lib/test/wav/websdr_test9.wav differ diff --git a/external/ft8_lib/utils/convert_generator.py b/external/ft8_lib/utils/convert_generator.py new file mode 100644 index 0000000..ea8de0e --- /dev/null +++ b/external/ft8_lib/utils/convert_generator.py @@ -0,0 +1,16 @@ +import sys +import re + +rx1 = re.compile(r'"([0-9a-z]+)"', re.I) + +for line in sys.stdin: + line = line.strip() + m = rx1.search(line) + if m == None: + continue + + line = m.group(1) + if len(line) % 2 == 1: + line += '0' + b = ['0x' + line[i*2:i*2+2] for i in range(len(line)/2)] + print '{ %s },' % (', '.join(b)) diff --git a/external/ft8_lib/utils/decode.py b/external/ft8_lib/utils/decode.py new file mode 100644 index 0000000..8805810 --- /dev/null +++ b/external/ft8_lib/utils/decode.py @@ -0,0 +1,310 @@ +import scipy.io.wavfile as wavfile +from scipy import signal +import numpy as np +import sys +import ldpc + +FT8_NUM_TONES = 8 +FT8_NUM_SYMBOLS = 79 +FT8_TONE_DEVIATION = 6.25 +FT8_SYMBOL_PERIOD = 0.160 +FT8_SYNC_SYMS = [3, 1, 4, 0, 6, 5, 2] +FT8_SYNC_POS = [0, 36, 72] +FT8_DATA_POS = [7, 43] +FT8_LDPC_PAYLOAD_BITS = 91 +FT8_PAYLOAD_BITS = 77 + +MIN_FREQ = 300 +MAX_FREQ = 3000 + +def lin_to_db(x, eps=1e-12): + return 20*np.log10(x + eps) + +def db_to_lin(x): + return 10**(x/20) + +def load_wav(path): + rate, samples = wavfile.read(path) + if samples.dtype == np.int16: + samples = np.array(samples / 32768.0) + return (rate, samples) + +def quantize(H, mag_db_step=0.5, phase_divs=256): + mag_db = lin_to_db(np.abs(H)) + mag_db = mag_db_step * np.ceil(mag_db / mag_db_step) + + phase = np.angle(H) + phase = np.ceil(0.5 + phase * phase_divs / (2*np.pi)) / phase_divs * (2*np.pi) + + return db_to_lin(mag_db) * np.exp(1j * phase) + +class Waterfall: + def __init__(self, freq_osr=2, time_osr=2, freq_min=300, freq_max=3000): + self.H = None + self.freq_osr = freq_osr + self.time_osr = time_osr + self.window_type = 'hann' + self.freq_step = FT8_TONE_DEVIATION / self.freq_osr # frequency step corresponding to one bin, Hz + self.time_step = FT8_SYMBOL_PERIOD / self.time_osr # time step corresponding to one STFT position, seconds + self.bin_min = int(freq_min / self.freq_step) + self.bin_max = int(freq_max / self.freq_step) + 1 + # self.freq_first = self.bin_min * self.freq_step + # self.time_first = FT8_SYMBOL_PERIOD * self.freq_osr / 2 + + def load_signal(self, sig, fs): + sym_size = int(fs * FT8_SYMBOL_PERIOD) + nfft = sym_size * self.freq_osr + _, _, H = signal.stft(sig, window=self.window_type, nfft=nfft, nperseg=nfft, noverlap=nfft - (sym_size//self.time_osr), boundary=None, padded=None) + self.H = quantize(H) + A = np.abs(H) + self.Apow = A**2 + self.Adb = lin_to_db(A) + print(f'Max magnitude {self.Adb[:, self.bin_min:self.bin_max].max(axis=(0, 1)):.1f} dB') + print(f'Waterfall shape {H.shape}') + + +def search_sync_coarse(wf, min_score=2.5, max_cand=30, snr_mode=2): + print(f'Using bins {wf.bin_min}..{wf.bin_max} ({wf.bin_max - wf.bin_min})') + score_map = dict() + for freq_sub in range(wf.freq_osr): + for bin_first in range(wf.bin_min + freq_sub, wf.bin_max - FT8_NUM_TONES * wf.freq_osr, wf.freq_osr): + for time_sub in range(time_osr): + for time_start in range(-10 * wf.time_osr + time_sub, 21 * wf.time_osr + time_sub, wf.time_osr): + # calc sync score at (bin_first, time_start) + score = [] + snr_sig = snr_noise = 0 + for sync_start in FT8_SYNC_POS: + for sync_pos, sync_tone in enumerate(FT8_SYNC_SYMS, start=sync_start): + pos = time_start + sync_pos * wf.time_osr + if pos >= 0 and pos < wf.Adb.shape[1]: + if snr_mode == 0: + snr_sig += wf.Apow[bin_first + sync_tone * wf.freq_osr, pos] + for noise_tone in range(7): + if noise_tone != sync_tone: + snr_noise += wf.Apow[bin_first + noise_tone * wf.freq_osr, pos] + else: + sym_db = wf.Adb[bin_first + sync_tone * wf.freq_osr, pos] + if bin_first + (sync_tone - 1) * freq_osr >= wf.bin_min: + sym_down_db = wf.Adb[bin_first + (sync_tone - 1) * wf.freq_osr, pos] + score.append(sym_db - sym_down_db) + if bin_first + (sync_tone + 1) * wf.freq_osr < wf.bin_max: + sym_up_db = wf.Adb[bin_first + (sync_tone + 1) * wf.freq_osr, pos] + score.append(sym_db - sym_up_db) + if snr_mode == 2: + if pos - 1 >= 0: + sym_prev_db = wf.Adb[bin_first + sync_tone * wf.freq_osr, pos - 1] + score.append(sym_db - sym_prev_db) + if pos + 1 < wf.Adb.shape[1]: + sym_next_db = wf.Adb[bin_first + sync_tone * wf.freq_osr, pos + 1] + score.append(sym_db - sym_next_db) + if snr_mode == 0: + score_avg = 10*np.log10(snr_sig / (snr_noise / 6)) + else: + score_avg = np.mean(score) + + if score_avg > min_score: + is_better = True + # if (bin_first, time_start) in score_map: + # if score_map[(bin_first, time_start)] >= score_avg: + # is_better = False + for delta_bin in [-2, -1, 0, 1, 2]: + for delta_pos in [-2, -1, 0, 1, 2]: + key = (bin_first + delta_bin, time_start + delta_pos) + if key in score_map: + if score_map[key] <= score_avg: + del score_map[key] + else: + is_better = False + if is_better: + score_map[(bin_first, time_start)] = score_avg + + top_keys = sorted(score_map.keys(), key=lambda x: score_map[x], reverse=True)[:max_cand] + for idx, (bin, pos) in enumerate(sorted(top_keys)): + print(f'{idx+1}: {wf.freq_step * bin:.2f}\t{wf.time_step * pos:+.02f}\t{score_map[(bin, pos)]:.2f}') + time_offset = FT8_SYMBOL_PERIOD / 4 + return [(wf.freq_step * bin, wf.time_step * pos - time_offset) for (bin, pos) in sorted(top_keys)] + + +def downsample_fft(H, bin_f0, fs2=100, freq_osr=1, time_osr=1): + sym_size2 = int(fs2 * FT8_SYMBOL_PERIOD) + nfft2 = sym_size2 * freq_osr + freq_step2 = fs2 / nfft2 + taper_width = 4 + pad_width = ((nfft2 - 2*taper_width - freq_osr*FT8_NUM_TONES) // 2) + H2 = H[bin_f0 - taper_width - pad_width: bin_f0 + freq_osr*FT8_NUM_TONES + taper_width + pad_width, :] + W_taper = np.linspace(0, 1, taper_width) + W_pad = [0] * pad_width + W = np.concatenate( (W_pad, W_taper, [1]*freq_osr*FT8_NUM_TONES, np.flipud(W_taper), W_pad) ) + H2 = np.multiply(H2, np.expand_dims(W, W.ndim)) + + shift = taper_width + pad_width + H2 = np.roll(H2, -shift, axis=0) + _, sig2 = signal.istft(H2, window='hann', nperseg=nfft2, noverlap=nfft2 - (sym_size2//time_osr), input_onesided=False) + + f0_down = (taper_width + pad_width - shift) * freq_step2 + return sig2, f0_down + + +def search_sync_fine(sig2, fs2, f0_down, pos_start): + sym_size2 = int(fs2 * FT8_SYMBOL_PERIOD) + n = np.arange(sym_size2) + + f_tones = np.arange(f0_down, f0_down + FT8_NUM_TONES*FT8_TONE_DEVIATION, FT8_TONE_DEVIATION) + ctones_conj = np.exp(-1j * 2*np.pi * np.expand_dims(n, n.ndim) * np.expand_dims(f_tones/fs2, 0)) + ctweak_plus_tone = np.exp(-1j * 2*np.pi * n * FT8_TONE_DEVIATION/fs2) + ctweak_minus_tone = np.exp(1j * 2*np.pi * n * FT8_TONE_DEVIATION/fs2) + + max_power, max_freq_offset, max_pos_offset = None, None, None + all_powers = [] + win = signal.windows.kaiser(sym_size2, beta=2.0) + for freq_offset in np.linspace(-3.2, 3.2, 21): + power_time = [] + ctweak = np.exp(-1j * 2*np.pi * n * freq_offset/fs2) + for pos_offset in range(-sym_size2//2, sym_size2//2 + 1): + power_sig = 0 + power_nse = 1e-12 + for sync_start in FT8_SYNC_POS: + for sync_pos, sync_tone in enumerate(FT8_SYNC_SYMS): + pos1 = pos_start + pos_offset + sym_size2 * (sync_start + sync_pos) + + if pos1 >= 0 and pos1 + sym_size2 < len(sig2): + demod = win * sig2[pos1:pos1 + sym_size2] * ctones_conj[:, sync_tone] * ctweak + mag2_sym = np.abs(np.sum(demod))**2 + mag2_minus = np.abs(np.sum(demod * ctweak_minus_tone))**2 + mag2_plus = np.abs(np.sum(demod * ctweak_plus_tone))**2 + power_sig += mag2_sym + power_nse += (mag2_minus + mag2_plus)/2 + + # demod_prev = win * sig2[pos1 - sym_size2:pos1] * ctones_conj[:, sync_tone] * ctweak + # demod_next = win * sig2[pos1 + sym_size2:pos1 + 2*sym_size2] * ctones_conj[:, sync_tone] * ctweak + # mag2_prev = np.abs(np.sum(demod_prev))**2 + # mag2_next = np.abs(np.sum(demod_next))**2 + # power += 2*mag2_sym - mag2_prev - mag2_next + # power = lin_to_db(power_sig / power_nse)/2 + power = power_sig / power_nse + power_time.append(power) + if max_power is None or power > max_power: + max_power = power + max_freq_offset = freq_offset + max_pos_offset = pos_offset + + # print(f'{freq_offset:.1f}, {(np.argmax(power_time) - sym_size2//2)/fs2:.3f}, {np.max(power_time)}') + all_powers.append(power_time) + + return max_freq_offset, max_pos_offset + + +def extract_logl_db(A2db): + # FT8 bits -> channel symbols 0, 1, 3, 2, 5, 6, 4, 7 + A2db_bit0 = np.max(A2db[[5, 6, 4, 7], :], axis=0) - np.max(A2db[[0, 1, 3, 2], :], axis=0) # 4/5/6/7 - 0/1/2/3 + A2db_bit1 = np.max(A2db[[3, 2, 4, 7], :], axis=0) - np.max(A2db[[0, 1, 5, 6], :], axis=0) # 2/3/6/7 - 0/1/4/5 + A2db_bit2 = np.max(A2db[[1, 2, 6, 7], :], axis=0) - np.max(A2db[[0, 3, 5, 4], :], axis=0) # 1/3/5/7 - 0/2/4/6 + A2db_bits = np.stack((A2db_bit0, A2db_bit1, A2db_bit2)).transpose() + + # a = [ + # A2db[7, :] - A2db[0, :], + # A2db[3, :] - A2db[0, :], + # A2db[6, :] - A2db[3, :], + # A2db[6, :] - A2db[2, :], + # A2db[7, :] - A2db[4, :], + # A2db[4, :] - A2db[1, :], + # A2db[5, :] - A2db[1, :], + # A2db[5, :] - A2db[2, :] + # ] + # W = np.array([[ 48., 6., 36., 30., 6., 36., 30., 24.], + # [ 42., 35., -28., -29., 1., 40., 5., -30.], + # [ 42., 1., 40., 5., 35., -28., -29., -30.]])/34/6 + # A2db_bits = np.matmul(W, a).transpose() + + bits_logl = np.concatenate((A2db_bits[7:36], A2db_bits[43:72])).flatten() * 0.6 + return bits_logl, A2db_bits + + +fs, sig = load_wav(sys.argv[1]) +print(f'Sample rate {fs} Hz') + +freq_osr = 2 +time_osr = 2 + +wf = Waterfall(freq_osr=freq_osr, time_osr=time_osr, freq_min=MIN_FREQ, freq_max=MAX_FREQ) +wf.load_signal(sig, fs) + +use_downsample = True +if len(sys.argv) > 2: + f0 = float(sys.argv[2]) + time_start = float(sys.argv[3]) + candidates = [(f0, time_start)] +else: + candidates = search_sync_coarse(wf) + +num_decoded = 0 +for f0, time_start in candidates: + bin_f0 = int(0.5 + f0 / wf.freq_step) + f0_real = bin_f0 * wf.freq_step + print(f'Frequency {f0:.2f} Hz (bin {bin_f0}), coarse {f0_real:.2f} Hz') + + if use_downsample: + fs2 = 100 + env_alpha = 0.06 + + sig2, f0_down = downsample_fft(wf.H[:, ::time_osr], bin_f0, fs2=fs2, freq_osr=freq_osr, time_osr=1) + print(f'Downsampled signal to {fs2} Hz sample rate, freq shift {f0_real} Hz -> {f0_down} Hz') + + pos_start = int(0.5 + time_start * fs2) + max_freq_offset, max_pos_offset = search_sync_fine(sig2, fs2, f0_down, pos_start) + f0_down_fine, pos_fine = max_freq_offset + f0_down, pos_start + max_pos_offset + print(f'Fine sync at {f0_real:.2f} + {max_freq_offset:.2f} = {f0_real + max_freq_offset:.2f} Hz, {pos_start/fs2:.3f} + {max_pos_offset/fs2:.3f} = {pos_fine/fs2:.3f} s') + + env = signal.filtfilt(env_alpha, [1, -(1-env_alpha)], np.abs(sig2)) + + sym_size2 = int(fs2 * FT8_SYMBOL_PERIOD) + ctweak = np.exp(-1j * 2*np.pi * np.arange(len(sig2)) * f0_down_fine/fs2) + slice_pos = pos_start + max_pos_offset + slice_length = int(FT8_NUM_SYMBOLS*FT8_SYMBOL_PERIOD*fs2) + pad_left = pad_right = 0 + if slice_pos < 0: + pad_left = -slice_pos + slice_pos = 0 + if slice_pos + slice_length > len(sig2) + pad_left: + pad_right = slice_pos + slice_length - (len(sig2) + pad_left) + sig3 = np.pad(sig2*ctweak, (pad_left, pad_right), mode='constant', constant_values=(0, 0))[slice_pos:slice_pos + slice_length] + _, _, H2 = signal.stft(sig3, window='boxcar', nperseg=sym_size2, noverlap=0, return_onesided=False, boundary=None, padded=False) + A2db = lin_to_db(np.abs(H2[0:FT8_NUM_TONES, :])) + else: + time_offset = FT8_SYMBOL_PERIOD / 4 + pos_start = int(0.5 + (time_start + time_offset) / wf.time_step) + print(f'Start time {time_start:.3f} s (pos {pos_start}), coarse {pos_start * wf.time_step - time_offset:.3f} s') + # TODO: zero padding for time axis + A2db = wf.Adb[bin_f0:bin_f0+freq_osr*FT8_NUM_TONES:freq_osr, pos_start:pos_start+FT8_NUM_SYMBOLS*time_osr:time_osr] + + A2db -= np.max(A2db, axis=0) + + bits_logl, A2db_bits = extract_logl_db(A2db) + (num_errors, bits) = ldpc.bp_solve(bits_logl, max_iters=30, max_no_improvement=15) + print(f'LDPC decode: {num_errors} errors') + if num_errors == 0: + print(f'Payload bits: {"".join([str(x) for x in bits[:FT8_PAYLOAD_BITS]])}') + print(f'CRC bits : {"".join([str(x) for x in bits[FT8_PAYLOAD_BITS:FT8_LDPC_PAYLOAD_BITS]])}') + print(f'Parity bits : {"".join([str(x) for x in bits[FT8_LDPC_PAYLOAD_BITS:]])}') + num_decoded += 1 + +print(f'Total decoded: {num_decoded}') + +import matplotlib.pyplot as plt +import matplotlib.ticker as plticker +import matplotlib.colors as pltcolors +fig, ax = plt.subplots(4) + +plt.colorbar(ax[0].imshow(A2db, cmap='inferno', norm=pltcolors.Normalize(-30, 0, clip=True)), orientation='horizontal', ax=ax[0]) +plt.colorbar(ax[1].imshow(A2db_bits.transpose(), cmap='bwr', norm=pltcolors.Normalize(-10, 10, clip=True)), orientation='horizontal', ax=ax[1]) +# ax[2].imshow(A2db_bits2, cmap='bwr', norm=pltcolors.Normalize(-10, 10, clip=True)) +ax[2].hist(bits_logl, bins=25) +# ax[3].plot(np.arange(len(sig3))/sym_size2, np.real(sig3)) +# ax[3].plot(np.arange(len(sig3))/sym_size2, np.abs(sig3)) +ax[3].margins(0, 0) +# loc = plticker.MultipleLocator(base=32.0) # this locator puts ticks at regular intervals +# ax[1].xaxis.set_major_locator(loc) +# ax[0].plot(np.array(all_powers).transpose()) + +plt.grid() +plt.show() diff --git a/external/ft8_lib/utils/ldpc.py b/external/ft8_lib/utils/ldpc.py new file mode 100644 index 0000000..79ce8ed --- /dev/null +++ b/external/ft8_lib/utils/ldpc.py @@ -0,0 +1,355 @@ +import numpy as np + +FTX_LDPC_N = 174 +FTX_LDPC_M = 83 + + +def ldpc_check(codeword): + errors = 0 + for m in range(FTX_LDPC_M): + x = False + for i in range(kFTX_LDPC_Num_rows[m]): + x ^= (codeword[kFTX_LDPC_Nm[m][i] - 1] > 0) + if x: + errors += 1 + + return errors + + +def bp_solve(codeword, max_iters=25, max_no_improvement=5): + tov = np.zeros((FTX_LDPC_N, 3)) + toc = np.zeros((FTX_LDPC_M, 7)) + + # int min_errors = FTX_LDPC_M; + min_errors = FTX_LDPC_M + last_errors = FTX_LDPC_M + plain = None + no_improvement = 1 + + for iter in range(max_iters): + # Do a hard decision guess (tov=0 in iter 0) + plain = np.where((codeword + tov[:, 0] + tov[:, 1] + tov[:, 2]) > 0, 1, 0) + if np.sum(plain) == 0: + # Message converged to all-zeros, which is prohibited + break + + # Check to see if we have a codeword (check before we do any iter) + errors = ldpc_check(plain) + print(f'iter {iter}, errors {errors}') + + if errors < last_errors: + no_improvement = 1 + last_errors = errors + else: + no_improvement += 1 + if no_improvement >= max_no_improvement: + break + + if errors < min_errors: + # We have a better guess - update the result + min_errors = errors + if errors == 0: + # Found a perfect answer + break + + # Send messages from bits to check nodes + for m in range(FTX_LDPC_M): + for n_idx in range(kFTX_LDPC_Num_rows[m]): + n = kFTX_LDPC_Nm[m][n_idx] - 1 + # for each (n, m) + Tnm = codeword[n] + for m_idx in range(3): + if (kFTX_LDPC_Mn[n][m_idx] - 1) != m: + Tnm += tov[n][m_idx] + toc[m][n_idx] = np.tanh(-Tnm / 2) + + # Send messages from check nodes to variable nodes + for n in range(FTX_LDPC_N): + for m_idx in range(3): + m = kFTX_LDPC_Mn[n][m_idx] - 1 + # for each (n, m) + Tmn = 1.0 + for n_idx in range(kFTX_LDPC_Num_rows[m]): + if (kFTX_LDPC_Nm[m][n_idx] - 1) != n: + Tmn *= toc[m][n_idx] + tov[n][m_idx] = -2 * np.arctanh(Tmn) + + return (min_errors, plain) + +# Each row describes one LDPC parity check. +# Each number is an index into the codeword (1-origin). +# The codeword bits mentioned in each row must XOR to zero. +kFTX_LDPC_Nm = [ + [ 4, 31, 59, 91, 92, 96, 153 ], + [ 5, 32, 60, 93, 115, 146, 0 ], + [ 6, 24, 61, 94, 122, 151, 0 ], + [ 7, 33, 62, 95, 96, 143, 0 ], + [ 8, 25, 63, 83, 93, 96, 148 ], + [ 6, 32, 64, 97, 126, 138, 0 ], + [ 5, 34, 65, 78, 98, 107, 154 ], + [ 9, 35, 66, 99, 139, 146, 0 ], + [ 10, 36, 67, 100, 107, 126, 0 ], + [ 11, 37, 67, 87, 101, 139, 158 ], + [ 12, 38, 68, 102, 105, 155, 0 ], + [ 13, 39, 69, 103, 149, 162, 0 ], + [ 8, 40, 70, 82, 104, 114, 145 ], + [ 14, 41, 71, 88, 102, 123, 156 ], + [ 15, 42, 59, 106, 123, 159, 0 ], + [ 1, 33, 72, 106, 107, 157, 0 ], + [ 16, 43, 73, 108, 141, 160, 0 ], + [ 17, 37, 74, 81, 109, 131, 154 ], + [ 11, 44, 75, 110, 121, 166, 0 ], + [ 45, 55, 64, 111, 130, 161, 173 ], + [ 8, 46, 71, 112, 119, 166, 0 ], + [ 18, 36, 76, 89, 113, 114, 143 ], + [ 19, 38, 77, 104, 116, 163, 0 ], + [ 20, 47, 70, 92, 138, 165, 0 ], + [ 2, 48, 74, 113, 128, 160, 0 ], + [ 21, 45, 78, 83, 117, 121, 151 ], + [ 22, 47, 58, 118, 127, 164, 0 ], + [ 16, 39, 62, 112, 134, 158, 0 ], + [ 23, 43, 79, 120, 131, 145, 0 ], + [ 19, 35, 59, 73, 110, 125, 161 ], + [ 20, 36, 63, 94, 136, 161, 0 ], + [ 14, 31, 79, 98, 132, 164, 0 ], + [ 3, 44, 80, 124, 127, 169, 0 ], + [ 19, 46, 81, 117, 135, 167, 0 ], + [ 7, 49, 58, 90, 100, 105, 168 ], + [ 12, 50, 61, 118, 119, 144, 0 ], + [ 13, 51, 64, 114, 118, 157, 0 ], + [ 24, 52, 76, 129, 148, 149, 0 ], + [ 25, 53, 69, 90, 101, 130, 156 ], + [ 20, 46, 65, 80, 120, 140, 170 ], + [ 21, 54, 77, 100, 140, 171, 0 ], + [ 35, 82, 133, 142, 171, 174, 0 ], + [ 14, 30, 83, 113, 125, 170, 0 ], + [ 4, 29, 68, 120, 134, 173, 0 ], + [ 1, 4, 52, 57, 86, 136, 152 ], + [ 26, 51, 56, 91, 122, 137, 168 ], + [ 52, 84, 110, 115, 145, 168, 0 ], + [ 7, 50, 81, 99, 132, 173, 0 ], + [ 23, 55, 67, 95, 172, 174, 0 ], + [ 26, 41, 77, 109, 141, 148, 0 ], + [ 2, 27, 41, 61, 62, 115, 133 ], + [ 27, 40, 56, 124, 125, 126, 0 ], + [ 18, 49, 55, 124, 141, 167, 0 ], + [ 6, 33, 85, 108, 116, 156, 0 ], + [ 28, 48, 70, 85, 105, 129, 158 ], + [ 9, 54, 63, 131, 147, 155, 0 ], + [ 22, 53, 68, 109, 121, 174, 0 ], + [ 3, 13, 48, 78, 95, 123, 0 ], + [ 31, 69, 133, 150, 155, 169, 0 ], + [ 12, 43, 66, 89, 97, 135, 159 ], + [ 5, 39, 75, 102, 136, 167, 0 ], + [ 2, 54, 86, 101, 135, 164, 0 ], + [ 15, 56, 87, 108, 119, 171, 0 ], + [ 10, 44, 82, 91, 111, 144, 149 ], + [ 23, 34, 71, 94, 127, 153, 0 ], + [ 11, 49, 88, 92, 142, 157, 0 ], + [ 29, 34, 87, 97, 147, 162, 0 ], + [ 30, 50, 60, 86, 137, 142, 162 ], + [ 10, 53, 66, 84, 112, 128, 165 ], + [ 22, 57, 85, 93, 140, 159, 0 ], + [ 28, 32, 72, 103, 132, 166, 0 ], + [ 28, 29, 84, 88, 117, 143, 150 ], + [ 1, 26, 45, 80, 128, 147, 0 ], + [ 17, 27, 89, 103, 116, 153, 0 ], + [ 51, 57, 98, 163, 165, 172, 0 ], + [ 21, 37, 73, 138, 152, 169, 0 ], + [ 16, 47, 76, 130, 137, 154, 0 ], + [ 3, 24, 30, 72, 104, 139, 0 ], + [ 9, 40, 90, 106, 134, 151, 0 ], + [ 15, 58, 60, 74, 111, 150, 163 ], + [ 18, 42, 79, 144, 146, 152, 0 ], + [ 25, 38, 65, 99, 122, 160, 0 ], + [ 17, 42, 75, 129, 170, 172, 0 ] +] + +# Each row corresponds to a codeword bit. +# The numbers indicate which three LDPC parity checks (rows in Nm) refer to the codeword bit. +# 1-origin. +kFTX_LDPC_Mn = [ + [ 16, 45, 73 ], + [ 25, 51, 62 ], + [ 33, 58, 78 ], + [ 1, 44, 45 ], + [ 2, 7, 61 ], + [ 3, 6, 54 ], + [ 4, 35, 48 ], + [ 5, 13, 21 ], + [ 8, 56, 79 ], + [ 9, 64, 69 ], + [ 10, 19, 66 ], + [ 11, 36, 60 ], + [ 12, 37, 58 ], + [ 14, 32, 43 ], + [ 15, 63, 80 ], + [ 17, 28, 77 ], + [ 18, 74, 83 ], + [ 22, 53, 81 ], + [ 23, 30, 34 ], + [ 24, 31, 40 ], + [ 26, 41, 76 ], + [ 27, 57, 70 ], + [ 29, 49, 65 ], + [ 3, 38, 78 ], + [ 5, 39, 82 ], + [ 46, 50, 73 ], + [ 51, 52, 74 ], + [ 55, 71, 72 ], + [ 44, 67, 72 ], + [ 43, 68, 78 ], + [ 1, 32, 59 ], + [ 2, 6, 71 ], + [ 4, 16, 54 ], + [ 7, 65, 67 ], + [ 8, 30, 42 ], + [ 9, 22, 31 ], + [ 10, 18, 76 ], + [ 11, 23, 82 ], + [ 12, 28, 61 ], + [ 13, 52, 79 ], + [ 14, 50, 51 ], + [ 15, 81, 83 ], + [ 17, 29, 60 ], + [ 19, 33, 64 ], + [ 20, 26, 73 ], + [ 21, 34, 40 ], + [ 24, 27, 77 ], + [ 25, 55, 58 ], + [ 35, 53, 66 ], + [ 36, 48, 68 ], + [ 37, 46, 75 ], + [ 38, 45, 47 ], + [ 39, 57, 69 ], + [ 41, 56, 62 ], + [ 20, 49, 53 ], + [ 46, 52, 63 ], + [ 45, 70, 75 ], + [ 27, 35, 80 ], + [ 1, 15, 30 ], + [ 2, 68, 80 ], + [ 3, 36, 51 ], + [ 4, 28, 51 ], + [ 5, 31, 56 ], + [ 6, 20, 37 ], + [ 7, 40, 82 ], + [ 8, 60, 69 ], + [ 9, 10, 49 ], + [ 11, 44, 57 ], + [ 12, 39, 59 ], + [ 13, 24, 55 ], + [ 14, 21, 65 ], + [ 16, 71, 78 ], + [ 17, 30, 76 ], + [ 18, 25, 80 ], + [ 19, 61, 83 ], + [ 22, 38, 77 ], + [ 23, 41, 50 ], + [ 7, 26, 58 ], + [ 29, 32, 81 ], + [ 33, 40, 73 ], + [ 18, 34, 48 ], + [ 13, 42, 64 ], + [ 5, 26, 43 ], + [ 47, 69, 72 ], + [ 54, 55, 70 ], + [ 45, 62, 68 ], + [ 10, 63, 67 ], + [ 14, 66, 72 ], + [ 22, 60, 74 ], + [ 35, 39, 79 ], + [ 1, 46, 64 ], + [ 1, 24, 66 ], + [ 2, 5, 70 ], + [ 3, 31, 65 ], + [ 4, 49, 58 ], + [ 1, 4, 5 ], + [ 6, 60, 67 ], + [ 7, 32, 75 ], + [ 8, 48, 82 ], + [ 9, 35, 41 ], + [ 10, 39, 62 ], + [ 11, 14, 61 ], + [ 12, 71, 74 ], + [ 13, 23, 78 ], + [ 11, 35, 55 ], + [ 15, 16, 79 ], + [ 7, 9, 16 ], + [ 17, 54, 63 ], + [ 18, 50, 57 ], + [ 19, 30, 47 ], + [ 20, 64, 80 ], + [ 21, 28, 69 ], + [ 22, 25, 43 ], + [ 13, 22, 37 ], + [ 2, 47, 51 ], + [ 23, 54, 74 ], + [ 26, 34, 72 ], + [ 27, 36, 37 ], + [ 21, 36, 63 ], + [ 29, 40, 44 ], + [ 19, 26, 57 ], + [ 3, 46, 82 ], + [ 14, 15, 58 ], + [ 33, 52, 53 ], + [ 30, 43, 52 ], + [ 6, 9, 52 ], + [ 27, 33, 65 ], + [ 25, 69, 73 ], + [ 38, 55, 83 ], + [ 20, 39, 77 ], + [ 18, 29, 56 ], + [ 32, 48, 71 ], + [ 42, 51, 59 ], + [ 28, 44, 79 ], + [ 34, 60, 62 ], + [ 31, 45, 61 ], + [ 46, 68, 77 ], + [ 6, 24, 76 ], + [ 8, 10, 78 ], + [ 40, 41, 70 ], + [ 17, 50, 53 ], + [ 42, 66, 68 ], + [ 4, 22, 72 ], + [ 36, 64, 81 ], + [ 13, 29, 47 ], + [ 2, 8, 81 ], + [ 56, 67, 73 ], + [ 5, 38, 50 ], + [ 12, 38, 64 ], + [ 59, 72, 80 ], + [ 3, 26, 79 ], + [ 45, 76, 81 ], + [ 1, 65, 74 ], + [ 7, 18, 77 ], + [ 11, 56, 59 ], + [ 14, 39, 54 ], + [ 16, 37, 66 ], + [ 10, 28, 55 ], + [ 15, 60, 70 ], + [ 17, 25, 82 ], + [ 20, 30, 31 ], + [ 12, 67, 68 ], + [ 23, 75, 80 ], + [ 27, 32, 62 ], + [ 24, 69, 75 ], + [ 19, 21, 71 ], + [ 34, 53, 61 ], + [ 35, 46, 47 ], + [ 33, 59, 76 ], + [ 40, 43, 83 ], + [ 41, 42, 63 ], + [ 49, 75, 83 ], + [ 20, 44, 48 ], + [ 42, 49, 57 ] +] + +kFTX_LDPC_Num_rows = [ + 7, 6, 6, 6, 7, 6, 7, 6, 6, 7, 6, 6, 7, 7, 6, 6, + 6, 7, 6, 7, 6, 7, 6, 6, 6, 7, 6, 6, 6, 7, 6, 6, + 6, 6, 7, 6, 6, 6, 7, 7, 6, 6, 6, 6, 7, 7, 6, 6, + 6, 6, 7, 6, 6, 6, 7, 6, 6, 6, 6, 7, 6, 6, 6, 7, + 6, 6, 6, 7, 7, 6, 6, 7, 6, 6, 6, 6, 6, 6, 6, 7, + 6, 6, 6 +] diff --git a/external/ft8_lib/utils/run_tests.py b/external/ft8_lib/utils/run_tests.py new file mode 100755 index 0000000..d00b509 --- /dev/null +++ b/external/ft8_lib/utils/run_tests.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 + +import sys, os, subprocess + +def parse(line): + fields = line.strip().split() + freq = fields[3] + dest = fields[5] if len(fields) > 5 else '' + source = fields[6] if len(fields) > 6 else '' + report = fields[7] if len(fields) > 7 else '' + if dest and dest[0] == '<' and dest[-1] == '>': + dest = '<...>' + if source and source[0] == '<' and source[-1] == '>': + source = '<...>' + return ' '.join([dest, source, report]) + +wav_dir = sys.argv[1] +wav_files = [os.path.join(wav_dir, f) for f in os.listdir(wav_dir)] +wav_files = [f for f in wav_files if os.path.isfile(f) and os.path.splitext(f)[1] == '.wav'] +txt_files = [os.path.splitext(f)[0] + '.txt' for f in wav_files] + +is_ft4 = False +if len(sys.argv) > 2 and sys.argv[2] == '-ft4': + is_ft4 = True + +n_extra = 0 +n_missed = 0 +n_total = 0 +for wav_file, txt_file in zip(wav_files, txt_files): + if not os.path.isfile(txt_file): continue + print(wav_file) + cmd_args = ['./decode_ft8', wav_file] + if is_ft4: + cmd_args.append('-ft4') + result = subprocess.run(cmd_args, stdout=subprocess.PIPE) + result = result.stdout.decode('utf-8').split('\n') + result = [parse(x) for x in result if len(x) > 0] + #print(result[0]) + result = set(result) + + expected = open(txt_file).read().split('\n') + expected = [parse(x) for x in expected if len(x) > 0] + #print(expected[0]) + expected = set(expected) + + extra_decodes = result - expected + missed_decodes = expected - result + print(len(result), '/', len(expected)) + if len(extra_decodes) > 0: + print('Extra decodes: ', list(extra_decodes)) + if len(missed_decodes) > 0: + print('Missed decodes: ', list(missed_decodes)) + + n_total += len(expected) + n_extra += len(extra_decodes) + n_missed += len(missed_decodes) + + #break + +print('Total: %d, extra: %d (%.1f%%), missed: %d (%.1f%%)' % + (n_total, n_extra, 100.0*n_extra/n_total, n_missed, 100.0*n_missed/n_total)) +recall = (n_total - n_missed) / float(n_total) +print('Recall: %.1f%%' % (100*recall, )) diff --git a/src/trx-client/src/audio_client.rs b/src/trx-client/src/audio_client.rs index c74259a..3b30263 100644 --- a/src/trx-client/src/audio_client.rs +++ b/src/trx-client/src/audio_client.rs @@ -16,7 +16,8 @@ use tracing::{info, warn}; use trx_core::audio::{ read_audio_msg, write_audio_msg, AudioStreamInfo, AUDIO_MSG_APRS_DECODE, - AUDIO_MSG_CW_DECODE, AUDIO_MSG_RX_FRAME, AUDIO_MSG_STREAM_INFO, AUDIO_MSG_TX_FRAME, + AUDIO_MSG_CW_DECODE, AUDIO_MSG_FT8_DECODE, AUDIO_MSG_RX_FRAME, AUDIO_MSG_STREAM_INFO, + AUDIO_MSG_TX_FRAME, }; use trx_core::decode::DecodedMessage; @@ -88,7 +89,7 @@ async fn handle_audio_connection( Ok((AUDIO_MSG_RX_FRAME, payload)) => { let _ = rx_tx.send(Bytes::from(payload)); } - Ok((AUDIO_MSG_APRS_DECODE | AUDIO_MSG_CW_DECODE, payload)) => { + Ok((AUDIO_MSG_APRS_DECODE | AUDIO_MSG_CW_DECODE | AUDIO_MSG_FT8_DECODE, payload)) => { if let Ok(msg) = serde_json::from_slice::(&payload) { let _ = decode_tx.send(msg); } diff --git a/src/trx-client/src/main.rs b/src/trx-client/src/main.rs index 65fa99c..187fb6f 100644 --- a/src/trx-client/src/main.rs +++ b/src/trx-client/src/main.rs @@ -277,8 +277,10 @@ async fn async_init() -> DynResult { cw_auto: true, cw_wpm: 15, cw_tone_hz: 700, + ft8_decode_enabled: false, aprs_decode_reset_seq: 0, cw_decode_reset_seq: 0, + ft8_decode_reset_seq: 0, }; let (state_tx, state_rx) = watch::channel(initial_state); diff --git a/src/trx-client/src/remote_client.rs b/src/trx-client/src/remote_client.rs index 2ff3099..75a8a5b 100644 --- a/src/trx-client/src/remote_client.rs +++ b/src/trx-client/src/remote_client.rs @@ -151,8 +151,10 @@ fn map_rig_command(cmd: trx_core::RigCommand) -> ClientCommand { trx_core::RigCommand::SetCwAuto(enabled) => ClientCommand::SetCwAuto { enabled }, trx_core::RigCommand::SetCwWpm(wpm) => ClientCommand::SetCwWpm { wpm }, trx_core::RigCommand::SetCwToneHz(tone_hz) => ClientCommand::SetCwToneHz { tone_hz }, + trx_core::RigCommand::SetFt8DecodeEnabled(enabled) => ClientCommand::SetFt8DecodeEnabled { enabled }, trx_core::RigCommand::ResetAprsDecoder => ClientCommand::ResetAprsDecoder, trx_core::RigCommand::ResetCwDecoder => ClientCommand::ResetCwDecoder, + trx_core::RigCommand::ResetFt8Decoder => ClientCommand::ResetFt8Decoder, } } @@ -196,8 +198,10 @@ pub fn state_from_snapshot(snapshot: trx_core::RigSnapshot) -> RigState { cw_auto: snapshot.cw_auto, cw_wpm: snapshot.cw_wpm, cw_tone_hz: snapshot.cw_tone_hz, + ft8_decode_enabled: snapshot.ft8_decode_enabled, aprs_decode_reset_seq: 0, cw_decode_reset_seq: 0, + ft8_decode_reset_seq: 0, } } diff --git a/src/trx-client/trx-frontend/trx-frontend-http-json/src/server.rs b/src/trx-client/trx-frontend/trx-frontend-http-json/src/server.rs index f788734..a3deb30 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http-json/src/server.rs +++ b/src/trx-client/trx-frontend/trx-frontend-http-json/src/server.rs @@ -145,8 +145,10 @@ async fn handle_client( ClientCommand::SetCwAuto { enabled } => RigCommand::SetCwAuto(enabled), ClientCommand::SetCwWpm { wpm } => RigCommand::SetCwWpm(wpm), ClientCommand::SetCwToneHz { tone_hz } => RigCommand::SetCwToneHz(tone_hz), + ClientCommand::SetFt8DecodeEnabled { enabled } => RigCommand::SetFt8DecodeEnabled(enabled), ClientCommand::ResetAprsDecoder => RigCommand::ResetAprsDecoder, ClientCommand::ResetCwDecoder => RigCommand::ResetCwDecoder, + ClientCommand::ResetFt8Decoder => RigCommand::ResetFt8Decoder, }; let (resp_tx, resp_rx) = oneshot::channel(); diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js index a60e44d..6153336 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js @@ -248,12 +248,17 @@ function render(update) { const modeUpper = update.status && update.status.mode ? normalizeMode(update.status.mode).toUpperCase() : ""; const aprsStatus = document.getElementById("aprs-status"); const cwStatus = document.getElementById("cw-status"); + const ft8Status = document.getElementById("ft8-status"); if (aprsStatus && modeUpper !== "PKT" && aprsStatus.textContent === "Receiving") { aprsStatus.textContent = "Connected, listening for packets"; } if (cwStatus && modeUpper !== "CW" && modeUpper !== "CWR" && cwStatus.textContent === "Receiving") { cwStatus.textContent = "Connected, listening for packets"; } + const ft8Enabled = !!update.ft8_decode_enabled; + if (ft8Status && (!ft8Enabled || (modeUpper !== "DIG" && modeUpper !== "USB")) && ft8Status.textContent === "Receiving") { + ft8Status.textContent = "Connected, listening for packets"; + } if (update.status && typeof update.status.tx_en === "boolean") { lastTxEn = update.status.tx_en; pttBtn.textContent = update.status.tx_en ? "PTT On" : "PTT Off"; @@ -267,6 +272,13 @@ function render(update) { pttBtn.style.color = ""; } } + const ft8ToggleBtn = document.getElementById("ft8-decode-toggle-btn"); + if (ft8ToggleBtn) { + const ft8On = !!update.ft8_decode_enabled; + ft8ToggleBtn.textContent = ft8On ? "Disable FT8" : "Enable FT8"; + ft8ToggleBtn.style.borderColor = ft8On ? "#00d17f" : ""; + ft8ToggleBtn.style.color = ft8On ? "#00d17f" : ""; + } const cwAutoEl = document.getElementById("cw-auto"); const cwWpmEl = document.getElementById("cw-wpm"); const cwToneEl = document.getElementById("cw-tone"); @@ -1156,8 +1168,10 @@ let decodeConnected = false; function updateDecodeStatus(text) { const aprs = document.getElementById("aprs-status"); const cw = document.getElementById("cw-status"); + const ft8 = document.getElementById("ft8-status"); if (aprs && aprs.textContent !== "Receiving") aprs.textContent = text; if (cw && cw.textContent !== "Receiving") cw.textContent = text; + if (ft8 && ft8.textContent !== "Receiving") ft8.textContent = text; } function connectDecode() { if (decodeSource) { decodeSource.close(); } @@ -1171,6 +1185,7 @@ function connectDecode() { const msg = JSON.parse(evt.data); if (msg.type === "aprs" && window.onServerAprs) window.onServerAprs(msg); if (msg.type === "cw" && window.onServerCw) window.onServerCw(msg); + if (msg.type === "ft8" && window.onServerFt8) window.onServerFt8(msg); } catch (e) { // ignore parse errors } diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html index 455667b..63b0a51 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html @@ -124,6 +124,7 @@ +
@@ -150,6 +151,14 @@
+