Finished library

pull/21/head
chrnadig 2021-11-08 21:44:37 +01:00
rodzic cdb9c58412
commit 13f1fe7ea3
29 zmienionych plików z 1123 dodań i 751 usunięć

7
.gitignore vendored
Wyświetl plik

@ -2,11 +2,14 @@
gen_ft8
decode_ft8
test
looptest
libft8.a
libft8.dylib
## Mac Finder
## macOS Finder metadata
.DS_Store
## Xcode
## the following is for Xcode
## User settings
xcuserdata/

Wyświetl plik

@ -1,27 +1,38 @@
CFLAGS = -O3
CPPFLAGS = -std=c11 -I.
LDFLAGS = -lm
CC = cc
CFLAGS = -O3 -std=c11 -I. -Ift8 -DLOG_LEVEL=LOG_NONE
LDFLAGS = -lm -L.
TARGETS = gen_ft8 decode_ft8 test
VERSION = 1.0.0
LIB_TARGETS = libft8.a libft8.dylib
DEMO_TARGETS = gen_ft8 decode_ft8
TEST_TARGETS = test looptest
TARGETS = $(LIB_TARGETS) $(DEMO_TARGETS) $(TEST_TARGETS)
LIB_OBJECTS = ft8/constants.o ft8/crc.o ft8/decode.o ft8/encode.o ft8/ldpc.o ft8/pack.o ft8/text.o ft8/unpack.o fft/kiss_fft.o fft/kiss_fftr.o
.PHONY: run_tests all clean
all: $(TARGETS)
run_tests: test
$(DEMO_TARGETS) $(TEST_TARGETS): % : %.o common/wave.o libft8.a
$(CC) $(LDFLAGS) -o $@ $^ -lft8
libft8.a: $(LIB_OBJECTS)
$(AR) rc $@ $^
libft8.dylib: $(LIB_OBJECTS)
$(CC) $(LDFLAGS) -fPIC -shared -o $@ $^
run_tests: $(TEST_TARGETS)
@./test
gen_ft8: gen_ft8.o ft8/constants.o ft8/text.o ft8/pack.o ft8/encode.o ft8/crc.o common/wave.o
$(CXX) $(LDFLAGS) -o $@ $^
test: test.o ft8/pack.o ft8/encode.o ft8/crc.o ft8/text.o ft8/constants.o fft/kiss_fftr.o fft/kiss_fft.o
$(CXX) $(LDFLAGS) -o $@ $^
decode_ft8: decode_ft8.o fft/kiss_fftr.o fft/kiss_fft.o ft8/decode.o ft8/encode.o ft8/crc.o ft8/ldpc.o ft8/unpack.o ft8/text.o ft8/constants.o common/wave.o
$(CXX) $(LDFLAGS) -o $@ $^
@./looptest
clean:
rm -f *.o ft8/*.o common/*.o fft/*.o $(TARGETS)
install:
$(AR) rc libft8.a ft8/constants.o ft8/encode.o ft8/pack.o ft8/text.o common/wave.o
install libft8.a /usr/lib/libft8.a
install: $(LIB_TARGETS)
install ft8/ft8.h /usr/local/include/ft8.h
install libft8.a /usr/local/lib/libft8.a
install libft8.dylib /usr/local/lib/libf8-$(VERSION).dylib

Wyświetl plik

@ -1,4 +1,5 @@
#pragma once
#ifndef _INCLUDE_DEBUG_H_
#define _INCLUDE_DEBUG_H_
#include <stdio.h>
@ -7,6 +8,12 @@
#define LOG_WARN 2
#define LOG_ERROR 3
#define LOG_FATAL 4
#define LOG_NONE 5
#ifndef LOG_LEVEL
#define LOG_LEVEL LOG_NONE
#endif
#define LOG(level, ...) if (level >= LOG_LEVEL) fprintf(stderr, __VA_ARGS__)
#endif // _INCLUDE_DEBUG_H_

Wyświetl plik

@ -83,6 +83,10 @@ int load_wav(float *signal, int *num_samples, int *sample_rate, const char *path
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);

Wyświetl plik

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

Wyświetl plik

@ -36,13 +36,13 @@ uint16_t ft8_crc(const uint8_t message[], int num_bits)
return remainder & ((TOPBIT << 1) - 1u);
}
uint16_t extract_crc(const uint8_t a91[])
uint16_t ft8_extract_crc(const uint8_t a91[])
{
uint16_t chksum = ((a91[9] & 0x07) << 11) | (a91[10] << 3) | (a91[11] >> 5);
return chksum;
}
void add_crc(const uint8_t payload[], uint8_t a91[])
void ft8_add_crc(const uint8_t payload[], uint8_t a91[])
{
// Copy 77 bits of payload data
for (int i = 0; i < 10; i++)

Wyświetl plik

@ -12,11 +12,11 @@ uint16_t ft8_crc(const uint8_t message[], int num_bits);
/// Extract the FT8 CRC of a packed message (during decoding)
/// @param[in] a91 77 bits of payload data + CRC
/// @return Extracted CRC
uint16_t extract_crc(const uint8_t a91[]);
uint16_t ft8_extract_crc(const uint8_t a91[]);
/// Add the FT8 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 add_crc(const uint8_t payload[], uint8_t a91[]);
void ft8_add_crc(const uint8_t payload[], uint8_t a91[]);
#endif // _INCLUDE_CRC_H_
#endif // _INCLUDE_CRC_H_

Wyświetl plik

@ -1,19 +1,71 @@
#include "decode.h"
#include <stdbool.h>
#include <math.h>
#include "ft8/ft8.h"
#include "constants.h"
#include "crc.h"
#include "ldpc.h"
#include "unpack.h"
#include "fft/kiss_fftr.h"
#include "common/debug.h"
#include <stdbool.h>
#include <math.h>
const int kMin_score = 10; // Minimum sync score threshold for candidates
const int kMax_candidates = 120;
const int kLDPC_iterations = 20;
/// Compute log likelihood log(p(1) / p(0)) of 174 message bits for later use in soft-decision LDPC decoding
/// @param[in] power 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
const int kMax_decoded_messages = 50;
const int kFreq_osr = 2;
const int kTime_osr = 2;
const float kFSK_dev = 6.25f; // tone deviation in Hz and symbol rate
/// Input structure to 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 num_blocks; ///< number of total blocks (symbols) in terms of 160 ms time periods
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
uint8_t *mag; ///< FFT magnitudes stored as uint8_t[blocks][time_osr][freq_osr][num_bins]
} waterfall_t;
/// Output structure of find_sync() and input structure of extract_likelihood().
/// 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
} candidate_t;
/// Structure that holds the decoded message
typedef struct
{
// TODO: check again that this size is enough
char text[25]; // plain text
uint16_t hash; // hash value to be used in hash table and quick checking for duplicates
} message_t;
/// Structure that contains the status of various steps during decoding of a message
typedef struct
{
int ldpc_errors;
uint16_t crc_extracted;
uint16_t crc_calculated;
int unpack_status;
} decode_status_t;
// forward declarations
static void extract_likelihood(const waterfall_t *power, const candidate_t *cand, float *log174);
static float max2(float a, float b);
static float max4(float a, float b, float c, float d);
static void heapify_down(candidate_t heap[], int heap_size);
@ -26,7 +78,7 @@ static int get_index(const waterfall_t *power, int block, int time_sub, int freq
return ((((block * power->time_osr) + time_sub) * power->freq_osr + freq_sub) * power->num_bins) + bin;
}
int find_sync(const waterfall_t *power, int num_candidates, candidate_t heap[], int min_score)
static int find_sync(const waterfall_t *power, int num_candidates, candidate_t heap[], int min_score)
{
int heap_size = 0;
int sym_stride = power->time_osr * power->freq_osr * power->num_bins;
@ -143,15 +195,17 @@ int find_sync(const waterfall_t *power, int num_candidates, candidate_t heap[],
return heap_size;
}
void extract_likelihood(const waterfall_t *power, const candidate_t *cand, float *log174)
/// Compute log likelihood log(p(1) / p(0)) of 174 message bits for later use in soft-decision LDPC decoding
/// @param[in] power Waterfall data collected during message slot
/// @param[in] cand Candidate to extract the message from
/// @param[out] log174 Output of decoded log likelihoods for each of the 174 message bits
static void extract_likelihood(const waterfall_t *power, const candidate_t *cand, float *log174)
{
int sym_stride = power->time_osr * power->freq_osr * power->num_bins;
int offset = get_index(power, cand->time_offset, cand->time_sub, cand->freq_sub, cand->freq_offset);
// Go over FSK tones and skip Costas sync symbols
const int n_syms = 1;
const int n_bits = 3 * n_syms;
const int n_tones = (1 << n_bits);
for (int k = 0; k < FT8_ND; k += n_syms)
{
// Add either 7 or 14 extra symbols to account for sync
@ -193,13 +247,13 @@ void extract_likelihood(const waterfall_t *power, const candidate_t *cand, float
}
}
bool decode(const waterfall_t *power, const candidate_t *cand, message_t *message, int max_iterations, decode_status_t *status)
static bool decode(const waterfall_t *power, const candidate_t *cand, message_t *message, int max_iterations, decode_status_t *status)
{
float log174[FT8_LDPC_N]; // message bits encoded as likelihood
extract_likelihood(power, cand, log174);
uint8_t plain174[FT8_LDPC_N]; // message bits (0/1)
bp_decode(log174, max_iterations, plain174, &status->ldpc_errors);
ft8_bp_decode(log174, max_iterations, plain174, &status->ldpc_errors);
// ldpc_decode(log174, max_iterations, plain174, &status->ldpc_errors);
if (status->ldpc_errors > 0)
@ -209,10 +263,10 @@ bool decode(const waterfall_t *power, const candidate_t *cand, message_t *messag
// Extract payload + CRC (first FT8_LDPC_K bits) packed into a byte array
uint8_t a91[FT8_LDPC_K_BYTES];
pack_bits(plain174, FT8_LDPC_K, a91);
ft8_pack_bits(plain174, FT8_LDPC_K, a91);
// Extract CRC and check it
status->crc_extracted = extract_crc(a91);
status->crc_extracted = ft8_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;
@ -223,7 +277,7 @@ bool decode(const waterfall_t *power, const candidate_t *cand, message_t *messag
return false;
}
status->unpack_status = unpack77(a91, message->text);
status->unpack_status = ft8_unpack77(a91, message->text);
if (status->unpack_status < 0)
{
@ -367,3 +421,210 @@ static void decode_multi_symbols(const uint8_t *power, int num_bins, int n_syms,
log174[bit_idx + i] = max_one - max_zero;
}
}
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;
}
// Compute FFT magnitudes (log power) for each timeslot in the signal
static void extract_power(const float signal[], waterfall_t *power, int block_size)
{
const int subblock_size = block_size / power->time_osr;
const int nfft = block_size * power->freq_osr;
const float fft_norm = 2.0f / nfft;
const int len_window = 1.8f * block_size; // hand-picked and optimized
float window[nfft];
for (int i = 0; i < nfft; ++i)
{
// window[i] = 1;
// window[i] = hann_i(i, nfft);
// window[i] = blackman_i(i, nfft);
// window[i] = hamming_i(i, nfft);
window[i] = (i < len_window) ? hann_i(i, len_window) : 0;
}
size_t fft_work_size;
kiss_fftr_alloc(nfft, 0, 0, &fft_work_size);
LOG(LOG_INFO, "Block size = %d\n", block_size);
LOG(LOG_INFO, "Subblock size = %d\n", subblock_size);
LOG(LOG_INFO, "N_FFT = %d\n", nfft);
LOG(LOG_INFO, "FFT work area = %lu\n", fft_work_size);
void *fft_work = malloc(fft_work_size);
kiss_fftr_cfg fft_cfg = kiss_fftr_alloc(nfft, 0, fft_work, &fft_work_size);
int offset = 0;
float max_mag = -120.0f;
for (int idx_block = 0; idx_block < power->num_blocks; ++idx_block)
{
// Loop over two possible time offsets (0 and block_size/2)
for (int time_sub = 0; time_sub < power->time_osr; ++time_sub)
{
kiss_fft_scalar timedata[nfft];
kiss_fft_cpx freqdata[nfft / 2 + 1];
float mag_db[nfft / 2 + 1];
// Extract windowed signal block
for (int pos = 0; pos < nfft; ++pos)
{
timedata[pos] = window[pos] * signal[(idx_block * block_size) + (time_sub * subblock_size) + pos];
}
kiss_fftr(fft_cfg, timedata, freqdata);
// Compute log magnitude in decibels
for (int idx_bin = 0; idx_bin < nfft / 2 + 1; ++idx_bin)
{
float mag2 = (freqdata[idx_bin].i * freqdata[idx_bin].i) + (freqdata[idx_bin].r * freqdata[idx_bin].r);
mag_db[idx_bin] = 10.0f * log10f(1E-12f + mag2 * fft_norm * fft_norm);
}
// Loop over two possible frequency bin offsets (for averaging)
for (int freq_sub = 0; freq_sub < power->freq_osr; ++freq_sub)
{
for (int pos = 0; pos < power->num_bins; ++pos)
{
float db = mag_db[pos * power->freq_osr + freq_sub];
// Scale decibels to unsigned 8-bit range and clamp the value
// Range 0-240 covers -120..0 dB in 0.5 dB steps
int scaled = (int)(2 * db + 240);
power->mag[offset] = (scaled < 0) ? 0 : ((scaled > 255) ? 255 : scaled);
++offset;
if (db > max_mag)
max_mag = db;
}
}
}
}
LOG(LOG_INFO, "Max magnitude: %.1f dB\n", max_mag);
free(fft_work);
}
int ft8_decode(float *signal, int num_samples, int sample_rate, ft8_decode_callback_t callback, void *ctx)
{
// compute DSP parameters that depend on the sample rate
const int num_bins = (int)(sample_rate / (2 * kFSK_dev)); // number bins of FSK tone width that the spectrum can be divided into
const int block_size = (int)(sample_rate / kFSK_dev); // samples corresponding to one FSK symbol
const int subblock_size = block_size / kTime_osr;
const int nfft = block_size * kFreq_osr;
const int num_blocks = (num_samples - nfft + subblock_size) / block_size;
// Compute FFT over the whole signal and store it
uint8_t mag_power[num_blocks * kFreq_osr * kTime_osr * num_bins];
waterfall_t power = {
.num_blocks = num_blocks,
.num_bins = num_bins,
.time_osr = kTime_osr,
.freq_osr = kFreq_osr,
.mag = mag_power};
extract_power(signal, &power, block_size);
// Find top candidates by Costas sync score and localize them in time and frequency
candidate_t candidate_list[kMax_candidates];
int num_candidates = find_sync(&power, kMax_candidates, candidate_list, kMin_score);
// Hash table for decoded messages (to check for duplicates)
int num_decoded = 0;
message_t decoded[kMax_decoded_messages];
message_t *decoded_hashtable[kMax_decoded_messages];
// Initialize hash table pointers
for (int i = 0; i < kMax_decoded_messages; ++i)
{
decoded_hashtable[i] = NULL;
}
// Go over candidates and attempt to decode messages
for (int idx = 0; idx < num_candidates; ++idx)
{
const candidate_t *cand = &candidate_list[idx];
if (cand->score < kMin_score)
continue;
float freq_hz = (cand->freq_offset + (float)cand->freq_sub / kFreq_osr) * kFSK_dev;
float time_sec = (cand->time_offset + (float)cand->time_sub / kTime_osr) / kFSK_dev;
message_t message;
decode_status_t status;
if (!decode(&power, cand, &message, kLDPC_iterations, &status))
{
if (status.ldpc_errors > 0)
{
LOG(LOG_DEBUG, "LDPC decode: %d errors\n", status.ldpc_errors);
}
else if (status.crc_calculated != status.crc_extracted)
{
LOG(LOG_DEBUG, "CRC mismatch!\n");
}
else if (status.unpack_status != 0)
{
LOG(LOG_DEBUG, "Error while unpacking!\n");
}
continue;
}
int idx_hash = message.hash % kMax_decoded_messages;
bool found_empty_slot = false;
bool found_duplicate = false;
do
{
if (decoded_hashtable[idx_hash] == NULL)
{
found_empty_slot = true;
}
else if ((decoded_hashtable[idx_hash]->hash == message.hash) && (0 == strcmp(decoded_hashtable[idx_hash]->text, message.text)))
{
found_duplicate = true;
}
else
{
// 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;
// report message through callback
// TODO: compute SNR
callback(message.text, freq_hz, time_sec, 0.0, cand->score, ctx);
}
}
return num_decoded;
}

Wyświetl plik

@ -1,69 +0,0 @@
#ifndef _INCLUDE_DECODE_H_
#define _INCLUDE_DECODE_H_
#include <stdint.h>
#include <stdbool.h>
/// Input structure to 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 num_blocks; ///< number of total blocks (symbols) in terms of 160 ms time periods
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
uint8_t *mag; ///< FFT magnitudes stored as uint8_t[blocks][time_osr][freq_osr][num_bins]
} waterfall_t;
/// Output structure of find_sync() and input structure of extract_likelihood().
/// 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
} candidate_t;
/// Structure that holds the decoded message
typedef struct
{
// TODO: check again that this size is enough
char text[25]; // plain text
uint16_t hash; // hash value to be used in hash table and quick checking for duplicates
} message_t;
/// Structure that contains the status of various steps during decoding of a message
typedef struct
{
int ldpc_errors;
uint16_t crc_extracted;
uint16_t crc_calculated;
int unpack_status;
} 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 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 find_sync(const waterfall_t *power, int num_candidates, 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[out] message message_t structure that will receive the decoded message
/// @param[in] max_iterations Maximum allowed LDPC iterations (lower number means faster decode, but less precise)
/// @param[out] status 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 decode(const waterfall_t *power, const candidate_t *cand, message_t *message, int max_iterations, decode_status_t *status);
#endif // _INCLUDE_DECODE_H_

Wyświetl plik

@ -1,11 +1,27 @@
#include "encode.h"
#include <math.h>
#include <stdio.h>
#include <string.h>
#include "ft8.h"
#include "pack.h"
#include "constants.h"
#include "crc.h"
#include "common/debug.h"
#warning replace with typed constants
#define FT8_SYMBOL_RATE 6.25f // tone deviation (and symbol rate) in Hz
#define FT8_SYMBOL_BT 2.0f // symbol smoothing filter bandwidth factor (BT)
#define FT4_SYMBOL_RATE 20.833333f // tone deviation (and symbol rate) in Hz
#define FT4_SYMBOL_BT 1.0f // symbol smoothing filter bandwidth factor (BT)
// pi * sqrt(2 / log(2))
static const float kFT8_GFSK_const = 5.336446f;
#include <stdio.h>
// Returns 1 if an odd number of bits are set in x, zero otherwise
uint8_t parity8(uint8_t x)
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
@ -19,7 +35,7 @@ uint8_t parity8(uint8_t x)
// 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)
void encode174(const uint8_t *message, uint8_t *codeword)
static void encode174(const uint8_t *message, uint8_t *codeword)
{
// This implementation accesses the generator bits straight from the packed binary representation in kFT8_LDPC_generator
@ -62,13 +78,13 @@ void encode174(const uint8_t *message, uint8_t *codeword)
}
}
void genft8(const uint8_t *payload, uint8_t *tones)
static void genft8(const uint8_t *payload, uint8_t *tones)
{
uint8_t a91[12]; // 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
add_crc(payload, a91);
ft8_add_crc(payload, a91);
uint8_t codeword[22];
encode174(a91, codeword);
@ -124,13 +140,13 @@ void genft8(const uint8_t *payload, uint8_t *tones)
}
}
void genft4(const uint8_t *payload, uint8_t *tones)
static void genft4(const uint8_t *payload, uint8_t *tones)
{
uint8_t a91[12]; // 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
add_crc(payload, a91);
ft8_add_crc(payload, a91);
uint8_t codeword[22];
encode174(a91, codeword); // 91 bits -> 174 bits
@ -185,3 +201,172 @@ void genft4(const uint8_t *payload, uint8_t *tones)
}
}
}
/// 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] symbol_bt Shape parameter (values defined for FT8/FT4)
/// @param[out] pulse Output array of pulse samples
///
static 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 = kFT8_GFSK_const * symbol_bt * (t + 0.5f);
float arg2 = kFT8_GFSK_const * symbol_bt * (t - 0.5f);
pulse[i] = (erff(arg1) - erff(arg2)) / 2;
}
}
/// Synthesize waveform data using GFSK phase shaping.
/// The output waveform will contain n_sym symbols.
/// @param[in] symbols Array of symbols (tones) (0-7 for FT8)
/// @param[in] n_sym Number of symbols in the symbol array
/// @param[in] f0 Audio frequency in Hertz for the symbol 0 (base frequency)
/// @param[in] symbol_bt Symbol smoothing filter bandwidth (2 for FT8, 1 for FT4)
/// @param[in] symbol_rate Rate of symbols per second, Hertz
/// @param[in] signal_rate Sample rate of synthesized signal, Hertz
/// @param[out] signal Output array of signal waveform samples (should have space for n_sym*n_spsym samples)
///
static void synth_gfsk(const uint8_t *symbols, int n_sym, float f0, float symbol_bt, float symbol_rate, int signal_rate, float *signal)
{
int n_spsym = (int)(0.5f + signal_rate / symbol_rate); // Samples per symbol
int n_wave = n_sym * n_spsym; // Number of output samples
float hmod = 1.0f;
LOG(LOG_INFO, "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;
}
}
// generate FT4 or FT8 signal for message
static int ftX_encode(char *message, float *signal, int num_samples, float frequency, int sample_rate, bool is_ft4)
{
// number of used samples in signal
int num_tones = (is_ft4) ? FT4_NN : FT8_NN;
float symbol_rate = (is_ft4) ? FT4_SYMBOL_RATE : FT8_SYMBOL_RATE;
int signal_samples = 0.5f + num_tones / symbol_rate * sample_rate;
float symbol_bt = (is_ft4) ? FT4_SYMBOL_BT : FT8_SYMBOL_BT;
// check if we were provided with enough space
if (num_samples >= signal_samples)
{
// first, pack the text data into binary message
uint8_t packed[FT8_LDPC_K_BYTES];
int rc = ft8_pack77(message, packed);
if (rc >= 0)
{
// array of 79 tones (symbols)
uint8_t tones[num_tones];
if (LOG_LEVEL == LOG_DEBUG) {
LOG(LOG_LEVEL, "Packed data: ");
for (int j = 0; j < 10; ++j)
{
LOG(LOG_LEVEL, "%02x ", packed[j]);
}
LOG(LOG_LEVEL, "\n");
}
// '[..] for FT4 only, in order to avoid transmitting a long string of zeros when sending CQ messages,
// the assembled 77-bit message is bitwise exclusive-ORed with [a] pseudorandom sequence before computing the CRC and FEC parity bits'
if (is_ft4) {
for (int i = 0; i < 10; ++i)
{
packed[i] ^= kFT4_XOR_sequence[i];
}
}
// second, encode the binary message as a sequence of FSK tones
if (is_ft4)
{
genft4(packed, tones);
}
else
{
genft8(packed, tones);
}
if (LOG_LEVEL == LOG_DEBUG) {
LOG(LOG_LEVEL, "FSK tones: ");
for (int j = 0; j < num_tones; ++j)
{
LOG(LOG_LEVEL, "%d", tones[j]);
}
LOG(LOG_LEVEL, "\n");
}
// third, convert the FSK tones into an audio signal
synth_gfsk(tones, num_tones, frequency, symbol_bt, symbol_rate, sample_rate, signal);
// clear superfluous samples
memset(signal + signal_samples, 0, (num_samples - signal_samples) * sizeof(float));
return 0;
}
LOG(LOG_ERROR, "Cannot parse message '%s', rc = %d!\n", message, rc);
return -2;
}
LOG(LOG_ERROR, "Not enough space for samples, please provide at least space for %d (provided: %d)\n", signal_samples, num_samples);
return -1;
}
// generate FT4 signal for message
int ft4_encode(char *message, float *signal, int num_samples, float frequency, int sample_rate)
{
return ftX_encode(message, signal, num_samples, frequency, sample_rate, true);
}
// generate FT8 signal for message
int ft8_encode(char *message, float *signal, int num_samples, float frequency, int sample_rate)
{
return ftX_encode(message, signal, num_samples, frequency, sample_rate, false);
}

Wyświetl plik

@ -1,32 +0,0 @@
#ifndef _INCLUDE_ENCODE_H_
#define _INCLUDE_ENCODE_H_
#include <stdint.h>
// 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 genft8(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 genft4(const uint8_t *payload, uint8_t *tones);
#endif // _INCLUDE_ENCODE_H_

17
ft8/ft8.h 100644
Wyświetl plik

@ -0,0 +1,17 @@
#ifndef _INCLUDE_FT8_H_
#define _INCLUDE_FT8_H_
// decoder callback
typedef void (* ft8_decode_callback_t)(char *message, float frequences, float time_dev, float snr, int score, void *ctx);
// decode FT8 signal, call callback for every decoded message
int ft8_decode(float *signal, int num_samples, int sample_rate, ft8_decode_callback_t callback, void *ctx);
// generate FT4 signal for message
int ft4_encode(char *message, float *signal, int num_samples, float frequency, int sample_rate);
// generate FT8 signal for message
int ft8_encode(char *message, float *signal, int num_samples, float frequency, int sample_rate);
#endif // _INCLUDE_FT(_H_

Wyświetl plik

@ -23,7 +23,7 @@ static float fast_atanh(float x);
// 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[]
void pack_bits(const uint8_t plain[], int num_bits, uint8_t packed[])
void ft8_pack_bits(const uint8_t plain[], int num_bits, uint8_t packed[])
{
int num_bytes = (num_bits + 7) / 8;
for (int i = 0; i < num_bytes; ++i)
@ -52,7 +52,7 @@ void pack_bits(const uint8_t plain[], int num_bits, uint8_t packed[])
// 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)
void ft8_ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok)
{
float m[FT8_LDPC_M][FT8_LDPC_N]; // ~60 kB
float e[FT8_LDPC_M][FT8_LDPC_N]; // ~60 kB
@ -154,7 +154,7 @@ static int ldpc_check(uint8_t codeword[])
return errors;
}
void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok)
void ft8_bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok)
{
float tov[FT8_LDPC_N][3];
float toc[FT8_LDPC_M][7];

Wyświetl plik

@ -7,12 +7,12 @@
// 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 ft8_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);
void ft8_bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok);
// 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[]
void pack_bits(const uint8_t plain[], int num_bits, uint8_t packed[]);
void ft8_pack_bits(const uint8_t plain[], int num_bits, uint8_t packed[]);
#endif // _INCLUDE_LDPC_H_

Wyświetl plik

@ -19,7 +19,7 @@ const char A4[] = " ABCDEFGHIJKLMNOPQRSTUVWXYZ";
// Pack a special token, a 22-bit hash code, or a valid base call
// into a 28-bit integer.
int32_t pack28(const char *callsign)
static int32_t pack28(const char *callsign)
{
// Check for special tokens first
if (ft8_starts_with(callsign, "DE "))
@ -100,9 +100,9 @@ int32_t pack28(const char *callsign)
// Check if a string could be a valid standard callsign or a valid
// compound callsign.
// Return base call "bc" and a logical "cok" indicator.
bool chkcall(const char *call, char *bc)
static bool chkcall(const char *call, char *bc)
{
int length = strlen(call); // n1=len_trim(w)
size_t length = strlen(call); // n1=len_trim(w)
if (length > 11)
return false;
if (0 != strchr(call, '.'))
@ -121,7 +121,7 @@ bool chkcall(const char *call, char *bc)
return true;
}
uint16_t packgrid(const char *grid4)
static uint16_t packgrid(const char *grid4)
{
if (grid4 == 0)
{
@ -168,7 +168,7 @@ uint16_t packgrid(const char *grid4)
}
// Pack Type 1 (Standard 77-bit message) and Type 2 (ditto, with a "/P" call)
int pack77_1(const char *msg, uint8_t *b77)
static int pack77_1(const char *msg, uint8_t *b77)
{
// Locate the first delimiter
const char *s1 = strchr(msg, ' ');
@ -221,7 +221,7 @@ int pack77_1(const char *msg, uint8_t *b77)
return 0;
}
void packtext77(const char *text, uint8_t *b77)
static void packtext77(const char *text, uint8_t *b77)
{
int length = strlen(text);
@ -285,7 +285,7 @@ void packtext77(const char *text, uint8_t *b77)
b77[9] &= 0x00;
}
int pack77(const char *msg, uint8_t *c77)
int ft8_pack77(const char *msg, uint8_t *c77)
{
// Check Type 1 (Standard 77-bit message) or Type 2, with optional "/P"
if (0 == pack77_1(msg, c77))
@ -308,7 +308,7 @@ int pack77(const char *msg, uint8_t *c77)
#include <iostream>
bool test1()
static bool test1()
{
const char *inputs[] = {
"",
@ -332,7 +332,7 @@ bool test1()
return true;
}
bool test2()
static bool test2()
{
const char *inputs[] = {
"CQ LL3JG",
@ -358,7 +358,7 @@ bool test2()
return true;
}
int main()
int main(int argc, char *argv[])
{
test1();
test2();

Wyświetl plik

@ -6,6 +6,6 @@
// Pack FT8 text message into 72 bits
// [IN] msg - FT8 message (e.g. "CQ TE5T KN01")
// [OUT] c77 - 10 byte array to store the 77 bit payload (MSB first)
int pack77(const char *msg, uint8_t *c77);
int ft8_pack77(const char *msg, uint8_t *c77);
#endif // _INCLUDE_PACK_H_

Wyświetl plik

@ -9,7 +9,7 @@
// n28 is a 28-bit integer, e.g. n28a or n28b, containing all the
// call sign bits from a packed message.
int unpack_callsign(uint32_t n28, uint8_t ip, uint8_t i3, char *result)
static int unpack_callsign(uint32_t n28, uint8_t ip, uint8_t i3, char *result)
{
// Check for special tokens DE, QRZ, CQ, CQ_nnn, CQ_aaaa
if (n28 < NTOKENS)
@ -105,7 +105,7 @@ int unpack_callsign(uint32_t n28, uint8_t ip, uint8_t i3, char *result)
return 0; // Success
}
int unpack_type1(const uint8_t *a77, uint8_t i3, char *call_to, char *call_de, char *extra)
static int unpack_type1(const uint8_t *a77, uint8_t i3, char *call_to, char *call_de, char *extra)
{
uint32_t n28a, n28b;
uint16_t igrid4;
@ -202,7 +202,7 @@ int unpack_type1(const uint8_t *a77, uint8_t i3, char *call_to, char *call_de, c
return 0; // Success
}
int unpack_text(const uint8_t *a71, char *text)
static int unpack_text(const uint8_t *a71, char *text)
{
// TODO: test
uint8_t b71[9];
@ -234,7 +234,7 @@ int unpack_text(const uint8_t *a71, char *text)
return 0; // Success
}
int unpack_telemetry(const uint8_t *a71, char *telemetry)
static int unpack_telemetry(const uint8_t *a71, char *telemetry)
{
uint8_t b71[9];
@ -263,7 +263,7 @@ int unpack_telemetry(const uint8_t *a71, char *telemetry)
//none standard for wsjt-x 2.0
//by KD8CEC
int unpack_nonstandard(const uint8_t *a77, char *call_to, char *call_de, char *extra)
static int unpack_nonstandard(const uint8_t *a77, char *call_to, char *call_de, char *extra)
{
uint32_t n12, iflip, nrpt, icq;
uint64_t n58;
@ -331,7 +331,7 @@ int unpack_nonstandard(const uint8_t *a77, char *call_to, char *call_de, char *e
return 0;
}
int unpack77_fields(const uint8_t *a77, char *call_to, char *call_de, char *extra)
int ft8_unpack77_fields(const uint8_t *a77, char *call_to, char *call_de, char *extra)
{
call_to[0] = call_de[0] = extra[0] = '\0';
@ -387,13 +387,13 @@ int unpack77_fields(const uint8_t *a77, char *call_to, char *call_de, char *extr
return -1;
}
int unpack77(const uint8_t *a77, char *message)
int ft8_unpack77(const uint8_t *a77, char *message)
{
char call_to[14];
char call_de[14];
char extra[7];
int rc = unpack77_fields(a77, call_to, call_de, extra);
int rc = ft8_unpack77_fields(a77, call_to, call_de, extra);
if (rc < 0)
return rc;

Wyświetl plik

@ -6,9 +6,9 @@
// field1 - at least 14 bytes
// field2 - at least 14 bytes
// field3 - at least 7 bytes
int unpack77_fields(const uint8_t *a77, char *field1, char *field2, char *field3);
int ft8_unpack77_fields(const uint8_t *a77, char *field1, char *field2, char *field3);
// message should have at least 35 bytes allocated (34 characters + zero terminator)
int unpack77(const uint8_t *a77, char *message);
int ft8_unpack77(const uint8_t *a77, char *message);
#endif // _INCLUDE_UNPACK_H_

Wyświetl plik

@ -13,6 +13,8 @@
buildPhases = (
);
dependencies = (
6598F22F2737F0B00060A608 /* PBXTargetDependency */,
6598F2312737F0B20060A608 /* PBXTargetDependency */,
);
name = Libraries;
productName = ft8_lib;
@ -23,13 +25,35 @@
buildPhases = (
);
dependencies = (
6598F2272737F0A80060A608 /* PBXTargetDependency */,
6598F2292737F0AA0060A608 /* PBXTargetDependency */,
6598F22B2737F0AB0060A608 /* PBXTargetDependency */,
6598F22D2737F0AE0060A608 /* PBXTargetDependency */,
);
name = "Demos and Tests";
productName = "Demos and Tests";
};
6598F21E2737EFC40060A608 /* Build all */ = {
isa = PBXAggregateTarget;
buildConfigurationList = 6598F21F2737EFC40060A608 /* Build configuration list for PBXAggregateTarget "Build all" */;
buildPhases = (
);
dependencies = (
6598F2232737F0A10060A608 /* PBXTargetDependency */,
6598F2252737F0A30060A608 /* PBXTargetDependency */,
);
name = "Build all";
productName = "Build all";
};
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
650D013427355AB800811CC1 /* wave.c in Sources */ = {isa = PBXBuildFile; fileRef = 6598887827312ECE00F64B3C /* wave.c */; };
650D013C27355AC500811CC1 /* looptest.c in Sources */ = {isa = PBXBuildFile; fileRef = 650D012D2735596F00811CC1 /* looptest.c */; };
6552D4772735B68E00EE69E8 /* libft8.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 659888982731363800F64B3C /* libft8.dylib */; };
6552D4782735B6D500EE69E8 /* libft8.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 659888982731363800F64B3C /* libft8.dylib */; };
6552D4792735B6DE00EE69E8 /* libft8.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 659888982731363800F64B3C /* libft8.dylib */; };
6552D47A2735B6E200EE69E8 /* libft8.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 659888982731363800F64B3C /* libft8.dylib */; };
6590878C2731487E007667A1 /* gen_ft8.c in Sources */ = {isa = PBXBuildFile; fileRef = 659888B4273147F400F64B3C /* gen_ft8.c */; };
6590878D27314881007667A1 /* test.c in Sources */ = {isa = PBXBuildFile; fileRef = 659888B2273147F400F64B3C /* test.c */; };
6598883B27312EB100F64B3C /* kiss_fft.c in Sources */ = {isa = PBXBuildFile; fileRef = 6598883627312EB100F64B3C /* kiss_fft.c */; };
@ -50,14 +74,10 @@
6598886A27312EC200F64B3C /* text.c in Sources */ = {isa = PBXBuildFile; fileRef = 6598885027312EC200F64B3C /* text.c */; };
6598886C27312EC200F64B3C /* unpack.h in Headers */ = {isa = PBXBuildFile; fileRef = 6598885127312EC200F64B3C /* unpack.h */; };
6598886E27312EC200F64B3C /* crc.c in Sources */ = {isa = PBXBuildFile; fileRef = 6598885227312EC200F64B3C /* crc.c */; };
6598887027312EC200F64B3C /* encode.h in Headers */ = {isa = PBXBuildFile; fileRef = 6598885327312EC200F64B3C /* encode.h */; };
6598887227312EC200F64B3C /* ldpc.c in Sources */ = {isa = PBXBuildFile; fileRef = 6598885427312EC200F64B3C /* ldpc.c */; };
6598887427312EC200F64B3C /* decode.h in Headers */ = {isa = PBXBuildFile; fileRef = 6598885527312EC200F64B3C /* decode.h */; };
6598889C2731398800F64B3C /* constants.c in Sources */ = {isa = PBXBuildFile; fileRef = 6598884627312EC200F64B3C /* constants.c */; };
6598889D2731399300F64B3C /* unpack.h in Headers */ = {isa = PBXBuildFile; fileRef = 6598885127312EC200F64B3C /* unpack.h */; };
6598889E2731399300F64B3C /* constants.h in Headers */ = {isa = PBXBuildFile; fileRef = 6598884F27312EC200F64B3C /* constants.h */; };
6598889F2731399300F64B3C /* encode.h in Headers */ = {isa = PBXBuildFile; fileRef = 6598885327312EC200F64B3C /* encode.h */; };
659888A02731399300F64B3C /* decode.h in Headers */ = {isa = PBXBuildFile; fileRef = 6598885527312EC200F64B3C /* decode.h */; };
659888A12731399300F64B3C /* crc.c in Sources */ = {isa = PBXBuildFile; fileRef = 6598885227312EC200F64B3C /* crc.c */; };
659888A22731399300F64B3C /* unpack.c in Sources */ = {isa = PBXBuildFile; fileRef = 6598884927312EC200F64B3C /* unpack.c */; };
659888A32731399300F64B3C /* decode.c in Sources */ = {isa = PBXBuildFile; fileRef = 6598884C27312EC200F64B3C /* decode.c */; };
@ -80,7 +100,75 @@
659888C52731483800F64B3C /* wave.c in Sources */ = {isa = PBXBuildFile; fileRef = 6598887827312ECE00F64B3C /* wave.c */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
6598F2222737F0A10060A608 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 652CE595273127D200C91652 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 6590878E273148A5007667A1;
remoteInfo = "Demos and Tests";
};
6598F2242737F0A30060A608 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 652CE595273127D200C91652 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 652CE5BF27312BB900C91652;
remoteInfo = Libraries;
};
6598F2262737F0A80060A608 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 652CE595273127D200C91652 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 659888832731305000F64B3C;
remoteInfo = decode_ft8;
};
6598F2282737F0AA0060A608 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 652CE595273127D200C91652 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 659888B82731482C00F64B3C;
remoteInfo = gen_ft8;
};
6598F22A2737F0AB0060A608 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 652CE595273127D200C91652 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 650D013227355AB800811CC1;
remoteInfo = looptest;
};
6598F22C2737F0AE0060A608 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 652CE595273127D200C91652 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 659888C22731483800F64B3C;
remoteInfo = test;
};
6598F22E2737F0B00060A608 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 652CE595273127D200C91652 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 659888972731363800F64B3C;
remoteInfo = libft8.dylib;
};
6598F2302737F0B20060A608 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 652CE595273127D200C91652 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 652CE5AF273129E000C91652;
remoteInfo = libft8.a;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
650D013727355AB800811CC1 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = /usr/share/man/man1/;
dstSubfolderSpec = 0;
files = (
);
runOnlyForDeploymentPostprocessing = 1;
};
659888822731305000F64B3C /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
@ -111,6 +199,9 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
650D01262732C16600811CC1 /* ft8.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ft8.h; sourceTree = "<group>"; };
650D012D2735596F00811CC1 /* looptest.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = looptest.c; sourceTree = "<group>"; };
650D013B27355AB800811CC1 /* looptest */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = looptest; sourceTree = BUILT_PRODUCTS_DIR; };
652CE5B0273129E000C91652 /* libft8.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libft8.a; sourceTree = BUILT_PRODUCTS_DIR; };
6598883627312EB100F64B3C /* kiss_fft.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = kiss_fft.c; sourceTree = "<group>"; };
6598883727312EB100F64B3C /* kiss_fftr.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = kiss_fftr.c; sourceTree = "<group>"; };
@ -130,9 +221,7 @@
6598885027312EC200F64B3C /* text.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = text.c; sourceTree = "<group>"; };
6598885127312EC200F64B3C /* unpack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = unpack.h; sourceTree = "<group>"; };
6598885227312EC200F64B3C /* crc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = crc.c; sourceTree = "<group>"; };
6598885327312EC200F64B3C /* encode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = encode.h; sourceTree = "<group>"; };
6598885427312EC200F64B3C /* ldpc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ldpc.c; sourceTree = "<group>"; };
6598885527312EC200F64B3C /* decode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = decode.h; sourceTree = "<group>"; };
6598887727312ECE00F64B3C /* debug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = debug.h; sourceTree = "<group>"; };
6598887827312ECE00F64B3C /* wave.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = wave.c; sourceTree = "<group>"; };
6598887927312ECE00F64B3C /* wave.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wave.h; sourceTree = "<group>"; };
@ -146,6 +235,14 @@
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
650D013627355AB800811CC1 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
6552D4772735B68E00EE69E8 /* libft8.dylib in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
652CE5AE273129E000C91652 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@ -157,6 +254,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
6552D4782735B6D500EE69E8 /* libft8.dylib in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -171,6 +269,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
6552D4792735B6DE00EE69E8 /* libft8.dylib in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -178,6 +277,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
6552D47A2735B6E200EE69E8 /* libft8.dylib in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -204,6 +304,7 @@
659888982731363800F64B3C /* libft8.dylib */,
659888C12731482C00F64B3C /* gen_ft8 */,
659888CB2731483800F64B3C /* test */,
650D013B27355AB800811CC1 /* looptest */,
);
name = Products;
sourceTree = "<group>";
@ -223,13 +324,12 @@
6598884527312EC200F64B3C /* ft8 */ = {
isa = PBXGroup;
children = (
650D01262732C16600811CC1 /* ft8.h */,
6598884F27312EC200F64B3C /* constants.h */,
6598884627312EC200F64B3C /* constants.c */,
6598884A27312EC200F64B3C /* crc.h */,
6598885227312EC200F64B3C /* crc.c */,
6598885527312EC200F64B3C /* decode.h */,
6598884C27312EC200F64B3C /* decode.c */,
6598885327312EC200F64B3C /* encode.h */,
6598884D27312EC200F64B3C /* encode.c */,
6598884B27312EC200F64B3C /* ldpc.h */,
6598885427312EC200F64B3C /* ldpc.c */,
@ -246,9 +346,9 @@
6598887627312ECE00F64B3C /* common */ = {
isa = PBXGroup;
children = (
6598887727312ECE00F64B3C /* debug.h */,
6598887827312ECE00F64B3C /* wave.c */,
6598887927312ECE00F64B3C /* wave.h */,
6598887727312ECE00F64B3C /* debug.h */,
);
path = common;
sourceTree = "<group>";
@ -265,6 +365,7 @@
children = (
659888B3273147F400F64B3C /* decode_ft8.c */,
659888B4273147F400F64B3C /* gen_ft8.c */,
650D012D2735596F00811CC1 /* looptest.c */,
659888B2273147F400F64B3C /* test.c */,
);
name = "Demos and Tests";
@ -281,12 +382,10 @@
6598886C27312EC200F64B3C /* unpack.h in Headers */,
6598883F27312EB100F64B3C /* _kiss_fft_guts.h in Headers */,
6598884127312EB100F64B3C /* kiss_fft.h in Headers */,
6598887427312EC200F64B3C /* decode.h in Headers */,
6598885E27312EC200F64B3C /* crc.h in Headers */,
6598886027312EC200F64B3C /* ldpc.h in Headers */,
6598884327312EB100F64B3C /* kiss_fftr.h in Headers */,
6598886827312EC200F64B3C /* constants.h in Headers */,
6598887027312EC200F64B3C /* encode.h in Headers */,
6598885827312EC200F64B3C /* pack.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -303,15 +402,30 @@
659888AA2731399300F64B3C /* pack.h in Headers */,
659888AF2731399C00F64B3C /* kiss_fft.h in Headers */,
659888A62731399300F64B3C /* text.h in Headers */,
659888A02731399300F64B3C /* decode.h in Headers */,
659888A82731399300F64B3C /* crc.h in Headers */,
6598889F2731399300F64B3C /* encode.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
650D013227355AB800811CC1 /* looptest */ = {
isa = PBXNativeTarget;
buildConfigurationList = 650D013827355AB800811CC1 /* Build configuration list for PBXNativeTarget "looptest" */;
buildPhases = (
650D013327355AB800811CC1 /* Sources */,
650D013627355AB800811CC1 /* Frameworks */,
650D013727355AB800811CC1 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = looptest;
productName = decode_ft8;
productReference = 650D013B27355AB800811CC1 /* looptest */;
productType = "com.apple.product-type.tool";
};
652CE5AF273129E000C91652 /* libft8.a */ = {
isa = PBXNativeTarget;
buildConfigurationList = 652CE5B1273129E000C91652 /* Build configuration list for PBXNativeTarget "libft8.a" */;
@ -420,6 +534,9 @@
659888972731363800F64B3C = {
CreatedOnToolsVersion = 12.5;
};
6598F21E2737EFC40060A608 = {
CreatedOnToolsVersion = 12.5;
};
};
};
buildConfigurationList = 652CE598273127D200C91652 /* Build configuration list for PBXProject "ft8_lib" */;
@ -435,18 +552,29 @@
projectDirPath = "";
projectRoot = "";
targets = (
6598F21E2737EFC40060A608 /* Build all */,
6590878E273148A5007667A1 /* Demos and Tests */,
652CE5BF27312BB900C91652 /* Libraries */,
659888972731363800F64B3C /* libft8.dylib */,
652CE5AF273129E000C91652 /* libft8.a */,
659888832731305000F64B3C /* decode_ft8 */,
659888B82731482C00F64B3C /* gen_ft8 */,
650D013227355AB800811CC1 /* looptest */,
659888C22731483800F64B3C /* test */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
650D013327355AB800811CC1 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
650D013C27355AC500811CC1 /* looptest.c in Sources */,
650D013427355AB800811CC1 /* wave.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
652CE5AD273129E000C91652 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@ -510,7 +638,72 @@
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
6598F2232737F0A10060A608 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 6590878E273148A5007667A1 /* Demos and Tests */;
targetProxy = 6598F2222737F0A10060A608 /* PBXContainerItemProxy */;
};
6598F2252737F0A30060A608 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 652CE5BF27312BB900C91652 /* Libraries */;
targetProxy = 6598F2242737F0A30060A608 /* PBXContainerItemProxy */;
};
6598F2272737F0A80060A608 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 659888832731305000F64B3C /* decode_ft8 */;
targetProxy = 6598F2262737F0A80060A608 /* PBXContainerItemProxy */;
};
6598F2292737F0AA0060A608 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 659888B82731482C00F64B3C /* gen_ft8 */;
targetProxy = 6598F2282737F0AA0060A608 /* PBXContainerItemProxy */;
};
6598F22B2737F0AB0060A608 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 650D013227355AB800811CC1 /* looptest */;
targetProxy = 6598F22A2737F0AB0060A608 /* PBXContainerItemProxy */;
};
6598F22D2737F0AE0060A608 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 659888C22731483800F64B3C /* test */;
targetProxy = 6598F22C2737F0AE0060A608 /* PBXContainerItemProxy */;
};
6598F22F2737F0B00060A608 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 659888972731363800F64B3C /* libft8.dylib */;
targetProxy = 6598F22E2737F0B00060A608 /* PBXContainerItemProxy */;
};
6598F2312737F0B20060A608 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 652CE5AF273129E000C91652 /* libft8.a */;
targetProxy = 6598F2302737F0B20060A608 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
650D013927355AB800811CC1 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = DVX9JXSKVT;
ENABLE_HARDENED_RUNTIME = YES;
OTHER_LDFLAGS = "";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
650D013A27355AB800811CC1 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = DVX9JXSKVT;
ENABLE_HARDENED_RUNTIME = YES;
OTHER_LDFLAGS = "";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
652CE59F273127D200C91652 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -634,6 +827,7 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = DVX9JXSKVT;
EXECUTABLE_PREFIX = lib;
INSTALL_PATH = /usr/local/lib;
PRODUCT_NAME = ft8;
SKIP_INSTALL = YES;
};
@ -645,6 +839,7 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = DVX9JXSKVT;
EXECUTABLE_PREFIX = lib;
INSTALL_PATH = /usr/local/lib;
PRODUCT_NAME = ft8;
SKIP_INSTALL = YES;
};
@ -692,7 +887,6 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = DVX9JXSKVT;
ENABLE_HARDENED_RUNTIME = YES;
OTHER_LDFLAGS = "${BUILT_PRODUCTS_DIR}/libft8.a";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
@ -703,7 +897,6 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = DVX9JXSKVT;
ENABLE_HARDENED_RUNTIME = YES;
OTHER_LDFLAGS = "${BUILT_PRODUCTS_DIR}/libft8.a";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
@ -716,6 +909,7 @@
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
EXECUTABLE_PREFIX = lib;
INSTALL_PATH = "{$BUILT_PRODUCTS_DIR}/";
PRODUCT_NAME = ft8;
SKIP_INSTALL = YES;
};
@ -729,6 +923,7 @@
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
EXECUTABLE_PREFIX = lib;
INSTALL_PATH = /usr/local/lib;
PRODUCT_NAME = ft8;
SKIP_INSTALL = YES;
};
@ -740,7 +935,6 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = DVX9JXSKVT;
ENABLE_HARDENED_RUNTIME = YES;
OTHER_LDFLAGS = "${BUILT_PRODUCTS_DIR}/libft8.a";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
@ -751,7 +945,6 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = DVX9JXSKVT;
ENABLE_HARDENED_RUNTIME = YES;
OTHER_LDFLAGS = "${BUILT_PRODUCTS_DIR}/libft8.a";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
@ -762,7 +955,6 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = DVX9JXSKVT;
ENABLE_HARDENED_RUNTIME = YES;
OTHER_LDFLAGS = "${BUILT_PRODUCTS_DIR}/libft8.a";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
@ -773,7 +965,24 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = DVX9JXSKVT;
ENABLE_HARDENED_RUNTIME = YES;
OTHER_LDFLAGS = "${BUILT_PRODUCTS_DIR}/libft8.a";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
6598F2202737EFC40060A608 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = DVX9JXSKVT;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
6598F2212737EFC40060A608 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = DVX9JXSKVT;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
@ -781,6 +990,15 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
650D013827355AB800811CC1 /* Build configuration list for PBXNativeTarget "looptest" */ = {
isa = XCConfigurationList;
buildConfigurations = (
650D013927355AB800811CC1 /* Debug */,
650D013A27355AB800811CC1 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
652CE598273127D200C91652 /* Build configuration list for PBXProject "ft8_lib" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@ -853,6 +1071,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
6598F21F2737EFC40060A608 /* Build configuration list for PBXAggregateTarget "Build all" */ = {
isa = XCConfigurationList;
buildConfigurations = (
6598F2202737EFC40060A608 /* Debug */,
6598F2212737EFC40060A608 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 652CE595273127D200C91652 /* Project object */;

Wyświetl plik

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1250"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6598F21E2737EFC40060A608"
BuildableName = "Build all"
BlueprintName = "Build all"
ReferencedContainer = "container:ft8_lib.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6598F21E2737EFC40060A608"
BuildableName = "Build all"
BlueprintName = "Build all"
ReferencedContainer = "container:ft8_lib.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

Wyświetl plik

@ -20,48 +20,6 @@
ReferencedContainer = "container:ft8_lib.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "659888832731305000F64B3C"
BuildableName = "decode_ft8"
BlueprintName = "decode_ft8"
ReferencedContainer = "container:ft8_lib.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "659888B82731482C00F64B3C"
BuildableName = "gen_ft8"
BlueprintName = "gen_ft8"
ReferencedContainer = "container:ft8_lib.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "659888C22731483800F64B3C"
BuildableName = "test"
BlueprintName = "test"
ReferencedContainer = "container:ft8_lib.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction

Wyświetl plik

@ -20,34 +20,6 @@
ReferencedContainer = "container:ft8_lib.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "659888972731363800F64B3C"
BuildableName = "libft8.dylib"
BlueprintName = "libft8.dylib"
ReferencedContainer = "container:ft8_lib.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "652CE5AF273129E000C91652"
BuildableName = "libft8.a"
BlueprintName = "libft8.a"
ReferencedContainer = "container:ft8_lib.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction

Wyświetl plik

@ -20,20 +20,6 @@
ReferencedContainer = "container:ft8_lib.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "652CE5AF273129E000C91652"
BuildableName = "libft8.a"
BlueprintName = "libft8.a"
ReferencedContainer = "container:ft8_lib.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction

Wyświetl plik

@ -20,20 +20,6 @@
ReferencedContainer = "container:ft8_lib.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "652CE5AF273129E000C91652"
BuildableName = "libft8.a"
BlueprintName = "libft8.a"
ReferencedContainer = "container:ft8_lib.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction

Wyświetl plik

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1250"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "650D013227355AB800811CC1"
BuildableName = "looptest"
BlueprintName = "looptest"
ReferencedContainer = "container:ft8_lib.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "650D013227355AB800811CC1"
BuildableName = "looptest"
BlueprintName = "looptest"
ReferencedContainer = "container:ft8_lib.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "650D013227355AB800811CC1"
BuildableName = "looptest"
BlueprintName = "looptest"
ReferencedContainer = "container:ft8_lib.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

Wyświetl plik

@ -20,20 +20,6 @@
ReferencedContainer = "container:ft8_lib.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "652CE5AF273129E000C91652"
BuildableName = "libft8.a"
BlueprintName = "libft8.a"
ReferencedContainer = "container:ft8_lib.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction

212
gen_ft8.c
Wyświetl plik

@ -1,203 +1,49 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <stdbool.h>
#include <string.h>
#include "ft8.h"
#include "common/wave.h"
#include "common/debug.h"
#include "ft8/pack.h"
#include "ft8/encode.h"
#include "ft8/constants.h"
#define LOG_LEVEL LOG_INFO
#define FT4_SLOT_TIME 7.0f // total length of output waveform in seconds
#define FT8_SLOT_TIME 15.0f // total length of output waveform in seconds
#define FT8_SLOT_TIME 15.0f // total length of output waveform in seconds
#define FT8_SYMBOL_RATE 6.25f // tone deviation (and symbol rate) in Hz
#define FT8_SYMBOL_BT 2.0f // symbol smoothing filter bandwidth factor (BT)
#define FT4_SLOT_TIME 7.5f // total length of output waveform in seconds
#define FT4_SYMBOL_RATE 20.833333f // tone deviation (and symbol rate) in Hz
#define FT4_SYMBOL_BT 1.0f // symbol smoothing filter bandwidth factor (BT)
#define GFSK_CONST_K 5.336446f // pi * sqrt(2 / log(2))
/// Computes a GFSK smoothing pulse.
/// The pulse is theoretically infinitely long, however, here it's truncated at 3 times the symbol length.
/// This means the pulse array has to have space for 3*n_spsym elements.
/// @param[in] n_spsym Number of samples per symbol
/// @param[in] b Shape parameter (values defined for FT8/FT4)
/// @param[out] pulse Output array of pulse samples
///
void gfsk_pulse(int n_spsym, float symbol_bt, float *pulse)
int main(int argc, char **argv)
{
for (int i = 0; i < 3 * n_spsym; ++i)
int sample_rate = 8000;
// Expect two command-line arguments
if (argc ==3 || argc == 4)
{
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;
}
}
float frequency = 1000.0;
bool is_ft4 = (argc > 4) && (0 == strcmp(argv[4], "-ft4"));
int num_samples = sample_rate * (is_ft4 ? FT4_SLOT_TIME : FT8_SLOT_TIME);
float signal[num_samples];
/// Synthesize waveform data using GFSK phase shaping.
/// The output waveform will contain n_sym symbols.
/// @param[in] symbols Array of symbols (tones) (0-7 for FT8)
/// @param[in] n_sym Number of symbols in the symbol array
/// @param[in] f0 Audio frequency in Hertz for the symbol 0 (base frequency)
/// @param[in] symbol_bt Symbol smoothing filter bandwidth (2 for FT8, 1 for FT4)
/// @param[in] symbol_rate Rate of symbols per second, Hertz
/// @param[in] signal_rate Sample rate of synthesized signal, Hertz
/// @param[out] signal Output array of signal waveform samples (should have space for n_sym*n_spsym samples)
///
void synth_gfsk(const uint8_t *symbols, int n_sym, float f0, float symbol_bt, float symbol_rate, int signal_rate, float *signal)
{
int n_spsym = (int)(0.5f + signal_rate / symbol_rate); // Samples per symbol
int n_wave = n_sym * n_spsym; // Number of output samples
float hmod = 1.0f;
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)
if (argc > 3)
{
dphi[j + ib] += dphi_peak * symbols[i] * pulse[j];
frequency = atof(argv[3]);
}
int rc = is_ft4 ? ft4_encode(argv[1], signal, num_samples, frequency, sample_rate) : ft8_encode(argv[1], signal, num_samples, 1000.0, 8000.0);
if (rc == 0) {
save_wav(signal, num_samples, sample_rate, argv[2]);
}
else
{
LOG(LOG_ERROR, "Could not generate signal, rc = %d\n", rc);
}
return rc;
}
// 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()
{
// wrong number of arguments
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
uint8_t packed[FT8_LDPC_K_BYTES];
int rc = pack77(message, packed);
if (rc < 0)
{
printf("Cannot parse message!\n");
printf("RC = %d\n", rc);
return -2;
}
printf("Packed data: ");
for (int j = 0; j < 10; ++j)
{
printf("%02x ", packed[j]);
}
printf("\n");
if (is_ft4)
{
// '[..] for FT4 only, in order to avoid transmitting a long string of zeros when sending CQ messages,
// the assembled 77-bit message is bitwise exclusive-ORed with [a] pseudorandom sequence before computing the CRC and FEC parity bits'
for (int i = 0; i < 10; ++i)
{
packed[i] ^= kFT4_XOR_sequence[i];
}
}
int num_tones = (is_ft4) ? FT4_NN : FT8_NN;
float symbol_rate = (is_ft4) ? FT4_SYMBOL_RATE : FT8_SYMBOL_RATE;
float symbol_bt = (is_ft4) ? FT4_SYMBOL_BT : FT8_SYMBOL_BT;
float slot_time = (is_ft4) ? FT4_SLOT_TIME : FT8_SLOT_TIME;
// Second, encode the binary message as a sequence of FSK tones
uint8_t tones[num_tones]; // Array of 79 tones (symbols)
if (is_ft4)
{
genft4(packed, tones);
}
else
{
genft8(packed, tones);
}
printf("FSK tones: ");
for (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_rate * sample_rate); // Number of samples in the data signal
int num_silence = (slot_time * sample_rate - num_samples) / 2; // Silence padding at both ends to make 15 seconds
int num_total_samples = num_silence + num_samples + num_silence; // Number of samples in the padded signal
float signal[num_total_samples];
for (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_rate, sample_rate, signal + num_silence);
save_wav(signal, num_total_samples, sample_rate, wav_path);
return 0;
return -1;
}

115
looptest.c 100644
Wyświetl plik

@ -0,0 +1,115 @@
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include "ft8.h"
#define FT4_SLOT_TIME 7.0f // total length of output waveform in seconds
#define FT8_SLOT_TIME 15.0f // total length of output waveform in seconds
// white noise added - decoding errors start to show up around 12.0
#define NOISE_AMPLITUDE 0.0
#define RP(x, div, mod) ((x / div) % mod)
// passed as context into decoder callback
struct context {
char message[32];
float frequency;
};
static char *random_callsign(char *callsign)
{
int x = rand();
switch (x >> 29) {
case 0:
sprintf(callsign, "%c%d%c%c", 'A' + RP(x, 26, 26), RP(x, 260, 10), 'A' + RP(x, 6760, 26), 'A' + RP(x, 175760, 26));
break;
case 1:
sprintf(callsign, "%c%d%c%c%c", 'A' + RP(x, 26, 26), RP(x, 260, 10), 'A' + RP(x, 6760, 26), 'A' + RP(x, 175760, 26), 'A' + RP(x, 4569760, 26));
break;
case 2:
sprintf(callsign, "%c%c%d%c%c", 'A' + RP(x, 1, 26), 'A' + RP(x, 26, 26), RP(x, 260, 10), 'A' + RP(x, 6760, 26), 'A' + RP(x, 175760, 26));
break;
default:
sprintf(callsign, "%c%c%d%c%c%c", 'A' + RP(x, 1, 26), 'A' + RP(x, 26, 26), RP(x, 260, 10), 'A' + RP(x, 6760, 26), 'A' + RP(x, 175760, 26), 'A' + RP(x, 4569760, 26));
break;
}
return callsign;
}
static char *random_locator(char *locator)
{
int x = rand();
sprintf(locator, "%c%c%d%d", 'A' + RP(x, 1, 18), 'A' + RP(x, 18, 18), RP(x, 180, 10), RP(x, 1800, 10));
return locator;
}
static char *random_message(char *message)
{
int x = rand();
char callsign1[8], callsign2[8], locator[5];
switch (x >> 28) {
case 0:
sprintf(message, "CQ %s %s", random_callsign(callsign1), random_locator(locator));
break;
case 1:
sprintf(message, "%s %s %s", random_callsign(callsign1), random_callsign(callsign2), random_locator(locator));
break;
case 2:
sprintf(message, "%s %s -%02d", random_callsign(callsign1), random_callsign(callsign2), RP(x, 1, 30) + 1);
break;
case 3:
sprintf(message, "%s %s R-%02d", random_callsign(callsign1), random_callsign(callsign2), RP(x, 1, 30) + 1);
break;
case 4:
sprintf(message, "%s %s RRR", random_callsign(callsign1), random_callsign(callsign2));
break;
default:
sprintf(message, "%s %s 73", random_callsign(callsign1), random_callsign(callsign2));
break;
}
return message;
}
// decode callback, called by ft8_decode() for each decoded message
static void ft8_decode_callback(char *message, float frequency, float time_dev, float snr, int score, void *ctx)
{
struct context *context = ctx;
bool ok = strcmp(context->message, message) == 0;
printf("%-8s000000 %3d %+4.2f %4.0f ~ %s (%s)\n", ok ? "OK" : "ERROR", score, time_dev, frequency, message, context->message);
}
int main(int argc, char *argv[])
{
int sample_rate = 8000;
float frequency = 1200.0;
int num_samples = FT8_SLOT_TIME * sample_rate;
float signal[num_samples];
// initialize random number generator
srand((unsigned int)time(NULL));
// run loop test
for (int i = 0; i < 100; i++) {
struct context ctx;
// generate random but valid message
random_message(ctx.message);
ctx.frequency = frequency;
if (ft8_encode(ctx.message, signal, num_samples, frequency, sample_rate) == 0) {
// add noise
for (float *fp = signal; fp < signal + num_samples; fp++) {
*fp = (*fp + 2.0 * NOISE_AMPLITUDE * rand() / RAND_MAX - NOISE_AMPLITUDE) / (1.0 + NOISE_AMPLITUDE);
}
if (ft8_decode(signal, num_samples, sample_rate, ft8_decode_callback, &ctx) != 1) {
printf("*** ERROR decoding (%s)\n", ctx.message);
}
} else {
printf("*** ERROR encoding (%s)\n", ctx.message);
}
}
}

7
test.c
Wyświetl plik

@ -6,14 +6,11 @@
#include "ft8/text.h"
#include "ft8/pack.h"
#include "ft8/encode.h"
#include "ft8/constants.h"
#include "fft/kiss_fftr.h"
#include "common/debug.h"
#define LOG_LEVEL LOG_INFO
void convert_8bit_to_6bit(uint8_t *dst, const uint8_t *src, int nBits)
{
// Zero-fill the destination array as we will only be setting bits later
@ -114,7 +111,7 @@ void test_tones(float *log174)
}
}
void test4()
void test4(void)
{
const int nfft = 128;
const float fft_norm = 2.0 / nfft;
@ -155,4 +152,4 @@ int main()
test4();
return 0;
}
}