kopia lustrzana https://github.com/kgoba/ft8_lib
Added mag/phase data in waterfall and resynth function
commit
62f7aeecac
23
Makefile
23
Makefile
|
@ -1,22 +1,25 @@
|
|||
BUILD_DIR = .build
|
||||
|
||||
FT8_SRC = $(wildcard ft8/*.c)
|
||||
FT8_OBJ = $(patsubst %.c,$(BUILD_DIR)/%.o,$(FT8_SRC))
|
||||
FT8_SRC = $(wildcard ft8/*.c)
|
||||
FT8_OBJ = $(patsubst %.c,$(BUILD_DIR)/%.o,$(FT8_SRC))
|
||||
|
||||
COMMON_SRC = $(wildcard common/*.c)
|
||||
COMMON_OBJ = $(patsubst %.c,$(BUILD_DIR)/%.o,$(COMMON_SRC))
|
||||
|
||||
FFT_SRC = $(wildcard fft/*.c)
|
||||
FFT_OBJ = $(patsubst %.c,$(BUILD_DIR)/%.o,$(FFT_SRC))
|
||||
FFT_SRC = $(wildcard fft/*.c)
|
||||
FFT_OBJ = $(patsubst %.c,$(BUILD_DIR)/%.o,$(FFT_SRC))
|
||||
|
||||
TARGETS = gen_ft8 decode_ft8 test_ft8
|
||||
TARGETS = gen_ft8 decode_ft8 test_ft8
|
||||
|
||||
CFLAGS = -O3 -ggdb3 -fsanitize=address
|
||||
CPPFLAGS = -std=c11 -I. -I/opt/local/include
|
||||
LDFLAGS = -lm -fsanitize=address -lportaudio -L/opt/local/lib
|
||||
CFLAGS = -fsanitize=address -O3 -ggdb3
|
||||
CPPFLAGS = -std=c11 -I.
|
||||
LDFLAGS = -fsanitize=address -lm
|
||||
|
||||
CPPFLAGS += -DUSE_PORTAUDIO -I/opt/local/include
|
||||
LDFLAGS += -lportaudio -L/opt/local/lib
|
||||
# Optionally, use Portaudio for live audio input
|
||||
ifdef PORTAUDIO_PREFIX
|
||||
CPPFLAGS += -DUSE_PORTAUDIO -I$(PORTAUDIO_PREFIX)/include
|
||||
LDFLAGS += -lportaudio -L$(PORTAUDIO_PREFIX)/lib
|
||||
endif
|
||||
|
||||
.PHONY: all clean run_tests install
|
||||
|
||||
|
|
|
@ -167,4 +167,25 @@ int audio_read(float* buffer, int num_samples)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int audio_init(void)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
void audio_list(void)
|
||||
{
|
||||
}
|
||||
|
||||
int audio_open(const char* name)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int audio_read(float* buffer, int num_samples)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,4 +1,5 @@
|
|||
#include "monitor.h"
|
||||
#include <common/common.h>
|
||||
|
||||
#define LOG_LEVEL LOG_INFO
|
||||
#include <ft8/debug.h>
|
||||
|
@ -33,7 +34,7 @@ static float hann_i(int i, int N)
|
|||
// return a0 - a1 * x1 + a2 * x2;
|
||||
// }
|
||||
|
||||
static void waterfall_init(waterfall_t* me, int max_blocks, int num_bins, int time_osr, int freq_osr)
|
||||
static void waterfall_init(ftx_waterfall_t* me, int max_blocks, int num_bins, int time_osr, int freq_osr)
|
||||
{
|
||||
size_t mag_size = max_blocks * time_osr * freq_osr * num_bins * sizeof(me->mag[0]);
|
||||
me->max_blocks = max_blocks;
|
||||
|
@ -46,7 +47,7 @@ static void waterfall_init(waterfall_t* me, int max_blocks, int num_bins, int ti
|
|||
LOG(LOG_DEBUG, "Waterfall size = %zu\n", mag_size);
|
||||
}
|
||||
|
||||
static void waterfall_free(waterfall_t* me)
|
||||
static void waterfall_free(ftx_waterfall_t* me)
|
||||
{
|
||||
free(me->mag);
|
||||
}
|
||||
|
|
|
@ -25,16 +25,16 @@ typedef struct
|
|||
typedef struct
|
||||
{
|
||||
float symbol_period; ///< FT4/FT8 symbol period in seconds
|
||||
int min_bin;
|
||||
int max_bin;
|
||||
int block_size; ///< Number of samples per symbol (block)
|
||||
int subblock_size; ///< Analysis shift size (number of samples)
|
||||
int nfft; ///< FFT size
|
||||
float fft_norm; ///< FFT normalization factor
|
||||
float* window; ///< Window function for STFT analysis (nfft samples)
|
||||
float* last_frame; ///< Current STFT analysis frame (nfft samples)
|
||||
waterfall_t wf; ///< Waterfall object
|
||||
float max_mag; ///< Maximum detected magnitude (debug stats)
|
||||
int min_bin; ///< First FFT bin in the frequency range (begin)
|
||||
int max_bin; ///< First FFT bin outside the frequency range (end)
|
||||
int block_size; ///< Number of samples per symbol (block)
|
||||
int subblock_size; ///< Analysis shift size (number of samples)
|
||||
int nfft; ///< FFT size
|
||||
float fft_norm; ///< FFT normalization factor
|
||||
float* window; ///< Window function for STFT analysis (nfft samples)
|
||||
float* last_frame; ///< Current STFT analysis frame (nfft samples)
|
||||
ftx_waterfall_t wf; ///< Waterfall object
|
||||
float max_mag; ///< Maximum detected magnitude (debug stats)
|
||||
|
||||
// KISS FFT housekeeping variables
|
||||
void* fft_work; ///< Work area required by Kiss FFT
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
#include <ft8/decode.h>
|
||||
#include <ft8/encode.h>
|
||||
#include <ft8/unpack.h>
|
||||
#include <ft8/message.h>
|
||||
|
||||
#include <common/common.h>
|
||||
|
@ -33,7 +32,7 @@ void usage(const char* error_msg)
|
|||
{
|
||||
fprintf(stderr, "ERROR: %s\n", error_msg);
|
||||
}
|
||||
fprintf(stderr, "Usage: decode_ft8 [-list|-ft4] INPUT\n\n");
|
||||
fprintf(stderr, "Usage: decode_ft8 [-list|([-ft4] [INPUT|-dev DEVICE])]\n\n");
|
||||
fprintf(stderr, "Decode a 15-second (or slighly shorter) WAV file.\n");
|
||||
}
|
||||
|
||||
|
@ -41,8 +40,8 @@ void usage(const char* error_msg)
|
|||
|
||||
static struct
|
||||
{
|
||||
char callsign[12];
|
||||
uint32_t hash;
|
||||
char callsign[12]; ///> Up to 11 symbols of callsign + trailing zeros (always filled)
|
||||
uint32_t hash; ///> 8 MSBs contain the age of callsign; 22 LSBs contain hash value
|
||||
} callsign_hashtable[CALLSIGN_HASHTABLE_SIZE];
|
||||
|
||||
static int callsign_hashtable_size;
|
||||
|
@ -103,10 +102,10 @@ void hashtable_add(const char* callsign, uint32_t hash)
|
|||
callsign_hashtable[idx_hash].hash = hash;
|
||||
}
|
||||
|
||||
bool hashtable_lookup(ftx_callsign_hash_type_e hash_type, uint32_t hash, char* callsign)
|
||||
bool hashtable_lookup(ftx_callsign_hash_type_t hash_type, uint32_t hash, char* callsign)
|
||||
{
|
||||
uint8_t hash_shift = (hash_type == FTX_CALLSIGN_HASH_10_BITS) ? 12 : (hash_type == FTX_CALLSIGN_HASH_12_BITS ? 10 : 0);
|
||||
uint16_t hash10 = (hash >> (12 - hash_shift)) & 0x3FF;
|
||||
uint16_t hash10 = (hash >> (12 - hash_shift)) & 0x3FFu;
|
||||
int idx_hash = (hash10 * 23) % CALLSIGN_HASHTABLE_SIZE;
|
||||
while (callsign_hashtable[idx_hash].callsign[0] != '\0')
|
||||
{
|
||||
|
@ -127,12 +126,12 @@ ftx_callsign_hash_interface_t hash_if = {
|
|||
.save_hash = hashtable_add
|
||||
};
|
||||
|
||||
void decode(const monitor_t* mon)
|
||||
void decode(const monitor_t* mon, struct tm* tm_slot_start)
|
||||
{
|
||||
const waterfall_t* wf = &mon->wf;
|
||||
const ftx_waterfall_t* wf = &mon->wf;
|
||||
// Find top candidates by Costas sync score and localize them in time and frequency
|
||||
candidate_t candidate_list[kMax_candidates];
|
||||
int num_candidates = ftx_find_sync(wf, kMax_candidates, candidate_list, kMin_score);
|
||||
ftx_candidate_t candidate_list[kMax_candidates];
|
||||
int num_candidates = ftx_find_candidates(wf, kMax_candidates, candidate_list, kMin_score);
|
||||
|
||||
// Hash table for decoded messages (to check for duplicates)
|
||||
int num_decoded = 0;
|
||||
|
@ -148,7 +147,7 @@ void decode(const monitor_t* mon)
|
|||
// Go over candidates and attempt to decode messages
|
||||
for (int idx = 0; idx < num_candidates; ++idx)
|
||||
{
|
||||
const candidate_t* cand = &candidate_list[idx];
|
||||
const ftx_candidate_t* cand = &candidate_list[idx];
|
||||
|
||||
float freq_hz = (mon->min_bin + cand->freq_offset + (float)cand->freq_sub / wf->freq_osr) / mon->symbol_period;
|
||||
float time_sec = (cand->time_offset + (float)cand->time_sub / wf->time_osr) * mon->symbol_period;
|
||||
|
@ -167,11 +166,9 @@ void decode(const monitor_t* mon)
|
|||
#endif
|
||||
|
||||
ftx_message_t message;
|
||||
decode_status_t status;
|
||||
if (!ftx_decode(wf, cand, kLDPC_iterations, &message, &status))
|
||||
ftx_decode_status_t status;
|
||||
if (!ftx_decode_candidate(wf, cand, kLDPC_iterations, &message, &status))
|
||||
{
|
||||
// float snr = cand->score * 0.5f; // TODO: compute better approximation of SNR
|
||||
// printf("000000 %2.1f %+4.2f %4.0f ~ %s\n", snr, time_sec, freq_hz, "---");
|
||||
if (status.ldpc_errors > 0)
|
||||
{
|
||||
LOG(LOG_DEBUG, "LDPC decode: %d errors\n", status.ldpc_errors);
|
||||
|
@ -215,25 +212,17 @@ void decode(const monitor_t* mon)
|
|||
++num_decoded;
|
||||
|
||||
char text[FTX_MAX_MESSAGE_LENGTH];
|
||||
// int unpack_status = unpack77(message.payload, text, NULL);
|
||||
int unpack_status = ftx_message_decode(&message, &hash_if, text);
|
||||
if (unpack_status != 0)
|
||||
ftx_message_rc_t unpack_status = ftx_message_decode(&message, &hash_if, text);
|
||||
if (unpack_status != FTX_MESSAGE_RC_OK)
|
||||
{
|
||||
strcpy(text, "Error while unpacking!");
|
||||
snprintf(text, sizeof(text), "Error [%d] while unpacking!", (int)unpack_status);
|
||||
}
|
||||
|
||||
// uint8_t i3 = ftx_message_get_i3(&message);
|
||||
// if (i3 == 0)
|
||||
// {
|
||||
// uint8_t n3 = ftx_message_get_n3(&message);
|
||||
// printf("000000 %02d %+4.2f %4.0f [%d.%d] ~ %s\n", cand->score, time_sec, freq_hz, i3, n3, text);
|
||||
// }
|
||||
// else
|
||||
// printf("000000 %02d %+4.2f %4.0f [%d ] ~ %s\n", cand->score, time_sec, freq_hz, i3, text);
|
||||
|
||||
// Fake WSJT-X-like output for now
|
||||
float snr = cand->score * 0.5f; // TODO: compute better approximation of SNR
|
||||
printf("000000 %+05.1f %+4.2f %4.0f ~ %s\n", snr, time_sec, freq_hz, text);
|
||||
printf("%02d%02d%02d %+05.1f %+4.2f %4.0f ~ %s\n",
|
||||
tm_slot_start->tm_hour, tm_slot_start->tm_min, tm_slot_start->tm_sec,
|
||||
snr, time_sec, freq_hz, text);
|
||||
}
|
||||
}
|
||||
LOG(LOG_INFO, "Decoded %d messages, callsign hashtable size %d\n", num_decoded, callsign_hashtable_size);
|
||||
|
@ -306,9 +295,9 @@ int main(int argc, char** argv)
|
|||
return -1;
|
||||
}
|
||||
|
||||
float slot_time = ((protocol == FTX_PROTOCOL_FT8) ? FT8_SLOT_TIME : FT4_SLOT_TIME);
|
||||
float slot_period = ((protocol == FTX_PROTOCOL_FT8) ? FT8_SLOT_TIME : FT4_SLOT_TIME);
|
||||
int sample_rate = 12000;
|
||||
int num_samples = slot_time * sample_rate;
|
||||
int num_samples = slot_period * sample_rate;
|
||||
float signal[num_samples];
|
||||
bool is_live = false;
|
||||
|
||||
|
@ -326,7 +315,7 @@ int main(int argc, char** argv)
|
|||
{
|
||||
audio_init();
|
||||
audio_open(dev_name);
|
||||
num_samples = (slot_time - 0.4f) * sample_rate;
|
||||
num_samples = (slot_period - 0.4f) * sample_rate;
|
||||
is_live = true;
|
||||
}
|
||||
|
||||
|
@ -348,6 +337,7 @@ int main(int argc, char** argv)
|
|||
|
||||
do
|
||||
{
|
||||
struct tm tm_slot_start = { 0 };
|
||||
if (is_live)
|
||||
{
|
||||
// Wait for the start of time slot
|
||||
|
@ -355,12 +345,18 @@ int main(int argc, char** argv)
|
|||
{
|
||||
struct timespec spec;
|
||||
clock_gettime(CLOCK_REALTIME, &spec);
|
||||
float time_within_slot = fmod((double)spec.tv_sec + (spec.tv_nsec * 1e-9) - time_shift, slot_time);
|
||||
if (time_within_slot > slot_time / 3)
|
||||
double time = (double)spec.tv_sec + (spec.tv_nsec / 1e9);
|
||||
double time_within_slot = fmod(time - time_shift, slot_period);
|
||||
if (time_within_slot > slot_period / 4)
|
||||
{
|
||||
audio_read(signal, mon.block_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(LOG_INFO, "Time within slot: %.3f s\n", time_within_slot);
|
||||
time_t time_slot_start = (time_t)(time - time_within_slot);
|
||||
gmtime_r(&time_slot_start, &tm_slot_start);
|
||||
LOG(LOG_INFO, "Time within slot %02d%02d%02d: %.3f s\n", tm_slot_start.tm_hour,
|
||||
tm_slot_start.tm_min, tm_slot_start.tm_sec, time_within_slot);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -383,7 +379,7 @@ int main(int argc, char** argv)
|
|||
LOG(LOG_INFO, "Max magnitude: %.1f dB\n", mon.max_mag);
|
||||
|
||||
// Decode accumulated data (containing slightly less than a full time slot)
|
||||
decode(&mon);
|
||||
decode(&mon, &tm_slot_start);
|
||||
|
||||
// Reset internal variables for the next time slot
|
||||
monitor_reset(&mon);
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
|
||||
#include "common/common.h"
|
||||
#include "common/wave.h"
|
||||
#include "ft8/debug.h"
|
||||
#include "ft8/pack.h"
|
||||
#include "ft8/message.h"
|
||||
#include "ft8/encode.h"
|
||||
#include "ft8/constants.h"
|
||||
|
||||
#define LOG_LEVEL LOG_INFO
|
||||
#include "ft8/debug.h"
|
||||
|
||||
#define FT8_SYMBOL_BT 2.0f ///< symbol smoothing filter bandwidth factor (BT)
|
||||
#define FT4_SYMBOL_BT 1.0f ///< symbol smoothing filter bandwidth factor (BT)
|
||||
|
@ -130,19 +130,19 @@ int main(int argc, char** argv)
|
|||
bool is_ft4 = (argc > 4) && (0 == strcmp(argv[4], "-ft4"));
|
||||
|
||||
// First, pack the text data into binary message
|
||||
uint8_t packed[FTX_LDPC_K_BYTES];
|
||||
int rc = pack77(message, packed);
|
||||
if (rc < 0)
|
||||
ftx_message_t msg;
|
||||
ftx_message_rc_t rc = ftx_message_encode(&msg, NULL, message);
|
||||
if (rc != FTX_MESSAGE_RC_OK)
|
||||
{
|
||||
printf("Cannot parse message!\n");
|
||||
printf("RC = %d\n", rc);
|
||||
printf("RC = %d\n", (int)rc);
|
||||
return -2;
|
||||
}
|
||||
|
||||
printf("Packed data: ");
|
||||
for (int j = 0; j < 10; ++j)
|
||||
{
|
||||
printf("%02x ", packed[j]);
|
||||
printf("%02x ", msg.payload[j]);
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
|
@ -155,11 +155,11 @@ int main(int argc, char** argv)
|
|||
uint8_t tones[num_tones]; // Array of 79 tones (symbols)
|
||||
if (is_ft4)
|
||||
{
|
||||
ft4_encode(packed, tones);
|
||||
ft4_encode(msg.payload, tones);
|
||||
}
|
||||
else
|
||||
{
|
||||
ft8_encode(packed, tones);
|
||||
ft8_encode(msg.payload, tones);
|
||||
}
|
||||
|
||||
printf("FSK tones: ");
|
||||
|
|
37
ft8/decode.c
37
ft8/decode.c
|
@ -2,7 +2,6 @@
|
|||
#include "constants.h"
|
||||
#include "crc.h"
|
||||
#include "ldpc.h"
|
||||
#include "unpack.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <math.h>
|
||||
|
@ -15,8 +14,8 @@
|
|||
/// @param[in] cand Candidate to extract the message from
|
||||
/// @param[in] code_map Symbol encoding map
|
||||
/// @param[out] log174 Output of decoded log likelihoods for each of the 174 message bits
|
||||
static void ft4_extract_likelihood(const waterfall_t* wf, const candidate_t* cand, float* log174);
|
||||
static void ft8_extract_likelihood(const waterfall_t* wf, const candidate_t* cand, float* log174);
|
||||
static void ft4_extract_likelihood(const ftx_waterfall_t* wf, const ftx_candidate_t* cand, float* log174);
|
||||
static void ft8_extract_likelihood(const ftx_waterfall_t* wf, const ftx_candidate_t* cand, float* log174);
|
||||
|
||||
/// Packs a string of bits each represented as a zero/non-zero byte in bit_array[],
|
||||
/// as a string of packed bits starting from the MSB of the first byte of packed[]
|
||||
|
@ -27,15 +26,15 @@ static void pack_bits(const uint8_t bit_array[], int num_bits, uint8_t packed[])
|
|||
|
||||
static float max2(float a, float b);
|
||||
static float max4(float a, float b, float c, float d);
|
||||
static void heapify_down(candidate_t heap[], int heap_size);
|
||||
static void heapify_up(candidate_t heap[], int heap_size);
|
||||
static void heapify_down(ftx_candidate_t heap[], int heap_size);
|
||||
static void heapify_up(ftx_candidate_t heap[], int heap_size);
|
||||
|
||||
static void ftx_normalize_logl(float* log174);
|
||||
static void ft4_extract_symbol(const WF_ELEM_T* wf, float* logl);
|
||||
static void ft8_extract_symbol(const WF_ELEM_T* wf, float* logl);
|
||||
static void ft8_decode_multi_symbols(const WF_ELEM_T* wf, int num_bins, int n_syms, int bit_idx, float* log174);
|
||||
|
||||
static const WF_ELEM_T* get_cand_mag(const waterfall_t* wf, const candidate_t* candidate)
|
||||
static const WF_ELEM_T* get_cand_mag(const ftx_waterfall_t* wf, const ftx_candidate_t* candidate)
|
||||
{
|
||||
int offset = candidate->time_offset;
|
||||
offset = (offset * wf->time_osr) + candidate->time_sub;
|
||||
|
@ -44,7 +43,7 @@ static const WF_ELEM_T* get_cand_mag(const waterfall_t* wf, const candidate_t* c
|
|||
return wf->mag + offset;
|
||||
}
|
||||
|
||||
static int ft8_sync_score(const waterfall_t* wf, const candidate_t* candidate)
|
||||
static int ft8_sync_score(const ftx_waterfall_t* wf, const ftx_candidate_t* candidate)
|
||||
{
|
||||
int score = 0;
|
||||
int num_average = 0;
|
||||
|
@ -110,7 +109,7 @@ static int ft8_sync_score(const waterfall_t* wf, const candidate_t* candidate)
|
|||
return score;
|
||||
}
|
||||
|
||||
static int ft4_sync_score(const waterfall_t* wf, const candidate_t* candidate)
|
||||
static int ft4_sync_score(const ftx_waterfall_t* wf, const ftx_candidate_t* candidate)
|
||||
{
|
||||
int score = 0;
|
||||
int num_average = 0;
|
||||
|
@ -173,13 +172,13 @@ static int ft4_sync_score(const waterfall_t* wf, const candidate_t* candidate)
|
|||
return score;
|
||||
}
|
||||
|
||||
int ftx_find_sync(const waterfall_t* wf, int num_candidates, candidate_t heap[], int min_score)
|
||||
int ftx_find_candidates(const ftx_waterfall_t* wf, int num_candidates, ftx_candidate_t heap[], int min_score)
|
||||
{
|
||||
int (*sync_fun)(const waterfall_t*, const candidate_t*) = (wf->protocol == FTX_PROTOCOL_FT4) ? ft4_sync_score : ft8_sync_score;
|
||||
int (*sync_fun)(const ftx_waterfall_t*, const ftx_candidate_t*) = (wf->protocol == FTX_PROTOCOL_FT4) ? ft4_sync_score : ft8_sync_score;
|
||||
int num_tones = (wf->protocol == FTX_PROTOCOL_FT4) ? 4 : 8;
|
||||
|
||||
int heap_size = 0;
|
||||
candidate_t candidate;
|
||||
ftx_candidate_t candidate;
|
||||
|
||||
// Here we allow time offsets that exceed signal boundaries, as long as we still have all data bits.
|
||||
// I.e. we can afford to skip the first 7 or the last 7 Costas symbols, as long as we track how many
|
||||
|
@ -226,7 +225,7 @@ int ftx_find_sync(const waterfall_t* wf, int num_candidates, candidate_t heap[],
|
|||
// exchange it with the last element in the heap, and decrease the heap size.
|
||||
// Then restore the heap property in the new, smaller heap.
|
||||
// At the end the elements will be sorted in descending order.
|
||||
candidate_t tmp = heap[len_unsorted - 1];
|
||||
ftx_candidate_t tmp = heap[len_unsorted - 1];
|
||||
heap[len_unsorted - 1] = heap[0];
|
||||
heap[0] = tmp;
|
||||
len_unsorted--;
|
||||
|
@ -236,7 +235,7 @@ int ftx_find_sync(const waterfall_t* wf, int num_candidates, candidate_t heap[],
|
|||
return heap_size;
|
||||
}
|
||||
|
||||
static void ft4_extract_likelihood(const waterfall_t* wf, const candidate_t* cand, float* log174)
|
||||
static void ft4_extract_likelihood(const ftx_waterfall_t* wf, const ftx_candidate_t* cand, float* log174)
|
||||
{
|
||||
const WF_ELEM_T* mag = get_cand_mag(wf, cand); // Pointer to 4 magnitude bins of the first symbol
|
||||
|
||||
|
@ -262,7 +261,7 @@ static void ft4_extract_likelihood(const waterfall_t* wf, const candidate_t* can
|
|||
}
|
||||
}
|
||||
|
||||
static void ft8_extract_likelihood(const waterfall_t* wf, const candidate_t* cand, float* log174)
|
||||
static void ft8_extract_likelihood(const ftx_waterfall_t* wf, const ftx_candidate_t* cand, float* log174)
|
||||
{
|
||||
const WF_ELEM_T* mag = get_cand_mag(wf, cand); // Pointer to 8 magnitude bins of the first symbol
|
||||
|
||||
|
@ -310,7 +309,7 @@ static void ftx_normalize_logl(float* log174)
|
|||
}
|
||||
}
|
||||
|
||||
bool ftx_decode(const waterfall_t* wf, const candidate_t* cand, int max_iterations, ftx_message_t* message, decode_status_t* status)
|
||||
bool ftx_decode_candidate(const ftx_waterfall_t* wf, const ftx_candidate_t* cand, int max_iterations, ftx_message_t* message, ftx_decode_status_t* status)
|
||||
{
|
||||
float log174[FTX_LDPC_N]; // message bits encoded as likelihood
|
||||
if (wf->protocol == FTX_PROTOCOL_FT4)
|
||||
|
@ -383,7 +382,7 @@ static float max4(float a, float b, float c, float d)
|
|||
return max2(max2(a, b), max2(c, d));
|
||||
}
|
||||
|
||||
static void heapify_down(candidate_t heap[], int heap_size)
|
||||
static void heapify_down(ftx_candidate_t heap[], int heap_size)
|
||||
{
|
||||
// heapify from the root down
|
||||
int current = 0; // root node
|
||||
|
@ -409,14 +408,14 @@ static void heapify_down(candidate_t heap[], int heap_size)
|
|||
}
|
||||
|
||||
// Exchange the current node with the smallest child and move down to it
|
||||
candidate_t tmp = heap[smallest];
|
||||
ftx_candidate_t tmp = heap[smallest];
|
||||
heap[smallest] = heap[current];
|
||||
heap[current] = tmp;
|
||||
current = smallest;
|
||||
}
|
||||
}
|
||||
|
||||
static void heapify_up(candidate_t heap[], int heap_size)
|
||||
static void heapify_up(ftx_candidate_t heap[], int heap_size)
|
||||
{
|
||||
// heapify from the last node up
|
||||
int current = heap_size - 1;
|
||||
|
@ -429,7 +428,7 @@ static void heapify_up(candidate_t heap[], int heap_size)
|
|||
}
|
||||
|
||||
// Exchange the current node with its parent and move up
|
||||
candidate_t tmp = heap[parent];
|
||||
ftx_candidate_t tmp = heap[parent];
|
||||
heap[parent] = heap[current];
|
||||
heap[current] = tmp;
|
||||
current = parent;
|
||||
|
|
14
ft8/decode.h
14
ft8/decode.h
|
@ -46,7 +46,7 @@ typedef struct
|
|||
WF_ELEM_T* mag; ///< FFT magnitudes stored as uint8_t[blocks][time_osr][freq_osr][num_bins]
|
||||
int block_stride; ///< Helper value = time_osr * freq_osr * num_bins
|
||||
ftx_protocol_t protocol; ///< Indicate if using FT4 or FT8
|
||||
} waterfall_t;
|
||||
} ftx_waterfall_t;
|
||||
|
||||
/// Output structure of ftx_find_sync() and input structure of ftx_decode().
|
||||
/// Holds the position of potential start of a message in time and frequency.
|
||||
|
@ -57,7 +57,7 @@ typedef struct
|
|||
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;
|
||||
} ftx_candidate_t;
|
||||
|
||||
/// Structure that contains the status of various steps during decoding of a message
|
||||
typedef struct
|
||||
|
@ -68,26 +68,26 @@ typedef struct
|
|||
uint16_t crc_extracted; ///< CRC value recovered from the message
|
||||
uint16_t crc_calculated; ///< CRC value calculated over the payload
|
||||
// int unpack_status; ///< Return value of the unpack routine
|
||||
} decode_status_t;
|
||||
} ftx_decode_status_t;
|
||||
|
||||
/// Localize top N candidates in frequency and time according to their sync strength (looking at Costas symbols)
|
||||
/// We treat and organize the candidate list as a min-heap (empty initially).
|
||||
/// @param[in] power Waterfall data collected during message slot
|
||||
/// @param[in] sync_pattern Synchronization pattern
|
||||
/// @param[in] num_candidates Number of maximum candidates (size of heap array)
|
||||
/// @param[in,out] heap Array of candidate_t type entries (with num_candidates allocated entries)
|
||||
/// @param[in,out] heap Array of ftx_candidate_t type entries (with num_candidates allocated entries)
|
||||
/// @param[in] min_score Minimal score allowed for pruning unlikely candidates (can be zero for no effect)
|
||||
/// @return Number of candidates filled in the heap
|
||||
int ftx_find_sync(const waterfall_t* power, int num_candidates, candidate_t heap[], int min_score);
|
||||
int ftx_find_candidates(const ftx_waterfall_t* power, int num_candidates, ftx_candidate_t heap[], int min_score);
|
||||
|
||||
/// Attempt to decode a message candidate. Extracts the bit probabilities, runs LDPC decoder, checks CRC and unpacks the message in plain text.
|
||||
/// @param[in] power Waterfall data collected during message slot
|
||||
/// @param[in] cand Candidate to decode
|
||||
/// @param[in] max_iterations Maximum allowed LDPC iterations (lower number means faster decode, but less precise)
|
||||
/// @param[out] message ftx_message_t structure that will receive the decoded message
|
||||
/// @param[out] status decode_status_t structure that will be filled with the status of various decoding steps
|
||||
/// @param[out] status ftx_decode_status_t structure that will be filled with the status of various decoding steps
|
||||
/// @return True if the decoding was successful, false otherwise (check status for details)
|
||||
bool ftx_decode(const waterfall_t* power, const candidate_t* cand, int max_iterations, ftx_message_t* message, decode_status_t* status);
|
||||
bool ftx_decode_candidate(const ftx_waterfall_t* power, const ftx_candidate_t* cand, int max_iterations, ftx_message_t* message, ftx_decode_status_t* status);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ static void add_brackets(char* result, const char* original, int length);
|
|||
/// @param[out] n10_out Pointer to store 10-bit hash value (can be NULL)
|
||||
/// @return True on success
|
||||
static bool save_callsign(const ftx_callsign_hash_interface_t* hash_if, const char* callsign, uint32_t* n22_out, uint16_t* n12_out, uint16_t* n10_out);
|
||||
static bool lookup_callsign(const ftx_callsign_hash_interface_t* hash_if, ftx_callsign_hash_type_e hash_type, uint32_t hash, char* callsign);
|
||||
static bool lookup_callsign(const ftx_callsign_hash_interface_t* hash_if, ftx_callsign_hash_type_t hash_type, uint32_t hash, char* callsign);
|
||||
|
||||
static int32_t pack_basecall(const char* callsign, int length);
|
||||
|
||||
|
@ -591,7 +591,7 @@ static bool save_callsign(const ftx_callsign_hash_interface_t* hash_if, const ch
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool lookup_callsign(const ftx_callsign_hash_interface_t* hash_if, ftx_callsign_hash_type_e hash_type, uint32_t hash, char* callsign)
|
||||
static bool lookup_callsign(const ftx_callsign_hash_interface_t* hash_if, ftx_callsign_hash_type_t hash_type, uint32_t hash, char* callsign)
|
||||
{
|
||||
char c11[12];
|
||||
|
||||
|
|
|
@ -58,12 +58,12 @@ typedef enum
|
|||
FTX_CALLSIGN_HASH_22_BITS,
|
||||
FTX_CALLSIGN_HASH_12_BITS,
|
||||
FTX_CALLSIGN_HASH_10_BITS
|
||||
} ftx_callsign_hash_type_e;
|
||||
} ftx_callsign_hash_type_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
/// Called when a callsign is looked up by its 22/12/10 bit hash code
|
||||
bool (*lookup_hash)(ftx_callsign_hash_type_e hash_type, uint32_t hash, char* callsign);
|
||||
bool (*lookup_hash)(ftx_callsign_hash_type_t hash_type, uint32_t hash, char* callsign);
|
||||
/// Called when a callsign should hashed and stored (by its 22, 12 and 10 bit hash codes)
|
||||
void (*save_hash)(const char* callsign, uint32_t n22);
|
||||
} ftx_callsign_hash_interface_t;
|
||||
|
@ -78,9 +78,12 @@ typedef enum
|
|||
FTX_MESSAGE_RC_ERROR_TYPE
|
||||
} ftx_message_rc_t;
|
||||
|
||||
// Basecall - 1-2 letter/digit prefix (at least one letter), 1 digit area code, 1-3 letter suffix, total 3-6 chars (except for 7 char 3DA0- and 3X- calls)
|
||||
// Ext. basecall - basecall followed by /R or /P
|
||||
// Nonstd. call - all the rest, limited to 3-11 characters either alphanumeric or stroke (/)
|
||||
// Callsign types and sizes:
|
||||
// * Std. call (basecall) - 1-2 letter/digit prefix (at least one letter), 1 digit area code, 1-3 letter suffix,
|
||||
// total 3-6 chars (exception: 7 character calls with prefixes 3DA0- and 3XA..3XZ-)
|
||||
// * Ext. std. call - basecall followed by /R or /P
|
||||
// * Nonstd. call - all the rest, limited to 3-11 characters either alphanumeric or stroke (/)
|
||||
// In case a call is looked up from its hash value, the call is enclosed in angular brackets (<CA0LL>).
|
||||
|
||||
void ftx_message_init(ftx_message_t* msg);
|
||||
|
||||
|
|
365
ft8/pack.c
365
ft8/pack.c
|
@ -1,365 +0,0 @@
|
|||
#include "pack.h"
|
||||
#include "text.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define NTOKENS ((uint32_t)2063592L)
|
||||
#define MAX22 ((uint32_t)4194304L)
|
||||
#define MAXGRID4 ((uint16_t)32400)
|
||||
|
||||
// Pack a special token, a 22-bit hash code, or a valid base call
|
||||
// into a 28-bit integer.
|
||||
static int32_t pack28(const char* callsign)
|
||||
{
|
||||
// Check for special tokens first
|
||||
if (starts_with(callsign, "DE "))
|
||||
return 0;
|
||||
if (starts_with(callsign, "QRZ "))
|
||||
return 1;
|
||||
if (starts_with(callsign, "CQ "))
|
||||
return 2;
|
||||
|
||||
if (starts_with(callsign, "CQ_"))
|
||||
{
|
||||
int nnum = 0, nlet = 0;
|
||||
|
||||
// TODO:
|
||||
}
|
||||
|
||||
// TODO: Check for <...> callsign
|
||||
|
||||
char c6[6] = { ' ', ' ', ' ', ' ', ' ', ' ' };
|
||||
|
||||
int length = 0; // strlen(callsign); // We will need it later
|
||||
while (callsign[length] != ' ' && callsign[length] != 0)
|
||||
{
|
||||
length++;
|
||||
}
|
||||
|
||||
// Copy callsign to 6 character buffer
|
||||
if (starts_with(callsign, "3DA0") && length <= 7)
|
||||
{
|
||||
// Work-around for Swaziland prefix: 3DA0XYZ -> 3D0XYZ
|
||||
memcpy(c6, "3D0", 3);
|
||||
memcpy(c6 + 3, callsign + 4, length - 4);
|
||||
}
|
||||
else if (starts_with(callsign, "3X") && is_letter(callsign[2]) && length <= 7)
|
||||
{
|
||||
// Work-around for Guinea prefixes: 3XA0XYZ -> QA0XYZ
|
||||
memcpy(c6, "Q", 1);
|
||||
memcpy(c6 + 1, callsign + 2, length - 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check the position of callsign digit and make a right-aligned copy into c6
|
||||
if (is_digit(callsign[2]) && length <= 6)
|
||||
{
|
||||
// AB0XYZ
|
||||
memcpy(c6, callsign, length);
|
||||
}
|
||||
else if (is_digit(callsign[1]) && length <= 5)
|
||||
{
|
||||
// A0XYZ -> " A0XYZ"
|
||||
memcpy(c6 + 1, callsign, length);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for standard callsign
|
||||
int i0 = nchar(c6[0], FT8_CHAR_TABLE_ALPHANUM_SPACE);
|
||||
int i1 = nchar(c6[1], FT8_CHAR_TABLE_ALPHANUM);
|
||||
int i2 = nchar(c6[2], FT8_CHAR_TABLE_NUMERIC);
|
||||
int i3 = nchar(c6[3], FT8_CHAR_TABLE_LETTERS_SPACE);
|
||||
int i4 = nchar(c6[4], FT8_CHAR_TABLE_LETTERS_SPACE);
|
||||
int i5 = nchar(c6[5], FT8_CHAR_TABLE_LETTERS_SPACE);
|
||||
if ((i0 >= 0) && (i1 >= 0) && (i2 >= 0) && (i3 >= 0) && (i4 >= 0) && (i5 >= 0))
|
||||
{
|
||||
// This is a standard callsign
|
||||
int32_t n28 = i0;
|
||||
n28 = n28 * 36 + i1;
|
||||
n28 = n28 * 10 + i2;
|
||||
n28 = n28 * 27 + i3;
|
||||
n28 = n28 * 27 + i4;
|
||||
n28 = n28 * 27 + i5;
|
||||
return NTOKENS + MAX22 + n28;
|
||||
}
|
||||
|
||||
// char text[13];
|
||||
// if (length > 13) return -1;
|
||||
|
||||
// TODO:
|
||||
// Treat this as a nonstandard callsign: compute its 22-bit hash
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Check if a string could be a valid standard callsign or a valid
|
||||
// compound callsign.
|
||||
// Return base call "bc" and a logical "cok" indicator.
|
||||
static bool chkcall(const char* call, char* bc)
|
||||
{
|
||||
int length = strlen(call); // n1=len_trim(w)
|
||||
if (length > 11)
|
||||
return false;
|
||||
if (0 != strchr(call, '.'))
|
||||
return false;
|
||||
if (0 != strchr(call, '+'))
|
||||
return false;
|
||||
if (0 != strchr(call, '-'))
|
||||
return false;
|
||||
if (0 != strchr(call, '?'))
|
||||
return false;
|
||||
if (length > 6 && 0 != strchr(call, '/'))
|
||||
return false;
|
||||
|
||||
// TODO: implement suffix parsing (or rework?)
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint16_t packgrid(const char* grid4)
|
||||
{
|
||||
if (grid4 == 0)
|
||||
{
|
||||
// Two callsigns only, no report/grid
|
||||
return MAXGRID4 + 1;
|
||||
}
|
||||
|
||||
// Take care of special cases
|
||||
if (equals(grid4, "RRR"))
|
||||
return MAXGRID4 + 2;
|
||||
if (equals(grid4, "RR73"))
|
||||
return MAXGRID4 + 3;
|
||||
if (equals(grid4, "73"))
|
||||
return MAXGRID4 + 4;
|
||||
|
||||
// Check for standard 4 letter grid
|
||||
if (in_range(grid4[0], 'A', 'R') && in_range(grid4[1], 'A', 'R') && is_digit(grid4[2]) && is_digit(grid4[3]))
|
||||
{
|
||||
uint16_t igrid4 = (grid4[0] - 'A');
|
||||
igrid4 = igrid4 * 18 + (grid4[1] - 'A');
|
||||
igrid4 = igrid4 * 10 + (grid4[2] - '0');
|
||||
igrid4 = igrid4 * 10 + (grid4[3] - '0');
|
||||
return igrid4;
|
||||
}
|
||||
|
||||
// Parse report: +dd / -dd / R+dd / R-dd
|
||||
// TODO: check the range of dd
|
||||
if (grid4[0] == 'R')
|
||||
{
|
||||
int dd = dd_to_int(grid4 + 1, 3);
|
||||
uint16_t irpt = 35 + dd;
|
||||
return (MAXGRID4 + irpt) | 0x8000; // ir = 1
|
||||
}
|
||||
else
|
||||
{
|
||||
int dd = dd_to_int(grid4, 3);
|
||||
uint16_t irpt = 35 + dd;
|
||||
return (MAXGRID4 + irpt); // ir = 0
|
||||
}
|
||||
|
||||
return MAXGRID4 + 1;
|
||||
}
|
||||
|
||||
// Pack Type 1 (Standard 77-bit message) and Type 2 (ditto, with a "/P" call)
|
||||
static int pack77_1(const char* msg, uint8_t* b77)
|
||||
{
|
||||
// Locate the first delimiter
|
||||
const char* s1 = strchr(msg, ' ');
|
||||
if (s1 == 0)
|
||||
return -1;
|
||||
|
||||
const char* call1 = msg; // 1st call
|
||||
const char* call2 = s1 + 1; // 2nd call
|
||||
|
||||
int32_t n28a = pack28(call1);
|
||||
int32_t n28b = pack28(call2);
|
||||
|
||||
if (n28a < 0 || n28b < 0)
|
||||
return -1;
|
||||
|
||||
uint16_t igrid4;
|
||||
|
||||
// Locate the second delimiter
|
||||
const char* s2 = strchr(s1 + 1, ' ');
|
||||
if (s2 != 0)
|
||||
{
|
||||
igrid4 = packgrid(s2 + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Two callsigns, no grid/report
|
||||
igrid4 = packgrid(0);
|
||||
}
|
||||
|
||||
uint8_t i3 = 1; // No suffix or /R
|
||||
|
||||
// TODO: check for suffixes
|
||||
|
||||
// Shift in ipa and ipb bits into n28a and n28b
|
||||
n28a <<= 1; // ipa = 0
|
||||
n28b <<= 1; // ipb = 0
|
||||
|
||||
// Pack into (28 + 1) + (28 + 1) + (1 + 15) + 3 bits
|
||||
b77[0] = (n28a >> 21);
|
||||
b77[1] = (n28a >> 13);
|
||||
b77[2] = (n28a >> 5);
|
||||
b77[3] = (uint8_t)(n28a << 3) | (uint8_t)(n28b >> 26);
|
||||
b77[4] = (n28b >> 18);
|
||||
b77[5] = (n28b >> 10);
|
||||
b77[6] = (n28b >> 2);
|
||||
b77[7] = (uint8_t)(n28b << 6) | (uint8_t)(igrid4 >> 10);
|
||||
b77[8] = (igrid4 >> 2);
|
||||
b77[9] = (uint8_t)(igrid4 << 6) | (uint8_t)(i3 << 3);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void packtext77(const char* text, uint8_t* b77)
|
||||
{
|
||||
int length = strlen(text);
|
||||
|
||||
// Skip leading and trailing spaces
|
||||
while (*text == ' ' && *text != 0)
|
||||
{
|
||||
++text;
|
||||
--length;
|
||||
}
|
||||
while (length > 0 && text[length - 1] == ' ')
|
||||
{
|
||||
--length;
|
||||
}
|
||||
|
||||
// Clear the first 72 bits representing a long number
|
||||
for (int i = 0; i < 9; ++i)
|
||||
{
|
||||
b77[i] = 0;
|
||||
}
|
||||
|
||||
// Now express the text as base-42 number stored
|
||||
// in the first 72 bits of b77
|
||||
for (int j = 0; j < 13; ++j)
|
||||
{
|
||||
// Multiply the long integer in b77 by 42
|
||||
uint16_t x = 0;
|
||||
for (int i = 8; i >= 0; --i)
|
||||
{
|
||||
x += b77[i] * (uint16_t)42;
|
||||
b77[i] = (x & 0xFF);
|
||||
x >>= 8;
|
||||
}
|
||||
|
||||
// Get the index of the current char
|
||||
if (j < length)
|
||||
{
|
||||
int q = nchar(text[j], FT8_CHAR_TABLE_FULL);
|
||||
x = (q > 0) ? q : 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
x = 0;
|
||||
}
|
||||
// Here we double each added number in order to have the result multiplied
|
||||
// by two as well, so that it's a 71 bit number left-aligned in 72 bits (9 bytes)
|
||||
x <<= 1;
|
||||
|
||||
// Now add the number to our long number
|
||||
for (int i = 8; i >= 0; --i)
|
||||
{
|
||||
if (x == 0)
|
||||
break;
|
||||
x += b77[i];
|
||||
b77[i] = (x & 0xFF);
|
||||
x >>= 8;
|
||||
}
|
||||
}
|
||||
|
||||
// Set n3=0 (bits 71..73) and i3=0 (bits 74..76)
|
||||
b77[8] &= 0xFE;
|
||||
b77[9] &= 0x00;
|
||||
}
|
||||
|
||||
int 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))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// Check 0.5 (telemetry)
|
||||
|
||||
// Check Type 4 (One nonstandard call and one hashed call)
|
||||
|
||||
// Default to free text
|
||||
// i3=0 n3=0
|
||||
packtext77(msg, c77);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef UNIT_TEST
|
||||
|
||||
#include <iostream>
|
||||
|
||||
bool test1()
|
||||
{
|
||||
const char* inputs[] = {
|
||||
"",
|
||||
" ",
|
||||
"ABC",
|
||||
"A9",
|
||||
"L9A",
|
||||
"L7BC",
|
||||
"L0ABC",
|
||||
"LL3JG",
|
||||
"LL3AJG",
|
||||
"CQ ",
|
||||
0
|
||||
};
|
||||
|
||||
for (int i = 0; inputs[i]; ++i)
|
||||
{
|
||||
int32_t result = ft8_v2::pack28(inputs[i]);
|
||||
printf("pack28(\"%s\") = %d\n", inputs[i], result);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool test2()
|
||||
{
|
||||
const char* inputs[] = {
|
||||
"CQ LL3JG",
|
||||
"CQ LL3JG KO26",
|
||||
"L0UAA LL3JG KO26",
|
||||
"L0UAA LL3JG +02",
|
||||
"L0UAA LL3JG RRR",
|
||||
"L0UAA LL3JG 73",
|
||||
0
|
||||
};
|
||||
|
||||
for (int i = 0; inputs[i]; ++i)
|
||||
{
|
||||
uint8_t result[10];
|
||||
int rc = ft8_v2::pack77_1(inputs[i], result);
|
||||
printf("pack77_1(\"%s\") = %d\t[", inputs[i], rc);
|
||||
for (int j = 0; j < 10; ++j)
|
||||
{
|
||||
printf("%02x ", result[j]);
|
||||
}
|
||||
printf("]\n");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
test1();
|
||||
test2();
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
20
ft8/pack.h
20
ft8/pack.h
|
@ -1,20 +0,0 @@
|
|||
#ifndef _INCLUDE_PACK_H_
|
||||
#define _INCLUDE_PACK_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/// Parse and pack FT8/FT4 text message into 77 bit binary payload
|
||||
/// @param[in] msg FT8/FT4 message (e.g. "CQ TE5T KN01")
|
||||
/// @param[out] c77 10 byte array to store the 77 bit payload (MSB first)
|
||||
/// @return Parsing result (0 - success, otherwise error)
|
||||
int pack77(const char* msg, uint8_t* c77);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // _INCLUDE_PACK_H_
|
435
ft8/unpack.c
435
ft8/unpack.c
|
@ -1,435 +0,0 @@
|
|||
#ifdef __linux__
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "unpack.h"
|
||||
#include "text.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define MAX22 ((uint32_t)4194304L)
|
||||
#define NTOKENS ((uint32_t)2063592L)
|
||||
#define MAXGRID4 ((uint16_t)32400L)
|
||||
|
||||
// n28 is a 28-bit integer, e.g. n28a or n28b, containing all the
|
||||
// call sign bits from a packed message.
|
||||
static int unpack_callsign(uint32_t n28, uint8_t ip, uint8_t i3, char* result, const unpack_hash_interface_t* hash_if)
|
||||
{
|
||||
// Check for special tokens DE, QRZ, CQ, CQ_nnn, CQ_aaaa
|
||||
if (n28 < NTOKENS)
|
||||
{
|
||||
if (n28 <= 2)
|
||||
{
|
||||
if (n28 == 0)
|
||||
strcpy(result, "DE");
|
||||
if (n28 == 1)
|
||||
strcpy(result, "QRZ");
|
||||
if (n28 == 2)
|
||||
strcpy(result, "CQ");
|
||||
return 0; // Success
|
||||
}
|
||||
if (n28 <= 1002)
|
||||
{
|
||||
// CQ_nnn with 3 digits
|
||||
strcpy(result, "CQ ");
|
||||
int_to_dd(result + 3, n28 - 3, 3, false);
|
||||
return 0; // Success
|
||||
}
|
||||
if (n28 <= 532443L)
|
||||
{
|
||||
// CQ_aaaa with 4 alphanumeric symbols
|
||||
uint32_t n = n28 - 1003;
|
||||
char aaaa[5];
|
||||
|
||||
aaaa[4] = '\0';
|
||||
for (int i = 3; /* */; --i)
|
||||
{
|
||||
aaaa[i] = charn(n % 27, FT8_CHAR_TABLE_LETTERS_SPACE);
|
||||
if (i == 0)
|
||||
break;
|
||||
n /= 27;
|
||||
}
|
||||
|
||||
strcpy(result, "CQ ");
|
||||
strcat(result, trim_front(aaaa));
|
||||
return 0; // Success
|
||||
}
|
||||
// ? TODO: unspecified in the WSJT-X code
|
||||
return -1;
|
||||
}
|
||||
|
||||
n28 = n28 - NTOKENS;
|
||||
if (n28 < MAX22)
|
||||
{
|
||||
// This is a 22-bit hash of a result
|
||||
if (hash_if != NULL)
|
||||
{
|
||||
hash_if->hash22(n28, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
strcpy(result, "<...>");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Standard callsign
|
||||
uint32_t n = n28 - MAX22;
|
||||
|
||||
char callsign[7];
|
||||
callsign[6] = '\0';
|
||||
callsign[5] = charn(n % 27, FT8_CHAR_TABLE_LETTERS_SPACE);
|
||||
n /= 27;
|
||||
callsign[4] = charn(n % 27, FT8_CHAR_TABLE_LETTERS_SPACE);
|
||||
n /= 27;
|
||||
callsign[3] = charn(n % 27, FT8_CHAR_TABLE_LETTERS_SPACE);
|
||||
n /= 27;
|
||||
callsign[2] = charn(n % 10, FT8_CHAR_TABLE_NUMERIC);
|
||||
n /= 10;
|
||||
callsign[1] = charn(n % 36, FT8_CHAR_TABLE_ALPHANUM);
|
||||
n /= 36;
|
||||
callsign[0] = charn(n % 37, FT8_CHAR_TABLE_ALPHANUM_SPACE);
|
||||
|
||||
// Skip trailing and leading whitespace in case of a short callsign
|
||||
strcpy(result, trim(callsign));
|
||||
if (strlen(result) == 0)
|
||||
return -1;
|
||||
|
||||
// Check if we should append /R or /P suffix
|
||||
if (ip)
|
||||
{
|
||||
if (i3 == 1)
|
||||
{
|
||||
strcat(result, "/R");
|
||||
}
|
||||
else if (i3 == 2)
|
||||
{
|
||||
strcat(result, "/P");
|
||||
}
|
||||
}
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
static int unpack_type1(const uint8_t* a77, uint8_t i3, char* call_to, char* call_de, char* extra, const unpack_hash_interface_t* hash_if)
|
||||
{
|
||||
uint32_t n28a, n28b;
|
||||
uint16_t igrid4;
|
||||
uint8_t ir;
|
||||
|
||||
// Extract packed fields
|
||||
n28a = (a77[0] << 21);
|
||||
n28a |= (a77[1] << 13);
|
||||
n28a |= (a77[2] << 5);
|
||||
n28a |= (a77[3] >> 3);
|
||||
n28b = ((a77[3] & 0x07) << 26);
|
||||
n28b |= (a77[4] << 18);
|
||||
n28b |= (a77[5] << 10);
|
||||
n28b |= (a77[6] << 2);
|
||||
n28b |= (a77[7] >> 6);
|
||||
ir = ((a77[7] & 0x20) >> 5);
|
||||
igrid4 = ((a77[7] & 0x1F) << 10);
|
||||
igrid4 |= (a77[8] << 2);
|
||||
igrid4 |= (a77[9] >> 6);
|
||||
|
||||
// Unpack both callsigns
|
||||
if (unpack_callsign(n28a >> 1, n28a & 0x01, i3, call_to, hash_if) < 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (unpack_callsign(n28b >> 1, n28b & 0x01, i3, call_de, hash_if) < 0)
|
||||
{
|
||||
return -2;
|
||||
}
|
||||
// Fix "CQ_" to "CQ " -> already done in unpack_callsign()
|
||||
|
||||
// TODO: add to recent calls
|
||||
if ((call_to[0] != '<') && (strlen(call_to) >= 4) && (hash_if != NULL))
|
||||
{
|
||||
hash_if->save_hash(call_to);
|
||||
}
|
||||
if ((call_de[0] != '<') && (strlen(call_de) >= 4) && (hash_if != NULL))
|
||||
{
|
||||
hash_if->save_hash(call_de);
|
||||
}
|
||||
|
||||
char* dst = extra;
|
||||
|
||||
if (igrid4 <= MAXGRID4)
|
||||
{
|
||||
// Extract 4 symbol grid locator
|
||||
if (ir > 0)
|
||||
{
|
||||
// In case of ir=1 add an "R" before grid
|
||||
dst = stpcpy(dst, "R ");
|
||||
}
|
||||
|
||||
uint16_t n = igrid4;
|
||||
dst[4] = '\0';
|
||||
dst[3] = '0' + (n % 10);
|
||||
n /= 10;
|
||||
dst[2] = '0' + (n % 10);
|
||||
n /= 10;
|
||||
dst[1] = 'A' + (n % 18);
|
||||
n /= 18;
|
||||
dst[0] = 'A' + (n % 18);
|
||||
// if (ir > 0 && strncmp(call_to, "CQ", 2) == 0) return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Extract report
|
||||
int irpt = igrid4 - MAXGRID4;
|
||||
|
||||
// Check special cases first (irpt > 0 always)
|
||||
switch (irpt)
|
||||
{
|
||||
case 1:
|
||||
extra[0] = '\0';
|
||||
break;
|
||||
case 2:
|
||||
strcpy(dst, "RRR");
|
||||
break;
|
||||
case 3:
|
||||
strcpy(dst, "RR73");
|
||||
break;
|
||||
case 4:
|
||||
strcpy(dst, "73");
|
||||
break;
|
||||
default:
|
||||
// Extract signal report as a two digit number with a + or - sign
|
||||
if (ir > 0)
|
||||
{
|
||||
*dst++ = 'R'; // Add "R" before report
|
||||
}
|
||||
int_to_dd(dst, irpt - 35, 2, true);
|
||||
break;
|
||||
}
|
||||
// if (irpt >= 2 && strncmp(call_to, "CQ", 2) == 0) return -1;
|
||||
}
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
static int unpack_text(const uint8_t* a71, char* text)
|
||||
{
|
||||
uint8_t b71[9];
|
||||
|
||||
// Shift 71 bits right by 1 bit, so that it's right-aligned in the byte array
|
||||
uint8_t carry = 0;
|
||||
for (int i = 0; i < 9; ++i)
|
||||
{
|
||||
b71[i] = carry | (a71[i] >> 1);
|
||||
carry = (a71[i] & 1) ? 0x80 : 0;
|
||||
}
|
||||
|
||||
char c14[14];
|
||||
c14[13] = 0;
|
||||
for (int idx = 12; idx >= 0; --idx)
|
||||
{
|
||||
// Divide the long integer in b71 by 42
|
||||
uint16_t rem = 0;
|
||||
for (int i = 0; i < 9; ++i)
|
||||
{
|
||||
rem = (rem << 8) | b71[i];
|
||||
b71[i] = rem / 42;
|
||||
rem = rem % 42;
|
||||
}
|
||||
c14[idx] = charn(rem, FT8_CHAR_TABLE_FULL);
|
||||
}
|
||||
|
||||
strcpy(text, trim(c14));
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
static int unpack_telemetry(const uint8_t* a71, char* telemetry)
|
||||
{
|
||||
uint8_t b71[9];
|
||||
|
||||
// Shift bits in a71 right by 1 bit
|
||||
uint8_t carry = 0;
|
||||
for (int i = 0; i < 9; ++i)
|
||||
{
|
||||
b71[i] = (carry << 7) | (a71[i] >> 1);
|
||||
carry = (a71[i] & 0x01);
|
||||
}
|
||||
|
||||
// Convert b71 to hexadecimal string
|
||||
for (int i = 0; i < 9; ++i)
|
||||
{
|
||||
uint8_t nibble1 = (b71[i] >> 4);
|
||||
uint8_t nibble2 = (b71[i] & 0x0F);
|
||||
char c1 = (nibble1 > 9) ? (nibble1 - 10 + 'A') : nibble1 + '0';
|
||||
char c2 = (nibble2 > 9) ? (nibble2 - 10 + 'A') : nibble2 + '0';
|
||||
telemetry[i * 2] = c1;
|
||||
telemetry[i * 2 + 1] = c2;
|
||||
}
|
||||
|
||||
telemetry[18] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
// none standard for wsjt-x 2.0
|
||||
// by KD8CEC
|
||||
static int unpack_nonstandard(const uint8_t* a77, char* call_to, char* call_de, char* extra, const unpack_hash_interface_t* hash_if)
|
||||
{
|
||||
uint32_t n12, iflip, nrpt, icq;
|
||||
uint64_t n58;
|
||||
n12 = (a77[0] << 4); // 11 ~4 : 8
|
||||
n12 |= (a77[1] >> 4); // 3~0 : 12
|
||||
|
||||
n58 = ((uint64_t)(a77[1] & 0x0F) << 54); // 57 ~ 54 : 4
|
||||
n58 |= ((uint64_t)a77[2] << 46); // 53 ~ 46 : 12
|
||||
n58 |= ((uint64_t)a77[3] << 38); // 45 ~ 38 : 12
|
||||
n58 |= ((uint64_t)a77[4] << 30); // 37 ~ 30 : 12
|
||||
n58 |= ((uint64_t)a77[5] << 22); // 29 ~ 22 : 12
|
||||
n58 |= ((uint64_t)a77[6] << 14); // 21 ~ 14 : 12
|
||||
n58 |= ((uint64_t)a77[7] << 6); // 13 ~ 6 : 12
|
||||
n58 |= ((uint64_t)a77[8] >> 2); // 5 ~ 0 : 765432 10
|
||||
|
||||
iflip = (a77[8] >> 1) & 0x01; // 76543210
|
||||
nrpt = ((a77[8] & 0x01) << 1);
|
||||
nrpt |= (a77[9] >> 7); // 76543210
|
||||
icq = ((a77[9] >> 6) & 0x01);
|
||||
|
||||
char c11[12];
|
||||
c11[11] = '\0';
|
||||
|
||||
for (int i = 10; /* no condition */; --i)
|
||||
{
|
||||
c11[i] = charn(n58 % 38, FT8_CHAR_TABLE_ALPHANUM_SPACE_SLASH);
|
||||
if (i == 0)
|
||||
break;
|
||||
n58 /= 38;
|
||||
}
|
||||
|
||||
char call_3[15];
|
||||
if (hash_if != NULL)
|
||||
{
|
||||
hash_if->hash12(n12, call_3);
|
||||
}
|
||||
else
|
||||
{
|
||||
strcpy(call_3, "<...>");
|
||||
}
|
||||
|
||||
char* call_1 = trim((iflip) ? c11 : call_3);
|
||||
char* call_2 = trim((iflip) ? call_3 : c11);
|
||||
if (hash_if != NULL)
|
||||
{
|
||||
hash_if->save_hash(c11);
|
||||
}
|
||||
|
||||
if (icq == 0)
|
||||
{
|
||||
strcpy(call_to, call_1);
|
||||
if (nrpt == 1)
|
||||
strcpy(extra, "RRR");
|
||||
else if (nrpt == 2)
|
||||
strcpy(extra, "RR73");
|
||||
else if (nrpt == 3)
|
||||
strcpy(extra, "73");
|
||||
else
|
||||
{
|
||||
extra[0] = '\0';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
strcpy(call_to, "CQ");
|
||||
extra[0] = '\0';
|
||||
}
|
||||
strcpy(call_de, call_2);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int unpack77_fields(const uint8_t* a77, char* call_to, char* call_de, char* extra, const unpack_hash_interface_t* hash_if)
|
||||
{
|
||||
call_to[0] = call_de[0] = extra[0] = '\0';
|
||||
|
||||
// Extract i3 (bits 74..76)
|
||||
uint8_t i3 = (a77[9] >> 3) & 0x07;
|
||||
|
||||
if (i3 == 0)
|
||||
{
|
||||
// Extract n3 (bits 71..73)
|
||||
uint8_t n3 = ((a77[8] << 2) & 0x04) | ((a77[9] >> 6) & 0x03);
|
||||
|
||||
if (n3 == 0)
|
||||
{
|
||||
// 0.0 Free text
|
||||
return unpack_text(a77, extra);
|
||||
}
|
||||
// else if (i3 == 0 && n3 == 1) {
|
||||
// // 0.1 K1ABC RR73; W9XYZ <KH1/KH7Z> -11 28 28 10 5 71 DXpedition Mode
|
||||
// }
|
||||
// else if (i3 == 0 && n3 == 2) {
|
||||
// // 0.2 PA3XYZ/P R 590003 IO91NP 28 1 1 3 12 25 70 EU VHF contest
|
||||
// }
|
||||
// else if (i3 == 0 && (n3 == 3 || n3 == 4)) {
|
||||
// // 0.3 WA9XYZ KA1ABC R 16A EMA 28 28 1 4 3 7 71 ARRL Field Day
|
||||
// // 0.4 WA9XYZ KA1ABC R 32A EMA 28 28 1 4 3 7 71 ARRL Field Day
|
||||
// }
|
||||
else if (n3 == 5)
|
||||
{
|
||||
// 0.5 0123456789abcdef01 71 71 Telemetry (18 hex)
|
||||
return unpack_telemetry(a77, extra);
|
||||
}
|
||||
}
|
||||
else if (i3 == 1 || i3 == 2)
|
||||
{
|
||||
// Type 1 (standard message) or Type 2 ("/P" form for EU VHF contest)
|
||||
return unpack_type1(a77, i3, call_to, call_de, extra, hash_if);
|
||||
}
|
||||
// else if (i3 == 3) {
|
||||
// // Type 3: ARRL RTTY Contest
|
||||
// }
|
||||
else if (i3 == 4)
|
||||
{
|
||||
// Type 4: Nonstandard calls, e.g. <WA9XYZ> PJ4/KA1ABC RR73
|
||||
// One hashed call or "CQ"; one compound or nonstandard call with up
|
||||
// to 11 characters; and (if not "CQ") an optional RRR, RR73, or 73.
|
||||
return unpack_nonstandard(a77, call_to, call_de, extra, hash_if);
|
||||
}
|
||||
// else if (i3 == 5) {
|
||||
// // Type 5: TU; W9XYZ K1ABC R-09 FN 1 28 28 1 7 9 74 WWROF contest
|
||||
// }
|
||||
|
||||
// unknown type, should never get here
|
||||
return -1;
|
||||
}
|
||||
|
||||
int unpack77(const uint8_t* a77, char* message, const unpack_hash_interface_t* hash_if)
|
||||
{
|
||||
char call_to[14];
|
||||
char call_de[14];
|
||||
char extra[19];
|
||||
|
||||
int rc = unpack77_fields(a77, call_to, call_de, extra, hash_if);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
// int msg_sz = strlen(call_to) + strlen(call_de) + strlen(extra) + 2;
|
||||
char* dst = message;
|
||||
|
||||
dst[0] = '\0';
|
||||
|
||||
if (call_to[0] != '\0')
|
||||
{
|
||||
dst = stpcpy(dst, call_to);
|
||||
*dst++ = ' ';
|
||||
}
|
||||
|
||||
if (call_de[0] != '\0')
|
||||
{
|
||||
dst = stpcpy(dst, call_de);
|
||||
*dst++ = ' ';
|
||||
}
|
||||
|
||||
dst = stpcpy(dst, extra);
|
||||
*dst = '\0';
|
||||
|
||||
return 0;
|
||||
}
|
38
ft8/unpack.h
38
ft8/unpack.h
|
@ -1,38 +0,0 @@
|
|||
#ifndef _INCLUDE_UNPACK_H_
|
||||
#define _INCLUDE_UNPACK_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct
|
||||
{
|
||||
/// Called when a callsign is looked up by its 22 bit hash code
|
||||
void (*hash22)(uint32_t n22, char* callsign);
|
||||
/// Called when a callsign is looked up by its 12 bit hash code
|
||||
void (*hash12)(uint32_t n12, char* callsign);
|
||||
/// Called when a callsign should hashed and stored (both by its 22 and 12 bit hash code)
|
||||
void (*save_hash)(const char* callsign);
|
||||
} unpack_hash_interface_t;
|
||||
|
||||
/// Unpack a 77 bit message payload into three fields (typically call_to, call_de and grid/report/other)
|
||||
/// @param[in] a77 message payload in binary form (77 bits, MSB first)
|
||||
/// @param[out] field1 at least 14 bytes (typically call_to)
|
||||
/// @param[out] field2 at least 14 bytes (typically call_de)
|
||||
/// @param[out] field3 at least 7 bytes (typically grid/report/other)
|
||||
/// @param[in] hash_if hashing interface (can be NULL)
|
||||
int unpack77_fields(const uint8_t* a77, char* field1, char* field2, char* field3, const unpack_hash_interface_t* hash_if);
|
||||
|
||||
/// Unpack a 77 bit message payload into text message
|
||||
/// @param[in] a77 message payload in binary form (77 bits, MSB first)
|
||||
/// @param[out] message should have at least 35 bytes allocated (34 characters + zero terminator)
|
||||
/// @param[in] hash_if hashing interface (can be NULL)
|
||||
int unpack77(const uint8_t* a77, char* message, const unpack_hash_interface_t* hash_if);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // _INCLUDE_UNPACK_H_
|
|
@ -4,10 +4,7 @@
|
|||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "ft8/debug.h"
|
||||
|
||||
#include "ft8/text.h"
|
||||
#include "ft8/pack.h"
|
||||
#include "ft8/encode.h"
|
||||
#include "ft8/constants.h"
|
||||
|
||||
|
@ -16,6 +13,7 @@
|
|||
#include "ft8/message.h"
|
||||
|
||||
#define LOG_LEVEL LOG_INFO
|
||||
#include "ft8/debug.h"
|
||||
|
||||
// void convert_8bit_to_6bit(uint8_t* dst, const uint8_t* src, int nBits)
|
||||
// {
|
||||
|
@ -151,7 +149,7 @@ void hashtable_add(const char* callsign, uint32_t hash)
|
|||
callsign_hashtable[idx_hash].hash = hash;
|
||||
}
|
||||
|
||||
bool hashtable_lookup(ftx_callsign_hash_type_e hash_type, uint32_t hash, char* callsign)
|
||||
bool hashtable_lookup(ftx_callsign_hash_type_t hash_type, uint32_t hash, char* callsign)
|
||||
{
|
||||
uint32_t hash_mask = (hash_type == FTX_CALLSIGN_HASH_10_BITS) ? 0x3FFu : (hash_type == FTX_CALLSIGN_HASH_12_BITS ? 0xFFFu : 0x3FFFFFu);
|
||||
int idx_hash = (hash * 23) % CALLSIGN_HASHTABLE_SIZE;
|
||||
|
|
Ładowanie…
Reference in New Issue