Added FT4 encoding

pull/12/merge
Karlis Goba 2021-08-16 20:02:52 +03:00
rodzic 456eefede4
commit 78c3e25a08
10 zmienionych plików z 298 dodań i 139 usunięć

Wyświetl plik

@ -17,8 +17,8 @@
#define LOG_LEVEL LOG_INFO
const int kMin_score = 10; // Minimum sync score threshold for candidates
const int kMax_candidates = 150;
const int kMin_score = 12; // Minimum sync score threshold for candidates
const int kMax_candidates = 120;
const int kLDPC_iterations = 25;
const int kMax_decoded_messages = 50;
@ -163,7 +163,7 @@ void normalize_signal(float *signal, int num_samples)
void print_tones(const uint8_t *code_map, const float *log174)
{
for (int k = 0; k < FT8_N; k += 3)
for (int k = 0; k < FT8_LDPC_N; k += 3)
{
uint8_t max = 0;
if (log174[k + 0] > 0)

Wyświetl plik

@ -1,13 +1,31 @@
#include "constants.h"
// Costas 7x7 tone pattern
// Costas sync tone pattern
const uint8_t kFT8_Costas_pattern[7] = {3, 1, 4, 0, 6, 5, 2};
const uint8_t kFT4_Costas_pattern[4][4] = {{0, 1, 3, 2},
{1, 0, 2, 3},
{2, 3, 1, 0},
{3, 2, 0, 1}};
// Gray code map
const uint8_t kFT8_Gray_map[8] = {0, 1, 3, 2, 5, 6, 4, 7};
const uint8_t kFT4_Gray_map[4] = {0, 1, 3, 2};
const uint8_t kFT4_XOR_sequence[10] = {
0x4Au, // 01001010
0x5Eu, // 01011110
0x89u, // 10001001
0xB4u, // 10110100
0xB0u, // 10110000
0x8Au, // 10001010
0x79u, // 01111001
0x55u, // 01010101
0xBEu, // 10111110
0x28u, // 00101 [000]
};
// Parity generator matrix for (174,91) LDPC code, stored in bitpacked format (MSB first)
const uint8_t kFT8_LDPC_generator[FT8_M][FT8_K_BYTES] = {
const uint8_t kFT8_LDPC_generator[FT8_LDPC_M][FT8_LDPC_K_BYTES] = {
{0x83, 0x29, 0xce, 0x11, 0xbf, 0x31, 0xea, 0xf5, 0x09, 0xf2, 0x7f, 0xc0},
{0x76, 0x1c, 0x26, 0x4e, 0x25, 0xc2, 0x59, 0x33, 0x54, 0x93, 0x13, 0x20},
{0xdc, 0x26, 0x59, 0x02, 0xfb, 0x27, 0x7c, 0x64, 0x10, 0xa1, 0xbd, 0xc0},
@ -95,7 +113,7 @@ const uint8_t kFT8_LDPC_generator[FT8_M][FT8_K_BYTES] = {
// Each row describes one LDPC parity check.
// Each number is an index into the codeword (1-origin).
// The codeword bits mentioned in each row must XOR to zero.
const uint8_t kFT8_LDPC_Nm[FT8_M][7] = {
const uint8_t kFT8_LDPC_Nm[FT8_LDPC_M][7] = {
{4, 31, 59, 91, 92, 96, 153},
{5, 32, 60, 93, 115, 146, 0},
{6, 24, 61, 94, 122, 151, 0},
@ -183,7 +201,7 @@ const uint8_t kFT8_LDPC_Nm[FT8_M][7] = {
// Each row corresponds to a codeword bit.
// The numbers indicate which three LDPC parity checks (rows in Nm) refer to the codeword bit.
// 1-origin.
const uint8_t kFT8_LDPC_Mn[FT8_N][3] = {
const uint8_t kFT8_LDPC_Mn[FT8_LDPC_N][3] = {
{16, 45, 73},
{25, 51, 62},
{33, 58, 78},
@ -359,7 +377,7 @@ const uint8_t kFT8_LDPC_Mn[FT8_N][3] = {
{20, 44, 48},
{42, 49, 57}};
const uint8_t kFT8_LDPC_num_rows[FT8_M] = {
const uint8_t kFT8_LDPC_num_rows[FT8_LDPC_M] = {
7, 6, 6, 6, 7, 6, 7, 6, 6, 7, 6, 6, 7, 7, 6, 6,
6, 7, 6, 7, 6, 7, 6, 6, 6, 7, 6, 6, 6, 7, 6, 6,
6, 6, 7, 6, 6, 6, 7, 7, 6, 6, 6, 6, 7, 7, 6, 6,

Wyświetl plik

@ -4,16 +4,22 @@
#include <stdint.h>
// Define FT8 symbol counts
#define FT8_ND (58) ///< Data symbols
#define FT8_NS (21) ///< Sync symbols (3 @ Costas 7x7)
#define FT8_NN (FT8_NS + FT8_ND) ///< Total channel symbols (79)
#define FT8_ND (58) ///< Data symbols
#define FT8_NS (21) ///< Sync symbols (3 @ Costas 7x7)
#define FT8_NN (79) ///< Total channel symbols (FT8_NS + FT8_ND)
// Define FT4 symbol counts
#define FT4_ND (87) ///< Data symbols
#define FT4_NS (16) ///< Sync symbols (3 @ Costas 7x7)
#define FT4_NR (2) ///< Ramp symbols (beginning + end)
#define FT4_NN (105) ///< Total channel symbols (FT4_NS + FT4_ND + FT4_NR)
// Define LDPC parameters
#define FT8_N (174) ///< Number of bits in the encoded message (payload + checksum)
#define FT8_K (91) ///< Number of payload bits
#define FT8_M (FT8_N - FT8_K) ///< Number of LDPC checksum bits
#define FT8_N_BYTES ((FT8_N + 7) / 8) ///< Number of whole bytes needed to store N bits (full message)
#define FT8_K_BYTES ((FT8_K + 7) / 8) ///< Number of whole bytes needed to store K bits (payload only)
#define FT8_LDPC_N (174) ///< Number of bits in the encoded message (payload with LDPC checksum bits)
#define FT8_LDPC_K (91) ///< Number of payload bits (including CRC)
#define FT8_LDPC_M (83) ///< Number of LDPC checksum bits (FT8_LDPC_N - FT8_LDPC_K)
#define FT8_LDPC_N_BYTES ((FT8_LDPC_N + 7) / 8) ///< Number of whole bytes needed to store 174 bits (full message)
#define FT8_LDPC_K_BYTES ((FT8_LDPC_K + 7) / 8) ///< Number of whole bytes needed to store 91 bits (payload + CRC only)
// Define CRC parameters
#define FT8_CRC_POLYNOMIAL ((uint16_t)0x2757u) ///< CRC-14 polynomial without the leading (MSB) 1
@ -21,30 +27,34 @@
/// Costas 7x7 tone pattern for synchronization
extern const uint8_t kFT8_Costas_pattern[7];
extern const uint8_t kFT4_Costas_pattern[4][4];
/// Gray code map to encode 8 symbols (tones)
extern const uint8_t kFT8_Gray_map[8];
extern const uint8_t kFT4_Gray_map[4];
extern const uint8_t kFT4_XOR_sequence[10];
/// Parity generator matrix for (174,91) LDPC code, stored in bitpacked format (MSB first)
extern const uint8_t kFT8_LDPC_generator[FT8_M][FT8_K_BYTES];
extern const uint8_t kFT8_LDPC_generator[FT8_LDPC_M][FT8_LDPC_K_BYTES];
/// Column order (permutation) in which the bits in codeword are stored
/// (Not really used in FT8 v2 - instead the Nm, Mn and generator matrices are already permuted)
extern const uint8_t kFT8_LDPC_column_order[FT8_N];
extern const uint8_t kFT8_LDPC_column_order[FT8_LDPC_N];
/// LDPC(174,91) parity check matrix, containing 83 rows,
/// each row describes one parity check,
/// each number is an index into the codeword (1-origin).
/// The codeword bits mentioned in each row must xor to zero.
/// From WSJT-X's ldpc_174_91_c_reordered_parity.f90.
extern const uint8_t kFT8_LDPC_Nm[FT8_M][7];
extern const uint8_t kFT8_LDPC_Nm[FT8_LDPC_M][7];
/// Mn from WSJT-X's bpdecode174.f90. Each row corresponds to a codeword bit.
/// The numbers indicate which three parity checks (rows in Nm) refer to the codeword bit.
/// The numbers use 1 as the origin (first entry).
extern const uint8_t kFT8_LDPC_Mn[FT8_N][3];
extern const uint8_t kFT8_LDPC_Mn[FT8_LDPC_N][3];
/// Number of rows (columns in C/C++) in the array Nm.
extern const uint8_t kFT8_LDPC_num_rows[FT8_M];
extern const uint8_t kFT8_LDPC_num_rows[FT8_LDPC_M];
#endif // _INCLUDE_CONSTANTS_H_

Wyświetl plik

@ -36,15 +36,24 @@ uint16_t ft8_crc(const uint8_t message[], int num_bits)
return remainder & ((TOPBIT << 1) - 1u);
}
uint16_t extract_crc(uint8_t a91[])
uint16_t extract_crc(const uint8_t a91[])
{
uint16_t chksum = ((a91[9] & 0x07) << 11) | (a91[10] << 3) | (a91[11] >> 5);
return chksum;
}
void add_crc(uint8_t a91[])
void add_crc(const uint8_t payload[], uint8_t a91[])
{
// Calculate CRC of 12 bytes = 96 bits, see WSJT-X code
// Copy 77 bits of payload data
for (int i = 0; i < 10; i++)
a91[i] = payload[i];
// Clear 3 bits after the payload to make 82 bits
a91[9] &= 0xF8u;
a91[10] = 0;
// Calculate CRC of 82 bits (77 + 5 zeros)
// 'The CRC is calculated on the source-encoded message, zero-extended from 77 to 82 bits'
uint16_t checksum = ft8_crc(a91, 96 - 14);
// Store the CRC at the end of 77 bit message

Wyświetl plik

@ -9,10 +9,14 @@
// [IN] num_bits - number of bits in the sequence
uint16_t ft8_crc(const uint8_t message[], int num_bits);
/// Check the FT8 CRC of a packed message (during decoding)
uint16_t extract_crc(uint8_t a91[]);
/// 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[]);
/// Add the FT8 CRC to a packed message (during encoding)
void add_crc(uint8_t a91[]);
/// @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[]);
#endif // _INCLUDE_CRC_H_

Wyświetl plik

@ -38,7 +38,7 @@ int find_sync(const waterfall_t *power, int num_candidates, candidate_t heap[],
{
for (int freq_sub = 0; freq_sub < power->freq_osr; ++freq_sub)
{
for (int time_offset = -8; time_offset < power->num_blocks - FT8_NN + 8; ++time_offset)
for (int time_offset = -12; time_offset < power->num_blocks - FT8_NN + 12; ++time_offset)
{
for (int freq_offset = 0; freq_offset + 8 < power->num_bins; ++freq_offset)
{
@ -145,26 +145,36 @@ void extract_likelihood(const waterfall_t *power, const candidate_t *cand, float
int sym_idx = (k < FT8_ND / 2) ? (k + 7) : (k + 14);
int bit_idx = 3 * k;
// Pointer to 8 bins of the current symbol
const uint8_t *ps = power->mag + offset + (sym_idx * sym_stride);
int block = cand->time_offset + sym_idx;
decode_symbol(ps, bit_idx, log174);
// Check for time boundaries
if ((block < 0) || (block >= power->num_blocks))
{
log174[bit_idx] = 0;
}
else
{
// Pointer to 8 bins of the current symbol
const uint8_t *ps = power->mag + offset + (sym_idx * sym_stride);
decode_symbol(ps, bit_idx, log174);
}
}
// Compute the variance of log174
float sum = 0;
float sum2 = 0;
for (int i = 0; i < FT8_N; ++i)
for (int i = 0; i < FT8_LDPC_N; ++i)
{
sum += log174[i];
sum2 += log174[i] * log174[i];
}
float inv_n = 1.0f / FT8_N;
float inv_n = 1.0f / FT8_LDPC_N;
float variance = (sum2 - (sum * sum * inv_n)) * inv_n;
// Normalize log174 distribution and scale it with experimentally found coefficient
float norm_factor = sqrtf(32.0f / variance);
for (int i = 0; i < FT8_N; ++i)
for (int i = 0; i < FT8_LDPC_N; ++i)
{
log174[i] *= norm_factor;
}
@ -172,10 +182,10 @@ 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)
{
float log174[FT8_N]; // message bits encoded as likelihood
float log174[FT8_LDPC_N]; // message bits encoded as likelihood
extract_likelihood(power, cand, log174);
uint8_t plain174[FT8_N]; // message bits (0/1)
uint8_t plain174[FT8_LDPC_N]; // message bits (0/1)
bp_decode(log174, max_iterations, plain174, &status->ldpc_errors);
// ldpc_decode(log174, max_iterations, plain174, &status->ldpc_errors);
@ -184,9 +194,9 @@ bool decode(const waterfall_t *power, const candidate_t *cand, message_t *messag
return false;
}
// Extract payload + CRC (first FT8_K bits) packed into a byte array
uint8_t a91[FT8_K_BYTES];
pack_bits(plain174, FT8_K, a91);
// 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);
// Extract CRC and check it
status->crc_extracted = extract_crc(a91);
@ -321,7 +331,7 @@ static void decode_multi_symbols(const uint8_t *power, int num_bins, int n_syms,
// 8 FSK tones = 3 bits
for (int i = 0; i < n_bits; ++i)
{
if (bit_idx + i >= FT8_N)
if (bit_idx + i >= FT8_LDPC_N)
{
// Respect array size
break;

Wyświetl plik

@ -24,23 +24,23 @@ 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
// Fill the codeword with message and zeros, as we will only update binary ones later
for (int j = 0; j < FT8_N_BYTES; ++j)
for (int j = 0; j < FT8_LDPC_N_BYTES; ++j)
{
codeword[j] = (j < FT8_K_BYTES) ? message[j] : 0;
codeword[j] = (j < FT8_LDPC_K_BYTES) ? message[j] : 0;
}
// Compute the byte index and bit mask for the first checksum bit
uint8_t col_mask = (0x80u >> (FT8_K % 8u)); // bitmask of current byte
uint8_t col_idx = FT8_K_BYTES - 1; // index into byte array
uint8_t col_mask = (0x80u >> (FT8_LDPC_K % 8u)); // bitmask of current byte
uint8_t col_idx = FT8_LDPC_K_BYTES - 1; // index into byte array
// Compute the LDPC checksum bits and store them in codeword
for (int i = 0; i < FT8_M; ++i)
for (int i = 0; i < FT8_LDPC_M; ++i)
{
// Fast implementation of bitwise multiplication and parity checking
// Normally nsum would contain the result of dot product between message and kFT8_LDPC_generator[i],
// but we only compute the sum modulo 2.
uint8_t nsum = 0;
for (int j = 0; j < FT8_K_BYTES; ++j)
for (int j = 0; j < FT8_LDPC_K_BYTES; ++j)
{
uint8_t bits = message[j] & kFT8_LDPC_generator[i][j]; // bitwise AND (bitwise multiplication)
nsum ^= parity8(bits); // bitwise XOR (addition modulo 2)
@ -62,74 +62,126 @@ void encode174(const uint8_t *message, uint8_t *codeword)
}
}
// Generate FT8 tone sequence from payload data
// [IN] payload - 10 byte array consisting of 77 bit payload (MSB first)
// [OUT] itone - array of NN (79) bytes to store the generated tones (encoded as 0..7)
void genft8(const uint8_t *payload, uint8_t *itone)
void genft8(const uint8_t *payload, uint8_t *tones)
{
uint8_t a91[12]; // Store 77 bits of payload + 14 bits CRC
// Copy 77 bits of payload data
for (int i = 0; i < 10; i++)
a91[i] = payload[i];
// Clear 3 bits after the payload to make 80 bits
a91[9] &= 0xF8u;
a91[10] = 0;
a91[11] = 0;
// Compute and add CRC at the end of the message
// a91 contains 77 bits of payload + 14 bits of CRC
add_crc(a91);
add_crc(payload, a91);
uint8_t codeword[22];
encode174(a91, codeword);
// Message structure: S7 D29 S7 D29 S7
for (int i = 0; i < 7; ++i)
// Total symbols: 79 (FT8_NN)
uint8_t mask = 0x80u; // Mask to extract 1 bit from codeword
int i_byte = 0; // Index of the current byte of the codeword
for (int i_tone = 0; i_tone < FT8_NN; ++i_tone)
{
itone[i] = kFT8_Costas_pattern[i];
itone[36 + i] = kFT8_Costas_pattern[i];
itone[72 + i] = kFT8_Costas_pattern[i];
}
int k = 7; // Skip over the first set of Costas symbols
uint8_t mask = 0x80u;
int i_byte = 0;
for (int j = 0; j < FT8_ND; ++j)
{
if (j == 29)
if ((i_tone >= 0) && (i_tone < 7))
{
k += 7; // Skip over the second set of Costas symbols
tones[i_tone] = kFT8_Costas_pattern[i_tone];
}
// Extract 3 bits from codeword at i-th position
uint8_t bits3 = 0;
if (codeword[i_byte] & mask)
bits3 |= 4;
if (0 == (mask >>= 1))
else if ((i_tone >= 36) && (i_tone < 43))
{
mask = 0x80;
i_byte++;
tones[i_tone] = kFT8_Costas_pattern[i_tone - 36];
}
if (codeword[i_byte] & mask)
bits3 |= 2;
if (0 == (mask >>= 1))
else if ((i_tone >= 72) && (i_tone < 79))
{
mask = 0x80;
i_byte++;
tones[i_tone] = kFT8_Costas_pattern[i_tone - 72];
}
if (codeword[i_byte] & mask)
bits3 |= 1;
if (0 == (mask >>= 1))
else
{
mask = 0x80;
i_byte++;
}
// Extract 3 bits from codeword at i-th position
uint8_t bits3 = 0;
itone[k] = kFT8_Gray_map[bits3];
++k;
if (codeword[i_byte] & mask)
bits3 |= 4;
if (0 == (mask >>= 1))
{
mask = 0x80u;
i_byte++;
}
if (codeword[i_byte] & mask)
bits3 |= 2;
if (0 == (mask >>= 1))
{
mask = 0x80u;
i_byte++;
}
if (codeword[i_byte] & mask)
bits3 |= 1;
if (0 == (mask >>= 1))
{
mask = 0x80u;
i_byte++;
}
tones[i_tone] = kFT8_Gray_map[bits3];
}
}
}
void 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);
uint8_t codeword[22];
encode174(a91, codeword); // 91 bits -> 174 bits
// Message structure: R S4_1 D29 S4_2 D29 S4_3 D29 S4_4 R
// Total symbols: 105 (FT4_NN)
uint8_t mask = 0x80u; // Mask to extract 1 bit from codeword
int i_byte = 0; // Index of the current byte of the codeword
for (int i_tone = 0; i_tone < FT4_NN; ++i_tone)
{
if ((i_tone == 0) || (i_tone == 104))
{
tones[i_tone] = 0; // R (ramp) symbol
}
else if ((i_tone >= 1) && (i_tone < 5))
{
tones[i_tone] = kFT4_Costas_pattern[0][i_tone - 1];
}
else if ((i_tone >= 34) && (i_tone < 38))
{
tones[i_tone] = kFT4_Costas_pattern[1][i_tone - 34];
}
else if ((i_tone >= 67) && (i_tone < 71))
{
tones[i_tone] = kFT4_Costas_pattern[2][i_tone - 67];
}
else if ((i_tone >= 100) && (i_tone < 104))
{
tones[i_tone] = kFT4_Costas_pattern[3][i_tone - 100];
}
else
{
// Extract 2 bits from codeword at i-th position
uint8_t bits2 = 0;
if (codeword[i_byte] & mask)
bits2 |= 2;
if (0 == (mask >>= 1))
{
mask = 0x80u;
i_byte++;
}
if (codeword[i_byte] & mask)
bits2 |= 1;
if (0 == (mask >>= 1))
{
mask = 0x80u;
i_byte++;
}
tones[i_tone] = kFT4_Gray_map[bits2];
}
}
}

Wyświetl plik

@ -3,9 +3,30 @@
#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] itone - array of NN (79) bytes to store the generated tones (encoded as 0..7)
void genft8(const uint8_t *payload, uint8_t *itone);
/// @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_

Wyświetl plik

@ -54,13 +54,13 @@ void pack_bits(const uint8_t plain[], int num_bits, uint8_t packed[])
// ok == 87 means success.
void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok)
{
float m[FT8_M][FT8_N]; // ~60 kB
float e[FT8_M][FT8_N]; // ~60 kB
int min_errors = FT8_M;
float m[FT8_LDPC_M][FT8_LDPC_N]; // ~60 kB
float e[FT8_LDPC_M][FT8_LDPC_N]; // ~60 kB
int min_errors = FT8_LDPC_M;
for (int j = 0; j < FT8_M; j++)
for (int j = 0; j < FT8_LDPC_M; j++)
{
for (int i = 0; i < FT8_N; i++)
for (int i = 0; i < FT8_LDPC_N; i++)
{
m[j][i] = codeword[i];
e[j][i] = 0.0f;
@ -69,7 +69,7 @@ void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok)
for (int iter = 0; iter < max_iters; iter++)
{
for (int j = 0; j < FT8_M; j++)
for (int j = 0; j < FT8_LDPC_M; j++)
{
for (int ii1 = 0; ii1 < kFT8_LDPC_num_rows[j]; ii1++)
{
@ -87,7 +87,7 @@ void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok)
}
}
for (int i = 0; i < FT8_N; i++)
for (int i = 0; i < FT8_LDPC_N; i++)
{
float l = codeword[i];
for (int j = 0; j < 3; j++)
@ -108,7 +108,7 @@ void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int *ok)
}
}
for (int i = 0; i < FT8_N; i++)
for (int i = 0; i < FT8_LDPC_N; i++)
{
for (int ji1 = 0; ji1 < 3; ji1++)
{
@ -139,7 +139,7 @@ static int ldpc_check(uint8_t codeword[])
{
int errors = 0;
for (int m = 0; m < FT8_M; ++m)
for (int m = 0; m < FT8_LDPC_M; ++m)
{
uint8_t x = 0;
for (int i = 0; i < kFT8_LDPC_num_rows[m]; ++i)
@ -156,13 +156,13 @@ static int ldpc_check(uint8_t codeword[])
void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok)
{
float tov[FT8_N][3];
float toc[FT8_M][7];
float tov[FT8_LDPC_N][3];
float toc[FT8_LDPC_M][7];
int min_errors = FT8_M;
int min_errors = FT8_LDPC_M;
// initialize message data
for (int n = 0; n < FT8_N; ++n)
for (int n = 0; n < FT8_LDPC_N; ++n)
{
tov[n][0] = tov[n][1] = tov[n][2] = 0;
}
@ -171,7 +171,7 @@ void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok)
{
// Do a hard decision guess (tov=0 in iter 0)
int plain_sum = 0;
for (int n = 0; n < FT8_N; ++n)
for (int n = 0; n < FT8_LDPC_N; ++n)
{
plain[n] = ((codeword[n] + tov[n][0] + tov[n][1] + tov[n][2]) > 0) ? 1 : 0;
plain_sum += plain[n];
@ -198,7 +198,7 @@ void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok)
}
// Send messages from bits to check nodes
for (int m = 0; m < FT8_M; ++m)
for (int m = 0; m < FT8_LDPC_M; ++m)
{
for (int n_idx = 0; n_idx < kFT8_LDPC_num_rows[m]; ++n_idx)
{
@ -217,7 +217,7 @@ void bp_decode(float codeword[], int max_iters, uint8_t plain[], int *ok)
}
// send messages from check nodes to variable nodes
for (int n = 0; n < FT8_N; ++n)
for (int n = 0; n < FT8_LDPC_N; ++n)
{
for (int m_idx = 0; m_idx < 3; ++m_idx)
{

Wyświetl plik

@ -12,6 +12,16 @@
#define LOG_LEVEL LOG_INFO
#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.
@ -19,34 +29,36 @@
/// @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 b, float *pulse)
void gfsk_pulse(int n_spsym, float symbol_bt, float *pulse)
{
float c = M_PI * sqrtf(2 / logf(2));
for (int i = 0; i < 3 * n_spsym; ++i)
{
float t = i / (float)n_spsym - 1.5f;
pulse[i] = (erff(c * b * (t + 0.5f)) - erff(c * b * (t - 0.5f))) / 2;
float arg1 = GFSK_CONST_K * symbol_bt * (t + 0.5f);
float arg2 = GFSK_CONST_K * symbol_bt * (t - 0.5f);
pulse[i] = (erff(arg1) - erff(arg2)) / 2;
}
}
/// Synthesize waveform data using GFSK phase shaping.
/// The output waveform will contain n_sym+2 symbols (extra symbol at the beginning/end).
/// 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 symbols array
/// @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] n_spsym Number of samples per symbol (only integer number of samples supported)
/// @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_spsym*(n_sym+2) samples)
/// @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, int n_spsym, int signal_rate, float *signal)
void synth_gfsk(const uint8_t *symbols, int n_sym, float f0, float symbol_bt, float symbol_rate, int signal_rate, float *signal)
{
LOG(LOG_DEBUG, "n_spsym = %d\n", n_spsym);
int n_wave = n_sym * n_spsym; // Number of output samples
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)*nsps samples, first and last symbols extended
// 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];
@ -57,7 +69,7 @@ void synth_gfsk(const uint8_t *symbols, int n_sym, float f0, int n_spsym, int si
}
float pulse[3 * n_spsym];
gfsk_pulse(n_spsym, 2.0f, pulse);
gfsk_pulse(n_spsym, symbol_bt, pulse);
for (int i = 0; i < n_sym; ++i)
{
@ -119,9 +131,10 @@ int main(int argc, char **argv)
{
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_K_BYTES];
uint8_t packed[FT8_LDPC_K_BYTES];
int rc = pack77(message, packed);
if (rc < 0)
{
@ -137,30 +150,52 @@ int main(int argc, char **argv)
}
printf("\n");
if (is_ft4)
{
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[FT8_NN]; // Array of 79 tones (symbols)
genft8(packed, 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 < FT8_NN; ++j)
for (int j = 0; j < num_tones; ++j)
{
printf("%d", tones[j]);
}
printf("\n");
// Third, convert the FSK tones into an audio signal
const int sample_rate = 12000;
const float symbol_rate = 6.25f;
const int num_samples = (int)(0.5f + FT8_NN / symbol_rate * sample_rate);
const int num_silence = (15 * sample_rate - num_samples) / 2;
float signal[num_silence + num_samples + num_silence];
for (int i = 0; i < num_silence + num_samples + num_silence; i++)
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;
}
synth_gfsk(tones, FT8_NN, frequency, sample_rate / symbol_rate, sample_rate, signal + num_silence);
save_wav(signal, num_silence + num_samples + num_silence, sample_rate, wav_path);
// 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;
}