[refactor](trx-rs): convert external/ft8_lib to git submodule

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
2026-03-18 21:44:07 +01:00
parent 71b9a3128b
commit 974b9fa9ed
182 changed files with 4 additions and 9026 deletions
Vendored Submodule
+1
Submodule external/ft8_lib added at f5421c3972
-33
View File
@@ -1,33 +0,0 @@
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
-9
View File
@@ -1,9 +0,0 @@
gen_ft8
decode_ft8
test_ft8
libft8.a
wsjtx2/
.build/
.DS_Store
.vscode/
__pycache__/
-21
View File
@@ -1,21 +0,0 @@
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.
-58
View File
@@ -1,58 +0,0 @@
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)
-54
View File
@@ -1,54 +0,0 @@
# 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
-191
View File
@@ -1,191 +0,0 @@
#include "audio.h"
#include <stdio.h>
#include <string.h>
#ifdef USE_PORTAUDIO
#include <portaudio.h>
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
-18
View File
@@ -1,18 +0,0 @@
#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_
-3
View File
@@ -1,3 +0,0 @@
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
-261
View File
@@ -1,261 +0,0 @@
#include "monitor.h"
#include <common/common.h>
#define LOG_LEVEL LOG_INFO
#include <ft8/debug.h>
#include <stdlib.h>
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 = ftx_protocol_slot_time(cfg->protocol);
float symbol_period = ftx_protocol_symbol_period(cfg->protocol);
// 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 ftx_candidate_t* candidate, float* signal)
{
const int num_ifft = me->nifft;
const int num_shift = num_ifft / 2;
const int taper_width = 4;
// 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
-62
View File
@@ -1,62 +0,0 @@
#ifndef _INCLUDE_MONITOR_H_
#define _INCLUDE_MONITOR_H_
#ifdef __cplusplus
extern "C"
{
#endif
#include <ft8/decode.h>
#include <fft/kiss_fftr.h>
/// 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 ftx_candidate_t* candidate, float* signal);
#endif
#ifdef __cplusplus
}
#endif
#endif // _INCLUDE_MONITOR_H_
-133
View File
@@ -1,133 +0,0 @@
#include "wave.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdint.h>
// Save signal in floating point format (-1 .. +1) as a WAVE file using 16-bit signed integers.
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;
}
-19
View File
@@ -1,19 +0,0 @@
#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_
-393
View File
@@ -1,393 +0,0 @@
#define _POSIX_C_SOURCE 199309L
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <stdbool.h>
#include <time.h>
#include <ft8/decode.h>
#include <ft8/encode.h>
#include <ft8/message.h>
#include <common/common.h>
#include <common/wave.h>
#include <common/monitor.h>
#include <common/audio.h>
#define LOG_LEVEL LOG_INFO
#include <ft8/debug.h>
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;
}
-189
View File
@@ -1,189 +0,0 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <stdbool.h>
#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;
}
-158
View File
@@ -1,158 +0,0 @@
/*
* 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 <limits.h>
#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 <alloca.h>
#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
-402
View File
@@ -1,402 +0,0 @@
/*
* 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; u<m; ++u ) {
C_FIXDIV( *Fout0,5); C_FIXDIV( *Fout1,5); C_FIXDIV( *Fout2,5); C_FIXDIV( *Fout3,5); C_FIXDIV( *Fout4,5);
scratch[0] = *Fout0;
C_MUL(scratch[1] ,*Fout1, tw[u*fstride]);
C_MUL(scratch[2] ,*Fout2, tw[2*u*fstride]);
C_MUL(scratch[3] ,*Fout3, tw[3*u*fstride]);
C_MUL(scratch[4] ,*Fout4, tw[4*u*fstride]);
C_ADD( scratch[7],scratch[1],scratch[4]);
C_SUB( scratch[10],scratch[1],scratch[4]);
C_ADD( scratch[8],scratch[2],scratch[3]);
C_SUB( scratch[9],scratch[2],scratch[3]);
Fout0->r += 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<m; ++u ) {
k=u;
for ( q1=0 ; q1<p ; ++q1 ) {
scratch[q1] = Fout[ k ];
C_FIXDIV(scratch[q1],p);
k += m;
}
k=u;
for ( q1=0 ; q1<p ; ++q1 ) {
int twidx=0;
Fout[ k ] = scratch[0];
for (q=1;q<p;++q ) {
twidx += fstride * k;
if (twidx>=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<p;++k)
kf_work( Fout +k*m, f+ fstride*in_stride*k,fstride*p,in_stride,factors,st);
// all threads have joined by this point
switch (p) {
case 2: kf_bfly2(Fout,fstride,st,m); break;
case 3: kf_bfly3(Fout,fstride,st,m); break;
case 4: kf_bfly4(Fout,fstride,st,m); break;
case 5: kf_bfly5(Fout,fstride,st,m); break;
default: kf_bfly_generic(Fout,fstride,st,m,p); break;
}
return;
}
#endif
if (m==1) {
do{
*Fout = *f;
f += fstride*in_stride;
}while(++Fout != Fout_end );
}else{
do{
// recursive call:
// DFT of size m*p performed by doing
// p instances of smaller DFTs of size m,
// each one takes a decimated version of the input
kf_work( Fout , f, fstride*p, in_stride, factors,st);
f += fstride*in_stride;
}while( (Fout += m) != Fout_end );
}
Fout=Fout_beg;
// recombine the p smaller DFTs
switch (p) {
case 2: kf_bfly2(Fout,fstride,st,m); break;
case 3: kf_bfly3(Fout,fstride,st,m); break;
case 4: kf_bfly4(Fout,fstride,st,m); break;
case 5: kf_bfly5(Fout,fstride,st,m); break;
default: kf_bfly_generic(Fout,fstride,st,m,p); break;
}
}
/* facbuf is populated by p1,m1,p2,m2, ...
where
p[i] * m[i] = m[i-1]
m0 = n */
static
void kf_factor(int n,int * facbuf)
{
int p=4;
double floor_sqrt;
floor_sqrt = floor( sqrt((double)n) );
/*factor out powers of 4, powers of 2, then any remaining primes */
do {
while (n % p) {
switch (p) {
case 4: p = 2; break;
case 2: p = 3; break;
default: p += 2; break;
}
if (p > 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;i<nfft;++i) {
const double pi=3.141592653589793238462643383279502884197169399375105820974944;
double phase = -2*pi*i / nfft;
if (st->inverse)
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;
}
-132
View File
@@ -1,132 +0,0 @@
/*
* 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 <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#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 <xmmintrin.h>
# 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 <sys/types.h>
# 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
-153
View File
@@ -1,153 +0,0 @@
/*
* 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);
}
-54
View File
@@ -1,54 +0,0 @@
/*
* 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
-54
View File
@@ -1,54 +0,0 @@
#
# 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
-13
View File
@@ -1,13 +0,0 @@
! 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 "/
-67
View File
@@ -1,67 +0,0 @@
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 "<message>"'
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
-36
View File
@@ -1,36 +0,0 @@
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
-86
View File
@@ -1,86 +0,0 @@
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
-55
View File
@@ -1,55 +0,0 @@
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
-41
View File
@@ -1,41 +0,0 @@
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
-34
View File
@@ -1,34 +0,0 @@
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 <callsign>'
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
-24
View File
@@ -1,24 +0,0 @@
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 <callsign>'
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
-183
View File
@@ -1,183 +0,0 @@
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
-11
View File
@@ -1,11 +0,0 @@
! 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 "/
-31
View File
@@ -1,31 +0,0 @@
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 <call_std>'
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
-392
View File
@@ -1,392 +0,0 @@
#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
};
-121
View File
@@ -1,121 +0,0 @@
#ifndef _INCLUDE_CONSTANTS_H_
#define _INCLUDE_CONSTANTS_H_
#include <stdint.h>
#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 FT2_SYMBOL_PERIOD (0.024f) ///< FT2 symbol duration (288 samples @ 12 kHz)
#define FT2_SLOT_TIME (3.75f) ///< FT2 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
// FT2 reuses the FT4 channel structure with a shorter slot and symbol period.
#define FT2_ND FT4_ND
#define FT2_NR FT4_NR
#define FT2_NN FT4_NN
#define FT2_LENGTH_SYNC FT4_LENGTH_SYNC
#define FT2_NUM_SYNC FT4_NUM_SYNC
#define FT2_SYNC_OFFSET FT4_SYNC_OFFSET
// 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_FT2
} ftx_protocol_t;
static inline float ftx_protocol_symbol_period(ftx_protocol_t protocol)
{
return (protocol == FTX_PROTOCOL_FT8)
? FT8_SYMBOL_PERIOD
: ((protocol == FTX_PROTOCOL_FT2) ? FT2_SYMBOL_PERIOD : FT4_SYMBOL_PERIOD);
}
static inline float ftx_protocol_slot_time(ftx_protocol_t protocol)
{
return (protocol == FTX_PROTOCOL_FT8)
? FT8_SLOT_TIME
: ((protocol == FTX_PROTOCOL_FT2) ? FT2_SLOT_TIME : FT4_SLOT_TIME);
}
static inline int ftx_protocol_uses_ft4_layout(ftx_protocol_t protocol)
{
return (protocol == FTX_PROTOCOL_FT4) || (protocol == FTX_PROTOCOL_FT2);
}
/// 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_
-63
View File
@@ -1,63 +0,0 @@
#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);
}
-31
View File
@@ -1,31 +0,0 @@
#ifndef _INCLUDE_CRC_H_
#define _INCLUDE_CRC_H_
#include <stdint.h>
#include <stdbool.h>
#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_
-22
View File
@@ -1,22 +0,0 @@
#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 <stdio.h>
#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_
-773
View File
@@ -1,773 +0,0 @@
#include "decode.h"
#include "constants.h"
#include "crc.h"
#include "ldpc.h"
#include <stdbool.h>
#include <math.h>
#include <complex.h>
// #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 ft2_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 ft2_extract_logl_sequence(const float complex symbols[4][FT2_NN - FT2_NR], int start_sym, int n_syms, float* metrics);
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 inline float complex wf_elem_to_complex(const WF_ELEM_T elem)
{
float mag = WF_ELEM_MAG(elem);
float amplitude = powf(10.0f, mag / 20.0f);
return amplitude * cexpf(I * elem.phase);
}
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 ft2_sync_score(const ftx_waterfall_t* wf, const ftx_candidate_t* candidate)
{
const WF_ELEM_T* mag_cand = get_cand_mag(wf, candidate);
float score = 0.0f;
int groups = 0;
for (int m = 0; m < FT2_NUM_SYNC; ++m)
{
float complex sum = 0.0f;
bool complete = true;
for (int k = 0; k < FT2_LENGTH_SYNC; ++k)
{
int block = 1 + (FT2_SYNC_OFFSET * m) + k;
int block_abs = candidate->time_offset + block;
if ((block_abs < 0) || (block_abs >= wf->num_blocks))
{
complete = false;
break;
}
const WF_ELEM_T* sym = mag_cand + (block * wf->block_stride);
int tone = kFT4_Costas_pattern[m][k];
sum += wf_elem_to_complex(sym[tone]);
}
if (!complete)
continue;
score += cabsf(sum);
++groups;
}
if (groups == 0)
return 0;
return (int)lroundf((score / groups) * 8.0f);
}
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)
{
bool is_ft2 = (wf->protocol == FTX_PROTOCOL_FT2);
int (*sync_fun)(const ftx_waterfall_t*, const ftx_candidate_t*) =
is_ft2 ? ft2_sync_score : (ftx_protocol_uses_ft4_layout(wf->protocol) ? ft4_sync_score : ft8_sync_score);
int num_tones = ftx_protocol_uses_ft4_layout(wf->protocol) ? 4 : 8;
int time_offset_min = -10;
int time_offset_max = 20;
if (is_ft2)
{
time_offset_min = -2;
time_offset_max = wf->num_blocks - FT2_NN + 2;
if (time_offset_max <= time_offset_min)
{
time_offset_max = time_offset_min + 1;
}
}
else if (wf->protocol == FTX_PROTOCOL_FT4)
{
// Keep roughly the same +/- seconds search span used by FT8.
// FT4 symbols are much shorter, so it needs a wider symbol-index window.
time_offset_min = -34;
time_offset_max = wf->num_blocks - FT4_NN + 34;
if (time_offset_max <= time_offset_min)
{
time_offset_max = time_offset_min + 1;
}
}
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 = time_offset_min; candidate.time_offset < time_offset_max; ++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 ft2_extract_likelihood(const ftx_waterfall_t* wf, const ftx_candidate_t* cand, float* log174)
{
const WF_ELEM_T* mag = get_cand_mag(wf, cand);
float complex symbols[4][FT2_NN - FT2_NR];
float metric1[2 * (FT2_NN - FT2_NR)] = { 0 };
float metric2[2 * (FT2_NN - FT2_NR)] = { 0 };
float metric4[2 * (FT2_NN - FT2_NR)] = { 0 };
for (int frame_sym = 0; frame_sym < (FT2_NN - FT2_NR); ++frame_sym)
{
int sym_idx = frame_sym + 1; // skip ramp-up symbol
int block = cand->time_offset + sym_idx;
if ((block < 0) || (block >= wf->num_blocks))
{
for (int tone = 0; tone < 4; ++tone)
{
symbols[tone][frame_sym] = 0.0f;
}
continue;
}
const WF_ELEM_T* sym = mag + (sym_idx * wf->block_stride);
for (int tone = 0; tone < 4; ++tone)
{
symbols[tone][frame_sym] = wf_elem_to_complex(sym[tone]);
}
}
for (int start = 0; start <= (FT2_NN - FT2_NR) - 1; start += 1)
{
ft2_extract_logl_sequence(symbols, start, 1, metric1 + (2 * start));
}
for (int start = 0; start <= (FT2_NN - FT2_NR) - 2; start += 2)
{
ft2_extract_logl_sequence(symbols, start, 2, metric2 + (2 * start));
}
for (int start = 0; start <= (FT2_NN - FT2_NR) - 4; start += 4)
{
ft2_extract_logl_sequence(symbols, start, 4, metric4 + (2 * start));
}
metric2[204] = metric1[204];
metric2[205] = metric1[205];
metric4[200] = metric2[200];
metric4[201] = metric2[201];
metric4[202] = metric2[202];
metric4[203] = metric2[203];
metric4[204] = metric1[204];
metric4[205] = metric1[205];
for (int data_sym = 0; data_sym < FT2_ND; ++data_sym)
{
int frame_sym = data_sym + ((data_sym < 29) ? 4 : ((data_sym < 58) ? 8 : 12));
int src_bit = 2 * frame_sym;
int dst_bit = 2 * data_sym;
float a0 = metric1[src_bit + 0];
float b0 = metric2[src_bit + 0];
float c0 = metric4[src_bit + 0];
float a1 = metric1[src_bit + 1];
float b1 = metric2[src_bit + 1];
float c1 = metric4[src_bit + 1];
log174[dst_bit + 0] = (fabsf(a0) >= fabsf(b0) && fabsf(a0) >= fabsf(c0)) ? a0 : ((fabsf(b0) >= fabsf(c0)) ? b0 : c0);
log174[dst_bit + 1] = (fabsf(a1) >= fabsf(b1) && fabsf(a1) >= fabsf(c1)) ? a1 : ((fabsf(b1) >= fabsf(c1)) ? b1 : c1);
}
}
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_FT2)
{
ft2_extract_likelihood(wf, cand, log174);
}
else if (ftx_protocol_uses_ft4_layout(wf->protocol))
{
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 (ftx_protocol_uses_ft4_layout(wf->protocol))
{
// '[..] for FT4 only, in order to avoid transmitting a long string of zeros when sending CQ messages,
// the assembled 77-bit message is bitwise exclusive-ORed with [a] pseudorandom sequence before computing the CRC and FEC parity bits'
for (int i = 0; i < 10; ++i)
{
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]);
}
static void ft2_extract_logl_sequence(const float complex symbols[4][FT2_NN - FT2_NR], int start_sym, int n_syms, float* metrics)
{
const int n_bits = 2 * n_syms;
const int n_sequences = 1 << n_bits;
for (int bit = 0; bit < n_bits; ++bit)
{
float max_zero = -INFINITY;
float max_one = -INFINITY;
for (int seq = 0; seq < n_sequences; ++seq)
{
float complex sum = 0.0f;
for (int sym = 0; sym < n_syms; ++sym)
{
int shift = 2 * (n_syms - sym - 1);
int dibit = (seq >> shift) & 0x3;
int tone = kFT4_Gray_map[dibit];
sum += symbols[tone][start_sym + sym];
}
float strength = cabsf(sum);
int mask_bit = n_bits - bit - 1;
if (((seq >> mask_bit) & 0x1) != 0)
{
if (strength > max_one)
max_one = strength;
}
else
{
if (strength > max_zero)
max_zero = strength;
}
}
metrics[bit] = max_one - max_zero;
}
}
// 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;
}
}
}
-96
View File
@@ -1,96 +0,0 @@
#ifndef _INCLUDE_DECODE_H_
#define _INCLUDE_DECODE_H_
#include <stdint.h>
#include <stdbool.h>
#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_
-200
View File
@@ -1,200 +0,0 @@
#include "encode.h"
#include "constants.h"
#include "crc.h"
#include <stdio.h>
// 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-ORed 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];
}
}
}
void ft2_encode(const uint8_t* payload, uint8_t* tones)
{
ft4_encode(payload, tones);
}
-47
View File
@@ -1,47 +0,0 @@
#ifndef _INCLUDE_ENCODE_H_
#define _INCLUDE_ENCODE_H_
#include <stdint.h>
#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);
/// Generate FT2 tone sequence from payload data.
/// FT2 uses the FT4 framing with a doubled symbol rate.
/// @param[in] payload - 10 byte array consisting of 77 bit payload
/// @param[out] tones - array of FT2_NN (105) bytes to store the generated tones (encoded as 0..3)
void ft2_encode(const uint8_t* payload, uint8_t* tones);
#ifdef __cplusplus
}
#endif
#endif // _INCLUDE_ENCODE_H_
-251
View File
@@ -1,251 +0,0 @@
//
// 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 <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <stdbool.h>
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;
}
-23
View File
@@ -1,23 +0,0 @@
#ifndef _INCLUDE_LDPC_H_
#define _INCLUDE_LDPC_H_
#include <stdint.h>
#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_
-1156
View File
File diff suppressed because it is too large Load Diff
-160
View File
@@ -1,160 +0,0 @@
#ifndef _INCLUDE_MESSAGE_H_
#define _INCLUDE_MESSAGE_H_
#include <stdint.h>
#include <stdbool.h>
#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 <KH1/KH7Z> -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 <WA9XYZ> 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 <KH1/KH7Z> -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 <WA9XYZ> 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 (<CA0LL>).
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_
-303
View File
@@ -1,303 +0,0 @@
#include "text.h"
#include <string.h>
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;
}
-82
View File
@@ -1,82 +0,0 @@
#ifndef _INCLUDE_TEXT_H_
#define _INCLUDE_TEXT_H_
#include <stdbool.h>
#include <stdint.h>
#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_
-286
View File
@@ -1,286 +0,0 @@
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <stdbool.h>
#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,
"<EA8/G5LSI> R2RFE RR73", &hash_if);
test_msg("R2RFE/P EA8/G5LSI R+12", FTX_MESSAGE_TYPE_STANDARD,
"R2RFE/P <EA8/G5LSI> 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,
"<TNX> <BOB> 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;
}
-1
View File
@@ -1 +0,0 @@
110115 6 0.9 1234 ~ GJ0KYZ RK9AX MO05
Binary file not shown.
-5
View File
@@ -1,5 +0,0 @@
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
Binary file not shown.
-2
View File
@@ -1,2 +0,0 @@
110145 -4 1.0 322 ~ <...> RY8CAA
110145 7 1.0 1234 ~ GJ0KYZ RK9AX MO05
Binary file not shown.
-5
View File
@@ -1,5 +0,0 @@
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
Binary file not shown.
-4
View File
@@ -1,4 +0,0 @@
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
Binary file not shown.
-22
View File
@@ -1,22 +0,0 @@
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
Binary file not shown.
-15
View File
@@ -1,15 +0,0 @@
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
Binary file not shown.
-20
View File
@@ -1,20 +0,0 @@
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
Binary file not shown.
-16
View File
@@ -1,16 +0,0 @@
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
Binary file not shown.
-24
View File
@@ -1,24 +0,0 @@
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
Binary file not shown.
-24
View File
@@ -1,24 +0,0 @@
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
Binary file not shown.
-19
View File
@@ -1,19 +0,0 @@
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
Binary file not shown.
-20
View File
@@ -1,20 +0,0 @@
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
Binary file not shown.
-32
View File
@@ -1,32 +0,0 @@
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
Binary file not shown.
-27
View File
@@ -1,27 +0,0 @@
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
Binary file not shown.
-31
View File
@@ -1,31 +0,0 @@
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
Binary file not shown.
-19
View File
@@ -1,19 +0,0 @@
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
Binary file not shown.
-27
View File
@@ -1,27 +0,0 @@
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
Binary file not shown.
-20
View File
@@ -1,20 +0,0 @@
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
Binary file not shown.
-31
View File
@@ -1,31 +0,0 @@
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
Binary file not shown.
-18
View File
@@ -1,18 +0,0 @@
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
Binary file not shown.
-26
View File
@@ -1,26 +0,0 @@
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
Binary file not shown.
-17
View File
@@ -1,17 +0,0 @@
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
Binary file not shown.
-28
View File
@@ -1,28 +0,0 @@
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
Binary file not shown.
-16
View File
@@ -1,16 +0,0 @@
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
Binary file not shown.
-26
View File
@@ -1,26 +0,0 @@
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
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More