First draft
This commit is contained in:
parent
1efae07b2e
commit
5aa5bd077c
9 changed files with 919 additions and 2 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
*.o
|
5
Makefile
Normal file
5
Makefile
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
CXXFLAGS = -std=c++14
|
||||||
|
LDFLAGS = -lm
|
||||||
|
|
||||||
|
test: test.o encode.o pack.o
|
||||||
|
$(CXX) $(LDFLAGS) -o $@ $^
|
17
README.md
17
README.md
|
@ -1,2 +1,15 @@
|
||||||
# ft8_lib
|
# FT8 library
|
||||||
FT8 library
|
|
||||||
|
A C++ implementation of FT8 protocol, mostly intended for experimentation with microcontrollers. At the moment only encoding is implemented.
|
||||||
|
|
||||||
|
The encoding process is relatively light on resources, and an Arduino should be perfectly capable of running this code.
|
||||||
|
|
||||||
|
The intent of this library is to foster experimentation with e.g. automated beacons. FT8 already supports free-text messages and the upcoming new version will support raw telemetry data (71 bits).
|
||||||
|
|
||||||
|
Work is in progress (possibly taking forever) to explore decoding options. On a fast 32-bit microcontroller decoding might be possible, perhaps with some tradeoffs.
|
||||||
|
|
||||||
|
# References and credits
|
||||||
|
|
||||||
|
Thanks to Robert Morris, AB1HL, whose Python code (https://github.com/rtmrtmrtmrtm/weakmon) inspired this and helped to test various parts of the code.
|
||||||
|
|
||||||
|
This would not of course be possible without the original WSJT-X code, which is mostly written in Fortran (http://physics.princeton.edu/pulsar/K1JT/wsjtx.html). I believe that is the only 'documentation' of the FT8 protocol available, and the source code was used as such in this project.
|
||||||
|
|
262
encode.cpp
Normal file
262
encode.cpp
Normal file
|
@ -0,0 +1,262 @@
|
||||||
|
#include "encode.h"
|
||||||
|
|
||||||
|
constexpr int N = 174, K = 87, M = N-K;
|
||||||
|
|
||||||
|
constexpr uint16_t POLYNOMIAL = 0xC06; // CRC-12 polynomial without the leading (MSB) 1
|
||||||
|
|
||||||
|
// Parity generator matrix for (174,87) code, stored in bitpacked format (MSB first)
|
||||||
|
constexpr uint8_t G[87][11] = {
|
||||||
|
{ 0x23, 0xbb, 0xa8, 0x30, 0xe2, 0x3b, 0x6b, 0x6f, 0x50, 0x98, 0x2e },
|
||||||
|
{ 0x1f, 0x8e, 0x55, 0xda, 0x21, 0x8c, 0x5d, 0xf3, 0x30, 0x90, 0x52 },
|
||||||
|
{ 0xca, 0x7b, 0x32, 0x17, 0xcd, 0x92, 0xbd, 0x59, 0xa5, 0xae, 0x20 },
|
||||||
|
{ 0x56, 0xf7, 0x83, 0x13, 0x53, 0x7d, 0x0f, 0x43, 0x82, 0x96, 0x4e },
|
||||||
|
{ 0x29, 0xc2, 0x9d, 0xba, 0x9c, 0x54, 0x5e, 0x26, 0x77, 0x62, 0xfe },
|
||||||
|
{ 0x6b, 0xe3, 0x96, 0xb5, 0xe2, 0xe8, 0x19, 0xe3, 0x73, 0x34, 0x0c },
|
||||||
|
{ 0x29, 0x35, 0x48, 0xa1, 0x38, 0x85, 0x83, 0x28, 0xaf, 0x42, 0x10 },
|
||||||
|
{ 0xcb, 0x6c, 0x6a, 0xfc, 0xdc, 0x28, 0xbb, 0x3f, 0x7c, 0x6e, 0x86 },
|
||||||
|
{ 0x3f, 0x2a, 0x86, 0xf5, 0xc5, 0xbd, 0x22, 0x5c, 0x96, 0x11, 0x50 },
|
||||||
|
{ 0x84, 0x9d, 0xd2, 0xd6, 0x36, 0x73, 0x48, 0x18, 0x60, 0xf6, 0x2c },
|
||||||
|
{ 0x56, 0xcd, 0xae, 0xc6, 0xe7, 0xae, 0x14, 0xb4, 0x3f, 0xee, 0xee },
|
||||||
|
{ 0x04, 0xef, 0x5c, 0xfa, 0x37, 0x66, 0xba, 0x77, 0x8f, 0x45, 0xa4 },
|
||||||
|
{ 0xc5, 0x25, 0xae, 0x4b, 0xd4, 0xf6, 0x27, 0x32, 0x0a, 0x39, 0x74 },
|
||||||
|
{ 0xfe, 0x37, 0x80, 0x29, 0x41, 0xd6, 0x6d, 0xde, 0x02, 0xb9, 0x9c },
|
||||||
|
{ 0x41, 0xfd, 0x95, 0x20, 0xb2, 0xe4, 0xab, 0xeb, 0x2f, 0x98, 0x9c },
|
||||||
|
{ 0x40, 0x90, 0x7b, 0x01, 0x28, 0x0f, 0x03, 0xc0, 0x32, 0x39, 0x46 },
|
||||||
|
{ 0x7f, 0xb3, 0x6c, 0x24, 0x08, 0x5a, 0x34, 0xd8, 0xc1, 0xdb, 0xc4 },
|
||||||
|
{ 0x40, 0xfc, 0x3e, 0x44, 0xbb, 0x7d, 0x2b, 0xb2, 0x75, 0x6e, 0x44 },
|
||||||
|
{ 0xd3, 0x8a, 0xb0, 0xa1, 0xd2, 0xe5, 0x2a, 0x8e, 0xc3, 0xbc, 0x76 },
|
||||||
|
{ 0x3d, 0x0f, 0x92, 0x9e, 0xf3, 0x94, 0x9b, 0xd8, 0x4d, 0x47, 0x34 },
|
||||||
|
{ 0x45, 0xd3, 0x81, 0x4f, 0x50, 0x40, 0x64, 0xf8, 0x05, 0x49, 0xae },
|
||||||
|
{ 0xf1, 0x4d, 0xbf, 0x26, 0x38, 0x25, 0xd0, 0xbd, 0x04, 0xb0, 0x5e },
|
||||||
|
{ 0xf0, 0x8a, 0x91, 0xfb, 0x2e, 0x1f, 0x78, 0x29, 0x06, 0x19, 0xa8 },
|
||||||
|
{ 0x7a, 0x8d, 0xec, 0x79, 0xa5, 0x1e, 0x8a, 0xc5, 0x38, 0x80, 0x22 },
|
||||||
|
{ 0xca, 0x41, 0x86, 0xdd, 0x44, 0xc3, 0x12, 0x15, 0x65, 0xcf, 0x5c },
|
||||||
|
{ 0xdb, 0x71, 0x4f, 0x8f, 0x64, 0xe8, 0xac, 0x7a, 0xf1, 0xa7, 0x6e },
|
||||||
|
{ 0x8d, 0x02, 0x74, 0xde, 0x71, 0xe7, 0xc1, 0xa8, 0x05, 0x5e, 0xb0 },
|
||||||
|
{ 0x51, 0xf8, 0x15, 0x73, 0xdd, 0x40, 0x49, 0xb0, 0x82, 0xde, 0x14 },
|
||||||
|
{ 0xd0, 0x37, 0xdb, 0x82, 0x51, 0x75, 0xd8, 0x51, 0xf3, 0xaf, 0x00 },
|
||||||
|
{ 0xd8, 0xf9, 0x37, 0xf3, 0x18, 0x22, 0xe5, 0x7c, 0x56, 0x23, 0x70 },
|
||||||
|
{ 0x1b, 0xf1, 0x49, 0x06, 0x07, 0xc5, 0x40, 0x32, 0x66, 0x0e, 0xde },
|
||||||
|
{ 0x16, 0x16, 0xd7, 0x80, 0x18, 0xd0, 0xb4, 0x74, 0x5c, 0xa0, 0xf2 },
|
||||||
|
{ 0xa9, 0xfa, 0x8e, 0x50, 0xbc, 0xb0, 0x32, 0xc8, 0x5e, 0x33, 0x04 },
|
||||||
|
{ 0x83, 0xf6, 0x40, 0xf1, 0xa4, 0x8a, 0x8e, 0xbc, 0x04, 0x43, 0xea },
|
||||||
|
{ 0xec, 0xa9, 0xaf, 0xa0, 0xf6, 0xb0, 0x1d, 0x92, 0x30, 0x5e, 0xdc },
|
||||||
|
{ 0x37, 0x76, 0xaf, 0x54, 0xcc, 0xfb, 0xae, 0x91, 0x6a, 0xfd, 0xe6 },
|
||||||
|
{ 0x6a, 0xbb, 0x21, 0x2d, 0x97, 0x39, 0xdf, 0xc0, 0x25, 0x80, 0xf2 },
|
||||||
|
{ 0x05, 0x20, 0x9a, 0x0a, 0xbb, 0x53, 0x0b, 0x9e, 0x7e, 0x34, 0xb0 },
|
||||||
|
{ 0x61, 0x2f, 0x63, 0xac, 0xc0, 0x25, 0xb6, 0xab, 0x47, 0x6f, 0x7c },
|
||||||
|
{ 0x0a, 0xf7, 0x72, 0x31, 0x61, 0xec, 0x22, 0x30, 0x80, 0xbe, 0x86 },
|
||||||
|
{ 0xa8, 0xfc, 0x90, 0x69, 0x76, 0xc3, 0x56, 0x69, 0xe7, 0x9c, 0xe0 },
|
||||||
|
{ 0x45, 0xb7, 0xab, 0x62, 0x42, 0xb7, 0x74, 0x74, 0xd9, 0xf1, 0x1a },
|
||||||
|
{ 0xb2, 0x74, 0xdb, 0x8a, 0xbd, 0x3c, 0x6f, 0x39, 0x6e, 0xa3, 0x56 },
|
||||||
|
{ 0x90, 0x59, 0xdf, 0xa2, 0xbb, 0x20, 0xef, 0x7e, 0xf7, 0x3a, 0xd4 },
|
||||||
|
{ 0x3d, 0x18, 0x8e, 0xa4, 0x77, 0xf6, 0xfa, 0x41, 0x31, 0x7a, 0x4e },
|
||||||
|
{ 0x8d, 0x90, 0x71, 0xb7, 0xe7, 0xa6, 0xa2, 0xee, 0xd6, 0x96, 0x5e },
|
||||||
|
{ 0xa3, 0x77, 0x25, 0x37, 0x73, 0xea, 0x67, 0x83, 0x67, 0xc3, 0xf6 },
|
||||||
|
{ 0xec, 0xbd, 0x7c, 0x73, 0xb9, 0xcd, 0x34, 0xc3, 0x72, 0x0c, 0x8a },
|
||||||
|
{ 0xb6, 0x53, 0x7f, 0x41, 0x7e, 0x61, 0xd1, 0xa7, 0x08, 0x53, 0x36 },
|
||||||
|
{ 0x6c, 0x28, 0x0d, 0x2a, 0x05, 0x23, 0xd9, 0xc4, 0xbc, 0x59, 0x46 },
|
||||||
|
{ 0xd3, 0x6d, 0x66, 0x2a, 0x69, 0xae, 0x24, 0xb7, 0x4d, 0xcb, 0xd8 },
|
||||||
|
{ 0xd7, 0x47, 0xbf, 0xc5, 0xfd, 0x65, 0xef, 0x70, 0xfb, 0xd9, 0xbc },
|
||||||
|
{ 0xa9, 0xfa, 0x2e, 0xef, 0xa6, 0xf8, 0x79, 0x6a, 0x35, 0x57, 0x72 },
|
||||||
|
{ 0xcc, 0x9d, 0xa5, 0x5f, 0xe0, 0x46, 0xd0, 0xcb, 0x3a, 0x77, 0x0c },
|
||||||
|
{ 0xf6, 0xad, 0x48, 0x24, 0xb8, 0x7c, 0x80, 0xeb, 0xfc, 0xe4, 0x66 },
|
||||||
|
{ 0xcc, 0x6d, 0xe5, 0x97, 0x55, 0x42, 0x09, 0x25, 0xf9, 0x0e, 0xd2 },
|
||||||
|
{ 0x16, 0x4c, 0xc8, 0x61, 0xbd, 0xd8, 0x03, 0xc5, 0x47, 0xf2, 0xac },
|
||||||
|
{ 0xc0, 0xfc, 0x3e, 0xc4, 0xfb, 0x7d, 0x2b, 0xb2, 0x75, 0x66, 0x44 },
|
||||||
|
{ 0x0d, 0xbd, 0x81, 0x6f, 0xba, 0x15, 0x43, 0xf7, 0x21, 0xdc, 0x72 },
|
||||||
|
{ 0xa0, 0xc0, 0x03, 0x3a, 0x52, 0xab, 0x62, 0x99, 0x80, 0x2f, 0xd2 },
|
||||||
|
{ 0xbf, 0x4f, 0x56, 0xe0, 0x73, 0x27, 0x1f, 0x6a, 0xb4, 0xbf, 0x80 },
|
||||||
|
{ 0x57, 0xda, 0x6d, 0x13, 0xcb, 0x96, 0xa7, 0x68, 0x9b, 0x27, 0x90 },
|
||||||
|
{ 0x81, 0xcf, 0xc6, 0xf1, 0x8c, 0x35, 0xb1, 0xe1, 0xf1, 0x71, 0x14 },
|
||||||
|
{ 0x48, 0x1a, 0x2a, 0x0d, 0xf8, 0xa2, 0x35, 0x83, 0xf8, 0x2d, 0x6c },
|
||||||
|
{ 0x1a, 0xc4, 0x67, 0x2b, 0x54, 0x9c, 0xd6, 0xdb, 0xa7, 0x9b, 0xcc },
|
||||||
|
{ 0xc8, 0x7a, 0xf9, 0xa5, 0xd5, 0x20, 0x6a, 0xbc, 0xa5, 0x32, 0xa8 },
|
||||||
|
{ 0x97, 0xd4, 0x16, 0x9c, 0xb3, 0x3e, 0x74, 0x35, 0x71, 0x8d, 0x90 },
|
||||||
|
{ 0xa6, 0x57, 0x3f, 0x3d, 0xc8, 0xb1, 0x6c, 0x9d, 0x19, 0xf7, 0x46 },
|
||||||
|
{ 0x2c, 0x41, 0x42, 0xbf, 0x42, 0xb0, 0x1e, 0x71, 0x07, 0x6a, 0xcc },
|
||||||
|
{ 0x08, 0x1c, 0x29, 0xa1, 0x0d, 0x46, 0x8c, 0xcd, 0xbc, 0xec, 0xb6 },
|
||||||
|
{ 0x5b, 0x0f, 0x77, 0x42, 0xbc, 0xa8, 0x6b, 0x80, 0x12, 0x60, 0x9a },
|
||||||
|
{ 0x01, 0x2d, 0xee, 0x21, 0x98, 0xeb, 0xa8, 0x2b, 0x19, 0xa1, 0xda },
|
||||||
|
{ 0xf1, 0x62, 0x77, 0x01, 0xa2, 0xd6, 0x92, 0xfd, 0x94, 0x49, 0xe6 },
|
||||||
|
{ 0x35, 0xad, 0x3f, 0xb0, 0xfa, 0xeb, 0x5f, 0x1b, 0x0c, 0x30, 0xdc },
|
||||||
|
{ 0xb1, 0xca, 0x4e, 0xa2, 0xe3, 0xd1, 0x73, 0xba, 0xd4, 0x37, 0x9c },
|
||||||
|
{ 0x37, 0xd8, 0xe0, 0xaf, 0x92, 0x58, 0xb9, 0xe8, 0xc5, 0xf9, 0xb2 },
|
||||||
|
{ 0xcd, 0x92, 0x1f, 0xdf, 0x59, 0xe8, 0x82, 0x68, 0x37, 0x63, 0xf6 },
|
||||||
|
{ 0x61, 0x14, 0xe0, 0x84, 0x83, 0x04, 0x3f, 0xd3, 0xf3, 0x8a, 0x8a },
|
||||||
|
{ 0x2e, 0x54, 0x7d, 0xd7, 0xa0, 0x5f, 0x65, 0x97, 0xaa, 0xc5, 0x16 },
|
||||||
|
{ 0x95, 0xe4, 0x5e, 0xcd, 0x01, 0x35, 0xac, 0xa9, 0xd6, 0xe6, 0xae },
|
||||||
|
{ 0xb3, 0x3e, 0xc9, 0x7b, 0xe8, 0x3c, 0xe4, 0x13, 0xf9, 0xac, 0xc8 },
|
||||||
|
{ 0xc8, 0xb5, 0xdf, 0xfc, 0x33, 0x50, 0x95, 0xdc, 0xdc, 0xaf, 0x2a },
|
||||||
|
{ 0x3d, 0xd0, 0x1a, 0x59, 0xd8, 0x63, 0x10, 0x74, 0x3e, 0xc7, 0x52 },
|
||||||
|
{ 0x14, 0xcd, 0x0f, 0x64, 0x2f, 0xc0, 0xc5, 0xfe, 0x3a, 0x65, 0xca },
|
||||||
|
{ 0x3a, 0x0a, 0x1d, 0xfd, 0x7e, 0xee, 0x29, 0xc2, 0xe8, 0x27, 0xe0 },
|
||||||
|
{ 0x8a, 0xbd, 0xb8, 0x89, 0xef, 0xbe, 0x39, 0xa5, 0x10, 0xa1, 0x18 },
|
||||||
|
{ 0x3f, 0x23, 0x1f, 0x21, 0x20, 0x55, 0x37, 0x1c, 0xf3, 0xe2, 0xa2 }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Column order (permutation) in which the bits in codeword are stored
|
||||||
|
const uint8_t colorder[174] = {
|
||||||
|
0, 1, 2, 3, 30, 4, 5, 6, 7, 8, 9, 10, 11, 32, 12, 40, 13, 14, 15, 16,
|
||||||
|
17, 18, 37, 45, 29, 19, 20, 21, 41, 22, 42, 31, 33, 34, 44, 35, 47, 51, 50, 43,
|
||||||
|
36, 52, 63, 46, 25, 55, 27, 24, 23, 53, 39, 49, 59, 38, 48, 61, 60, 57, 28, 62,
|
||||||
|
56, 58, 65, 66, 26, 70, 64, 69, 68, 67, 74, 71, 54, 76, 72, 75, 78, 77, 80, 79,
|
||||||
|
73, 83, 84, 81, 82, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99,
|
||||||
|
100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,
|
||||||
|
120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,
|
||||||
|
140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,
|
||||||
|
160,161,162,163,164,165,166,167,168,169,170,171,172,173
|
||||||
|
};
|
||||||
|
|
||||||
|
// Costas 7x7 tone pattern
|
||||||
|
const uint8_t ICOS7[] = { 2,5,6,0,4,1,3 };
|
||||||
|
|
||||||
|
|
||||||
|
// Returns 1 if an odd number of bits are set in x, zero otherwise
|
||||||
|
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) & 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Encode an 87-bit message and return a 174-bit codeword.
|
||||||
|
// The generator matrix has dimensions (87,87).
|
||||||
|
// The code is a (174,87) regular ldpc code with column weight 3.
|
||||||
|
// The code was generated using the PEG algorithm.
|
||||||
|
// After creating the codeword, the columns are re-ordered according to
|
||||||
|
// "colorder" to make the codeword compatible with the parity-check matrix
|
||||||
|
// Arguments:
|
||||||
|
// * message - array of 87 bits stored as 11 bytes (MSB first)
|
||||||
|
// * codeword - array of 174 bits stored as 22 bytes (MSB first)
|
||||||
|
void encode174(const uint8_t *message, uint8_t *codeword) {
|
||||||
|
static bool first = true;
|
||||||
|
|
||||||
|
// Here we don't generate the generator bit matrix as in WSJT-X implementation
|
||||||
|
// Instead we access the generator bits straight from the binary representation in G
|
||||||
|
|
||||||
|
// Also we don't use the itmp temporary buffer, instead filling codeword bit by bit
|
||||||
|
// in the reordered order as we compute the result.
|
||||||
|
|
||||||
|
// For reference:
|
||||||
|
// itmp(1:M)=pchecks
|
||||||
|
// itmp(M+1:N)=message(1:K)
|
||||||
|
// codeword(colorder+1)=itmp(1:N)
|
||||||
|
|
||||||
|
int colidx = 0; // track the current column in codeword
|
||||||
|
|
||||||
|
// Fill the codeword with zeroes, as we will only update binary ones later
|
||||||
|
for (int i = 0; i < (N + 7) / 8; i++) {
|
||||||
|
codeword[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the first part of itmp (1:M) and store the result in codeword
|
||||||
|
for (int i = 0; i < M; ++i) { // do i=1,M
|
||||||
|
// Fast implementation of bitwise multiplication and parity checking
|
||||||
|
// Normally nsum would contain the result of dot product between message and G[i],
|
||||||
|
// but we only compute the sum modulo 2.
|
||||||
|
uint8_t nsum = 0;
|
||||||
|
for (int j = 0; j < 11; ++j) {
|
||||||
|
uint8_t bits = message[j] & G[i][j]; // bitwise AND (bitwise multiplication)
|
||||||
|
nsum ^= parity8(bits); // bitwise XOR (addition modulo 2)
|
||||||
|
}
|
||||||
|
// Check if we need to set a bit in codeword
|
||||||
|
if (nsum % 2) { // pchecks(i)=mod(nsum,2)
|
||||||
|
uint8_t col = colorder[colidx]; // Index of the bit to set
|
||||||
|
codeword[col/8] |= (1 << (7 - col%8));
|
||||||
|
}
|
||||||
|
++colidx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the second part of itmp (M+1:N) and store the result in codeword
|
||||||
|
uint8_t mask = 0x80; // Rolling mask starting with the MSB
|
||||||
|
for (int j = 0; j < K; ++j) {
|
||||||
|
// Copy the j-th bit from message to codeword
|
||||||
|
if (message[j/8] & mask) {
|
||||||
|
uint8_t col = colorder[colidx]; // Index of the bit to set
|
||||||
|
codeword[col/8] |= (1 << (7 - col%8));
|
||||||
|
}
|
||||||
|
++colidx;
|
||||||
|
|
||||||
|
// Roll the bitmask to the right
|
||||||
|
mask >>= 1;
|
||||||
|
if (mask == 0) mask = 0x80;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint16_t ft8_crc(uint8_t *message, int nBits) {
|
||||||
|
const int WIDTH = 12;
|
||||||
|
const uint16_t TOPBIT = (1 << (WIDTH - 1));
|
||||||
|
|
||||||
|
uint16_t remainder = 0;
|
||||||
|
int iByte = 0;
|
||||||
|
// Perform modulo-2 division, a bit at a time.
|
||||||
|
for (int iBit = 0; iBit < nBits; ++iBit) {
|
||||||
|
if (iBit % 8 == 0) {
|
||||||
|
// Bring the next byte into the remainder.
|
||||||
|
remainder ^= (message[iByte] << (WIDTH - 8));
|
||||||
|
++iByte;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to divide the current data bit.
|
||||||
|
if (remainder & TOPBIT) {
|
||||||
|
remainder = (remainder << 1) ^ POLYNOMIAL;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
remainder = (remainder << 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return remainder & ((1 << WIDTH) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Generate FT8 tone sequence from payload data
|
||||||
|
// [IN] payload - 9 byte array consisting of 72 bit payload
|
||||||
|
// [IN] i3 - 3 bits containing message type (zero?)
|
||||||
|
// [OUT] itone - array of NN (79) bytes to store the generated tones (encoded as 0..7)
|
||||||
|
void genft8(const uint8_t *payload, uint8_t i3, uint8_t *itone) {
|
||||||
|
uint8_t a87[11]; // Store 72 bits of payload + 3 bits i3 + 12 bits CRC
|
||||||
|
|
||||||
|
for (int i = 0; i < 9; i++)
|
||||||
|
a87[i] = payload[i];
|
||||||
|
|
||||||
|
// Append 3 bits of i3 at the end of 72 bit payload
|
||||||
|
a87[9] = ((i3 & 0x07) << 5);
|
||||||
|
|
||||||
|
// Calculate CRC of 76 bits (yes, 72 + 3 + 1 zero bit), see WSJT-X code
|
||||||
|
uint16_t checksum = ft8_crc(a87, 76);
|
||||||
|
|
||||||
|
// Store the CRC at the end of 75 bit message (yes, 72 + 3)
|
||||||
|
uint16_t tmp = (checksum << 1);
|
||||||
|
a87[9] |= (uint8_t)(tmp >> 8);
|
||||||
|
a87[10] = (uint8_t)tmp;
|
||||||
|
|
||||||
|
// a87 contains 72 bits of payload + 3 bits of i3 + 12 bits of CRC
|
||||||
|
uint8_t codeword[22];
|
||||||
|
encode174(a87, codeword);
|
||||||
|
|
||||||
|
// Message structure: S7 D29 S7 D29 S7
|
||||||
|
for (int i = 0; i < 7; ++i) {
|
||||||
|
itone[i] = ICOS7[i];
|
||||||
|
itone[36 + i] = ICOS7[i];
|
||||||
|
itone[72 + i] = ICOS7[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
int k = 7; // Skip over the first set of Costas symbols
|
||||||
|
for (int j = 0; j < ND; ++j) { // do j=1,ND
|
||||||
|
if (j == 29) {
|
||||||
|
k += 7; // Skip over the second set of Costas symbols
|
||||||
|
}
|
||||||
|
// Extract 3 bits from codeword at i-th position
|
||||||
|
itone[k] = 0;
|
||||||
|
int i = 3*j;
|
||||||
|
if (codeword[i/8] & (1 << (7 - i%8))) itone[k] |= 4;
|
||||||
|
++i;
|
||||||
|
if (codeword[i/8] & (1 << (7 - i%8))) itone[k] |= 2;
|
||||||
|
++i;
|
||||||
|
if (codeword[i/8] & (1 << (7 - i%8))) itone[k] |= 1;
|
||||||
|
++k;
|
||||||
|
}
|
||||||
|
}
|
28
encode.h
Normal file
28
encode.h
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
constexpr int ND = 58; // Data symbols
|
||||||
|
constexpr int NS = 21; // Sync symbols (3 @ Costas 7x7)
|
||||||
|
constexpr int NN = NS+ND; // Total channel symbols (79)
|
||||||
|
|
||||||
|
|
||||||
|
// Generate FT8 tone sequence from payload data
|
||||||
|
// [IN] payload - 9 byte array consisting of 72 bit payload
|
||||||
|
// [IN] i3 - 3 bits containing message type (zero?)
|
||||||
|
// [OUT] itone - array of NN (79) bytes to store the generated tones (encoded as 0..7)
|
||||||
|
void genft8(const uint8_t *payload, uint8_t i3, uint8_t *itone);
|
||||||
|
|
||||||
|
|
||||||
|
// Encode an 87-bit message and return a 174-bit codeword.
|
||||||
|
// The generator matrix has dimensions (87,87).
|
||||||
|
// The code is a (174,87) regular ldpc code with column weight 3.
|
||||||
|
// The code was generated using the PEG algorithm.
|
||||||
|
// After creating the codeword, the columns are re-ordered according to
|
||||||
|
// "colorder" to make the codeword compatible with the parity-check matrix
|
||||||
|
// Arguments:
|
||||||
|
// * message - array of 87 bits stored as 11 bytes (MSB first)
|
||||||
|
// * codeword - array of 174 bits stored as 22 bytes (MSB first)
|
||||||
|
void encode174(const uint8_t *message, uint8_t *codeword);
|
||||||
|
|
||||||
|
uint16_t ft8_crc(uint8_t *message, int nBits);
|
401
pack.cpp
Normal file
401
pack.cpp
Normal file
|
@ -0,0 +1,401 @@
|
||||||
|
#include <string.h>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
#include "pack.h"
|
||||||
|
|
||||||
|
constexpr int NBASE = 37*36*10*27*27*27;
|
||||||
|
constexpr int NGBASE = 180*180;
|
||||||
|
|
||||||
|
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 starts_with(const char *string, const char *prefix) {
|
||||||
|
return 0 == memcmp(string, prefix, strlen(prefix));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool equals(const char *string1, const char *string2) {
|
||||||
|
return 0 == strcmp(string1, string2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 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 integer encoding of a character (number/digit/space).
|
||||||
|
// * Digits are encoded as 0..9
|
||||||
|
// * Letters a..z are encoded as 10..35 (case insensitive)
|
||||||
|
// * Space is encoded as 36
|
||||||
|
uint8_t nchar(char c) {
|
||||||
|
if (is_digit(c)) {
|
||||||
|
return (c - '0');
|
||||||
|
}
|
||||||
|
if (is_letter(c)) {
|
||||||
|
return (to_upper(c) - 'A') + 10;
|
||||||
|
}
|
||||||
|
if (c == ' ') {
|
||||||
|
return 36;
|
||||||
|
}
|
||||||
|
// we should never reach here
|
||||||
|
return 36;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Pack FT8 source/destination and grid data into 72 bits (stored as 9 bytes)
|
||||||
|
// [IN] nc1 - first callsign data (28 bits)
|
||||||
|
// [IN] nc2 - second callsign data (28 bits)
|
||||||
|
// [IN] ng - grid data (16 bits)
|
||||||
|
// [OUT] payload - 9 byte array to store the 72 bit payload (MSB first)
|
||||||
|
void pack3_8bit(uint32_t nc1, uint32_t nc2, uint16_t ng, uint8_t *payload) {
|
||||||
|
payload[0] = (uint8_t)(nc1 >> 20);
|
||||||
|
payload[1] = (uint8_t)(nc1 >> 12);
|
||||||
|
payload[2] = (uint8_t)(nc1 >> 4);
|
||||||
|
payload[3] = (uint8_t)(nc1 << 4) | (uint8_t)(nc2 >> 24);
|
||||||
|
payload[4] = (uint8_t)(nc2 >> 16);
|
||||||
|
payload[5] = (uint8_t)(nc2 >> 8);
|
||||||
|
payload[6] = (uint8_t)(nc2);
|
||||||
|
payload[7] = (uint8_t)(ng >> 8);
|
||||||
|
payload[8] = (uint8_t)(ng);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Pack FT8 source/destination and grid data into 72 bits (stored as 12 bytes of 6-bit values)
|
||||||
|
// (For compatibility with WSJT-X and testing)
|
||||||
|
// [IN] nc1 - first callsign data (28 bits)
|
||||||
|
// [IN] nc2 - second callsign data (28 bits)
|
||||||
|
// [IN] ng - grid data (16 bits)
|
||||||
|
// [OUT] payload - 12 byte array to store the 72 bit payload (MSB first)
|
||||||
|
void pack3_6bit(uint32_t nc1, uint32_t nc2, uint16_t ng, uint8_t *payload) {
|
||||||
|
payload[0] = (nc1 >> 22) & 0x3f; // 6 bits
|
||||||
|
payload[1] = (nc1 >> 16) & 0x3f; // 6 bits
|
||||||
|
payload[2] = (nc1 >> 10) & 0x3f; // 6 bits
|
||||||
|
payload[3] = (nc1 >> 4) & 0x3f; // 6 bits
|
||||||
|
payload[4] = ((nc1 & 0xf) << 2) | ((nc2 >> 26) & 0x3); // 4+2 bits
|
||||||
|
payload[5] = (nc2 >> 20) & 0x3f; // 6 bits
|
||||||
|
payload[6] = (nc2 >> 14) & 0x3f; // 6 bits
|
||||||
|
payload[7] = (nc2 >> 8) & 0x3f; // 6 bits
|
||||||
|
payload[8] = (nc2 >> 2) & 0x3f; // 6 bits
|
||||||
|
payload[9] = ((nc2 & 0x3) << 4) | ((ng >> 12) & 0xf); // 2+4 bits
|
||||||
|
payload[10] = (ng >> 6) & 0x3f; // 6 bits
|
||||||
|
payload[11] = (ng >> 0) & 0x3f; // 6 bits
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
negative = false;
|
||||||
|
i = (str[0] == '+') ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void int_to_dd(char *str, int value, int width) {
|
||||||
|
if (value < 0) {
|
||||||
|
*str = '-';
|
||||||
|
str++;
|
||||||
|
value = -value;
|
||||||
|
}
|
||||||
|
|
||||||
|
int divisor = 1;
|
||||||
|
for (int i = 0; i < width; i++) {
|
||||||
|
divisor *= 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (divisor > 1) {
|
||||||
|
int digit = value / divisor;
|
||||||
|
|
||||||
|
*str = '0' + digit;
|
||||||
|
str++;
|
||||||
|
|
||||||
|
value -= digit * divisor;
|
||||||
|
divisor /= 10;
|
||||||
|
}
|
||||||
|
*str = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Pack a valid callsign into a 28-bit integer.
|
||||||
|
int32_t packcall(const char *callsign) {
|
||||||
|
printf("Callsign = [%s]\n", callsign);
|
||||||
|
if (equals(callsign, "CQ")) {
|
||||||
|
// TODO: support 'CQ nnn' frequency specification
|
||||||
|
//if (callsign(4:4).ge.'0' .and. callsign(4:4).le.'9' .and. &
|
||||||
|
// callsign(5:5).ge.'0' .and. callsign(5:5).le.'9' .and. &
|
||||||
|
// callsign(6:6).ge.'0' .and. callsign(6:6).le.'9') then
|
||||||
|
// read(callsign(4:6),*) nfreq
|
||||||
|
// ncall=NBASE + 3 + nfreq
|
||||||
|
//endif
|
||||||
|
return NBASE + 1;
|
||||||
|
}
|
||||||
|
if (equals(callsign, "QRZ")) {
|
||||||
|
return NBASE + 2;
|
||||||
|
}
|
||||||
|
if (equals(callsign, "DE")) {
|
||||||
|
return 267796945;
|
||||||
|
}
|
||||||
|
|
||||||
|
int len = strlen(callsign);
|
||||||
|
if (len > 6) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char callsign2[7] = {' ', ' ', ' ', ' ', ' ', ' ', 0}; // 6 spaces with zero terminator
|
||||||
|
|
||||||
|
// Work-around for Swaziland prefix (see WSJT-X code):
|
||||||
|
if (starts_with(callsign, "3DA0")) {
|
||||||
|
// callsign='3D0'//callsign(5:6)
|
||||||
|
memcpy(callsign2, "3D0", 3);
|
||||||
|
memcpy(callsign2 + 3, callsign + 4, 2);
|
||||||
|
}
|
||||||
|
// Work-around for Guinea prefixes (see WSJT-X code):
|
||||||
|
else if (starts_with(callsign, "3X") && is_letter(callsign[2])) {
|
||||||
|
//callsign='Q'//callsign(3:6)
|
||||||
|
memcpy(callsign2, "Q", 1);
|
||||||
|
memcpy(callsign2 + 1, callsign + 2, 4);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Just copy, no modifications needed
|
||||||
|
// Check for callsigns with 1 symbol prefix
|
||||||
|
if (!is_digit(callsign[2]) && is_digit(callsign[1])) {
|
||||||
|
if (len > 5) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
// Leave one space at the beginning as padding
|
||||||
|
memcpy(callsign2 + 1, callsign, len);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
memcpy(callsign2, callsign, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the callsign consists of valid characters
|
||||||
|
if (!is_digit(callsign2[0]) && !is_letter(callsign2[0]) && !is_space(callsign2[0]))
|
||||||
|
return -3;
|
||||||
|
if (!is_digit(callsign2[1]) && !is_letter(callsign2[1]))
|
||||||
|
return -3;
|
||||||
|
if (!is_digit(callsign2[2]))
|
||||||
|
return -3;
|
||||||
|
if (!is_letter(callsign2[3]) && !is_space(callsign2[3]))
|
||||||
|
return -3;
|
||||||
|
if (!is_letter(callsign2[4]) && !is_space(callsign2[4]))
|
||||||
|
return -3;
|
||||||
|
if (!is_letter(callsign2[5]) && !is_space(callsign2[5]))
|
||||||
|
return -3;
|
||||||
|
|
||||||
|
// Form a 28 bit integer from callsign parts
|
||||||
|
int32_t ncall = nchar(callsign2[0]);
|
||||||
|
ncall = 36*ncall + nchar(callsign2[1]);
|
||||||
|
ncall = 10*ncall + nchar(callsign2[2]);
|
||||||
|
ncall = 27*ncall + nchar(callsign2[3]) - 10;
|
||||||
|
ncall = 27*ncall + nchar(callsign2[4]) - 10;
|
||||||
|
ncall = 27*ncall + nchar(callsign2[5]) - 10;
|
||||||
|
|
||||||
|
return ncall;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Pack a valid grid locator into an integer.
|
||||||
|
int16_t packgrid(const char *grid) {
|
||||||
|
printf("Grid = [%s]\n", grid);
|
||||||
|
int len = strlen(grid);
|
||||||
|
|
||||||
|
if (len == 0) {
|
||||||
|
// Blank grid is OK
|
||||||
|
return NGBASE + 1;
|
||||||
|
}
|
||||||
|
// Check for RO, RRR, or 73 in the message field normally used for grid
|
||||||
|
if (equals(grid, "RO")) {
|
||||||
|
return NGBASE + 62;
|
||||||
|
}
|
||||||
|
if (equals(grid, "RRR")) {
|
||||||
|
return NGBASE + 63;
|
||||||
|
}
|
||||||
|
if (equals(grid, "73")) {
|
||||||
|
return NGBASE + 64;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
char c1 = grid[0];
|
||||||
|
int n;
|
||||||
|
if (c1 == 'R') {
|
||||||
|
n = dd_to_int(grid + 1, 3); // read(grid(2:4),*,err=30,end=30) n
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
n = dd_to_int(grid, 3); // read(grid,*,err=20,end=20) n
|
||||||
|
}
|
||||||
|
|
||||||
|
// First, handle signal reports in the original range, -01 to -30 dB
|
||||||
|
if (n >= -30 && n <= -1) {
|
||||||
|
if (c1 == 'R') {
|
||||||
|
return NGBASE + 31 + (-n);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return NGBASE + 1 + (-n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char grid4[4];
|
||||||
|
memcpy(grid4, grid, 4);
|
||||||
|
|
||||||
|
// Check for extended-range signal reports: -50 to -31, and 0 to +49
|
||||||
|
// if (n >= -50 && n <= 49) {
|
||||||
|
// if (c1 == 'R') {
|
||||||
|
// // write(grid,1002) n+50 1002 format('LA',i2.2)
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// // write(grid,1003) n+50 1003 format('KA',i2.2)
|
||||||
|
// }
|
||||||
|
// // go to 40
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// // error
|
||||||
|
// return -1;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Check the locator
|
||||||
|
if (len != 4) return -1;
|
||||||
|
if (grid4[0] < 'A' || grid4[0] > 'R') return -1;
|
||||||
|
if (grid4[1] < 'A' || grid4[1] > 'R') return -1;
|
||||||
|
if (grid4[2] < '0' || grid4[2] > '9') return -1;
|
||||||
|
if (grid4[3] < '0' || grid4[3] > '9') return -1;
|
||||||
|
|
||||||
|
// OK, we have a properly formatted grid locator
|
||||||
|
int lng = (grid4[0] - 'A') * 20;
|
||||||
|
lng += (grid4[2] - '0') * 2;
|
||||||
|
lng = 179 - lng;
|
||||||
|
|
||||||
|
int lat = (grid4[1] - 'A') * 10;
|
||||||
|
lat += (grid4[3] - '0') * 1;
|
||||||
|
lat -= 90;
|
||||||
|
|
||||||
|
int16_t ng = (lng + 180) / 2;
|
||||||
|
ng *= 180;
|
||||||
|
ng += lat + 90;
|
||||||
|
|
||||||
|
return ng;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int packmsg(const char *msg, uint8_t *dat) { // , itype, bcontest
|
||||||
|
// TODO: maximum allowed length?
|
||||||
|
if (strlen(msg) > 18) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char msg2[19]; // Including zero terminator!
|
||||||
|
|
||||||
|
fmtmsg(msg2, msg);
|
||||||
|
|
||||||
|
//printf("msg2 = [%s]\n", msg2);
|
||||||
|
|
||||||
|
// TODO: Change 'CQ n ' type messages to 'CQ 00n '
|
||||||
|
//if(msg(1:3).eq.'CQ ' .and. msg(4:4).ge.'0' .and. msg(4:4).le.'9' &
|
||||||
|
// .and. msg(5:5).eq.' ') msg='CQ 00'//msg(4:)
|
||||||
|
|
||||||
|
if (starts_with(msg2, "CQ ")) {
|
||||||
|
if (msg2[3] == 'D' && msg2[4] == 'X' && is_space(msg2[5])) {
|
||||||
|
// Change 'CQ DX ' to 'CQ9DX '
|
||||||
|
msg2[2] = '9';
|
||||||
|
}
|
||||||
|
else if (is_letter(msg2[3]) && is_letter(msg2[4]) && is_space(msg2[5])) {
|
||||||
|
// Change 'CQ xy ' type messages to 'E9xy '
|
||||||
|
msg2[0] = 'E';
|
||||||
|
msg2[1] = '9';
|
||||||
|
// Delete the extra space
|
||||||
|
char *ptr = msg2 + 2;
|
||||||
|
while (*ptr) {
|
||||||
|
ptr[0] = ptr[1];
|
||||||
|
++ptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locate the first space in the message and replace it with zero terminator
|
||||||
|
char *s1 = strchr(msg2, ' ');
|
||||||
|
if (s1 == NULL) {
|
||||||
|
// TODO: handle this (plain text message?)
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
*s1 = 0;
|
||||||
|
++s1;
|
||||||
|
|
||||||
|
// Locate the second space in the message
|
||||||
|
char *s2 = strchr(s1 + 1, ' ');
|
||||||
|
if (s2 == NULL) {
|
||||||
|
// If the second space is not found, point to the end of string
|
||||||
|
// to allow for blank grid (third field)
|
||||||
|
s2 = msg2 + strlen(msg2);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
*s2 = 0;
|
||||||
|
++s2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pack message fields into integers
|
||||||
|
int nc1 = packcall(msg2);
|
||||||
|
int nc2 = packcall(s1);
|
||||||
|
int ng = packgrid(s2);
|
||||||
|
|
||||||
|
// TODO: callsign prefixes/suffixes
|
||||||
|
|
||||||
|
// TODO: plain text messages
|
||||||
|
//call packtext(msg,nc1,nc2,ng)
|
||||||
|
//ng=ng+32768
|
||||||
|
|
||||||
|
if (nc1 < 0 || nc2 < 0 || ng < 0) {
|
||||||
|
return -3;
|
||||||
|
}
|
||||||
|
//printf("nc1 = %d [%04X], nc2 = %d [%04X], ng = %d\n", nc1, nc1, nc2, nc2, ng);
|
||||||
|
|
||||||
|
// Originally the data was packed in bytes of 6 bits.
|
||||||
|
// This seems to waste memory unnecessary and complicate the code, so we pack it in 8 bit values.
|
||||||
|
pack3_8bit(nc1, nc2, ng, dat);
|
||||||
|
//pack3_6bit(nc1, nc2, ng, dat);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
9
pack.h
Normal file
9
pack.h
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
|
||||||
|
// Pack FT8 text message into 72 bits
|
||||||
|
// [IN] msg - FT8 message (e.g. "CQ TE5T KN01")
|
||||||
|
// [OUT] packed - 9 byte array to store the 72 bit payload (MSB first)
|
||||||
|
int packmsg(const char *msg, uint8_t *dat);
|
192
test.cpp
Normal file
192
test.cpp
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "pack.h"
|
||||||
|
#include "encode.h"
|
||||||
|
|
||||||
|
|
||||||
|
void convert_8bit_to_6bit(uint8_t *dst, const uint8_t *src, int nBits) {
|
||||||
|
// Zero-fill the destination array as we will only be setting bits later
|
||||||
|
for (int j = 0; j < (nBits + 5) / 6; ++j) {
|
||||||
|
dst[j] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the relevant bits
|
||||||
|
uint8_t mask_src = (1 << 7);
|
||||||
|
uint8_t mask_dst = (1 << 5);
|
||||||
|
for (int i = 0, j = 0; nBits > 0; --nBits) {
|
||||||
|
if (src[i] & mask_src) {
|
||||||
|
dst[j] |= mask_dst;
|
||||||
|
}
|
||||||
|
mask_src >>= 1;
|
||||||
|
if (mask_src == 0) {
|
||||||
|
mask_src = (1 << 7);
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
mask_dst >>= 1;
|
||||||
|
if (mask_dst == 0) {
|
||||||
|
mask_dst = (1 << 5);
|
||||||
|
++j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void synth_fsk(const uint8_t *symbols, int nSymbols, float f0, float spacing, float symbol_rate, float signal_rate, float *signal) {
|
||||||
|
float phase = 0;
|
||||||
|
float dt = 1/signal_rate;
|
||||||
|
float dt_sym = 1/symbol_rate;
|
||||||
|
float t = 0;
|
||||||
|
int j = 0;
|
||||||
|
int i = 0;
|
||||||
|
while (j < nSymbols) {
|
||||||
|
float f = f0 + symbols[j] * spacing;
|
||||||
|
phase += 2 * M_PI * f / signal_rate;
|
||||||
|
signal[i] = sin(phase);
|
||||||
|
t += dt;
|
||||||
|
if (t >= dt_sym) {
|
||||||
|
// Move to the next symbol
|
||||||
|
t -= dt_sym;
|
||||||
|
++j;
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void save_wav(const float *signal, int num_samples, int sample_rate, const char *path) {
|
||||||
|
FILE *f = fopen(path, "wb");
|
||||||
|
char subChunk1ID[4] = {'f', 'm', 't', ' '};
|
||||||
|
uint32_t subChunk1Size = 16; // 16 for PCM
|
||||||
|
uint16_t audioFormat = 1; // PCM = 1
|
||||||
|
uint16_t numChannels = 1;
|
||||||
|
uint16_t bitsPerSample = 16;
|
||||||
|
uint32_t sampleRate = sample_rate;
|
||||||
|
uint16_t blockAlign = numChannels * bitsPerSample / 8;
|
||||||
|
uint32_t byteRate = sampleRate * blockAlign;
|
||||||
|
|
||||||
|
char subChunk2ID[4] = {'d', 'a', 't', 'a'};
|
||||||
|
uint32_t subChunk2Size = num_samples * blockAlign;
|
||||||
|
|
||||||
|
char chunkID[4] = {'R', 'I', 'F', 'F'};
|
||||||
|
uint32_t chunkSize = 4 + (8 + subChunk1Size) + (8 + subChunk2Size);
|
||||||
|
char format[4] = {'W', 'A', 'V', 'E'};
|
||||||
|
|
||||||
|
int16_t *raw_data = (int16_t *)malloc(num_samples * blockAlign);
|
||||||
|
for (int i = 0; i < num_samples; i++) {
|
||||||
|
float x = signal[i];
|
||||||
|
if (x > 1.0) x = 1.0;
|
||||||
|
else if (x < -1.0) x = -1.0;
|
||||||
|
raw_data[i] = int(0.5 + (x * 32767.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: works only on little-endian architecture
|
||||||
|
fwrite(chunkID, sizeof(chunkID), 1, f);
|
||||||
|
fwrite(&chunkSize, sizeof(chunkSize), 1, f);
|
||||||
|
fwrite(format, sizeof(format), 1, f);
|
||||||
|
|
||||||
|
fwrite(subChunk1ID, sizeof(subChunk1ID), 1, f);
|
||||||
|
fwrite(&subChunk1Size, sizeof(subChunk1Size), 1, f);
|
||||||
|
fwrite(&audioFormat, sizeof(audioFormat), 1, f);
|
||||||
|
fwrite(&numChannels, sizeof(numChannels), 1, f);
|
||||||
|
fwrite(&sampleRate, sizeof(sampleRate), 1, f);
|
||||||
|
fwrite(&byteRate, sizeof(byteRate), 1, f);
|
||||||
|
fwrite(&blockAlign, sizeof(blockAlign), 1, f);
|
||||||
|
fwrite(&bitsPerSample, sizeof(bitsPerSample), 1, f);
|
||||||
|
|
||||||
|
fwrite(subChunk2ID, sizeof(subChunk2ID), 1, f);
|
||||||
|
fwrite(&subChunk2Size, sizeof(subChunk2Size), 1, f);
|
||||||
|
|
||||||
|
fwrite(raw_data, blockAlign, num_samples, f);
|
||||||
|
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
free(raw_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void test1() {
|
||||||
|
//const char *test_in3 = "CQ DL7ACA JO40"; // 62, 32, 32, 49, 37, 27, 59, 2, 30, 19, 49, 16
|
||||||
|
//const char *test_in3 = "VA3UG F1HMR 73"; // 52, 54, 60, 12, 55, 54, 7, 19, 2, 23, 59, 16
|
||||||
|
const char *test_in3 = "RA3Y VE3NLS 73"; // 46, 6, 32, 22, 55, 20, 11, 32, 53, 23, 59, 16
|
||||||
|
uint8_t test_out3[9];
|
||||||
|
int rc = packmsg(test_in3, test_out3);
|
||||||
|
|
||||||
|
printf("RC = %d\n", rc);
|
||||||
|
|
||||||
|
for (int i = 0; i < 9; ++i) {
|
||||||
|
printf("%02x ", test_out3[i]);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
uint8_t test_out4[12];
|
||||||
|
convert_8bit_to_6bit(test_out4, test_out3, 72);
|
||||||
|
for (int i = 0; i < 12; ++i) {
|
||||||
|
printf("%d ", test_out4[i]);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void test2() {
|
||||||
|
uint8_t test_in[11] = { 0xF1, 0x02, 0x03, 0x04, 0x05, 0x60, 0x70, 0x80, 0x90, 0xA0, 0xFF };
|
||||||
|
uint8_t test_out[22];
|
||||||
|
|
||||||
|
encode174(test_in, test_out);
|
||||||
|
|
||||||
|
for (int j = 0; j < 22; ++j) {
|
||||||
|
printf("%02x ", test_out[j]);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void test3() {
|
||||||
|
uint8_t test_in2[10] = { 0x11, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x10, 0x04, 0x01, 0x00 };
|
||||||
|
uint16_t crc1 = ft8_crc(test_in2, 76); // Calculate CRC of 76 bits only
|
||||||
|
printf("CRC: %04x\n", crc1); // should be 0x0708
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
if (argc < 3) return -1;
|
||||||
|
|
||||||
|
//const char *message = "G0UPL YL3JG 73";
|
||||||
|
const char *message = argv[1];
|
||||||
|
const char *wav_path = argv[2];
|
||||||
|
|
||||||
|
uint8_t packed[9];
|
||||||
|
int rc = packmsg(message, packed);
|
||||||
|
if (rc < 0) {
|
||||||
|
printf("Cannot parse message!\n");
|
||||||
|
printf("RC = %d\n", rc);
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Packed data: ");
|
||||||
|
for (int j = 0; j < 9; ++j) {
|
||||||
|
printf("%02x ", packed[j]);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
uint8_t tones[NN];
|
||||||
|
genft8(packed, 0, tones);
|
||||||
|
|
||||||
|
printf("FSK tones: ");
|
||||||
|
for (int j = 0; j < NN; ++j) {
|
||||||
|
printf("%d", tones[j]);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
const int num_samples = (int)(0.5 + NN / 6.25 * 12000);
|
||||||
|
const int num_silence = (15 * 12000 - num_samples) / 2;
|
||||||
|
float signal[num_silence + num_samples + num_silence];
|
||||||
|
for (int i = 0; i < num_silence + num_samples + num_silence; i++) {
|
||||||
|
signal[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
synth_fsk(tones, NN, 1000, 6.25, 6.25, 12000, signal + num_silence);
|
||||||
|
save_wav(signal, num_silence + num_samples + num_silence, 12000, wav_path);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
6
utils/convert_generator.py
Normal file
6
utils/convert_generator.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
for line in sys.stdin:
|
||||||
|
line = line.strip()
|
||||||
|
b = ['0x' + line[i*2:i*2+2] for i in range(len(line)/2)]
|
||||||
|
print '{ %s },' % (', '.join(b))
|
Loading…
Reference in a new issue