[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:
+1
Submodule external/ft8_lib added at f5421c3972
Vendored
-33
@@ -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
|
||||
@@ -1,9 +0,0 @@
|
||||
gen_ft8
|
||||
decode_ft8
|
||||
test_ft8
|
||||
libft8.a
|
||||
wsjtx2/
|
||||
.build/
|
||||
.DS_Store
|
||||
.vscode/
|
||||
__pycache__/
|
||||
Vendored
-21
@@ -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.
|
||||
Vendored
-58
@@ -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)
|
||||
Vendored
-54
@@ -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
|
||||
Vendored
-191
@@ -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
|
||||
Vendored
-18
@@ -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_
|
||||
Vendored
-3
@@ -1,3 +0,0 @@
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
Vendored
-261
@@ -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
|
||||
Vendored
-62
@@ -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_
|
||||
Vendored
-133
@@ -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;
|
||||
}
|
||||
Vendored
-19
@@ -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_
|
||||
Vendored
-393
@@ -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;
|
||||
}
|
||||
Vendored
-189
@@ -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
@@ -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
|
||||
Vendored
-402
@@ -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;
|
||||
}
|
||||
Vendored
-132
@@ -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
|
||||
Vendored
-153
@@ -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);
|
||||
}
|
||||
Vendored
-54
@@ -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
@@ -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
|
||||
@@ -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 "/
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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 "/
|
||||
@@ -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
|
||||
Vendored
-392
@@ -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
|
||||
};
|
||||
Vendored
-121
@@ -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_
|
||||
Vendored
-63
@@ -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);
|
||||
}
|
||||
Vendored
-31
@@ -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_
|
||||
Vendored
-22
@@ -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_
|
||||
Vendored
-773
@@ -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-OR’ed with [a] pseudorandom sequence before computing the CRC and FEC parity bits'
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
message->payload[i] = a91[i] ^ kFT4_XOR_sequence[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
message->payload[i] = a91[i];
|
||||
}
|
||||
}
|
||||
|
||||
// LOG(LOG_DEBUG, "Decoded message (CRC %04x), trying to unpack...\n", status->crc_extracted);
|
||||
return true;
|
||||
}
|
||||
|
||||
static float max2(float a, float b)
|
||||
{
|
||||
return (a >= b) ? a : b;
|
||||
}
|
||||
|
||||
static float max4(float a, float b, float c, float d)
|
||||
{
|
||||
return max2(max2(a, b), max2(c, d));
|
||||
}
|
||||
|
||||
static void heapify_down(ftx_candidate_t heap[], int heap_size)
|
||||
{
|
||||
// heapify from the root down
|
||||
int current = 0; // root node
|
||||
while (true)
|
||||
{
|
||||
int left = 2 * current + 1;
|
||||
int right = left + 1;
|
||||
|
||||
// Find the smallest value of (parent, left child, right child)
|
||||
int smallest = current;
|
||||
if ((left < heap_size) && (heap[left].score < heap[smallest].score))
|
||||
{
|
||||
smallest = left;
|
||||
}
|
||||
if ((right < heap_size) && (heap[right].score < heap[smallest].score))
|
||||
{
|
||||
smallest = right;
|
||||
}
|
||||
|
||||
if (smallest == current)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Exchange the current node with the smallest child and move down to it
|
||||
ftx_candidate_t tmp = heap[smallest];
|
||||
heap[smallest] = heap[current];
|
||||
heap[current] = tmp;
|
||||
current = smallest;
|
||||
}
|
||||
}
|
||||
|
||||
static void heapify_up(ftx_candidate_t heap[], int heap_size)
|
||||
{
|
||||
// heapify from the last node up
|
||||
int current = heap_size - 1;
|
||||
while (current > 0)
|
||||
{
|
||||
int parent = (current - 1) / 2;
|
||||
if (!(heap[current].score < heap[parent].score))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Exchange the current node with its parent and move up
|
||||
ftx_candidate_t tmp = heap[parent];
|
||||
heap[parent] = heap[current];
|
||||
heap[current] = tmp;
|
||||
current = parent;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute unnormalized log likelihood log(p(1) / p(0)) of 2 message bits (1 FSK symbol)
|
||||
static void ft4_extract_symbol(const WF_ELEM_T* wf, float* logl)
|
||||
{
|
||||
// Cleaned up code for the simple case of n_syms==1
|
||||
float s2[4];
|
||||
|
||||
for (int j = 0; j < 4; ++j)
|
||||
{
|
||||
s2[j] = WF_ELEM_MAG(wf[kFT4_Gray_map[j]]);
|
||||
}
|
||||
|
||||
logl[0] = max2(s2[2], s2[3]) - max2(s2[0], s2[1]);
|
||||
logl[1] = max2(s2[1], s2[3]) - max2(s2[0], s2[2]);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
-96
@@ -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_
|
||||
Vendored
-200
@@ -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-OR’ed with [a] pseudorandom sequence before computing the CRC and FEC parity bits'
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
payload_xor[i] = payload[i] ^ kFT4_XOR_sequence[i];
|
||||
}
|
||||
|
||||
// Compute and add CRC at the end of the message
|
||||
// a91 contains 77 bits of payload + 14 bits of CRC
|
||||
ftx_add_crc(payload_xor, a91);
|
||||
|
||||
uint8_t codeword[FTX_LDPC_N_BYTES];
|
||||
encode174(a91, codeword); // 91 bits -> 174 bits
|
||||
|
||||
// Message structure: R S4_1 D29 S4_2 D29 S4_3 D29 S4_4 R
|
||||
// Total symbols: 105 (FT4_NN)
|
||||
|
||||
uint8_t mask = 0x80u; // Mask to extract 1 bit from codeword
|
||||
int i_byte = 0; // Index of the current byte of the codeword
|
||||
for (int i_tone = 0; i_tone < FT4_NN; ++i_tone)
|
||||
{
|
||||
if ((i_tone == 0) || (i_tone == 104))
|
||||
{
|
||||
tones[i_tone] = 0; // R (ramp) symbol
|
||||
}
|
||||
else if ((i_tone >= 1) && (i_tone < 5))
|
||||
{
|
||||
tones[i_tone] = kFT4_Costas_pattern[0][i_tone - 1];
|
||||
}
|
||||
else if ((i_tone >= 34) && (i_tone < 38))
|
||||
{
|
||||
tones[i_tone] = kFT4_Costas_pattern[1][i_tone - 34];
|
||||
}
|
||||
else if ((i_tone >= 67) && (i_tone < 71))
|
||||
{
|
||||
tones[i_tone] = kFT4_Costas_pattern[2][i_tone - 67];
|
||||
}
|
||||
else if ((i_tone >= 100) && (i_tone < 104))
|
||||
{
|
||||
tones[i_tone] = kFT4_Costas_pattern[3][i_tone - 100];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Extract 2 bits from codeword at i-th position
|
||||
uint8_t bits2 = 0;
|
||||
|
||||
if (codeword[i_byte] & mask)
|
||||
bits2 |= 2;
|
||||
if (0 == (mask >>= 1))
|
||||
{
|
||||
mask = 0x80u;
|
||||
i_byte++;
|
||||
}
|
||||
if (codeword[i_byte] & mask)
|
||||
bits2 |= 1;
|
||||
if (0 == (mask >>= 1))
|
||||
{
|
||||
mask = 0x80u;
|
||||
i_byte++;
|
||||
}
|
||||
tones[i_tone] = kFT4_Gray_map[bits2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ft2_encode(const uint8_t* payload, uint8_t* tones)
|
||||
{
|
||||
ft4_encode(payload, tones);
|
||||
}
|
||||
Vendored
-47
@@ -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_
|
||||
Vendored
-251
@@ -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;
|
||||
}
|
||||
Vendored
-23
@@ -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_
|
||||
Vendored
-1156
File diff suppressed because it is too large
Load Diff
Vendored
-160
@@ -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_
|
||||
Vendored
-303
@@ -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;
|
||||
}
|
||||
Vendored
-82
@@ -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_
|
||||
Vendored
-286
@@ -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
@@ -1 +0,0 @@
|
||||
110115 6 0.9 1234 ~ GJ0KYZ RK9AX MO05
|
||||
BIN
Binary file not shown.
-5
@@ -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
|
||||
BIN
Binary file not shown.
-2
@@ -1,2 +0,0 @@
|
||||
110145 -4 1.0 322 ~ <...> RY8CAA
|
||||
110145 7 1.0 1234 ~ GJ0KYZ RK9AX MO05
|
||||
BIN
Binary file not shown.
-5
@@ -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
|
||||
BIN
Binary file not shown.
-4
@@ -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
|
||||
BIN
Binary file not shown.
-22
@@ -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
|
||||
BIN
Binary file not shown.
-15
@@ -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
|
||||
BIN
Binary file not shown.
-20
@@ -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
|
||||
BIN
Binary file not shown.
-16
@@ -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
|
||||
BIN
Binary file not shown.
-24
@@ -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
|
||||
BIN
Binary file not shown.
-24
@@ -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
|
||||
BIN
Binary file not shown.
-19
@@ -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
|
||||
BIN
Binary file not shown.
-20
@@ -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
|
||||
BIN
Binary file not shown.
-32
@@ -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
|
||||
BIN
Binary file not shown.
-27
@@ -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
|
||||
BIN
Binary file not shown.
-31
@@ -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
|
||||
BIN
Binary file not shown.
-19
@@ -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
|
||||
BIN
Binary file not shown.
-27
@@ -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
|
||||
BIN
Binary file not shown.
-20
@@ -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
|
||||
BIN
Binary file not shown.
-31
@@ -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
|
||||
BIN
Binary file not shown.
-18
@@ -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
|
||||
BIN
Binary file not shown.
-26
@@ -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
|
||||
BIN
Binary file not shown.
-17
@@ -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
|
||||
BIN
Binary file not shown.
-28
@@ -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
|
||||
BIN
Binary file not shown.
-16
@@ -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
|
||||
BIN
Binary file not shown.
-26
@@ -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
|
||||
BIN
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user