8000 Merge upstream by jcyfkimi · Pull Request #1 · jcyfkimi/ft8_lib · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Merge upstream #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and pri 8000 vacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 31 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
7e9926f
Fix M_PI related compilation isues on Linux
kholia Nov 1, 2021
c21ef45
Merge pull request #14 from kholia/master
kgoba Nov 7, 2021
23247aa
Misc. fixes required on Ubuntu 21.04 (Linux)
kholia Nov 7, 2021
550f3dc
Merge pull request #15 from kholia/master
kgoba Nov 7, 2021
26decf9
Added prefixes to ft8/ft4/ftx functions and clang-formatted code
Nov 17, 2021
acea172
Further steps towards FT4 decoding
kgoba Dec 8, 2021
927fae9
Updated the decoder from whole signal processing to frame processing
kgoba Dec 11, 2021
cbc656c
Added FT4 decoding
Dec 14, 2021
569d1f6
Update README.md
kgoba Dec 14, 2021
6fb6c2d
Update README.md
kgoba Dec 14, 2021
e7c23cb
Update README.md
kgoba Dec 14, 2021
759f59d
Allow compilation on Android as ISO C++ code
kholia Dec 18, 2021
95473cd
Merge pull request #17 from kholia/master
kgoba Dec 18, 2021
5fb933b
Fix a memory leak in decode_ft8.c
kholia Feb 6, 2022
cd0dc2e
Merge pull request #20 from kholia/master
kgoba Feb 6, 2022
7d534db
Added extern C around function prototypes for C++ compatibility
Apr 23, 2022
72754d0
Some code cleanup
May 11, 2022
a05dbae
Added initial message interface, moved demo/example code, initial por…
Jun 16, 2022
6f52812
Added live decoding based on Portaudio
Jun 19, 2022
7dc84b9
Naming consistency; removed legacy pack/unpack
kgoba Jun 26, 2022
a285936
Naming consistency + time slot display in decode_ft8
kgoba Jun 28, 2022
aea0ed8
Added mag/phase data in waterfall and resynth function
Aug 3, 2022
62f7aee
Added mag/phase data in waterfall and resynth function
Aug 3, 2022
62ee453
Added relative dB lookup table (for SNR)
Aug 7, 2022
757948b
Code sanitization
Aug 7, 2022
56c0e34
Initial version of python decoder
kgoba Aug 9, 2022
2683de6
Updated decode processing
Aug 10, 2022
d2b9790
Added fine/coarse sync search
Aug 12, 2022
aec8f76
Added more files/dirs
Aug 12, 2022
5f512a2
Added Waterfall class and candidate search+decoding
Aug 15, 2022
50ee0c0
Merge pull request #37 from kgoba/update_to_0_2
kgoba Oct 31, 2023
File filter

Filter by extension

Filter by extension

Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
BasedOnStyle: WebKit
# Cpp11BracedListStyle: false
# ColumnLimit: 120
IndentCaseLabels: false
IndentExternBlock: false
IndentWidth: 4
TabWidth: 8
UseTab: Never
PointerAlignment: Left
SortIncludes: false
AlignConsecutiveMacros: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AllowShortFunctionsOnASingleLine: false
AlignTrailingComments: true
BreakConstructorInitializers: BeforeColon
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 0
BreakBeforeBraces: Custom
BreakBeforeBinaryOperators: All
BraceWrapping:
AfterControlStatement: true
AfterClass: true
AfterEnum: true
AfterFunction: true
AfterNamespace: true
AfterStruct: true
AfterUnion: true
AfterExternBlock: true
BeforeElse: true
BeforeCatch: true
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
*.o
gen_ft8
decode_ft8
test_ft8
libft8.a
wsjtx2/
.build/
.DS_Store
.vscode/
__pycache__/
54 changes: 38 additions & 16 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,27 +1,49 @@
CFLAGS = -O3
CPPFLAGS = -std=c11 -I.
LDFLAGS = -lm
BUILD_DIR = .build

