kopia lustrzana https://github.com/kgoba/ft8_lib
Finished library
rodzic
cdb9c58412
commit
13f1fe7ea3
|
@ -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/
|
||||
|
||||
|
|
45
Makefile
45
Makefile
|
@ -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
|
||||
|
|
|
@ -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_
|
||||
|
|
|
@ -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);
|
||||
|
|
273
decode_ft8.c
273
decode_ft8.c
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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++)
|
||||
|
|
|
@ -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_
|
||||
|
|
297
ft8/decode.c
297
ft8/decode.c
|
@ -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;
|
||||
}
|
||||
|
|
69
ft8/decode.h
69
ft8/decode.h
|
@ -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_
|
201
ft8/encode.c
201
ft8/encode.c
|
@ -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-OR’ed 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);
|
||||
}
|
||||
|
|
32
ft8/encode.h
32
ft8/encode.h
|
@ -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_
|
|
@ -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_
|
|
@ -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];
|
||||
|
|
|
@ -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_
|
||||
|
|
20
ft8/pack.c
20
ft8/pack.c
|
@ -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();
|
||||
|
|
|
@ -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_
|
||||
|
|
16
ft8/unpack.c
16
ft8/unpack.c
|
@ -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;
|
||||
|
||||
|
|
|
@ -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_
|
||||
|
|
|
@ -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 */;
|
||||
|
|
|
@ -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>
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
|
@ -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
212
gen_ft8.c
|
@ -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-OR’ed 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;
|
||||
}
|
||||
|
|
|
@ -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
7
test.c
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue