Add radioDemodulator_F32 and associated examples

pull/16/merge
boblark 2 years ago
parent a6f329e36c
commit bf1cfa561a
  1. 1
      OpenAudio_ArduinoLibrary.h
  2. 104
      docs/index.html
  3. 332
      examples/FT8Receive/FT8Receive.ino
  4. BIN
      examples/FT8Receive/ProbFailureVsSNR.gnumeric
  5. 303
      examples/FT8Receive/Process_DSP_R.ino
  6. 474
      examples/FT8Receive/constantsR.ino
  7. 294
      examples/FT8Receive/decodeR.ino
  8. 290
      examples/FT8Receive/decode_ft8R.ino
  9. 211
      examples/FT8Receive/encodeR.ino
  10. 314
      examples/FT8Receive/ldpcR.ino
  11. 75
      examples/FT8Receive/locatorR.ino
  12. 78
      examples/FT8Receive/maidenheadR.ino
  13. 206
      examples/FT8Receive/textR.ino
  14. 400
      examples/FT8Receive/unpackR.ino
  15. 25
      examples/FT8Transmit/FT8Transmit.ino
  16. 347
      examples/FT8Transmit7/FT8Transmit7.ino
  17. 348
      examples/FT8Transmit7HP/FT8Transmit7HP.ino
  18. BIN
      gui/DesignTool_F32.zip
  19. 213
      radioFT8Demodulator_F32.cpp
  20. 407
      radioFT8Demodulator_F32.h

@ -58,6 +58,7 @@
#include "RadioFMDetector_F32.h"
#include "radioBFSKmodulator_F32.h"
#include "radioFT8Modulator_F32.h"
#include "radioFT8Demodulator_F32.h"
#include "RadioFMDiscriminator_F32.h"
#include "radioNoiseBlanker_F32.h"
#include "synth_sin_cos_f32.h"