TARGETS = gen_ft8 decode_ft8 test
FT8_SRC = $(wildcard ft8/*.c)
FT8_OBJ = $(patsubst %.c,$(BUILD_DIR)/%.o,$(FT8_SRC))

.PHONY: run_tests all clean
COMMON_SRC = $(wildcard common/*.c)
COMMON_OBJ = $(patsubst %.c,$(BUILD_DIR)/%.o,$(COMMON_SRC))

all: $(TARGETS)
FFT_SRC = $(wildcard fft/*.c)
FFT_OBJ = $(patsubst %.c,$(BUILD_DIR)/%.o,$(FFT_SRC))

TARGETS = gen_ft8 decode_ft8 test_ft8

run_tests: test
@./test
CFLAGS = -fsanitize=address -O3 -ggdb3
CPPFLAGS = -std=c11 -I.
LDFLAGS = -fsanitize=address -lm

gen_ft8: gen_ft8.o ft8/constants.o ft8/text.o ft8/pack.o ft8/encode.o ft8/crc.o common/wave.o
$(CXX) $(LDFLAGS) -o $@ $^
# Optionally, use Portaudio for live audio input
ifdef PORTAUDIO_PREFIX
CPPFLAGS += -DUSE_PORTAUDIO -I$(PORTAUDIO_PREFIX)/include
LDFLAGS += -lportaudio -L$(PORTAUDIO_PREFIX)/lib
endif

test: test.o ft8/pack.o ft8/encode.o ft8/crc.o ft8/text.o ft8/constants.o fft/kiss_fftr.o fft/kiss_fft.o
$(CXX) $(LDFLAGS) -o $@ $^
.PHONY: all clean run_tests install

decode_ft8: decode_ft8.o fft/kiss_fftr.o fft/kiss_fft.o ft8/decode.o ft8/encode.o ft8/crc.o ft8/ldpc.o ft8/unpack.o ft8/text.o ft8/constants.o common/wave.o
$(CXX) $(LDFLAGS) -o $@ $^
all: $(TARGETS)

clean:
rm -f *.o ft8/*.o common/*.o fft/*.o $(TARGETS)
rm -rf $(BUILD_DIR) $(TARGETS)

run_tests: test_ft8
@./test_ft8

install:
$(AR) rc libft8.a ft8/constants.o ft8/encode.o ft8/pack.o ft8/text.o common/wave.o
$(AR) rc libft8.a $(FT8_OBJ) $(COMMON_OBJ)
install libft8.a /usr/lib/libft8.a

gen_ft8: $(BUILD_DIR)/demo/gen_ft8.o $(FT8_OBJ) $(COMMON_OBJ) $(FFT_OBJ)
$(CC) $(LDFLAGS) -o $@ $^

decode_ft8: $(BUILD_DIR)/demo/decode_ft8.o $(FT8_OBJ) $(COMMON_OBJ) $(FFT_OBJ)
$(CC) $(LDFLAGS) -o $@ $^

test_ft8: $(BUILD_DIR)/test/test.o $(FT8_OBJ)
$(CC) $(LDFLAGS) -o $@ $^

$(BUILD_DIR)/%.o: %.c
@mkdir -p $(dir $@)
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ -c $^
34 changes: 17 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,54 +1,54 @@
# FT8 library
# FT8 (and now FT4) library

C implementation of FT8 protocol decoder and encoder, mostly intended for experimental use on microcontrollers.
C implementation of a lightweight FT8/FT4 decoder and encoder, mostly intended for experimental use on microcontrollers.

The intent of this library is to foster experimentation with e.g. automated beacons. For example, FT8 supports free-text messages and raw telemetry data (71 bits).
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 decoder uses slightly less than 200 KB of RAM.
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.

# What works
# Current state

Currently the basic message set of the revised FT8 protocol with 77-bit payload (introduced since WSJT-X version 2.0) is supported:
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)

There is historical code that supports the same set of FT8 version 1 (75-bit) messages minus telemetry data.
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.

# What doesn't
The code is not yet really a library, rather a collection of routines and example code.

I'm currently working on decoding, which still needs refactoring. 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.

FT4 is not supported, but I would like to add it one day soon.

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```. Currently messages are modulated at 1000-1050 Hz.
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).
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 to Robert Morris, AB1HL, whose Python code (https://github.com/rtmrtmrtmrtm/weakmon) inspired this and helped to test various parts of the code.
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 defails of FT4 and FT8 procotols and decoding/encoding are described here: https://physics.princeton.edu/pulsar/k1jt/FT4_FT8_QEX.pdf
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.

Thanks to Mark Borgerding for his FFT implementation (https://github.com/mborgerding/kissfft). I have included a portion of his code.

Karlis Goba,
YL3JG
191 changes: 191 additions & 0 deletions common/audio.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
#include "audio.h"

#include <stdio.h>
#include <string.h>

#ifdef USE_PORTAUDIO
#include <portaudio.h>

typedef struct
{
PaStream* instream;
} audio_context_t;

static audio_context_t audio_context;

static int audio_cb(void* inputBuffer, void* outputBuffer, unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void* userData)
{
audio_context_t* context = (audio_context_t*)userData;
float* samples_in = (float*)inputBuffer;

// PaTime time = data->startTime + timeInfo->inputBufferAdcTime;
printf("Callback with %ld samples\n", framesPerBuffer);
return 0;
}

void audio_list(void)
{
PaError pa_rc;

pa_rc = Pa_Initialize(); // Initialize PortAudio
if (pa_rc != paNoError)
{
printf("Error initializing PortAudio.\n");
printf("\tErrortext: %s\n\tNumber: %d\n", Pa_GetErrorText(pa_rc), pa_rc);
return;
}

int numDevices;
numDevices = Pa_GetDeviceCount();
if (numDevices < 0)
{
printf("ERROR: Pa_CountDevices returned 0x%x\n", numDevices);
return;
}

printf("%d audio devices found:\n", numDevices);
for (int i = 0; i < numDevices; i++)
{
const PaDeviceInfo* deviceInfo = Pa_GetDeviceInfo(i);

PaStreamParameters inputParameters = {
.device = i,
.channelCount = 1, // 1 = mono, 2 = stereo
.sampleFormat = paFloat32,
.suggestedLatency = 0.2,
.hostApiSpecificStreamInfo = NULL
};
double sample_rate = 12000; // sample rate (frames per second)
pa_rc = Pa_IsFormatSupported(&inputParameters, NULL, sample_rate);

printf("%d: [%s] [%s]\n", (i + 1), deviceInfo->name, (pa_rc == paNoError) ? "OK" : "NOT SUPPORTED");
}
}

int audio_init(void)
{
PaError pa_rc;

pa_rc = Pa_Initialize(); // Initialize PortAudio
if (pa_rc != paNoError)
{
printf("Error initializing PortAudio.\n");
printf("\tErrortext: %s\n\tNumber: %d\n", Pa_GetErrorText(pa_rc), pa_rc);
Pa_Terminate(); // I don't think we need this but...
return -1;
}
return 0;
}

int audio_open(const char* name)
{
PaError pa_rc;
audio_context.instream = NULL;

PaDeviceIndex ndevice_in = -1;
int numDevices = Pa_GetDeviceCount();
for (int i = 0; i < numDevices; i++)
{
const PaDeviceInfo* deviceInfo = Pa_GetDeviceInfo(i);
if (0 == strcmp(deviceInfo->name, name))
{
ndevice_in = i;
break;
}
}

if (ndevice_in < 0)
{
printf("Could not find device [%s].\n", name);
audio_list();
return -1;
}

unsigned long nfpb = 1920 / 4; // frames per buffer
double sample_rate = 12000; // sample rate (frames per second)

PaStreamParameters inputParameters = {
.device = ndevice_in,
.channelCount = 1, // 1 = mono, 2 = stereo
.sampleFormat = paFloat32,
.suggestedLatency = 0.2,
.hostApiSpecificStreamInfo = NULL
};

// Test if this configuration actually works, so we do not run into an ugly assertion
pa_rc = Pa_IsFormatSupported(&inputParameters, NULL, sample_rate);
if (pa_rc != paNoError)
{
printf("Error opening input audio stream.\n");
printf("\tErrortext: %s\n\tNumber: %d\n", Pa_GetErrorText(pa_rc), pa_rc);
return -2;
}

PaStream* instream;
pa_rc = Pa_OpenStream(
&instream, // address of stream
&inputParameters,
NULL,
sample_rate, // Sample rate
nfpb, // Frames per buffer
paNoFlag,
NULL /*(PaStreamCallback*)audio_cb*/, // Callback routine
NULL /*(void*)&audio_context*/); // address of data structure
if (pa_rc != paNoError)
{ // We should have no error here usually
printf("Error opening input audio stream:\n");
printf("\tErrortext: %s\n\tNumber: %d\n", Pa_GetErrorText(pa_rc), pa_rc);
return -3;
}
// printf("Successfully opened audio input.\n");

