[feat](trx-rs): add ft8 decoder
Co-authored-by: Codex <codex@openai.com> Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
This commit is contained in:
Vendored
+392
@@ -0,0 +1,392 @@
|
||||
#include "constants.h"
|
||||
|
||||
// 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 (FTx bits -> channel symbols)
|
||||
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 kFTX_LDPC_generator[FTX_LDPC_M][FTX_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 },
|
||||
{ 0x1b, 0x3f, 0x41, 0x78, 0x58, 0xcd, 0x2d, 0xd3, 0x3e, 0xc7, 0xf6, 0x20 },
|
||||
{ 0x09, 0xfd, 0xa4, 0xfe, 0xe0, 0x41, 0x95, 0xfd, 0x03, 0x47, 0x83, 0xa0 },
|
||||
{ 0x07, 0x7c, 0xcc, 0xc1, 0x1b, 0x88, 0x73, 0xed, 0x5c, 0x3d, 0x48, 0xa0 },
|
||||
{ 0x29, 0xb6, 0x2a, 0xfe, 0x3c, 0xa0, 0x36, 0xf4, 0xfe, 0x1a, 0x9d, 0xa0 },
|
||||
{ 0x60, 0x54, 0xfa, 0xf5, 0xf3, 0x5d, 0x96, 0xd3, 0xb0, 0xc8, 0xc3, 0xe0 },
|
||||
{ 0xe2, 0x07, 0x98, 0xe4, 0x31, 0x0e, 0xed, 0x27, 0x88, 0x4a, 0xe9, 0x00 },
|
||||
{ 0x77, 0x5c, 0x9c, 0x08, 0xe8, 0x0e, 0x26, 0xdd, 0xae, 0x56, 0x31, 0x80 },
|
||||
{ 0xb0, 0xb8, 0x11, 0x02, 0x8c, 0x2b, 0xf9, 0x97, 0x21, 0x34, 0x87, 0xc0 },
|
||||
{ 0x18, 0xa0, 0xc9, 0x23, 0x1f, 0xc6, 0x0a, 0xdf, 0x5c, 0x5e, 0xa3, 0x20 },
|
||||
{ 0x76, 0x47, 0x1e, 0x83, 0x02, 0xa0, 0x72, 0x1e, 0x01, 0xb1, 0x2b, 0x80 },
|
||||
{ 0xff, 0xbc, 0xcb, 0x80, 0xca, 0x83, 0x41, 0xfa, 0xfb, 0x47, 0xb2, 0xe0 },
|
||||
{ 0x66, 0xa7, 0x2a, 0x15, 0x8f, 0x93, 0x25, 0xa2, 0xbf, 0x67, 0x17, 0x00 },
|
||||
{ 0xc4, 0x24, 0x36, 0x89, 0xfe, 0x85, 0xb1, 0xc5, 0x13, 0x63, 0xa1, 0x80 },
|
||||
{ 0x0d, 0xff, 0x73, 0x94, 0x14, 0xd1, 0xa1, 0xb3, 0x4b, 0x1c, 0x27, 0x00 },
|
||||
{ 0x15, 0xb4, 0x88, 0x30, 0x63, 0x6c, 0x8b, 0x99, 0x89, 0x49, 0x72, 0xe0 },
|
||||
{ 0x29, 0xa8, 0x9c, 0x0d, 0x3d, 0xe8, 0x1d, 0x66, 0x54, 0x89, 0xb0, 0xe0 },
|
||||
{ 0x4f, 0x12, 0x6f, 0x37, 0xfa, 0x51, 0xcb, 0xe6, 0x1b, 0xd6, 0xb9, 0x40 },
|
||||
{ 0x99, 0xc4, 0x72, 0x39, 0xd0, 0xd9, 0x7d, 0x3c, 0x84, 0xe0, 0x94, 0x00 },
|
||||
{ 0x19, 0x19, 0xb7, 0x51, 0x19, 0x76, 0x56, 0x21, 0xbb, 0x4f, 0x1e, 0x80 },
|
||||
{ 0x09, 0xdb, 0x12, 0xd7, 0x31, 0xfa, 0xee, 0x0b, 0x86, 0xdf, 0x6b, 0x80 },
|
||||
{ 0x48, 0x8f, 0xc3, 0x3d, 0xf4, 0x3f, 0xbd, 0xee, 0xa4, 0xea, 0xfb, 0x40 },
|
||||
{ 0x82, 0x74, 0x23, 0xee, 0x40, 0xb6, 0x75, 0xf7, 0x56, 0xeb, 0x5f, 0xe0 },
|
||||
{ 0xab, 0xe1, 0x97, 0xc4, 0x84, 0xcb, 0x74, 0x75, 0x71, 0x44, 0xa9, 0xa0 },
|
||||
{ 0x2b, 0x50, 0x0e, 0x4b, 0xc0, 0xec, 0x5a, 0x6d, 0x2b, 0xdb, 0xdd, 0x00 },
|
||||
{ 0xc4, 0x74, 0xaa, 0x53, 0xd7, 0x02, 0x18, 0x76, 0x16, 0x69, 0x36, 0x00 },
|
||||
{ 0x8e, 0xba, 0x1a, 0x13, 0xdb, 0x33, 0x90, 0xbd, 0x67, 0x18, 0xce, 0xc0 },
|
||||
{ 0x75, 0x38, 0x44, 0x67, 0x3a, 0x27, 0x78, 0x2c, 0xc4, 0x20, 0x12, 0xe0 },
|
||||
{ 0x06, 0xff, 0x83, 0xa1, 0x45, 0xc3, 0x70, 0x35, 0xa5, 0xc1, 0x26, 0x80 },
|
||||
{ 0x3b, 0x37, 0x41, 0x78, 0x58, 0xcc, 0x2d, 0xd3, 0x3e, 0xc3, 0xf6, 0x20 },
|
||||
{ 0x9a, 0x4a, 0x5a, 0x28, 0xee, 0x17, 0xca, 0x9c, 0x32, 0x48, 0x42, 0xc0 },
|
||||
{ 0xbc, 0x29, 0xf4, 0x65, 0x30, 0x9c, 0x97, 0x7e, 0x89, 0x61, 0x0a, 0x40 },
|
||||
{ 0x26, 0x63, 0xae, 0x6d, 0xdf, 0x8b, 0x5c, 0xe2, 0xbb, 0x29, 0x48, 0x80 },
|
||||
{ 0x46, 0xf2, 0x31, 0xef, 0xe4, 0x57, 0x03, 0x4c, 0x18, 0x14, 0x41, 0x80 },
|
||||
{ 0x3f, 0xb2, 0xce, 0x85, 0xab, 0xe9, 0xb0, 0xc7, 0x2e, 0x06, 0xfb, 0xe0 },
|
||||
{ 0xde, 0x87, 0x48, 0x1f, 0x28, 0x2c, 0x15, 0x39, 0x71, 0xa0, 0xa2, 0xe0 },
|
||||
{ 0xfc, 0xd7, 0xcc, 0xf2, 0x3c, 0x69, 0xfa, 0x99, 0xbb, 0xa1, 0x41, 0x20 },
|
||||
{ 0xf0, 0x26, 0x14, 0x47, 0xe9, 0x49, 0x0c, 0xa8, 0xe4, 0x74, 0xce, 0xc0 },
|
||||
{ 0x44, 0x10, 0x11, 0x58, 0x18, 0x19, 0x6f, 0x95, 0xcd, 0xd7, 0x01, 0x20 },
|
||||
{ 0x08, 0x8f, 0xc3, 0x1d, 0xf4, 0xbf, 0xbd, 0xe2, 0xa4, 0xea, 0xfb, 0x40 },
|
||||
{ 0xb8, 0xfe, 0xf1, 0xb6, 0x30, 0x77, 0x29, 0xfb, 0x0a, 0x07, 0x8c, 0x00 },
|
||||
{ 0x5a, 0xfe, 0xa7, 0xac, 0xcc, 0xb7, 0x7b, 0xbc, 0x9d, 0x99, 0xa9, 0x00 },
|
||||
{ 0x49, 0xa7, 0x01, 0x6a, 0xc6, 0x53, 0xf6, 0x5e, 0xcd, 0xc9, 0x07, 0x60 },
|
||||
{ 0x19, 0x44, 0xd0, 0x85, 0xbe, 0x4e, 0x7d, 0xa8, 0xd6, 0xcc, 0x7d, 0x00 },
|
||||
{ 0x25, 0x1f, 0x62, 0xad, 0xc4, 0x03, 0x2f, 0x0e, 0xe7, 0x14, 0x00, 0x20 },
|
||||
{ 0x56, 0x47, 0x1f, 0x87, 0x02, 0xa0, 0x72, 0x1e, 0x00, 0xb1, 0x2b, 0x80 },
|
||||
{ 0x2b, 0x8e, 0x49, 0x23, 0xf2, 0xdd, 0x51, 0xe2, 0xd5, 0x37, 0xfa, 0x00 },
|
||||
{ 0x6b, 0x55, 0x0a, 0x40, 0xa6, 0x6f, 0x47, 0x55, 0xde, 0x95, 0xc2, 0x60 },
|
||||
{ 0xa1, 0x8a, 0xd2, 0x8d, 0x4e, 0x27, 0xfe, 0x92, 0xa4, 0xf6, 0xc8, 0x40 },
|
||||
{ 0x10, 0xc2, 0xe5, 0x86, 0x38, 0x8c, 0xb8, 0x2a, 0x3d, 0x80, 0x75, 0x80 },
|
||||
{ 0xef, 0x34, 0xa4, 0x18, 0x17, 0xee, 0x02, 0x13, 0x3d, 0xb2, 0xeb, 0x00 },
|
||||
{ 0x7e, 0x9c, 0x0c, 0x54, 0x32, 0x5a, 0x9c, 0x15, 0x83, 0x6e, 0x00, 0x00 },
|
||||
{ 0x36, 0x93, 0xe5, 0x72, 0xd1, 0xfd, 0xe4, 0xcd, 0xf0, 0x79, 0xe8, 0x60 },
|
||||
{ 0xbf, 0xb2, 0xce, 0xc5, 0xab, 0xe1, 0xb0, 0xc7, 0x2e, 0x07, 0xfb, 0xe0 },
|
||||
{ 0x7e, 0xe1, 0x82, 0x30, 0xc5, 0x83, 0xcc, 0xcc, 0x57, 0xd4, 0xb0, 0x80 },
|
||||
{ 0xa0, 0x66, 0xcb, 0x2f, 0xed, 0xaf, 0xc9, 0xf5, 0x26, 0x64, 0x12, 0x60 },
|
||||
{ 0xbb, 0x23, 0x72, 0x5a, 0xbc, 0x47, 0xcc, 0x5f, 0x4c, 0xc4, 0xcd, 0x20 },
|
||||
{ 0xde, 0xd9, 0xdb, 0xa3, 0xbe, 0xe4, 0x0c, 0x59, 0xb5, 0x60, 0x9b, 0x40 },
|
||||
{ 0xd9, 0xa7, 0x01, 0x6a, 0xc6, 0x53, 0xe6, 0xde, 0xcd, 0xc9, 0x03, 0x60 },
|
||||
{ 0x9a, 0xd4, 0x6a, 0xed, 0x5f, 0x70, 0x7f, 0x28, 0x0a, 0xb5, 0xfc, 0x40 },
|
||||
{ 0xe5, 0x92, 0x1c, 0x77, 0x82, 0x25, 0x87, 0x31, 0x6d, 0x7d, 0x3c, 0x20 },
|
||||
{ 0x4f, 0x14, 0xda, 0x82, 0x42, 0xa8, 0xb8, 0x6d, 0xca, 0x73, 0x35, 0x20 },
|
||||
{ 0x8b, 0x8b, 0x50, 0x7a, 0xd4, 0x67, 0xd4, 0x44, 0x1d, 0xf7, 0x70, 0xe0 },
|
||||
{ 0x22, 0x83, 0x1c, 0x9c, 0xf1, 0x16, 0x94, 0x67, 0xad, 0x04, 0xb6, 0x80 },
|
||||
{ 0x21, 0x3b, 0x83, 0x8f, 0xe2, 0xae, 0x54, 0xc3, 0x8e, 0xe7, 0x18, 0x00 },
|
||||
{ 0x5d, 0x92, 0x6b, 0x6d, 0xd7, 0x1f, 0x08, 0x51, 0x81, 0xa4, 0xe1, 0x20 },
|
||||
{ 0x66, 0xab, 0x79, 0xd4, 0xb2, 0x9e, 0xe6, 0xe6, 0x95, 0x09, 0xe5, 0x60 },
|
||||
{ 0x95, 0x81, 0x48, 0x68, 0x2d, 0x74, 0x8a, 0x38, 0xdd, 0x68, 0xba, 0xa0 },
|
||||
{ 0xb8, 0xce, 0x02, 0x0c, 0xf0, 0x69, 0xc3, 0x2a, 0x72, 0x3a, 0xb1, 0x40 },
|
||||
{ 0xf4, 0x33, 0x1d, 0x6d, 0x46, 0x16, 0x07, 0xe9, 0x57, 0x52, 0x74, 0x60 },
|
||||
{ 0x6d, 0xa2, 0x3b, 0xa4, 0x24, 0xb9, 0x59, 0x61, 0x33, 0xcf, 0x9c, 0x80 },
|
||||
{ 0xa6, 0x36, 0xbc, 0xbc, 0x7b, 0x30, 0xc5, 0xfb, 0xea, 0xe6, 0x7f, 0xe0 },
|
||||
{ 0x5c, 0xb0, 0xd8, 0x6a, 0x07, 0xdf, 0x65, 0x4a, 0x90, 0x89, 0xa2, 0x00 },
|
||||
{ 0xf1, 0x1f, 0x10, 0x68, 0x48, 0x78, 0x0f, 0xc9, 0xec, 0xdd, 0x80, 0xa0 },
|
||||
{ 0x1f, 0xbb, 0x53, 0x64, 0xfb, 0x8d, 0x2c, 0x9d, 0x73, 0x0d, 0x5b, 0xa0 },
|
||||
{ 0xfc, 0xb8, 0x6b, 0xc7, 0x0a, 0x50, 0xc9, 0xd0, 0x2a, 0x5d, 0x03, 0x40 },
|
||||
{ 0xa5, 0x34, 0x43, 0x30, 0x29, 0xea, 0xc1, 0x5f, 0x32, 0x2e, 0x34, 0xc0 },
|
||||
{ 0xc9, 0x89, 0xd9, 0xc7, 0xc3, 0xd3, 0xb8, 0xc5, 0x5d, 0x75, 0x13, 0x00 },
|
||||
{ 0x7b, 0xb3, 0x8b, 0x2f, 0x01, 0x86, 0xd4, 0x66, 0x43, 0xae, 0x96, 0x20 },
|
||||
{ 0x26, 0x44, 0xeb, 0xad, 0xeb, 0x44, 0xb9, 0x46, 0x7d, 0x1f, 0x42, 0xc0 },
|
||||
{ 0x60, 0x8c, 0xc8, 0x57, 0x59, 0x4b, 0xfb, 0xb5, 0x5d, 0x69, 0x60, 0x00 }
|
||||
};
|
||||
|
||||
// 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 kFTX_LDPC_Nm[FTX_LDPC_M][7] = {
|
||||
{ 4, 31, 59, 91, 92, 96, 153 },
|
||||
{ 5, 32, 60, 93, 115, 146, 0 },
|
||||
{ 6, 24, 61, 94, 122, 151, 0 },
|
||||
{ 7, 33, 62, 95, 96, 143, 0 },
|
||||
{ 8, 25, 63, 83, 93, 96, 148 },
|
||||
{ 6, 32, 64, 97, 126, 138, 0 },
|
||||
{ 5, 34, 65, 78, 98, 107, 154 },
|
||||
{ 9, 35, 66, 99, 139, 146, 0 },
|
||||
{ 10, 36, 67, 100, 107, 126, 0 },
|
||||
{ 11, 37, 67, 87, 101, 139, 158 },
|
||||
{ 12, 38, 68, 102, 105, 155, 0 },
|
||||
{ 13, 39, 69, 103, 149, 162, 0 },
|
||||
{ 8, 40, 70, 82, 104, 114, 145 },
|
||||
{ 14, 41, 71, 88, 102, 123, 156 },
|
||||
{ 15, 42, 59, 106, 123, 159, 0 },
|
||||
{ 1, 33, 72, 106, 107, 157, 0 },
|
||||
{ 16, 43, 73, 108, 141, 160, 0 },
|
||||
{ 17, 37, 74, 81, 109, 131, 154 },
|
||||
{ 11, 44, 75, 110, 121, 166, 0 },
|
||||
{ 45, 55, 64, 111, 130, 161, 173 },
|
||||
{ 8, 46, 71, 112, 119, 166, 0 },
|
||||
{ 18, 36, 76, 89, 113, 114, 143 },
|
||||
{ 19, 38, 77, 104, 116, 163, 0 },
|
||||
{ 20, 47, 70, 92, 138, 165, 0 },
|
||||
{ 2, 48, 74, 113, 128, 160, 0 },
|
||||
{ 21, 45, 78, 83, 117, 121, 151 },
|
||||
{ 22, 47, 58, 118, 127, 164, 0 },
|
||||
{ 16, 39, 62, 112, 134, 158, 0 },
|
||||
{ 23, 43, 79, 120, 131, 145, 0 },
|
||||
{ 19, 35, 59, 73, 110, 125, 161 },
|
||||
{ 20, 36, 63, 94, 136, 161, 0 },
|
||||
{ 14, 31, 79, 98, 132, 164, 0 },
|
||||
{ 3, 44, 80, 124, 127, 169, 0 },
|
||||
{ 19, 46, 81, 117, 135, 167, 0 },
|
||||
{ 7, 49, 58, 90, 100, 105, 168 },
|
||||
{ 12, 50, 61, 118, 119, 144, 0 },
|
||||
{ 13, 51, 64, 114, 118, 157, 0 },
|
||||
{ 24, 52, 76, 129, 148, 149, 0 },
|
||||
{ 25, 53, 69, 90, 101, 130, 156 },
|
||||
{ 20, 46, 65, 80, 120, 140, 170 },
|
||||
{ 21, 54, 77, 100, 140, 171, 0 },
|
||||
{ 35, 82, 133, 142, 171, 174, 0 },
|
||||
{ 14, 30, 83, 113, 125, 170, 0 },
|
||||
{ 4, 29, 68, 120, 134, 173, 0 },
|
||||
{ 1, 4, 52, 57, 86, 136, 152 },
|
||||
{ 26, 51, 56, 91, 122, 137, 168 },
|
||||
{ 52, 84, 110, 115, 145, 168, 0 },
|
||||
{ 7, 50, 81, 99, 132, 173, 0 },
|
||||
{ 23, 55, 67, 95, 172, 174, 0 },
|
||||
{ 26, 41, 77, 109, 141, 148, 0 },
|
||||
{ 2, 27, 41, 61, 62, 115, 133 },
|
||||
{ 27, 40, 56, 124, 125, 126, 0 },
|
||||
{ 18, 49, 55, 124, 141, 167, 0 },
|
||||
{ 6, 33, 85, 108, 116, 156, 0 },
|
||||
{ 28, 48, 70, 85, 105, 129, 158 },
|
||||
{ 9, 54, 63, 131, 147, 155, 0 },
|
||||
{ 22, 53, 68, 109, 121, 174, 0 },
|
||||
{ 3, 13, 48, 78, 95, 123, 0 },
|
||||
{ 31, 69, 133, 150, 155, 169, 0 },
|
||||
{ 12, 43, 66, 89, 97, 135, 159 },
|
||||
{ 5, 39, 75, 102, 136, 167, 0 },
|
||||
{ 2, 54, 86, 101, 135, 164, 0 },
|
||||
{ 15, 56, 87, 108, 119, 171, 0 },
|
||||
{ 10, 44, 82, 91, 111, 144, 149 },
|
||||
{ 23, 34, 71, 94, 127, 153, 0 },
|
||||
{ 11, 49, 88, 92, 142, 157, 0 },
|
||||
{ 29, 34, 87, 97, 147, 162, 0 },
|
||||
{ 30, 50, 60, 86, 137, 142, 162 },
|
||||
{ 10, 53, 66, 84, 112, 128, 165 },
|
||||
{ 22, 57, 85, 93, 140, 159, 0 },
|
||||
{ 28, 32, 72, 103, 132, 166, 0 },
|
||||
{ 28, 29, 84, 88, 117, 143, 150 },
|
||||
{ 1, 26, 45, 80, 128, 147, 0 },
|
||||
{ 17, 27, 89, 103, 116, 153, 0 },
|
||||
{ 51, 57, 98, 163, 165, 172, 0 },
|
||||
{ 21, 37, 73, 138, 152, 169, 0 },
|
||||
{ 16, 47, 76, 130, 137, 154, 0 },
|
||||
{ 3, 24, 30, 72, 104, 139, 0 },
|
||||
{ 9, 40, 90, 106, 134, 151, 0 },
|
||||
{ 15, 58, 60, 74, 111, 150, 163 },
|
||||
{ 18, 42, 79, 144, 146, 152, 0 },
|
||||
{ 25, 38, 65, 99, 122, 160, 0 },
|
||||
{ 17, 42, 75, 129, 170, 172, 0 }
|
||||
};
|
||||
|
||||
// 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 kFTX_LDPC_Mn[FTX_LDPC_N][3] = {
|
||||
{ 16, 45, 73 },
|
||||
{ 25, 51, 62 },
|
||||
{ 33, 58, 78 },
|
||||
{ 1, 44, 45 },
|
||||
{ 2, 7, 61 },
|
||||
{ 3, 6, 54 },
|
||||
{ 4, 35, 48 },
|
||||
{ 5, 13, 21 },
|
||||
{ 8, 56, 79 },
|
||||
{ 9, 64, 69 },
|
||||
{ 10, 19, 66 },
|
||||
{ 11, 36, 60 },
|
||||
{ 12, 37, 58 },
|
||||
{ 14, 32, 43 },
|
||||
{ 15, 63, 80 },
|
||||
{ 17, 28, 77 },
|
||||
{ 18, 74, 83 },
|
||||
{ 22, 53, 81 },
|
||||
{ 23, 30, 34 },
|
||||
{ 24, 31, 40 },
|
||||
{ 26, 41, 76 },
|
||||
{ 27, 57, 70 },
|
||||
{ 29, 49, 65 },
|
||||
{ 3, 38, 78 },
|
||||
{ 5, 39, 82 },
|
||||
{ 46, 50, 73 },
|
||||
{ 51, 52, 74 },
|
||||
{ 55, 71, 72 },
|
||||
{ 44, 67, 72 },
|
||||
{ 43, 68, 78 },
|
||||
{ 1, 32, 59 },
|
||||
{ 2, 6, 71 },
|
||||
{ 4, 16, 54 },
|
||||
{ 7, 65, 67 },
|
||||
{ 8, 30, 42 },
|
||||
{ 9, 22, 31 },
|
||||
{ 10, 18, 76 },
|
||||
{ 11, 23, 82 },
|
||||
{ 12, 28, 61 },
|
||||
{ 13, 52, 79 },
|
||||
{ 14, 50, 51 },
|
||||
{ 15, 81, 83 },
|
||||
{ 17, 29, 60 },
|
||||
{ 19, 33, 64 },
|
||||
{ 20, 26, 73 },
|
||||
{ 21, 34, 40 },
|
||||
{ 24, 27, 77 },
|
||||
{ 25, 55, 58 },
|
||||
{ 35, 53, 66 },
|
||||
{ 36, 48, 68 },
|
||||
{ 37, 46, 75 },
|
||||
{ 38, 45, 47 },
|
||||
{ 39, 57, 69 },
|
||||
{ 41, 56, 62 },
|
||||
{ 20, 49, 53 },
|
||||
{ 46, 52, 63 },
|
||||
{ 45, 70, 75 },
|
||||
{ 27, 35, 80 },
|
||||
{ 1, 15, 30 },
|
||||
{ 2, 68, 80 },
|
||||
{ 3, 36, 51 },
|
||||
{ 4, 28, 51 },
|
||||
{ 5, 31, 56 },
|
||||
{ 6, 20, 37 },
|
||||
{ 7, 40, 82 },
|
||||
{ 8, 60, 69 },
|
||||
{ 9, 10, 49 },
|
||||
{ 11, 44, 57 },
|
||||
{ 12, 39, 59 },
|
||||
{ 13, 24, 55 },
|
||||
{ 14, 21, 65 },
|
||||
{ 16, 71, 78 },
|
||||
{ 17, 30, 76 },
|
||||
{ 18, 25, 80 },
|
||||
{ 19, 61, 83 },
|
||||
{ 22, 38, 77 },
|
||||
{ 23, 41, 50 },
|
||||
{ 7, 26, 58 },
|
||||
{ 29, 32, 81 },
|
||||
{ 33, 40, 73 },
|
||||
{ 18, 34, 48 },
|
||||
{ 13, 42, 64 },
|
||||
{ 5, 26, 43 },
|
||||
{ 47, 69, 72 },
|
||||
{ 54, 55, 70 },
|
||||
{ 45, 62, 68 },
|
||||
{ 10, 63, 67 },
|
||||
{ 14, 66, 72 },
|
||||
{ 22, 60, 74 },
|
||||
{ 35, 39, 79 },
|
||||
{ 1, 46, 64 },
|
||||
{ 1, 24, 66 },
|
||||
{ 2, 5, 70 },
|
||||
{ 3, 31, 65 },
|
||||
{ 4, 49, 58 },
|
||||
{ 1, 4, 5 },
|
||||
{ 6, 60, 67 },
|
||||
{ 7, 32, 75 },
|
||||
{ 8, 48, 82 },
|
||||
{ 9, 35, 41 },
|
||||
{ 10, 39, 62 },
|
||||
{ 11, 14, 61 },
|
||||
{ 12, 71, 74 },
|
||||
{ 13, 23, 78 },
|
||||
{ 11, 35, 55 },
|
||||
{ 15, 16, 79 },
|
||||
{ 7, 9, 16 },
|
||||
{ 17, 54, 63 },
|
||||
{ 18, 50, 57 },
|
||||
{ 19, 30, 47 },
|
||||
{ 20, 64, 80 },
|
||||
{ 21, 28, 69 },
|
||||
{ 22, 25, 43 },
|
||||
{ 13, 22, 37 },
|
||||
{ 2, 47, 51 },
|
||||
{ 23, 54, 74 },
|
||||
{ 26, 34, 72 },
|
||||
{ 27, 36, 37 },
|
||||
{ 21, 36, 63 },
|
||||
{ 29, 40, 44 },
|
||||
{ 19, 26, 57 },
|
||||
{ 3, 46, 82 },
|
||||
{ 14, 15, 58 },
|
||||
{ 33, 52, 53 },
|
||||
{ 30, 43, 52 },
|
||||
{ 6, 9, 52 },
|
||||
{ 27, 33, 65 },
|
||||
{ 25, 69, 73 },
|
||||
{ 38, 55, 83 },
|
||||
{ 20, 39, 77 },
|
||||
{ 18, 29, 56 },
|
||||
{ 32, 48, 71 },
|
||||
{ 42, 51, 59 },
|
||||
{ 28, 44, 79 },
|
||||
{ 34, 60, 62 },
|
||||
{ 31, 45, 61 },
|
||||
{ 46, 68, 77 },
|
||||
{ 6, 24, 76 },
|
||||
{ 8, 10, 78 },
|
||||
{ 40, 41, 70 },
|
||||
{ 17, 50, 53 },
|
||||
{ 42, 66, 68 },
|
||||
{ 4, 22, 72 },
|
||||
{ 36, 64, 81 },
|
||||
{ 13, 29, 47 },
|
||||
{ 2, 8, 81 },
|
||||
{ 56, 67, 73 },
|
||||
{ 5, 38, 50 },
|
||||
{ 12, 38, 64 },
|
||||
{ 59, 72, 80 },
|
||||
{ 3, 26, 79 },
|
||||
{ 45, 76, 81 },
|
||||
{ 1, 65, 74 },
|
||||
{ 7, 18, 77 },
|
||||
{ 11, 56, 59 },
|
||||
{ 14, 39, 54 },
|
||||
{ 16, 37, 66 },
|
||||
{ 10, 28, 55 },
|
||||
{ 15, 60, 70 },
|
||||
{ 17, 25, 82 },
|
||||
{ 20, 30, 31 },
|
||||
{ 12, 67, 68 },
|
||||
{ 23, 75, 80 },
|
||||
{ 27, 32, 62 },
|
||||
{ 24, 69, 75 },
|
||||
{ 19, 21, 71 },
|
||||
{ 34, 53, 61 },
|
||||
{ 35, 46, 47 },
|
||||
{ 33, 59, 76 },
|
||||
{ 40, 43, 83 },
|
||||
{ 41, 42, 63 },
|
||||
{ 49, 75, 83 },
|
||||
{ 20, 44, 48 },
|
||||
{ 42, 49, 57 }
|
||||
};
|
||||
|
||||
const uint8_t kFTX_LDPC_Num_rows[FTX_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,
|
||||
6, 6, 7, 6, 6, 6, 7, 6, 6, 6, 6, 7, 6, 6, 6, 7,
|
||||
6, 6, 6, 7, 7, 6, 6, 7, 6, 6, 6, 6, 6, 6, 6, 7,
|
||||
6, 6, 6
|
||||
};
|
||||
Vendored
+90
@@ -0,0 +1,90 @@
|
||||
#ifndef _INCLUDE_CONSTANTS_H_
|
||||
#define _INCLUDE_CONSTANTS_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
#define FT8_SYMBOL_PERIOD (0.160f) ///< FT8 symbol duration, defines tone deviation in Hz and symbol rate
|
||||
#define FT8_SLOT_TIME (15.0f) ///< FT8 slot period
|
||||
|
||||
#define FT4_SYMBOL_PERIOD (0.048f) ///< FT4 symbol duration, defines tone deviation in Hz and symbol rate
|
||||
#define FT4_SLOT_TIME (7.5f) ///< FT4 slot period
|
||||
|
||||
// Define FT8 symbol counts
|
||||
// FT8 message structure:
|
||||
// S D1 S D2 S
|
||||
// S - sync block (7 symbols of Costas pattern)
|
||||
// D1 - first data block (29 symbols each encoding 3 bits)
|
||||
#define FT8_ND (58) ///< Data symbols
|
||||
#define FT8_NN (79) ///< Total channel symbols (FT8_NS + FT8_ND)
|
||||
#define FT8_LENGTH_SYNC (7) ///< Length of each sync group
|
||||
#define FT8_NUM_SYNC (3) ///< Number of sync groups
|
||||
#define FT8_SYNC_OFFSET (36) ///< Offset between sync groups
|
||||
|
||||
// Define FT4 symbol counts
|
||||
// FT4 message structure:
|
||||
// R Sa D1 Sb D2 Sc D3 Sd R
|
||||
// R - ramping symbol (no payload information conveyed)
|
||||
// Sx - one of four _different_ sync blocks (4 symbols of Costas pattern)
|
||||
// Dy - data block (29 symbols each encoding 2 bits)
|
||||
#define FT4_ND (87) ///< Data symbols
|
||||
#define FT4_NR (2) ///< Ramp symbols (beginning + end)
|
||||
#define FT4_NN (105) ///< Total channel symbols (FT4_NS + FT4_ND + FT4_NR)
|
||||
#define FT4_LENGTH_SYNC (4) ///< Length of each sync group
|
||||
#define FT4_NUM_SYNC (4) ///< Number of sync groups
|
||||
#define FT4_SYNC_OFFSET (33) ///< Offset between sync groups
|
||||
|
||||
// Define LDPC parameters
|
||||
#define FTX_LDPC_N (174) ///< Number of bits in the encoded message (payload with LDPC checksum bits)
|
||||
#define FTX_LDPC_K (91) ///< Number of payload bits (including CRC)
|
||||
#define FTX_LDPC_M (83) ///< Number of LDPC checksum bits (FTX_LDPC_N - FTX_LDPC_K)
|
||||
#define FTX_LDPC_N_BYTES ((FTX_LDPC_N + 7) / 8) ///< Number of whole bytes needed to store 174 bits (full message)
|
||||
#define FTX_LDPC_K_BYTES ((FTX_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
|
||||
#define FT8_CRC_WIDTH (14)
|
||||
|
||||
typedef enum
|
||||
{
|
||||
FTX_PROTOCOL_FT4,
|
||||
FTX_PROTOCOL_FT8
|
||||
} ftx_protocol_t;
|
||||
|
||||
/// 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 kFTX_LDPC_generator[FTX_LDPC_M][FTX_LDPC_K_BYTES];
|
||||
|
||||
/// 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 kFTX_LDPC_Nm[FTX_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 kFTX_LDPC_Mn[FTX_LDPC_N][3];
|
||||
|
||||
/// Number of rows (columns in C/C++) in the array Nm.
|
||||
extern const uint8_t kFTX_LDPC_Num_rows[FTX_LDPC_M];
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // _INCLUDE_CONSTANTS_H_
|
||||
Vendored
+63
@@ -0,0 +1,63 @@
|
||||
#include "crc.h"
|
||||
#include "constants.h"
|
||||
|
||||
#define TOPBIT (1u << (FT8_CRC_WIDTH - 1))
|
||||
|
||||
// Compute 14-bit CRC for a sequence of given number of bits
|
||||
// Adapted from https://barrgroup.com/Embedded-Systems/How-To/CRC-Calculation-C-Code
|
||||
// [IN] message - byte sequence (MSB first)
|
||||
// [IN] num_bits - number of bits in the sequence
|
||||
uint16_t ftx_compute_crc(const uint8_t message[], int num_bits)
|
||||
{
|
||||
uint16_t remainder = 0;
|
||||
int idx_byte = 0;
|
||||
|
||||
// Perform modulo-2 division, a bit at a time.
|
||||
for (int idx_bit = 0; idx_bit < num_bits; ++idx_bit)
|
||||
{
|
||||
if (idx_bit % 8 == 0)
|
||||
{
|
||||
// Bring the next byte into the remainder.
|
||||
remainder ^= (message[idx_byte] << (FT8_CRC_WIDTH - 8));
|
||||
++idx_byte;
|
||||
}
|
||||
|
||||
// Try to divide the current data bit.
|
||||
if (remainder & TOPBIT)
|
||||
{
|
||||
remainder = (remainder << 1) ^ FT8_CRC_POLYNOMIAL;
|
||||
}
|
||||
else
|
||||
{
|
||||
remainder = (remainder << 1);
|
||||
}
|
||||
}
|
||||
|
||||
return remainder & ((TOPBIT << 1) - 1u);
|
||||
}
|
||||
|
||||
uint16_t ftx_extract_crc(const uint8_t a91[])
|
||||
{
|
||||
uint16_t chksum = ((a91[9] & 0x07) << 11) | (a91[10] << 3) | (a91[11] >> 5);
|
||||
return chksum;
|
||||
}
|
||||
|
||||
void ftx_add_crc(const uint8_t payload[], uint8_t a91[])
|
||||
{
|
||||
// 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 = ftx_compute_crc(a91, 96 - 14);
|
||||
|
||||
// Store the CRC at the end of 77 bit message
|
||||
a91[9] |= (uint8_t)(checksum >> 11);
|
||||
a91[10] = (uint8_t)(checksum >> 3);
|
||||
a91[11] = (uint8_t)(checksum << 5);
|
||||
}
|
||||
Vendored
+31
@@ -0,0 +1,31 @@
|
||||
#ifndef _INCLUDE_CRC_H_
|
||||
#define _INCLUDE_CRC_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
// Compute 14-bit CRC for a sequence of given number of bits using FT8/FT4 CRC polynomial
|
||||
// [IN] message - byte sequence (MSB first)
|
||||
// [IN] num_bits - number of bits in the sequence
|
||||
uint16_t ftx_compute_crc(const uint8_t message[], int num_bits);
|
||||
|
||||
/// Extract the FT8/FT4 CRC of a packed message (during decoding)
|
||||
/// @param[in] a91 77 bits of payload data + CRC
|
||||
/// @return Extracted CRC
|
||||
uint16_t ftx_extract_crc(const uint8_t a91[]);
|
||||
|
||||
/// Add FT8/FT4 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 ftx_add_crc(const uint8_t payload[], uint8_t a91[]);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // _INCLUDE_CRC_H_
|
||||
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
#ifndef _DEBUG_H_INCLUDED_
|
||||
#define _DEBUG_H_INCLUDED_
|
||||
|
||||
#define LOG_DEBUG 0
|
||||
#define LOG_INFO 1
|
||||
#define LOG_WARN 2
|
||||
#define LOG_ERROR 3
|
||||
#define LOG_FATAL 4
|
||||
|
||||
#ifdef LOG_LEVEL
|
||||
#ifndef LOG_PRINTF
|
||||
#include <stdio.h>
|
||||
#define LOG_PRINTF(...) fprintf(stderr, __VA_ARGS__)
|
||||
#endif
|
||||
#define LOG(level, ...) \
|
||||
if (level >= LOG_LEVEL) \
|
||||
LOG_PRINTF(__VA_ARGS__)
|
||||
#else // ifdef LOG_LEVEL
|
||||
#define LOG(level, ...)
|
||||
#endif
|
||||
|
||||
#endif // _DEBUG_H_INCLUDED_
|
||||
Vendored
+592
@@ -0,0 +1,592 @@
|
||||
#include "decode.h"
|
||||
#include "constants.h"
|
||||
#include "crc.h"
|
||||
#include "ldpc.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <math.h>
|
||||
|
||||
// #define LOG_LEVEL LOG_DEBUG
|
||||
// #include "debug.h"
|
||||
|
||||
// Lookup table for y = 10*log10(1 + 10^(x/10)), where
|
||||
// y - increase in signal level dB when adding a weaker independent signal
|
||||
// x - specific relative strength of the weaker signal in dB
|
||||
// Table index corresponds to x in dB (index 0: 0 dB, index 1: -1 dB etc)
|
||||
static const float db_power_sum[40] = {
|
||||
3.01029995663981f, 2.53901891043867f, 2.1244260279434f, 1.76434862436485f, 1.45540463109294f,
|
||||
1.19331048066095f, 0.973227937086954f, 0.790097496525665f, 0.638920341433796f, 0.514969420252302f,
|
||||
0.413926851582251f, 0.331956199884278f, 0.265723755961025f, 0.212384019142551f, 0.16954289279533f,
|
||||
0.135209221080382f, 0.10774225511957f, 0.085799992300358f, 0.06829128312453f, 0.054333142200458f,
|
||||
0.043213737826426f, 0.034360947517284f, 0.027316043349389f, 0.021711921641451f, 0.017255250287928f,
|
||||
0.013711928326833f, 0.010895305999614f, 0.008656680827934f, 0.006877654943187f, 0.005464004928574f,
|
||||
0.004340774793186f, 0.003448354310253f, 0.002739348814965f, 0.002176083232619f, 0.001728613409904f,
|
||||
0.001373142636584f, 0.001090761428665f, 0.000866444976964f, 0.000688255828734f, 0.000546709946839f
|
||||
};
|
||||
|
||||
/// Compute log likelihood log(p(1) / p(0)) of 174 message bits for later use in soft-decision LDPC decoding
|
||||
/// @param[in] wf 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
|
||||
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[]
|
||||
/// @param[in] plain Array of bits (0 and nonzero values) with num_bits entires
|
||||
/// @param[in] num_bits Number of bits (entries) passed in bit_array
|
||||
/// @param[out] packed Byte-packed bits representing the data in bit_array
|
||||
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(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 ftx_waterfall_t* wf, const ftx_candidate_t* candidate)
|
||||
{
|
||||
int offset = candidate->time_offset;
|
||||
offset = (offset * wf->time_osr) + candidate->time_sub;
|
||||
offset = (offset * wf->freq_osr) + candidate->freq_sub;
|
||||
offset = (offset * wf->num_bins) + candidate->freq_offset;
|
||||
return wf->mag + offset;
|
||||
}
|
||||
|
||||
static int ft8_sync_score(const ftx_waterfall_t* wf, const ftx_candidate_t* candidate)
|
||||
{
|
||||
int score = 0;
|
||||
int num_average = 0;
|
||||
|
||||
// Get the pointer to symbol 0 of the candidate
|
||||
const WF_ELEM_T* mag_cand = get_cand_mag(wf, candidate);
|
||||
|
||||
// Compute average score over sync symbols (m+k = 0-7, 36-43, 72-79)
|
||||
for (int m = 0; m < FT8_NUM_SYNC; ++m)
|
||||
{
|
||||
for (int k = 0; k < FT8_LENGTH_SYNC; ++k)
|
||||
{
|
||||
int block = (FT8_SYNC_OFFSET * m) + k; // relative to the message
|
||||
int block_abs = candidate->time_offset + block; // relative to the captured signal
|
||||
// Check for time boundaries
|
||||
if (block_abs < 0)
|
||||
continue;
|
||||
if (block_abs >= wf->num_blocks)
|
||||
break;
|
||||
|
||||
// Get the pointer to symbol 'block' of the candidate
|
||||
const WF_ELEM_T* p8 = mag_cand + (block * wf->block_stride);
|
||||
|
||||
// Weighted difference between the expected and all other symbols
|
||||
// Does not work as well as the alternative score below
|
||||
// score += 8 * p8[kFT8_Costas_pattern[k]] -
|
||||
// p8[0] - p8[1] - p8[2] - p8[3] -
|
||||
// p8[4] - p8[5] - p8[6] - p8[7];
|
||||
// ++num_average;
|
||||
|
||||
// Check only the neighbors of the expected symbol frequency- and time-wise
|
||||
int sm = kFT8_Costas_pattern[k]; // Index of the expected bin
|
||||
if (sm > 0)
|
||||
{
|
||||
// look at one frequency bin lower
|
||||
score += WF_ELEM_MAG_INT(p8[sm]) - WF_ELEM_MAG_INT(p8[sm - 1]);
|
||||
++num_average;
|
||||
}
|
||||
if (sm < 7)
|
||||
{
|
||||
// look at one frequency bin higher
|
||||
score += WF_ELEM_MAG_INT(p8[sm]) - WF_ELEM_MAG_INT(p8[sm + 1]);
|
||||
++num_average;
|
||||
}
|
||||
if ((k > 0) && (block_abs > 0))
|
||||
{
|
||||
// look one symbol back in time
|
||||
score += WF_ELEM_MAG_INT(p8[sm]) - WF_ELEM_MAG_INT(p8[sm - wf->block_stride]);
|
||||
++num_average;
|
||||
}
|
||||
if (((k + 1) < FT8_LENGTH_SYNC) && ((block_abs + 1) < wf->num_blocks))
|
||||
{
|
||||
// look one symbol forward in time
|
||||
score += WF_ELEM_MAG_INT(p8[sm]) - WF_ELEM_MAG_INT(p8[sm + wf->block_stride]);
|
||||
++num_average;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (num_average > 0)
|
||||
score /= num_average;
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
static int ft4_sync_score(const ftx_waterfall_t* wf, const ftx_candidate_t* candidate)
|
||||
{
|
||||
int score = 0;
|
||||
int num_average = 0;
|
||||
|
||||
// Get the pointer to symbol 0 of the candidate
|
||||
const WF_ELEM_T* mag_cand = get_cand_mag(wf, candidate);
|
||||
|
||||
// Compute average score over sync symbols (block = 1-4, 34-37, 67-70, 100-103)
|
||||
for (int m = 0; m < FT4_NUM_SYNC; ++m)
|
||||
{
|
||||
for (int k = 0; k < FT4_LENGTH_SYNC; ++k)
|
||||
{
|
||||
int block = 1 + (FT4_SYNC_OFFSET * m) + k;
|
||||
int block_abs = candidate->time_offset + block;
|
||||
// Check for time boundaries
|
||||
if (block_abs < 0)
|
||||
continue;
|
||||
if (block_abs >= wf->num_blocks)
|
||||
break;
|
||||
|
||||
// Get the pointer to symbol 'block' of the candidate
|
||||
const WF_ELEM_T* p4 = mag_cand + (block * wf->block_stride);
|
||||
|
||||
int sm = kFT4_Costas_pattern[m][k]; // Index of the expected bin
|
||||
|
||||
// score += (4 * p4[sm]) - p4[0] - p4[1] - p4[2] - p4[3];
|
||||
// num_average += 4;
|
||||
|
||||
// Check only the neighbors of the expected symbol frequency- and time-wise
|
||||
if (sm > 0)
|
||||
{
|
||||
// look at one frequency bin lower
|
||||
score += WF_ELEM_MAG_INT(p4[sm]) - WF_ELEM_MAG_INT(p4[sm - 1]);
|
||||
++num_average;
|
||||
}
|
||||
if (sm < 3)
|
||||
{
|
||||
// look at one frequency bin higher
|
||||
score += WF_ELEM_MAG_INT(p4[sm]) - WF_ELEM_MAG_INT(p4[sm + 1]);
|
||||
++num_average;
|
||||
}
|
||||
if ((k > 0) && (block_abs > 0))
|
||||
{
|
||||
// look one symbol back in time
|
||||
score += WF_ELEM_MAG_INT(p4[sm]) - WF_ELEM_MAG_INT(p4[sm - wf->block_stride]);
|
||||
++num_average;
|
||||
}
|
||||
if (((k + 1) < FT4_LENGTH_SYNC) && ((block_abs + 1) < wf->num_blocks))
|
||||
{
|
||||
// look one symbol forward in time
|
||||
score += WF_ELEM_MAG_INT(p4[sm]) - WF_ELEM_MAG_INT(p4[sm + wf->block_stride]);
|
||||
++num_average;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (num_average > 0)
|
||||
score /= num_average;
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
int ftx_find_candidates(const ftx_waterfall_t* wf, int num_candidates, ftx_candidate_t heap[], int min_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;
|
||||
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
|
||||
// sync symbols we included in the score, so the score is averaged.
|
||||
for (candidate.time_sub = 0; candidate.time_sub < wf->time_osr; ++candidate.time_sub)
|
||||
{
|
||||
for (candidate.freq_sub = 0; candidate.freq_sub < wf->freq_osr; ++candidate.freq_sub)
|
||||
{
|
||||
for (candidate.time_offset = -10; candidate.time_offset < 20; ++candidate.time_offset)
|
||||
{
|
||||
for (candidate.freq_offset = 0; (candidate.freq_offset + num_tones - 1) < wf->num_bins; ++candidate.freq_offset)
|
||||
{
|
||||
candidate.score = sync_fun(wf, &candidate);
|
||||
|
||||
if (candidate.score < min_score)
|
||||
continue;
|
||||
|
||||
// If the heap is full AND the current candidate is better than
|
||||
// the worst in the heap, we remove the worst and make space
|
||||
if ((heap_size == num_candidates) && (candidate.score > heap[0].score))
|
||||
{
|
||||
--heap_size;
|
||||
heap[0] = heap[heap_size];
|
||||
heapify_down(heap, heap_size);
|
||||
}
|
||||
|
||||
// If there's free space in the heap, we add the current candidate
|
||||
if (heap_size < num_candidates)
|
||||
{
|
||||
heap[heap_size] = candidate;
|
||||
++heap_size;
|
||||
heapify_up(heap, heap_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the candidates by sync strength - here we benefit from the heap structure
|
||||
int len_unsorted = heap_size;
|
||||
while (len_unsorted > 1)
|
||||
{
|
||||
// Take the top (index 0) element which is guaranteed to have the smallest score,
|
||||
// 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.
|
||||
ftx_candidate_t tmp = heap[len_unsorted - 1];
|
||||
heap[len_unsorted - 1] = heap[0];
|
||||
heap[0] = tmp;
|
||||
len_unsorted--;
|
||||
heapify_down(heap, len_unsorted);
|
||||
}
|
||||
|
||||
return heap_size;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// Go over FSK tones and skip Costas sync symbols
|
||||
for (int k = 0; k < FT4_ND; ++k)
|
||||
{
|
||||
// Skip either 5, 9 or 13 sync symbols
|
||||
// TODO: replace magic numbers with constants
|
||||
int sym_idx = k + ((k < 29) ? 5 : ((k < 58) ? 9 : 13));
|
||||
int bit_idx = 2 * k;
|
||||
|
||||
// Check for time boundaries
|
||||
int block = cand->time_offset + sym_idx;
|
||||
if ((block < 0) || (block >= wf->num_blocks))
|
||||
{
|
||||
log174[bit_idx + 0] = 0;
|
||||
log174[bit_idx + 1] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
ft4_extract_symbol(mag + (sym_idx * wf->block_stride), log174 + bit_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// Go over FSK tones and skip Costas sync symbols
|
||||
for (int k = 0; k < FT8_ND; ++k)
|
||||
{
|
||||
// Skip either 7 or 14 sync symbols
|
||||
// TODO: replace magic numbers with constants
|
||||
int sym_idx = k + ((k < 29) ? 7 : 14);
|
||||
int bit_idx = 3 * k;
|
||||
|
||||
// Check for time boundaries
|
||||
int block = cand->time_offset + sym_idx;
|
||||
if ((block < 0) || (block >= wf->num_blocks))
|
||||
{
|
||||
log174[bit_idx + 0] = 0;
|
||||
log174[bit_idx + 1] = 0;
|
||||
log174[bit_idx + 2] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
ft8_extract_symbol(mag + (sym_idx * wf->block_stride), log174 + bit_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ftx_normalize_logl(float* log174)
|
||||
{
|
||||
// Compute the variance of log174
|
||||
float sum = 0;
|
||||
float sum2 = 0;
|
||||
for (int i = 0; i < FTX_LDPC_N; ++i)
|
||||
{
|
||||
sum += log174[i];
|
||||
sum2 += log174[i] * log174[i];
|
||||
}
|
||||
float inv_n = 1.0f / FTX_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(24.0f / variance);
|
||||
for (int i = 0; i < FTX_LDPC_N; ++i)
|
||||
{
|
||||
log174[i] *= norm_factor;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
ft4_extract_likelihood(wf, cand, log174);
|
||||
}
|
||||
else
|
||||
{
|
||||
ft8_extract_likelihood(wf, cand, log174);
|
||||
}
|
||||
|
||||
ftx_normalize_logl(log174);
|
||||
|
||||
uint8_t plain174[FTX_LDPC_N]; // message bits (0/1)
|
||||
bp_decode(log174, max_iterations, plain174, &status->ldpc_errors);
|
||||
// ldpc_decode(log174, max_iterations, plain174, &status->ldpc_errors);
|
||||
|
||||
if (status->ldpc_errors > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract payload + CRC (first FTX_LDPC_K bits) packed into a byte array
|
||||
uint8_t a91[FTX_LDPC_K_BYTES];
|
||||
pack_bits(plain174, FTX_LDPC_K, a91);
|
||||
|
||||
// Extract CRC and check it
|
||||
status->crc_extracted = ftx_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;
|
||||
status->crc_calculated = ftx_compute_crc(a91, 96 - 14);
|
||||
|
||||
if (status->crc_extracted != status->crc_calculated)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reuse CRC value as a hash for the message (TODO: 14 bits only, should perhaps use full 16 or 32 bits?)
|
||||
message->hash = status->crc_calculated;
|
||||
|
||||
if (wf->protocol == FTX_PROTOCOL_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)
|
||||
{
|
||||
message->payload[i] = a91[i] ^ kFT4_XOR_sequence[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
message->payload[i] = a91[i];
|
||||
}
|
||||
}
|
||||
|
||||
// LOG(LOG_DEBUG, "Decoded message (CRC %04x), trying to unpack...\n", status->crc_extracted);
|
||||
return true;
|
||||
}
|
||||
|
||||
static float max2(float a, float b)
|
||||
{
|
||||
return (a >= b) ? a : b;
|
||||
}
|
||||
|
||||
static float max4(float a, float b, float c, float d)
|
||||
{
|
||||
return max2(max2(a, b), max2(c, d));
|
||||
}
|
||||
|
||||
static void heapify_down(ftx_candidate_t heap[], int heap_size)
|
||||
{
|
||||
// heapify from the root down
|
||||
int current = 0; // root node
|
||||
while (true)
|
||||
{
|
||||
int left = 2 * current + 1;
|
||||
int right = left + 1;
|
||||
|
||||
// Find the smallest value of (parent, left child, right child)
|
||||
int smallest = current;
|
||||
if ((left < heap_size) && (heap[left].score < heap[smallest].score))
|
||||
{
|
||||
smallest = left;
|
||||
}
|
||||
if ((right < heap_size) && (heap[right].score < heap[smallest].score))
|
||||
{
|
||||
smallest = right;
|
||||
}
|
||||
|
||||
if (smallest == current)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Exchange the current node with the smallest child and move down to it
|
||||
ftx_candidate_t tmp = heap[smallest];
|
||||
heap[smallest] = heap[current];
|
||||
heap[current] = tmp;
|
||||
current = smallest;
|
||||
}
|
||||
}
|
||||
|
||||
static void heapify_up(ftx_candidate_t heap[], int heap_size)
|
||||
{
|
||||
// heapify from the last node up
|
||||
int current = heap_size - 1;
|
||||
while (current > 0)
|
||||
{
|
||||
int parent = (current - 1) / 2;
|
||||
if (!(heap[current].score < heap[parent].score))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Exchange the current node with its parent and move up
|
||||
ftx_candidate_t tmp = heap[parent];
|
||||
heap[parent] = heap[current];
|
||||
heap[current] = tmp;
|
||||
current = parent;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute unnormalized log likelihood log(p(1) / p(0)) of 2 message bits (1 FSK symbol)
|
||||
static void ft4_extract_symbol(const WF_ELEM_T* wf, float* logl)
|
||||
{
|
||||
// Cleaned up code for the simple case of n_syms==1
|
||||
float s2[4];
|
||||
|
||||
for (int j = 0; j < 4; ++j)
|
||||
{
|
||||
s2[j] = WF_ELEM_MAG(wf[kFT4_Gray_map[j]]);
|
||||
}
|
||||
|
||||
logl[0] = max2(s2[2], s2[3]) - max2(s2[0], s2[1]);
|
||||
logl[1] = max2(s2[1], s2[3]) - max2(s2[0], s2[2]);
|
||||
}
|
||||
|
||||
// Compute unnormalized log likelihood log(p(1) / p(0)) of 3 message bits (1 FSK symbol)
|
||||
static void ft8_extract_symbol(const WF_ELEM_T* wf, float* logl)
|
||||
{
|
||||
// Cleaned up code for the simple case of n_syms==1
|
||||
#if 1
|
||||
float s2[8];
|
||||
|
||||
for (int j = 0; j < 8; ++j)
|
||||
{
|
||||
s2[j] = WF_ELEM_MAG(wf[kFT8_Gray_map[j]]);
|
||||
}
|
||||
|
||||
logl[0] = max4(s2[4], s2[5], s2[6], s2[7]) - max4(s2[0], s2[1], s2[2], s2[3]);
|
||||
logl[1] = max4(s2[2], s2[3], s2[6], s2[7]) - max4(s2[0], s2[1], s2[4], s2[5]);
|
||||
logl[2] = max4(s2[1], s2[3], s2[5], s2[7]) - max4(s2[0], s2[2], s2[4], s2[6]);
|
||||
#else
|
||||
float a[7] = {
|
||||
// (float)wf[7] - (float)wf[0], // 0: p(111) / p(000)
|
||||
(float)wf[5] - (float)wf[2], // 0: p(100) / p(011)
|
||||
(float)wf[3] - (float)wf[0], // 1: p(010) / p(000)
|
||||
(float)wf[6] - (float)wf[3], // 2: p(101) / p(010)
|
||||
(float)wf[6] - (float)wf[2], // 3: p(101) / p(011)
|
||||
(float)wf[7] - (float)wf[4], // 4: p(111) / p(110)
|
||||
(float)wf[4] - (float)wf[1], // 5: p(110) / p(001)
|
||||
(float)wf[5] - (float)wf[1] // 6: p(100) / p(001)
|
||||
};
|
||||
float k = 1.0f;
|
||||
|
||||
// logl[0] = k * (a[0] + a[2] + a[3] + a[5] + a[6]) / 5;
|
||||
// logl[1] = k * (a[0] / 4 + (a[1] - a[3]) * 5 / 24 + (a[5] - a[2]) / 6 + (a[4] - a[6]) / 24);
|
||||
// logl[2] = k * (a[0] / 4 + (a[1] - a[3]) / 24 + (a[2] - a[5]) / 6 + (a[4] - a[6]) * 5 / 24);
|
||||
logl[0] = k * (a[1] / 6 + a[2] / 3 + a[3] / 6 + a[4] / 6 + a[5] / 3 + a[6] / 6);
|
||||
logl[1] = k * (-a[0] / 4 + a[1] * 7 / 24 + (a[4] - a[3]) / 8 + a[5] / 3 + a[6] / 24);
|
||||
logl[2] = k * (-a[0] / 4 + (a[1] - a[6]) / 8 + a[2] / 3 + a[3] / 24 + a[4] * 7 / 24 - a[5] * 5 / 18);
|
||||
#endif
|
||||
// for (int i = 0; i < 8; ++i)
|
||||
// printf("%d ", WF_ELEM_MAG_INT(wf[i]));
|
||||
// for (int i = 0; i < 3; ++i)
|
||||
// printf("%.1f ", logl[i]);
|
||||
// printf("\n");
|
||||
}
|
||||
|
||||
// Compute unnormalized log likelihood log(p(1) / p(0)) of bits corresponding to several FSK symbols at once
|
||||
static void ft8_decode_multi_symbols(const WF_ELEM_T* wf, int num_bins, int n_syms, int bit_idx, float* log174)
|
||||
{
|
||||
const int n_bits = 3 * n_syms;
|
||||
const int n_tones = (1 << n_bits);
|
||||
|
||||
float s2[n_tones];
|
||||
|
||||
for (int j = 0; j < n_tones; ++j)
|
||||
{
|
||||
int j1 = j & 0x07;
|
||||
if (n_syms == 1)
|
||||
{
|
||||
s2[j] = WF_ELEM_MAG(wf[kFT8_Gray_map[j1]]);
|
||||
continue;
|
||||
}
|
||||
int j2 = (j >> 3) & 0x07;
|
||||
if (n_syms == 2)
|
||||
{
|
||||
s2[j] = WF_ELEM_MAG(wf[kFT8_Gray_map[j2]]);
|
||||
s2[j] += WF_ELEM_MAG(wf[kFT8_Gray_map[j1] + 4 * num_bins]);
|
||||
continue;
|
||||
}
|
||||
int j3 = (j >> 6) & 0x07;
|
||||
s2[j] = WF_ELEM_MAG(wf[kFT8_Gray_map[j3]]);
|
||||
s2[j] += WF_ELEM_MAG(wf[kFT8_Gray_map[j2] + 4 * num_bins]);
|
||||
s2[j] += WF_ELEM_MAG(wf[kFT8_Gray_map[j1] + 8 * num_bins]);
|
||||
}
|
||||
|
||||
// Extract bit significance (and convert them to float)
|
||||
// 8 FSK tones = 3 bits
|
||||
for (int i = 0; i < n_bits; ++i)
|
||||
{
|
||||
if (bit_idx + i >= FTX_LDPC_N)
|
||||
{
|
||||
// Respect array size
|
||||
break;
|
||||
}
|
||||
|
||||
uint16_t mask = (n_tones >> (i + 1));
|
||||
float max_zero = -1000, max_one = -1000;
|
||||
for (int n = 0; n < n_tones; ++n)
|
||||
{
|
||||
if (n & mask)
|
||||
{
|
||||
max_one = max2(max_one, s2[n]);
|
||||
}
|
||||
else
|
||||
{
|
||||
max_zero = max2(max_zero, s2[n]);
|
||||
}
|
||||
}
|
||||
|
||||
log174[bit_idx + i] = max_one - max_zero;
|
||||
}
|
||||
}
|
||||
|
||||
// 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[]
|
||||
static void pack_bits(const uint8_t bit_array[], int num_bits, uint8_t packed[])
|
||||
{
|
||||
int num_bytes = (num_bits + 7) / 8;
|
||||
for (int i = 0; i < num_bytes; ++i)
|
||||
{
|
||||
packed[i] = 0;
|
||||
}
|
||||
|
||||
uint8_t mask = 0x80;
|
||||
int byte_idx = 0;
|
||||
for (int i = 0; i < num_bits; ++i)
|
||||
{
|
||||
if (bit_array[i])
|
||||
{
|
||||
packed[byte_idx] |= mask;
|
||||
}
|
||||
mask >>= 1;
|
||||
if (!mask)
|
||||
{
|
||||
mask = 0x80;
|
||||
++byte_idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+96
@@ -0,0 +1,96 @@
|
||||
#ifndef _INCLUDE_DECODE_H_
|
||||
#define _INCLUDE_DECODE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "constants.h"
|
||||
#include "message.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
typedef struct
|
||||
{
|
||||
float mag;
|
||||
float phase;
|
||||
} waterfall_cpx_t;
|
||||
|
||||
// #define WATERFALL_USE_PHASE
|
||||
|
||||
#ifdef WATERFALL_USE_PHASE
|
||||
#define WF_ELEM_T waterfall_cpx_t
|
||||
#define WF_ELEM_MAG(x) ((x).mag)
|
||||
#define WF_ELEM_MAG_INT(x) (int)(2 * ((x).mag + 120.0f))
|
||||
#else
|
||||
#define WF_ELEM_T uint8_t
|
||||
#define WF_ELEM_MAG(x) ((float)(x)*0.5f - 120.0f)
|
||||
#define WF_ELEM_MAG_INT(x) (int)(x)
|
||||
#endif
|
||||
|
||||
/// Input structure to ftx_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 max_blocks; ///< number of blocks (symbols) allocated in the mag array
|
||||
int num_blocks; ///< number of blocks (symbols) stored in the mag array
|
||||
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
|
||||
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
|
||||
} 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.
|
||||
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
|
||||
} ftx_candidate_t;
|
||||
|
||||
/// Structure that contains the status of various steps during decoding of a message
|
||||
typedef struct
|
||||
{
|
||||
float freq;
|
||||
float time;
|
||||
int ldpc_errors; ///< Number of LDPC errors during decoding
|
||||
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
|
||||
} 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 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_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 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_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
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // _INCLUDE_DECODE_H_
|
||||
Vendored
+195
@@ -0,0 +1,195 @@
|
||||
#include "encode.h"
|
||||
#include "constants.h"
|
||||
#include "crc.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
// Returns 1 if an odd number of bits are set in x, zero otherwise
|
||||
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
|
||||
x ^= x >> 1; // a ab bac acbd bdcae caedbf aecgbfdh
|
||||
return x % 2; // modulo 2
|
||||
}
|
||||
|
||||
// Encode via LDPC a 91-bit message and return a 174-bit codeword.
|
||||
// The generator matrix has dimensions (87,87).
|
||||
// The code is a (174,91) regular LDPC code with column weight 3.
|
||||
// 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)
|
||||
static void encode174(const uint8_t* message, uint8_t* codeword)
|
||||
{
|
||||
// This implementation accesses the generator bits straight from the packed binary representation in kFTX_LDPC_generator
|
||||
|
||||
// Fill the codeword with message and zeros, as we will only update binary ones later
|
||||
for (int j = 0; j < FTX_LDPC_N_BYTES; ++j)
|
||||
{
|
||||
codeword[j] = (j < FTX_LDPC_K_BYTES) ? message[j] : 0;
|
||||
}
|
||||
|
||||
// Compute the byte index and bit mask for the first checksum bit
|
||||
uint8_t col_mask = (0x80u >> (FTX_LDPC_K % 8u)); // bitmask of current byte
|
||||
uint8_t col_idx = FTX_LDPC_K_BYTES - 1; // index into byte array
|
||||
|
||||
// Compute the LDPC checksum bits and store them in codeword
|
||||
for (int i = 0; i < FTX_LDPC_M; ++i)
|
||||
{
|
||||
// Fast implementation of bitwise multiplication and parity checking
|
||||
// Normally nsum would contain the result of dot product between message and kFTX_LDPC_generator[i],
|
||||
// but we only compute the sum modulo 2.
|
||||
uint8_t nsum = 0;
|
||||
for (int j = 0; j < FTX_LDPC_K_BYTES; ++j)
|
||||
{
|
||||
uint8_t bits = message[j] & kFTX_LDPC_generator[i][j]; // bitwise AND (bitwise multiplication)
|
||||
nsum ^= parity8(bits); // bitwise XOR (addition modulo 2)
|
||||
}
|
||||
|
||||
// Set the current checksum bit in codeword if nsum is odd
|
||||
if (nsum % 2)
|
||||
{
|
||||
codeword[col_idx] |= col_mask;
|
||||
}
|
||||
|
||||
// Update the byte index and bit mask for the next checksum bit
|
||||
col_mask >>= 1;
|
||||
if (col_mask == 0)
|
||||
{
|
||||
col_mask = 0x80u;
|
||||
++col_idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ft8_encode(const uint8_t* payload, uint8_t* tones)
|
||||
{
|
||||
uint8_t a91[FTX_LDPC_K_BYTES]; // 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
|
||||
ftx_add_crc(payload, a91);
|
||||
|
||||
uint8_t codeword[FTX_LDPC_N_BYTES];
|
||||
encode174(a91, codeword);
|
||||
|
||||
// Message structure: S7 D29 S7 D29 S7
|
||||
// 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)
|
||||
{
|
||||
if ((i_tone >= 0) && (i_tone < 7))
|
||||
{
|
||||
tones[i_tone] = kFT8_Costas_pattern[i_tone];
|
||||
}
|
||||
else if ((i_tone >= 36) && (i_tone < 43))
|
||||
{
|
||||
tones[i_tone] = kFT8_Costas_pattern[i_tone - 36];
|
||||
}
|
||||
else if ((i_tone >= 72) && (i_tone < 79))
|
||||
{
|
||||
tones[i_tone] = kFT8_Costas_pattern[i_tone - 72];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Extract 3 bits from codeword at i-th position
|
||||
uint8_t bits3 = 0;
|
||||
|
||||
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 ft4_encode(const uint8_t* payload, uint8_t* tones)
|
||||
{
|
||||
uint8_t a91[FTX_LDPC_K_BYTES]; // Store 77 bits of payload + 14 bits CRC
|
||||
uint8_t payload_xor[10]; // Encoded payload data
|
||||
|
||||
// '[..] 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)
|
||||
{
|
||||
payload_xor[i] = payload[i] ^ kFT4_XOR_sequence[i];
|
||||
}
|
||||
|
||||
// Compute and add CRC at the end of the message
|
||||
// a91 contains 77 bits of payload + 14 bits of CRC
|
||||
ftx_add_crc(payload_xor, a91);
|
||||
|
||||
uint8_t codeword[FTX_LDPC_N_BYTES];
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+41
@@ -0,0 +1,41 @@
|
||||
#ifndef _INCLUDE_ENCODE_H_
|
||||
#define _INCLUDE_ENCODE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
// 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 ft8_encode(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 ft4_encode(const uint8_t* payload, uint8_t* tones);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // _INCLUDE_ENCODE_H_
|
||||
Vendored
+251
@@ -0,0 +1,251 @@
|
||||
//
|
||||
// LDPC decoder for FT8.
|
||||
//
|
||||
// given a 174-bit codeword as an array of log-likelihood of zero,
|
||||
// return a 174-bit corrected codeword, or zero-length array.
|
||||
// last 87 bits are the (systematic) plain-text.
|
||||
// this is an implementation of the sum-product algorithm
|
||||
// from Sarah Johnson's Iterative Error Correction book.
|
||||
// codeword[i] = log ( P(x=0) / P(x=1) )
|
||||
//
|
||||
|
||||
#include "ldpc.h"
|
||||
#include "constants.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
static int ldpc_check(uint8_t codeword[]);
|
||||
static float fast_tanh(float x);
|
||||
static float fast_atanh(float x);
|
||||
|
||||
// codeword is 174 log-likelihoods.
|
||||
// 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)
|
||||
{
|
||||
float m[FTX_LDPC_M][FTX_LDPC_N]; // ~60 kB
|
||||
float e[FTX_LDPC_M][FTX_LDPC_N]; // ~60 kB
|
||||
int min_errors = FTX_LDPC_M;
|
||||
|
||||
for (int j = 0; j < FTX_LDPC_M; j++)
|
||||
{
|
||||
for (int i = 0; i < FTX_LDPC_N; i++)
|
||||
{
|
||||
m[j][i] = codeword[i];
|
||||
e[j][i] = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
for (int iter = 0; iter < max_iters; iter++)
|
||||
{
|
||||
for (int j = 0; j < FTX_LDPC_M; j++)
|
||||
{
|
||||
for (int ii1 = 0; ii1 < kFTX_LDPC_Num_rows[j]; ii1++)
|
||||
{
|
||||
int i1 = kFTX_LDPC_Nm[j][ii1] - 1;
|
||||
float a = 1.0f;
|
||||
for (int ii2 = 0; ii2 < kFTX_LDPC_Num_rows[j]; ii2++)
|
||||
{
|
||||
int i2 = kFTX_LDPC_Nm[j][ii2] - 1;
|
||||
if (i2 != i1)
|
||||
{
|
||||
a *= fast_tanh(-m[j][i2] / 2.0f);
|
||||
}
|
||||
}
|
||||
e[j][i1] = -2.0f * fast_atanh(a);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < FTX_LDPC_N; i++)
|
||||
{
|
||||
float l = codeword[i];
|
||||
for (int j = 0; j < 3; j++)
|
||||
l += e[kFTX_LDPC_Mn[i][j] - 1][i];
|
||||
plain[i] = (l > 0) ? 1 : 0;
|
||||
}
|
||||
|
||||
int errors = ldpc_check(plain);
|
||||
|
||||
if (errors < min_errors)
|
||||
{
|
||||
// Update the current best result
|
||||
min_errors = errors;
|
||||
|
||||
if (errors == 0)
|
||||
{
|
||||
break; // Found a perfect answer
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < FTX_LDPC_N; i++)
|
||||
{
|
||||
for (int ji1 = 0; ji1 < 3; ji1++)
|
||||
{
|
||||
int j1 = kFTX_LDPC_Mn[i][ji1] - 1;
|
||||
float l = codeword[i];
|
||||
for (int ji2 = 0; ji2 < 3; ji2++)
|
||||
{
|
||||
if (ji1 != ji2)
|
||||
{
|
||||
int j2 = kFTX_LDPC_Mn[i][ji2] - 1;
|
||||
l += e[j2][i];
|
||||
}
|
||||
}
|
||||
m[j1][i] = l;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*ok = min_errors;
|
||||
}
|
||||
|
||||
//
|
||||
// does a 174-bit codeword pass the FT8's LDPC parity checks?
|
||||
// returns the number of parity errors.
|
||||
// 0 means total success.
|
||||
//
|
||||
static int ldpc_check(uint8_t codeword[])
|
||||
{
|
||||
int errors = 0;
|
||||
|
||||
for (int m = 0; m < FTX_LDPC_M; ++m)
|
||||
{
|
||||
uint8_t x = 0;
|
||||
for (int i = 0; i < kFTX_LDPC_Num_rows[m]; ++i)
|
||||
{
|
||||
x ^= codeword[kFTX_LDPC_Nm[m][i] - 1];
|
||||
}
|
||||
if (x != 0)
|
||||
{
|
||||
++errors;
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
void bp_decode(float codeword[], int max_iters, uint8_t plain[], int* ok)
|
||||
{
|
||||
float tov[FTX_LDPC_N][3];
|
||||
float toc[FTX_LDPC_M][7];
|
||||
|
||||
int min_errors = FTX_LDPC_M;
|
||||
|
||||
// initialize message data
|
||||
for (int n = 0; n < FTX_LDPC_N; ++n)
|
||||
{
|
||||
tov[n][0] = tov[n][1] = tov[n][2] = 0;
|
||||
}
|
||||
|
||||
for (int iter = 0; iter < max_iters; ++iter)
|
||||
{
|
||||
// Do a hard decision guess (tov=0 in iter 0)
|
||||
int plain_sum = 0;
|
||||
for (int n = 0; n < FTX_LDPC_N; ++n)
|
||||
{
|
||||
plain[n] = ((codeword[n] + tov[n][0] + tov[n][1] + tov[n][2]) > 0) ? 1 : 0;
|
||||
plain_sum += plain[n];
|
||||
}
|
||||
|
||||
if (plain_sum == 0)
|
||||
{
|
||||
// message converged to all-zeros, which is prohibited
|
||||
break;
|
||||
}
|
||||
|
||||
// Check to see if we have a codeword (check before we do any iter)
|
||||
int errors = ldpc_check(plain);
|
||||
|
||||
if (errors < min_errors)
|
||||
{
|
||||
// we have a better guess - update the result
|
||||
min_errors = errors;
|
||||
|
||||
if (errors == 0)
|
||||
{
|
||||
break; // Found a perfect answer
|
||||
}
|
||||
}
|
||||
|
||||
// Send messages from bits to check nodes
|
||||
for (int m = 0; m < FTX_LDPC_M; ++m)
|
||||
{
|
||||
for (int n_idx = 0; n_idx < kFTX_LDPC_Num_rows[m]; ++n_idx)
|
||||
{
|
||||
int n = kFTX_LDPC_Nm[m][n_idx] - 1;
|
||||
// for each (n, m)
|
||||
float Tnm = codeword[n];
|
||||
for (int m_idx = 0; m_idx < 3; ++m_idx)
|
||||
{
|
||||
if ((kFTX_LDPC_Mn[n][m_idx] - 1) != m)
|
||||
{
|
||||
Tnm += tov[n][m_idx];
|
||||
}
|
||||
}
|
||||
toc[m][n_idx] = fast_tanh(-Tnm / 2);
|
||||
}
|
||||
}
|
||||
|
||||
// send messages from check nodes to variable nodes
|
||||
for (int n = 0; n < FTX_LDPC_N; ++n)
|
||||
{
|
||||
for (int m_idx = 0; m_idx < 3; ++m_idx)
|
||||
{
|
||||
int m = kFTX_LDPC_Mn[n][m_idx] - 1;
|
||||
// for each (n, m)
|
||||
float Tmn = 1.0f;
|
||||
for (int n_idx = 0; n_idx < kFTX_LDPC_Num_rows[m]; ++n_idx)
|
||||
{
|
||||
if ((kFTX_LDPC_Nm[m][n_idx] - 1) != n)
|
||||
{
|
||||
Tmn *= toc[m][n_idx];
|
||||
}
|
||||
}
|
||||
tov[n][m_idx] = -2 * fast_atanh(Tmn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*ok = min_errors;
|
||||
}
|
||||
|
||||
// Ideas for approximating tanh/atanh:
|
||||
// * https://varietyofsound.wordpress.com/2011/02/14/efficient-tanh-computation-using-lamberts-continued-fraction/
|
||||
// * http://functions.wolfram.com/ElementaryFunctions/ArcTanh/10/0001/
|
||||
// * https://mathr.co.uk/blog/2017-09-06_approximating_hyperbolic_tangent.html
|
||||
// * https://math.stackexchange.com/a/446411
|
||||
|
||||
static float fast_tanh(float x)
|
||||
{
|
||||
if (x < -4.97f)
|
||||
{
|
||||
return -1.0f;
|
||||
}
|
||||
if (x > 4.97f)
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
float x2 = x * x;
|
||||
// float a = x * (135135.0f + x2 * (17325.0f + x2 * (378.0f + x2)));
|
||||
// float b = 135135.0f + x2 * (62370.0f + x2 * (3150.0f + x2 * 28.0f));
|
||||
// float a = x * (10395.0f + x2 * (1260.0f + x2 * 21.0f));
|
||||
// float b = 10395.0f + x2 * (4725.0f + x2 * (210.0f + x2));
|
||||
float a = x * (945.0f + x2 * (105.0f + x2));
|
||||
float b = 945.0f + x2 * (420.0f + x2 * 15.0f);
|
||||
return a / b;
|
||||
}
|
||||
|
||||
static float fast_atanh(float x)
|
||||
{
|
||||
float x2 = x * x;
|
||||
// float a = x * (-15015.0f + x2 * (19250.0f + x2 * (-5943.0f + x2 * 256.0f)));
|
||||
// float b = (-15015.0f + x2 * (24255.0f + x2 * (-11025.0f + x2 * 1225.0f)));
|
||||
// float a = x * (-1155.0f + x2 * (1190.0f + x2 * -231.0f));
|
||||
// float b = (-1155.0f + x2 * (1575.0f + x2 * (-525.0f + x2 * 25.0f)));
|
||||
float a = x * (945.0f + x2 * (-735.0f + x2 * 64.0f));
|
||||
float b = (945.0f + x2 * (-1050.0f + x2 * 225.0f));
|
||||
return a / b;
|
||||
}
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
#ifndef _INCLUDE_LDPC_H_
|
||||
#define _INCLUDE_LDPC_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
// codeword is 174 log-likelihoods.
|
||||
// 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 bp_decode(float codeword[], int max_iters, uint8_t plain[], int* ok);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // _INCLUDE_LDPC_H_
|
||||
Vendored
+1155
File diff suppressed because it is too large
Load Diff
Vendored
+160
@@ -0,0 +1,160 @@
|
||||
#ifndef _INCLUDE_MESSAGE_H_
|
||||
#define _INCLUDE_MESSAGE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
#define FTX_PAYLOAD_LENGTH_BYTES 10 ///< number of bytes to hold 77 bits of FTx payload data
|
||||
#define FTX_MAX_MESSAGE_LENGTH 35 ///< max message length = callsign[13] + space + callsign[13] + space + report[6] + terminator
|
||||
#define FTX_MAX_MESSAGE_FIELDS 3 // may need to get longer for multi-part messages (DXpedition, contest etc.)
|
||||
|
||||
/// Structure that holds the decoded message
|
||||
typedef struct
|
||||
{
|
||||
uint8_t payload[FTX_PAYLOAD_LENGTH_BYTES];
|
||||
uint16_t hash; ///< Hash value to be used in hash table and quick checking for duplicates
|
||||
} ftx_message_t;
|
||||
|
||||
// ----------------------------------------------------------------------------------
|
||||
// i3.n3 Example message Bits Total Purpose
|
||||
// ----------------------------------------------------------------------------------
|
||||
// 0.0 FREE TEXT MSG 71 71 Free text
|
||||
// 0.1 K1ABC RR73; W9XYZ <KH1/KH7Z> -12 28 28 10 5 71 DXpedition Mode
|
||||
// 0.2 PA3XYZ/P R 590003 IO91NP 28 1 1 3 12 25 70 EU VHF contest
|
||||
// 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
|
||||
// 0.5 123456789ABCDEF012 71 71 Telemetry (18 hex)
|
||||
// 0.6 K1ABC RR73; CQ W9XYZ EN37 28 28 15 71 Contesting
|
||||
// 0.7 ... tbd
|
||||
// 1 WA9XYZ/R KA1ABC/R R FN42 28 1 28 1 1 15 74 Standard msg
|
||||
// 2 PA3XYZ/P GM4ABC/P R JO22 28 1 28 1 1 15 74 EU VHF contest
|
||||
// 3 TU; W9XYZ K1ABC R 579 MA 1 28 28 1 3 13 74 ARRL RTTY Roundup
|
||||
// 4 <WA9XYZ> PJ4/KA1ABC RR73 12 58 1 2 1 74 Nonstandard calls
|
||||
// 5 TU; W9XYZ K1ABC R-07 FN 1 28 28 1 7 9 74 WWROF contest ?
|
||||
|
||||
typedef enum
|
||||
{
|
||||
FTX_MESSAGE_TYPE_FREE_TEXT, // 0.0 FREE TEXT MSG 71 71 Free text
|
||||
FTX_MESSAGE_TYPE_DXPEDITION, // 0.1 K1ABC RR73; W9XYZ <KH1/KH7Z> -12 28 28 10 5 71 DXpedition Mode
|
||||
FTX_MESSAGE_TYPE_EU_VHF, // 0.2 PA3XYZ/P R 590003 IO91NP 28 1 1 3 12 25 70 EU VHF contest
|
||||
FTX_MESSAGE_TYPE_ARRL_FD, // 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
|
||||
FTX_MESSAGE_TYPE_TELEMETRY, // 0.5 0123456789abcdef01 71 71 Telemetry (18 hex)
|
||||
FTX_MESSAGE_TYPE_CONTESTING, // 0.6 K1ABC RR73; CQ W9XYZ EN37 28 28 15 71 Contesting
|
||||
FTX_MESSAGE_TYPE_STANDARD, // 1 WA9XYZ/R KA1ABC/R R FN42 28 1 28 1 1 15 74 Standard msg
|
||||
// 2 PA3XYZ/P GM4ABC/P R JO22 28 1 28 1 1 15 74 EU VHF contest
|
||||
FTX_MESSAGE_TYPE_ARRL_RTTY, // 3 TU; W9XYZ K1ABC R 579 MA 1 28 28 1 3 13 74 ARRL RTTY Roundup
|
||||
FTX_MESSAGE_TYPE_NONSTD_CALL, // 4 <WA9XYZ> PJ4/KA1ABC RR73 12 58 1 2 1 74 Nonstandard calls
|
||||
FTX_MESSAGE_TYPE_WWROF, // 5 TU; W9XYZ K1ABC R-07 FN 1 28 28 1 7 9 74 WWROF contest ?
|
||||
FTX_MESSAGE_TYPE_UNKNOWN // Unknown or invalid type
|
||||
} ftx_message_type_t;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
FTX_CALLSIGN_HASH_22_BITS,
|
||||
FTX_CALLSIGN_HASH_12_BITS,
|
||||
FTX_CALLSIGN_HASH_10_BITS
|
||||
} 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_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;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
FTX_MESSAGE_RC_OK,
|
||||
FTX_MESSAGE_RC_ERROR_CALLSIGN1,
|
||||
FTX_MESSAGE_RC_ERROR_CALLSIGN2,
|
||||
FTX_MESSAGE_RC_ERROR_SUFFIX,
|
||||
FTX_MESSAGE_RC_ERROR_GRID,
|
||||
FTX_MESSAGE_RC_ERROR_TYPE
|
||||
} ftx_message_rc_t;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
FTX_FIELD_UNKNOWN,
|
||||
FTX_FIELD_NONE,
|
||||
FTX_FIELD_TOKEN, // RRR, RR73, 73, DE, QRZ, CQ, ...
|
||||
FTX_FIELD_TOKEN_WITH_ARG, // CQ nnn, CQ abcd
|
||||
FTX_FIELD_CALL,
|
||||
FTX_FIELD_GRID,
|
||||
FTX_FIELD_RST
|
||||
} ftx_field_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
// parallel arrays:
|
||||
// e.g. "CQ POTA W9XYZ AB12" generates
|
||||
// types { FTX_FIELD_TOKEN_WITH_ARG, FTX_FIELD_CALL, FTX_FIELD_CALL_GRID" }
|
||||
// offsets { 0, 8, 14 }
|
||||
// Both arrays end where offsets[i] < 0
|
||||
ftx_field_t types[FTX_MAX_MESSAGE_FIELDS];
|
||||
int16_t offsets[FTX_MAX_MESSAGE_FIELDS];
|
||||
} ftx_message_offsets_t;
|
||||
|
||||
// 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);
|
||||
|
||||
uint8_t ftx_message_get_i3(const ftx_message_t* msg);
|
||||
uint8_t ftx_message_get_n3(const ftx_message_t* msg);
|
||||
ftx_message_type_t ftx_message_get_type(const ftx_message_t* msg);
|
||||
|
||||
// bool ftx_message_check_recipient(const ftx_message_t* msg, const char* callsign);
|
||||
|
||||
/// Pack (encode) a callsign in the standard way, and return the numeric representation.
|
||||
/// Returns -1 if \a callsign cannot be encoded in the standard way.
|
||||
/// This function can be used to decide whether to call ftx_message_encode_std() or ftx_message_decode_nonstd().
|
||||
/// Alternatively, ftx_message_encode_std() itself fails when one of the callsigns cannot be packed this way.
|
||||
int32_t pack_basecall(const char* callsign, int length);
|
||||
|
||||
/// Pack (encode) a text message, guessing which message type to use and falling back on failure:
|
||||
/// if there are 3 or fewer tokens, try ftx_message_encode_std first,
|
||||
/// then ftx_message_encode_nonstd if that fails because of a non-standard callsign;
|
||||
/// otherwise fall back to ftx_message_encode_free.
|
||||
/// If you already know which type to use, you can call one of those functions directly.
|
||||
ftx_message_rc_t ftx_message_encode(ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if, const char* message_text);
|
||||
|
||||
/// Pack Type 1 (Standard 77-bit message) or Type 2 (ditto, with a "/P" call) message
|
||||
/// Rules of callsign validity:
|
||||
/// - call_to can be 'DE', 'CQ', 'QRZ', 'CQ_nnn' (three digits), or 'CQ_abcd' (four letters)
|
||||
/// - nonstandard calls within <> brackets are allowed, if they don't contain '/'
|
||||
ftx_message_rc_t ftx_message_encode_std(ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if, const char* call_to, const char* call_de, const char* extra);
|
||||
|
||||
/// Pack Type 4 (One nonstandard call and one hashed call) message
|
||||
ftx_message_rc_t ftx_message_encode_nonstd(ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if, const char* call_to, const char* call_de, const char* extra);
|
||||
|
||||
/// Pack plain text, up to 13 characters
|
||||
ftx_message_rc_t ftx_message_encode_free(ftx_message_t* msg, const char* text);
|
||||
ftx_message_rc_t ftx_message_encode_telemetry(ftx_message_t* msg, const uint8_t* telemetry);
|
||||
|
||||
ftx_message_rc_t ftx_message_decode(const ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if, char* message, ftx_message_offsets_t* offsets);
|
||||
ftx_message_rc_t ftx_message_decode_std(const ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if, char* call_to, char* call_de, char* extra, ftx_field_t field_types[FTX_MAX_MESSAGE_FIELDS]);
|
||||
ftx_message_rc_t ftx_message_decode_nonstd(const ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if, char* call_to, char* call_de, char* extra, ftx_field_t field_types[FTX_MAX_MESSAGE_FIELDS]);
|
||||
void ftx_message_decode_free(const ftx_message_t* msg, char* text);
|
||||
void ftx_message_decode_telemetry_hex(const ftx_message_t* msg, char* telemetry_hex);
|
||||
void ftx_message_decode_telemetry(const ftx_message_t* msg, uint8_t* telemetry);
|
||||
|
||||
#ifdef FTX_DEBUG_PRINT
|
||||
void ftx_message_print(ftx_message_t* msg);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // _INCLUDE_MESSAGE_H_
|
||||
Vendored
+303
@@ -0,0 +1,303 @@
|
||||
#include "text.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
const char* trim_front(const char* str, char to_trim)
|
||||
{
|
||||
// Skip leading to_trim characters
|
||||
while (*str == to_trim)
|
||||
{
|
||||
str++;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
void trim_back(char* str, char to_trim)
|
||||
{
|
||||
// Skip trailing to_trim characters by replacing them with '\0' characters
|
||||
int idx = strlen(str) - 1;
|
||||
while (idx >= 0 && str[idx] == to_trim)
|
||||
{
|
||||
str[idx--] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
char* trim(char* str)
|
||||
{
|
||||
str = (char*)trim_front(str, ' ');
|
||||
trim_back(str, ' ');
|
||||
// return a pointer to the first non-whitespace character
|
||||
return str;
|
||||
}
|
||||
|
||||
char* trim_brackets(char* str)
|
||||
{
|
||||
str = (char*)trim_front(str, '<');
|
||||
trim_back(str, '>');
|
||||
// return a pointer to the first non-whitespace character
|
||||
return str;
|
||||
}
|
||||
|
||||
void trim_copy(char* trimmed, const char* str)
|
||||
{
|
||||
str = (char*)trim_front(str, ' ');
|
||||
int len = strlen(str) - 1;
|
||||
while (len >= 0 && str[len] == ' ')
|
||||
{
|
||||
len--;
|
||||
}
|
||||
strncpy(trimmed, str, len + 1);
|
||||
trimmed[len + 1] = '\0';
|
||||
}
|
||||
|
||||
char to_upper(char c)
|
||||
{
|
||||
return (c >= 'a' && c <= 'z') ? (c - 'a' + 'A') : c;
|
||||
}
|
||||
|
||||
bool is_digit(char c)
|
||||
{
|
||||
return (c >= '0') && (c <= '9');
|
||||
}
|
||||
|
||||
bool is_letter(char c)
|
||||
{
|
||||
return ((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z'));
|
||||
}
|
||||
|
||||
bool is_space(char c)
|
||||
{
|
||||
return (c == ' ');
|
||||
}
|
||||
|
||||
bool in_range(char c, char min, char max)
|
||||
{
|
||||
return (c >= min) && (c <= max);
|
||||
}
|
||||
|
||||
bool starts_with(const char* string, const char* prefix)
|
||||
{
|
||||
return 0 == memcmp(string, prefix, strlen(prefix));
|
||||
}
|
||||
|
||||
bool ends_with(const char* string, const char* suffix)
|
||||
{
|
||||
int pos = strlen(string) - strlen(suffix);
|
||||
if (pos >= 0)
|
||||
{
|
||||
return 0 == memcmp(string + pos, suffix, strlen(suffix));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool equals(const char* string1, const char* string2)
|
||||
{
|
||||
return 0 == strcmp(string1, string2);
|
||||
}
|
||||
|
||||
// Text message formatting:
|
||||
// - replaces lowercase letters with uppercase
|
||||
// - merges consecutive spaces into single space
|
||||
void fmtmsg(char* msg_out, const char* msg_in)
|
||||
{
|
||||
char c;
|
||||
char last_out = 0;
|
||||
while ((c = *msg_in))
|
||||
{
|
||||
if (c != ' ' || last_out != ' ')
|
||||
{
|
||||
last_out = to_upper(c);
|
||||
*msg_out = last_out;
|
||||
++msg_out;
|
||||
}
|
||||
++msg_in;
|
||||
}
|
||||
*msg_out = 0; // Add zero termination
|
||||
}
|
||||
|
||||
// Returns pointer to the null terminator within the given string (destination)
|
||||
char* append_string(char* string, const char* token)
|
||||
{
|
||||
while (*token != '\0')
|
||||
{
|
||||
*string = *token;
|
||||
string++;
|
||||
token++;
|
||||
}
|
||||
*string = '\0';
|
||||
return string;
|
||||
}
|
||||
|
||||
const char* copy_token(char* token, int length, const char* string)
|
||||
{
|
||||
// Copy characters until a whitespace character or the end of string
|
||||
while (*string != ' ' && *string != '\0')
|
||||
{
|
||||
if (length > 1)
|
||||
{
|
||||
*token = *string;
|
||||
token++;
|
||||
length--;
|
||||
}
|
||||
string++;
|
||||
}
|
||||
// Fill up the rest of token with \0 terminators
|
||||
while (length > 0)
|
||||
{
|
||||
*token = '\0';
|
||||
token++;
|
||||
length--;
|
||||
}
|
||||
// Skip whitespace characters
|
||||
while (*string == ' ')
|
||||
{
|
||||
string++;
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
// Parse a 2 digit integer from string
|
||||
int dd_to_int(const char* str, int length)
|
||||
{
|
||||
int result = 0;
|
||||
bool negative;
|
||||
int i;
|
||||
if (str[0] == '-')
|
||||
{
|
||||
negative = true;
|
||||
i = 1; // Consume the - sign
|
||||
}
|
||||
else
|
||||
{
|
||||
negative = false;
|
||||
i = (str[0] == '+') ? 1 : 0; // Consume a + sign if found
|
||||
}
|
||||
|
||||
while (i < length)
|
||||
{
|
||||
if (str[i] == 0)
|
||||
break;
|
||||
if (!is_digit(str[i]))
|
||||
break;
|
||||
result *= 10;
|
||||
result += (str[i] - '0');
|
||||
++i;
|
||||
}
|
||||
|
||||
return negative ? -result : result;
|
||||
}
|
||||
|
||||
// Convert a 2 digit integer to string
|
||||
void int_to_dd(char* str, int value, int width, bool full_sign)
|
||||
{
|
||||
if (value < 0)
|
||||
{
|
||||
*str = '-';
|
||||
++str;
|
||||
value = -value;
|
||||
}
|
||||
else if (full_sign)
|
||||
{
|
||||
*str = '+';
|
||||
++str;
|
||||
}
|
||||
|
||||
int divisor = 1;
|
||||
for (int i = 0; i < width - 1; ++i)
|
||||
{
|
||||
divisor *= 10;
|
||||
}
|
||||
|
||||
while (divisor >= 1)
|
||||
{
|
||||
int digit = value / divisor;
|
||||
|
||||
*str = '0' + digit;
|
||||
++str;
|
||||
|
||||
value -= digit * divisor;
|
||||
divisor /= 10;
|
||||
}
|
||||
*str = 0; // Add zero terminator
|
||||
}
|
||||
|
||||
char charn(int c, ft8_char_table_e table)
|
||||
{
|
||||
if ((table != FT8_CHAR_TABLE_ALPHANUM) && (table != FT8_CHAR_TABLE_NUMERIC))
|
||||
{
|
||||
if (c == 0)
|
||||
return ' ';
|
||||
c -= 1;
|
||||
}
|
||||
if (table != FT8_CHAR_TABLE_LETTERS_SPACE)
|
||||
{
|
||||
if (c < 10)
|
||||
return '0' + c;
|
||||
c -= 10;
|
||||
}
|
||||
if (table != FT8_CHAR_TABLE_NUMERIC)
|
||||
{
|
||||
if (c < 26)
|
||||
return 'A' + c;
|
||||
c -= 26;
|
||||
}
|
||||
|
||||
if (table == FT8_CHAR_TABLE_FULL)
|
||||
{
|
||||
if (c < 5)
|
||||
return "+-./?"[c];
|
||||
}
|
||||
else if (table == FT8_CHAR_TABLE_ALPHANUM_SPACE_SLASH)
|
||||
{
|
||||
if (c == 0)
|
||||
return '/';
|
||||
}
|
||||
|
||||
return '_'; // unknown character, should never get here
|
||||
}
|
||||
|
||||
// Convert character to its index (charn in reverse) according to a table
|
||||
int nchar(char c, ft8_char_table_e table)
|
||||
{
|
||||
int n = 0;
|
||||
if ((table != FT8_CHAR_TABLE_ALPHANUM) && (table != FT8_CHAR_TABLE_NUMERIC))
|
||||
{
|
||||
if (c == ' ')
|
||||
return n + 0;
|
||||
n += 1;
|
||||
}
|
||||
if (table != FT8_CHAR_TABLE_LETTERS_SPACE)
|
||||
{
|
||||
if (c >= '0' && c <= '9')
|
||||
return n + (c - '0');
|
||||
n += 10;
|
||||
}
|
||||
if (table != FT8_CHAR_TABLE_NUMERIC)
|
||||
{
|
||||
if (c >= 'A' && c <= 'Z')
|
||||
return n + (c - 'A');
|
||||
n += 26;
|
||||
}
|
||||
|
||||
if (table == FT8_CHAR_TABLE_FULL)
|
||||
{
|
||||
if (c == '+')
|
||||
return n + 0;
|
||||
if (c == '-')
|
||||
return n + 1;
|
||||
if (c == '.')
|
||||
return n + 2;
|
||||
if (c == '/')
|
||||
return n + 3;
|
||||
if (c == '?')
|
||||
return n + 4;
|
||||
}
|
||||
else if (table == FT8_CHAR_TABLE_ALPHANUM_SPACE_SLASH)
|
||||
{
|
||||
if (c == '/')
|
||||
return n + 0;
|
||||
}
|
||||
|
||||
// Character not found
|
||||
return -1;
|
||||
}
|
||||
Vendored
+82
@@ -0,0 +1,82 @@
|
||||
#ifndef _INCLUDE_TEXT_H_
|
||||
#define _INCLUDE_TEXT_H_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
// Utility functions for characters and strings
|
||||
|
||||
const char* trim_front(const char* str, char to_trim);
|
||||
void trim_back(char* str, char to_trim);
|
||||
|
||||
/// In-place whitespace trim from front and back:
|
||||
/// 1) trims the back by changing whitespaces to '\0'
|
||||
/// 2) trims the front by skipping whitespaces
|
||||
/// @return trimmed string (pointer to first non-whitespace character)
|
||||
char* trim(char* str);
|
||||
|
||||
/// Trim whitespace from start and end of string
|
||||
void trim_copy(char* trimmed, const char* str);
|
||||
|
||||
/// In-place trim of <> characters from front and back:
|
||||
/// 1) trims the back by changing > to '\0'
|
||||
/// 2) trims the front by skipping <
|
||||
/// @return trimmed string (pointer to first non-whitespace character)
|
||||
char* trim_brackets(char* str);
|
||||
|
||||
char to_upper(char c);
|
||||
bool is_digit(char c);
|
||||
bool is_letter(char c);
|
||||
bool is_space(char c);
|
||||
bool in_range(char c, char min, char max);
|
||||
bool starts_with(const char* string, const char* prefix);
|
||||
bool ends_with(const char* string, const char* suffix);
|
||||
bool equals(const char* string1, const char* string2);
|
||||
|
||||
// Text message formatting:
|
||||
// - replaces lowercase letters with uppercase
|
||||
// - merges consecutive spaces into single space
|
||||
void fmtmsg(char* msg_out, const char* msg_in);
|
||||
|
||||
/// Extract and copy a space-delimited token from a string.
|
||||
/// When the last token has been extracted, the return value points to the terminating zero character.
|
||||
/// @param[out] token Buffer to receive the extracted token
|
||||
/// @param[in] length Length of the token buffer (number of characters)
|
||||
/// @param[in] string Pointer to the string
|
||||
/// @return Pointer to the next token (can be passed to copy_token to extract the next token)
|
||||
const char* copy_token(char* token, int length, const char* string);
|
||||
|
||||
char* append_string(char* string, const char* token);
|
||||
|
||||
// Parse a 2 digit integer from string
|
||||
int dd_to_int(const char* str, int length);
|
||||
|
||||
// Convert a 2 digit integer to string
|
||||
void int_to_dd(char* str, int value, int width, bool full_sign);
|
||||
|
||||
typedef enum
|
||||
{
|
||||
FT8_CHAR_TABLE_FULL, // table[42] " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+-./?"
|
||||
FT8_CHAR_TABLE_ALPHANUM_SPACE_SLASH, // table[38] " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ/"
|
||||
FT8_CHAR_TABLE_ALPHANUM_SPACE, // table[37] " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
FT8_CHAR_TABLE_LETTERS_SPACE, // table[27] " ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
FT8_CHAR_TABLE_ALPHANUM, // table[36] "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
FT8_CHAR_TABLE_NUMERIC, // table[10] "0123456789"
|
||||
} ft8_char_table_e;
|
||||
|
||||
/// Convert integer index to ASCII character according to one of character tables
|
||||
char charn(int c, ft8_char_table_e table);
|
||||
|
||||
/// Look up the index of an ASCII character in one of character tables
|
||||
int nchar(char c, ft8_char_table_e table);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // _INCLUDE_TEXT_H_
|
||||
Reference in New Issue
Block a user