@ -420,10 +420,8 @@ span.mainfunction {color: #993300; font-weight: bolder}
{"type":"radioBFSKModulator_F32","data":{"defaults":{"name":{"value":"new"}},"shortName":"BFSKMod","inputs":"0","output":"0","category":"radio-function","color":"#E6E0F8","icon":"arrow-in.png","outputs":"1"}},
{"type":"UART_F32","data":{"defaults":{"name":{"value":"new"}},"shortName":"UART","inputs":"1","output":"0","category":"radio-function","color":"#E6E0F8","icon":"arrow-in.png","outputs":"0"}},
{"type":"radioFT8Modulator_F32","data":{"defaults":{"name":{"value":"new"}},"shortName":"FT8Mod","inputs":"0","output":"0","category":"radio-function","color":"#E6E0F8","icon":"arrow-in.png","outputs":"1"}},
{"type":"radioFT8Demodulator_F32","data":{"defaults":{"name":{"value":"new"}},"shortName":"FT8Demod","inputs":"1","output":"0","category":"radio-function","color":"#E6E0F8","icon":"arrow-in.png","outputs":"0"}},
{"type":"RadioIQMixer_F32","data":{"defaults":{"name":{"value":"new"}},"shortName":"I-QMixer","inputs":"2","output":"0","category":"radio-function","color":"#E6E0F8","icon":"arrow-in.png","outputs":"2"}},
{"type":"AudioControlSGTL5000","data":{"defaults":{"name":{"value":"new"}},"shortName":"sgtl5000","inputs":0,"outputs":0,"category":"control-function","color":"#E6E0F8","icon":"arrow-in.png"}},
@ -3406,7 +3404,6 @@ The actual packets are taken
</script>
<script type="text/x-red" data-help-name="radioFT8Modulator_F32">
<!-- ============ radioFT8Modulator_F32 ========= -->
<h3>Summary</h3>
@ -3483,8 +3480,9 @@ The actual packets are taken
</p>
<h3>Examples</h3>
<p class=exam>File &gt; Examples &gt; OpenAudio_ArduinoLibrary &gt; FT8Transmit
</p>
<p class=exam>File &gt; Examples &gt; OpenAudio_ArduinoLibrary &gt; FT8Transmit</p>
<p class=exam>File &gt; Examples &gt; OpenAudio_ArduinoLibrary &gt; FT8Transmit7</p>
<p class=exam>File &gt; Examples &gt; OpenAudio_ArduinoLibrary &gt; FT8Transmit7HP</p>
<h3>Notes</h3>
<p>See the library file radioFT8Modulator.h for much more information,
@ -3504,6 +3502,100 @@ The actual packets are taken
</div>
</script>
<script type="text/x-red" data-help-name="radioFT8Demodulator_F32">
<!-- ============ radioFT8Demodulator_F32 ========= -->
<h3>Summary</h3>
<div class=tooltipinfo>
<p>Receives audio, containing multiple FT8 signals, and bundles them
up into 2048 sample float arrays, ready for an FFT. The system audio
sample rate is decimated to 6400 Hz and high rejection filtering is
provided with a final audio upper limit at 2800 Hz.
The full data array is updated and available every 0.16 second
to support 50% overlap for FFT windowing. The class also includes a
broad-band power detector for noise measurement.
</p>
</div>
<h3>Audio Connections</h3>
<table class=doc align=center cellpadding=3>
<tr class=top><th>Port</th><th>Purpose</th></tr>
<tr class=odd><td align=center>In 0</td><td>FT8 receive audio</td></tr>
</table>
<h3>Functions</h3>
<p class=func><span class=keyword>setSampleRate_Hz</span>
(<strong>const float32_t</strong> &fs_Hz);</p>
<p class=desc>The two values are 48000.0 and 96000.0. </p>
<p class=func><span class=keyword>initialize</span>();</p>
<p class=desc>This computes several filters that are needed.</p>
<p class=func><span class=keyword>getDataPtr</span>()</p>
<p class=desc>Returns pointer to an array of 2048 float32_t (aka float)
holding latest received audio data. To support windowed FFT's,
this data provides 50% overlap. This means that, even though the
2048 data points require 0.32 seconds to acquire, a new set of data is
available every 0.16 seconds. </p>
<p class=func><span class=keyword>startDataCollect</span>();</p>
<p class=desc>Begins a 14.7 second data collection period with 194
2048 float data arrays being made available.
No return value.</p>
<p class=func><span class=keyword>cancelDataCollect</span>();</p>
<p class=desc>Cancels the 14.7 second data collection period.
No return value.</p>
<p class=func><span class=keyword>receivingData</span>();</p>
<p class=desc>Returns <strong>bool</strong> true if the 14.7 data collection
is in progress.</p>
<p class=func><span class=keyword>powerAvailable</span>();</p>
<p class=desc>See the next function. Returns <strong>bool</strong>
true if the power measurement is available. </p>
<p class=func><span class=keyword>readPower</span>();</p>
<p class=desc>Returns the dB power level <strong>once</strong>
per 128 samples at the 6.4kHz rate.
This can be used to provide a noise level measurement when signals are not
being transmitted. See the receive example listed below.</p>
<p>Note that there are other fuctions available to support the W5BAA 128-int
data transfer. The details are in the radioFT8Demodulator_F32.h file.
Use of those functions requires compiling with "#define W5BAA_INTERFACE" in
the .h file.</p>
<h3>Examples</h3>
<p class=exam>File &gt; Examples &gt; OpenAudio_ArduinoLibrary &gt; FT8Receive
</p>
<h3>Notes</h3>
<p>See the library file radioFT8Demodulator.h for much more information,
references, sample data, and most importantly, credits to the many contributors
upon which these FT8 library classes are based.</p>
<p> This class does not provide true (absolute) clock timing.
The startDataCollect() function
should be called when it is time for a new 15-sec receive period.</p>
<p>The receive example, along with the corresponding transmit examples
for radioFT8Modulator_F32, are complete basic capabilities, but
do not serve as a ham radio controller. The
<a href="https://github.com/WB2CBA/W5BAA-FT8-POCKET-TERMINAL"
target="_blank">W5BAA Pocket project</a>
https://github.com/WB2CBA/W5BAA-FT8-POCKET-TERMINAL
is an example of a complete control app. It does not use this library.</p>
</script>
<script type="text/x-red" data-template-name="radioFT8Demodulator_F32">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
</script>
<div>
<script type="text/x-red" data-help-name="radioModulatedGenerator_F32">
<!-- ============ radioModulatedGenerator_F32 ========= -->

@ -0,0 +1,332 @@
// FT8Receive.ino 13 Oct 2022 Bob Larkin, W7PUA
// Simple command-line reception of WSJT FT8 signals for
// amateur radio.
/*
* Huge thanks to Charlie Hill, W5BAA, for his Pocket FT8 work, much of which
* is the basis for this INO: https://github.com/Rotron/Pocket-FT8
* That work started from the "FT8 Decoding Library" by
* Karlis Goba: https://github.com/kgoba/ft8_lib and thank you to both
* contributors.
*/
/*
* See Examples/Teensy/TimeTeensy3.ino for
* example code illustrating Time library with Real Time Clock.
*/
/* This INO uses the Teensy F32 Audio Library interface to test
* the radioFT8Demodulator_F32 data collector. This should
* not be used with "#define W5BAA_INTERFACE" in the file
* radioFT8Demodulator_F32.h, as that interface is different.
*/
/* The F32 Audio library class, radioFT8Demodulator_F32, performs the
* data collection and organization for FT8 reception. This occurs
* after every 128 audio samples, automatically. The sync and decode
* functions are time intensive and therefore not done with the audio
* interrupts. A complete set of Karlis Goba FT8 reception files is
* included with this INO and have been slightly renamed with an "R"
* at the end. The Goba functions have stayed the same and minimal
* changes are made to the functions.
*
* IMPORTANT -When one wants to build a full transmit and receive
* FT8 INO, maybe with waterfall, they should go back to the Goba and
* Hill files referenced above. This INO is intended as a minimal demonstration
* of FT8 reception with emphasis on testing the radioFT8Demodulator_F32
* class. That said, be aware that these receive files include
* snr estimation not available from the other sets.
*/
#include "Arduino.h"
#include <TimeLib.h>
#include <OpenAudio_ArduinoLibrary.h> // Added for F32 Teensy library RSL
#include <Audio.h>
#include "AudioStream.h"
#include "arm_math.h"
// The following debugging print dumps are available:
// DEBUG1 - Main INO and overall control
// DEBUG_N - Noise measurement data for snr estimates
// DEBUG_D - Decode measurements
// Uncomment the following for debugging:
// #define DEBUG1
// #define DEBUG_N
// #define DEBUG_D
#define FFT_SIZE 2048
#define block_size 128
#define input_gulp_size 1024
#define HIGH_FREQ_INDEX 368
#define LOW_FREQ_INDEX 48
#define FT8_FREQ_SPACING 6.25 // ??
#define ft8_min_freq FT8_FREQ_SPACING * LOW_FREQ_INDEX
#define ft8_msg_samples 92
// Alternate interface format.
// #define W5BAA
// Only 48 and 96 kHz audio sample rates are currently supported.
const float32_t sample_rate_Hz = 48000.0f;
const int audio_block_samples = 128;
AudioSettings_F32 audio_settings(sample_rate_Hz, audio_block_samples);
AudioInputI2S_F32 audioInI2S1(audio_settings); //xy=100,150
AudioEffectGain_F32 gain1; //xy=250,150
AudioAnalyzePeak_F32 peak1; //xy=400,250
radioFT8Demodulator_F32 demod1; //xy=400,150
AudioConnection_F32 patchCord1(audioInI2S1, 0, gain1, 0);
AudioConnection_F32 patchCord2(gain1, demod1);
AudioConnection_F32 patchCord3(gain1, peak1);
AudioControlSGTL5000 sgtl5000_1; //xy=100,250
// This is the big file of log powers
uint8_t export_fft_power[ft8_msg_samples*HIGH_FREQ_INDEX*4] ;
// Pointer to 2048 float data for FFT array in radioDemodulator_F32
float32_t* pData2K = NULL;
float32_t noisePowerEstimateL = 0.0f; // Works when big signals are absent
int16_t noisePwrDBIntL = 0;
float32_t noisePowerEstimateH = 0.0f; // Works for big signals and QRMt
int16_t noisePwrDBIntH = 0;
float32_t noisePeakAveRatio = 0.0f; // > about 100 for big sigs
// /char Station_Call[11]; //six character call sign + /0
// /char home_Locator[11];; // four character locator + /0
// /char Locator[11]; // four character locator + /0
char Station_Call[11]; // six character call sign + /0
char home_Locator[11]; // four character locator + /0
char Locator[11]; // four character locator + /0
uint16_t currentFrequency;
// Next 3 lines were uint32_t Sept 22 change to allow tOffset
int32_t current_time, start_time, ft8_time, ft8_mod_time, ft8_mod_time_last;
int32_t days_fraction, hours_fraction, minute_fraction;
int32_t tOffset = 0; // Added Sept 22
uint8_t ft8_hours, ft8_minutes, ft8_seconds;
int ft8_flag, FT_8_counter, ft8_marker, decode_flag;
int num_decoded_msg;
int xmit_flag, ft8_xmit_counter, Transmit_Armned;
int DSP_Flag; // =1 if new data is ready for FFT
int master_decoded;
// rcvFT8State
#define FT8_RCV_IDLE 0
#define FT8_RCV_DATA_COLLECT 1
#define FT8_RCV_FIND_POWERS 2
#define FT8_RCV_DECODE 3
int rcvFT8State = FT8_RCV_IDLE;
int master_offset, offset_step;
int Target_Flag = 0;
//From gen_ft8.cpp
char Target_Call[7]; //six character call sign + /0
char Target_Locator[5]; // four character locator + /0
int Target_RSL; // four character RSL + /0
// Define FT8 symbol counts
int ND = 58;
int NS = 21;
int NN = 79;
// Define the LDPC sizes
int N = 174;
int K = 91;
int M = 83;
int K_BYTES = 12;
// Define CRC parameters
uint16_t CRC_POLYNOMIAL = 0X2757; // CRC-14 polynomial without the leading (MSB) 1
int CRC_WIDTH = 14;
// Communicate amongst decode functions:
typedef struct Candidate {
int16_t score;
int16_t time_offset;
int16_t freq_offset;
uint8_t time_sub;
uint8_t freq_sub;
uint8_t alt; // Added for convenience Teensy, RSL
float32_t syncPower; // Added for Teensy, RSL
} Candidate;
uint8_t secLast = 0;
const int ledPin = 13;
bool showPower = false;
uint32_t tp = 0;
uint32_t tu;
uint32_t ct=0;
void setup(void) {
// set the Time library to use Teensy 4.x's RTC to keep time
setSyncProvider(getTeensy3Time);
AudioMemory_F32(50, audio_settings);
Serial.begin(9600);
delay(1000);
// Enable the audio shield, select input, and enable output
sgtl5000_1.enable();
sgtl5000_1.inputSelect(AUDIO_INPUT_LINEIN);
#ifndef W5BAA
pData2K = demod1.getDataPtr(); // 2048 floats in radioFT8Demodulator_F32
#endif
demod1.initialize();
demod1.setSampleRate_Hz(48000.0f);
init_DSP();
gain1.setGain(1.0);
update_synchronization();
Serial.println("FT8 Receive test");
if (timeStatus()!= timeSet)
Serial.println("Unable to sync with the RTC");
else
Serial.println("RTC has set the system time");
//demod1.startDataCollect(); NOT FOR W5BAA interface
}
void loop(void) {
int16_t inCmd;
if( Serial.available() )
{
inCmd = Serial.read();
if(inCmd=='=') //Set minute clock to zero
{
}
else if(inCmd=='p' || inCmd=='P') // Increase clock 0.1 sec
{
tOffset += 100;
Serial.println("Increase clock 0.1 sec");
Serial.println(0.001*(float)tOffset);
}
else if(inCmd=='l' || inCmd=='L') // Decrease clock 0.1 sec
{
tOffset -= 100;
Serial.println("Decrease clock 0.1 sec");
Serial.println(0.001*(float)tOffset);
}
else if(inCmd=='-') // Increase clock 1 sec
{
tOffset += 1000;
Serial.println("Increase clock 1 sec");
Serial.println(0.001*(float)tOffset);
}
else if(inCmd==',') // Decrease clock 1 sec
{
tOffset -= 1000;
Serial.println("Decrease clock 1 sec");
Serial.println(0.001*(float)tOffset);
}
else if(inCmd=='c' || inCmd=='C') // Clock display
{
Serial.print("Time Offset, millisecods = ");
Serial.println(tOffset);
}
else if(inCmd=='e' || inCmd=='E') // Toggle power display
{
showPower = !showPower;
Serial.print("Show Power = ");
Serial.println(showPower);
}
else if(inCmd=='?')
{
//Serial.println("= Set local FT-8 clock to 0 (60 sec range).");
Serial.println("p, P Increase local FT8 clock by 0.1 sec");
Serial.println("l, L Decrease local FT8 clock by 0.1 sec");
Serial.println("- Increase local FT8 clock by 1 sec");
Serial.println(", Decrease local FT8 clock by 1 sec");
Serial.println("c, C Display offset");
Serial.println("e, E Display received power");
Serial.println("? Help Display (this message)");
}
else if(inCmd>35) // Ignore anything below '#'
Serial.println("Cmd ???");
} // End, if Serial Available
// Print average power level to Serial Monitor
// Useful for testing and synchronizing the FT8 clock. Shows total band power.
if( showPower && demod1.powerAvailable() )
{
float32_t pwr=demod1.powerRead();
float32_t fl;
if(second()>secLast || (second()==0 && secLast==59))
{
secLast = second();
tp = millis();
}
fl = millis() + tOffset - start_time;
while(fl>=15000.0f)
fl -= 15000.0f;
Serial.print(0.001f*fl);
Serial.print(" ");
// Serial.print(0.001*(millis() - tp)+(float)second()); Serial.print(" ");
Serial.print(pwr); Serial.print(" ");
for(int jj=0; jj<2*(30-(int)(-0.5*pwr)); jj++)
Serial.print("*");
Serial.println();
}
update_synchronization();
ft8_mod_time = ft8_time%15000;
if( ft8_mod_time<100 && ft8_mod_time_last>14900 )
{
ft8_mod_time_last = ft8_mod_time;
rcvFT8State = FT8_RCV_DATA_COLLECT;
demod1.startDataCollect(); // Turn on decimation and data
#ifdef DEBUG1
Serial.println("= = = = = SYNC TIME 15 = = = = =");
#endif
digitalWrite(ledPin, HIGH); // set the LED on
delay(100);
digitalWrite(ledPin, LOW); // set the LED on
}
else
ft8_mod_time_last = ft8_mod_time;
if(rcvFT8State==FT8_RCV_DATA_COLLECT && demod1.available())
{
// Here every 80 mSec for FFT
rcvFT8State = FT8_RCV_FIND_POWERS;
// 1472 * 92 = 135424
//master_offset = offset_step * FT_8_counter; //offset_step=1472
master_offset = 736 * (demod1.getFFTCount() -1);
#ifdef DEBUG1
Serial.print("master offset = "); Serial.println(master_offset);
Serial.print("Extract Power, fft count = "); Serial.println( demod1.getFFTCount() );
#endif
extract_power(master_offset); // Do FFT and log powers
rcvFT8State = FT8_RCV_DATA_COLLECT;
#ifdef DEBUG1
Serial.println("Power array updated");
#endif
}
if(rcvFT8State!=FT8_RCV_IDLE && demod1.getFFTCount() >= 184)
{
rcvFT8State = FT8_RCV_DECODE;
#ifdef DEBUG1
Serial.println("FT8 Decode");
#endif
tu = micros();
num_decoded_msg = ft8_decode();
rcvFT8State = FT8_RCV_IDLE;
master_decoded = num_decoded_msg;
#ifdef DEBUG1
Serial.print("ft8_decode Time, uSec = ");
Serial.println(micros() - tu);
#endif
}
delay(1);
} // End loop()
time_t getTeensy3Time() {
return Teensy3Clock.get();
}
// This starts the receiving data collection every 15 sec
void update_synchronization() {
current_time = millis() + tOffset;
ft8_time = current_time - start_time;
ft8_hours = (int8_t)(ft8_time/3600000);
hours_fraction = ft8_time % 3600000;
ft8_minutes = (int8_t) (hours_fraction/60000);
ft8_seconds = (int8_t)((hours_fraction % 60000)/1000);
}

@ -0,0 +1,303 @@
/*
* Process_DSP_R.ino
* Basically the Hill code with changes for Teensy floating point
* OpenAudio_ArduinoLibrary.
* Bob Larkin W7PUA, September 2022.
*
*/
/* Thank you to Charlie Hill, W5BAA, https://github.com/Rotron/Pocket-FT8
* for the conversion to Teensy operation, as well as
* to Kฤrlis Goba, YL3JG, https://github.com/kgoba/ft8_lib.
* Thanks to all the contributors to the Joe Taylor WSJT project.
* See "The FT4 and FT8 Communication Protocols," Steve Franks, K9AN,
* Bill Somerville, G4WJS and Joe Taylor, K1JT, QEX July/August 2020
* pp 7-17 as well as https://www.physics.princeton.edu/pulsar/K1JT
*/
/* ***** MIT License ***
Copyright (C) 2021, Charles Hill
Copyright (C) 2022, Bob Larkin on changes for F32 library
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/* NOTE - The frequency range used here is the same as used by others.
* This is about bin 128 to 768, or 400 Hz to 2400 Hz.
* Stations do operate outside this range. It would be easy to
* increase the range here. The library function radioFT8Demodulator_F32 is filtered
* to pass all frequencies up to, at least 2800 Hz.
*/
// Following are used inside extract_power()
float32_t fft_buffer[2048];
float fftOutput[2048];
float window[2048]; // Change to 1024 by symmetry <<<<<<<<<<<<<<<<<<<
arm_rfft_fast_instance_f32 Sfft;
float32_t powerSum = 0.0f; // Use these for snr estimate
float32_t runningSum = 0.0f;
float32_t powerMax = 0.0f;
float32_t runningMax = 0.0f;
float32_t noiseBuffer[8]; // Circular storage
uint16_t noiseBufferWrite = 0; // Array index
bool noiseMeasured = false; // <<<<<<GLOBAL
uint8_t noisePower8 = 0; // half dB per noise estimate GLOBAL
void init_DSP(void) {
arm_rfft_fast_init_f32(&Sfft, 2048);
for (int i = 0; i < FFT_SIZE; ++i)
window[i] = ft_blackman_i(i, FFT_SIZE);
offset_step = 1472; // (int) HIGH_FREQ_INDEX*4; /// 1472
}
float ft_blackman_i(int i, int N) {
const float alpha = 0.16f; // or 2860/18608
const float a0 = (1 - alpha) / 2;
const float a1 = 1.0f / 2;
const float a2 = alpha / 2;
float x1 = cosf(2 * (float)M_PI * i / (N - 1));
//float x2 = cosf(4 * (float)M_PI * i / (N - 1));
float x2 = 2*x1*x1 - 1; // Use double angle formula
return a0 - a1*x1 + a2*x2;
}
// Compute FFT magnitudes (log power) for each timeslot in the signal
void extract_power( int offset) {
float32_t y[8];
float32_t noiseCoeff[3];
/* Format of export_fft_power[] array:
368 bytes of power for even time for 0.32 sec sample DESCRIBE BETTER <<<<<<<<<<<<<<<<<<<<<<
368 bytes of power for odd time for 0.32 sec sample
...
Repeated about 14.7/(0.08 sec) = 184 times. (Transmitted message length is 12.96 sec)
Total bytes 4 * 368 * 92 = 135424
The power byte is log encoded with a half dB MSB. This can handle a
dynamic range of 256/2 = 128 dB.
*/
for(int i=0; i<2048; i++)
{
fft_buffer[i] = window[i]*pData2K[i]; // Protect pData2K from in-place FFT (17 uSec)
}
// (float32_t* pIn, float32_t* pOut, uint8_t ifftFlag)
arm_rfft_fast_f32(&Sfft, fft_buffer, fftOutput, 0);
arm_cmplx_mag_squared_f32(fftOutput, fftOutput, 1024);
// Variables for estimating noise level for SNR
powerSum = 0.0f;
powerMax = 0.0f;
for(int i=1; i<1024; i++)
{
if(i>=128 && i<768) // Omit the first 400 Hz and last 800 Hz
powerSum += fftOutput[i];
if(fftOutput[i] > powerMax)
powerMax = fftOutput[i];
// Next, 20*log10() (not 10*) is to the make 8-bit resolution 0.5 dB.
// The floats range from nothing to 40*log10(1024)=120 for a
// pure sine wave. For FT8, we never encounter this. To keep
// the sine wave answer below 256 would use an upward
// offset of 256-120=136. This totally prevents overload!
// Borrow fft_buffer for a moment:
fft_buffer[i] = 136.0f + 20.0f*log10f( 0.0000001f + fftOutput[i] );
}
fft_buffer[0] = 0.000001; // Fake DC term
/* Noise needs to be estimated to determine snr. Two cases:
* runningMax/runningSum < 100 This is weak signal case for which
* the runningSum must be used alone.
* runningMax/runningSum > 100 Here the 2 second quiet period can
* can be found and running Sum used
* when runningMax/runningSum is high.
*/
runningSum = 0.80f*runningSum + 0.20f*powerSum; // Tracks changes in pwr
runningMax = 0.99f*runningMax + 0.01f*powerMax; // Slow decay
// Put the sum intocircular buffer
noiseBuffer[ 0X0007 & noiseBufferWrite++ ] = 0.00156f*runningSum;
for(int kk=0; kk<8; kk++)
y[kk] = (float32_t)noiseBuffer[ 0X0007 & (kk + noiseBufferWrite) ];
//fitCurve (int order, int nPoints, float32_t py[], int nCoeffs, float32_t *coeffs)
fitCurve(2, 8, y, 3, noiseCoeff);
float32_t y9 = noiseCoeff[2] + 9.0f*noiseCoeff[1] + 81.0f*noiseCoeff[0];
if(runningMax > 100.0f*0.00156f*runningSum && y9 > 2.0f*noiseCoeff[2] && !noiseMeasured)
{
// This measurement occurs once every 15 sec, but may be just before
// or just after decode. Either way, the "latest" noise estimate is used.
noiseMeasured = true; // Reset after decode()
noisePowerEstimateH = 0.2f*(y[0]+y[1]+y[2]+y[3]+y[4]);
noisePwrDBIntH = (int16_t)(10.0f*log10f(noisePowerEstimateH));
noisePeakAveRatio = runningMax/(0.00156*runningSum);
#ifdef DEBUG_N
Serial.println("Noise measurement between transmit time periods:");
Serial.print(" rSum, rMax= "); Serial.print(0.00156*runningSum, 5);
Serial.print(" "); Serial.print(runningMax, 5);
Serial.print(" Ratio= "); Serial.print(noisePeakAveRatio, 3);
Serial.print(" Int noise= ");
Serial.println(noisePwrDBIntH); // dB increments
#endif
}
// Loop over two frequency bin offsets. This first picks up 367 even
// numbered fft_buffer[] followed by 367 odd numbered bins. This is
// a frequency shift of 3.125 Hz. With windowing, the bandwidth
// of each FFT output is about 6 Hz, close to a match for the
// 0.16 sec transmission time.
/* First pass: j on (0, 367) j*2+freq_sub on (0, 734) (even)
* Secnd pass: j on (0, 367) j*2+freq_sub on (1, 735) (odd)
*/
for (int freq_sub=0; freq_sub<2; ++freq_sub)
{
for (int j=0; j<368; ++j)
{
export_fft_power[offset] = (uint8_t)fft_buffer[j*2 + freq_sub];
++offset;
}
}
} // End extract_power()
// ===============================================================
// CURVE FIT
/*
curveFitting - Library for fitting curves to given
points using Least Squares method, with Cramer's rule
used to solve the linear equation.
Created by Rowan Easter-Robinson, August 23, 2018.
Released into the public domain.
Converted to float32_t, made specific to FT8 case Bob L Oct 2022
*/
void cpyArray(float32_t *src, float32_t*dest, int n){
for (int i = 0; i < n*n; i++){
dest[i] = src[i];
}
}
void subCol(float32_t *mat, float32_t* sub, uint8_t coln, uint8_t n){
if (coln >= n) return;
for (int i = 0; i < n; i++){
mat[(i*n)+coln] = sub[i];
}
}
/*Determinant algorithm taken from
// https://codeforwin.org/2015/08/c-program-to-find-determinant-of-matrix.html */
int trianglize(float32_t **m, int n)
{
int sign = 1;
for (int i = 0; i < n; i++) {
int max = 0;
for (int row = i; row < n; row++)
if (fabs(m[row][i]) > fabs(m[max][i]))
max = row;
if (max) {
sign = -sign;
float32_t *tmp = m[i];
m[i] = m[max], m[max] = tmp;
}
if (!m[i][i]) return 0;
for (int row = i + 1; row < n; row++) {
float32_t r = m[row][i] / m[i][i];
if (!r) continue;
for (int col = i; col < n; col ++)
m[row][col] -= m[i][col] * r;
}
}
return sign;
}
float32_t det(float32_t *in, int n)
{
float32_t *m[n];
m[0] = in;
for (int i = 1; i < n; i++)
m[i] = m[i - 1] + n;
int sign = trianglize(m, n);
if (!sign)
return 0;
float32_t p = 1;
for (int i = 0; i < n; i++)
p *= m[i][i];
return p * sign;
}
/*End of Determinant algorithm*/
//Raise x to power
float32_t curveFitPower(float32_t base, int exponent){
if (exponent == 0){
return 1;
} else {
float32_t val = base;
for (int i = 1; i < exponent; i++){
val = val * base;
}
return val;
}
}
#define MAX_ORDER 4
int fitCurve (int order, int nPoints, float32_t py[], int nCoeffs, float32_t *coeffs) {
int i, j;
float32_t T[MAX_ORDER] = {0}; //Values to generate RHS of linear equation
float32_t S[MAX_ORDER*2+1] = {0}; //Values for LHS and RHS of linear equation
float32_t denom; //denominator for Cramer's rule, determinant of LHS linear equation
float32_t x, y;
float32_t px[nPoints]; //Generate X values, from 0 to n
for (i=0; i<nPoints; i++){
px[i] = i;
}
for (i=0; i<nPoints; i++) {//Generate matrix elements
x = px[i];
y = py[i];
for (j = 0; j < (nCoeffs*2)-1; j++){
S[j] += curveFitPower(x, j); // x^j iterated , S10 S20 S30 etc, x^0, x^1...
}
for (j = 0; j < nCoeffs; j++){
T[j] += y * curveFitPower(x, j); //y * x^j iterated, S01 S11 S21 etc, x^0*y, x^1*y, x^2*y...
}
}
float32_t masterMat[nCoeffs*nCoeffs]; //Master matrix LHS of linear equation
for (i = 0; i < nCoeffs ;i++){//index by matrix row each time
for (j = 0; j < nCoeffs; j++){//index within each row
masterMat[i*nCoeffs+j] = S[i+j];
}
}
float32_t mat[nCoeffs*nCoeffs]; //Temp matrix as det() method alters the matrix given
cpyArray(masterMat, mat, nCoeffs);
denom = det(mat, nCoeffs);
cpyArray(masterMat, mat, nCoeffs);
//Generate cramers rule mats
for (i = 0; i < nCoeffs; i++){ //Temporary matrix to substitute RHS of linear equation as per Cramer's rule
subCol(mat, T, i, nCoeffs);
coeffs[nCoeffs-i-1] = det(mat, nCoeffs)/denom; //Coefficients are det(M_i)/det(Master)
cpyArray(masterMat, mat, nCoeffs);
}
return 0;
}

@ -0,0 +1,474 @@
/*
* constantsR.ino
* Basically the Goba constants.h and .c with minor changes for Teensy
* Arduino use along with the floating point OpenAudio_ArduinoLibrary.
* Bob Larkin W7PUA, September 2022.
*
*/
/* Thank you to Kฤrlis Goba, YL3JG, https://github.com/kgoba/ft8_lib
* and to Charlie Hill, W5BAA, https://github.com/Rotron/Pocket-FT8
* as well as the all the contributors to the Joe Taylor WSJT project.
* See "The FT4 and FT8 Communication Protocols," Steve Franks, K9AN,
* Bill Somerville, G4WJS and Joe Taylor, K1JT, QEX July/August 2020
* pp 7-17 as well as https://www.physics.princeton.edu/pulsar/K1JT
*/
/* ***** MIT License ***
Copyright (c) 2018 Kฤrlis Goba
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#if 0
// Retained here as useful notes:
extern int K_BYTES;
extern uint8_t tones[79];
// Costas 7x7 tone pattern
extern const uint8_t kCostas_map[7];
// Gray code map
extern const uint8_t kGray_map[8];
// Parity generator matrix for (174,91) LDPC code, stored in bitpacked format (MSB first)
//extern const uint8_t kGenerator[M][K_BYTES];
extern uint8_t kGenerator[83][12];
// Column order (permutation) in which the bits in codeword are stored
// (Not really used in FT8 v2 - instead the Nm, Mn and generator matrices are already permuted)
//extern const uint8_t kColumn_order[N];
extern uint8_t kColumn_order[174];
// this is the LDPC(174,91) parity check matrix.
// 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 kNm[M][7];
extern uint8_t kNm[83][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.
// 1-origin.
//extern const uint8_t kMn[N][3];
extern uint8_t kMn[174][3];
// Number of rows (columns in C/C++) in the array Nm.
//extern const uint8_t kNrw[M];
extern uint8_t kNrw[83];
#endif
// See FT8Receive.ino for defines of some constant values.
// Costas 7x7 tone pattern
const uint8_t kCostas_map[7] = { 3,1,4,0,6,5,2 };
// Gray code map
const uint8_t kGray_map[8] = { 0,1,3,2,5,6,4,7 };
// Parity generator matrix for (174,91) LDPC code, stored in bitpacked format (MSB first)
uint8_t kGenerator[83][12] = {
{ 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 }
};
// Column order (permutation) in which the bits in codeword are stored
// (Not really used in FT8 v2 - instead the Nm, Mn and generator matrices are already permuted)
uint8_t kColumn_order[174] = {
0, 1, 2, 3, 28, 4, 5, 6, 7, 8, 9, 10, 11, 34, 12, 32, 13, 14, 15, 16,
17, 18, 36, 29, 43, 19, 20, 42, 21, 40, 30, 37, 22, 47, 61, 45, 44, 23, 41, 39,
49, 24, 46, 50, 48, 26, 31, 33, 51, 38, 52, 59, 55, 66, 57, 27, 60, 35, 54, 58,
25, 56, 62, 64, 67, 69, 63, 68, 70, 72, 65, 73, 75, 74, 71, 77, 78, 76, 79, 80,
53, 81, 83, 82, 84, 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
};
// this is the LDPC(174,91) parity check matrix.
// 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.
uint8_t kNm[83][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 }
};
// 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.
// 1-origin.
uint8_t kMn[174][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 }
};
uint8_t kNrw[83] = {
7,6,6,6,7,6,7,6,6,7,6,6,7,7,6,6,