pa_rc = Pa_StartStream(instream); // Start input stream
if (pa_rc != paNoError)
{
printf("Error starting input audio stream!\n");
printf("\tErrortext: %s\n\tNumber: %d\n", Pa_GetErrorText(pa_rc), pa_rc);
return -4;
}

audio_context.instream = instream;

// while (Pa_IsStreamActive(instream))
// {
// Pa_Sleep(100);
// }
// Pa_AbortStream(instream); // Abort stream
// Pa_CloseStream(instream); // Close stream, we're done.

return 0;
}

int audio_read(float* buffer, int num_samples)
{
PaError pa_rc;
pa_rc = Pa_ReadStream(audio_context.instream, (void*)buffer, num_samples);
return 0;
}

#else

int audio_init(void)
{
return -1;
}

void audio_list(void)
{
}

int audio_open(const char* name)
{
return -1;
}

int audio_read(float* buffer, int num_samples)
{
return -1;
}

#endif
18 changes: 18 additions & 0 deletions common/audio.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#ifndef _INCLUDE_AUDIO_H_
#define _INCLUDE_AUDIO_H_

#ifdef __cplusplus
extern "C"
{
#endif

int audio_init(void);
void audio_list(void);
int audio_open(const char* name);
int audio_read(float* buffer, int num_samples);

#ifdef __cplusplus
}
#endif

#endif // _INCLUDE_AUDIO_H_
Loading
0