You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
OpenAudio_ArduinoLibrary/radioFT8Demodulator_F32.h

414 lines
15 KiB

/*
* radioFT8Demodulator_F32.h Assembled by Bob Larkin 11 Sept 2022
*
* Note: Teensy 4.x Only, 3.x not supported
*
* (c) 2021 Bob Larkin
*
* 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, development funding 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.
*/
/* *** CREDITS ***
* There have been many contributors to this FT8 project. I will not
* catch them all, but a try is neeeded. I picked from
* multiple sources in making the transmission process consistent with the
* 128-block Teensy Audio Library. But, everything I did was a translation
* of existing software. The "specification" for FT8 is the paper, "The FT4
* and FT8 Communications Protocols," by Steve Franke, K9AN, Bill Somerville,
* G4WJS and Joe Taylor, K1JT, QEX July/August 2020, pp7-17. I was guided
* in the right direction for this implementation by Farhan, VU2ESE
* and Charley, W5BAA. This led to a pair of excellent Raspberry Pi sources,
* https://github.com/kgoba/ft8_lib and
* https://github.com/WB2CBA/W5BAA-FT8-POCKET-TERMINAL
* An FT8 derivative of the first web site, using the Raspberry Pi,
* is Farhan's sBitx:
* https://github.com/afarhan/sbitx
* For those not familiar with FT8, it is a widely used amateur radio
* communications format. A search of FT8 will lead to much information.
*
* Notes:
* 1. Sampling rate: The entire FT8 Demodulator runs at a decimated
* 6400 samples/sec. At this time, only 48 and 96 kHz sample rates are
* supported. For 48 kHz there is
* an interpolate to 96 kHz followed by decimation to 19.2 and 6.4 kHz.
* For 96 kHz there is no interpolation.
*
* 2. In order to process the decoding of FT8 as rapidly as possibly,
* after the 0.16 seconds of data are acquired (2048 data points),
* this library class only does the process that must be done in real
* time, either because of data un-availability or because the full data
* set for 0.16 sec is needed. Thus this class does the two stages
* of decimation in time needed to reduce the data rate and does the
* saving of signal samples to an array.
*
* 3. To allow two FFT's, offset in time by 0.16sec / 2, we need to gather
* 2048 data points every 0.08 seconds with 50% overlap in data.
* These are put into an array that is supplied by the calling
* INO/CPP program. It is arrange as signalData[2][1024]
* for convenience. The indices of the first of two is returned
* when data is available. In order to not need dual large storage, an
* 18 float auxiliary storage is provided to store data acquired during
* the "first two" update period (5.3 or 2.7 mSec for 48 or
* 96 kHz sample rate). This gives time for an FFT
* before altering the signal data.
*
* 4. This class does not provide true (absolute) clock timing.
* The startReceive() function should be called when it is time
* for a new reception period. This class will start that at the
* next 128 audio sample period.
*
* 5. Update time required for a T4.x is uSec.
*/
// The Charley Hill W5BAA_INTERFACE is from the Pocket-FT9 project and
// transfers data as 128 int's. It is included as a compile time
// option to allow testing with the Pocket-FT8 Teensy software.
// The default interface transfers the collected data as a pointer to
// 2048 F32 float's ready for windowing and FFT's. Both
// interfaces includes 50% overlap of the data to correct for window losses.
// Rev 16 Jan 2023 Corrected position of endif for T4.x only Bob
// #define W5BAA_INTERFACE
#ifndef radioFT8Demodulator_h_
#define radioFT8Demodulator_h_
#define SR_NONE 0
#define SR48000 1
#define SR96000 2
// *************** TEENSY 4.X ONLY ****************
#if defined(__IMXRT1062__)
#include "Arduino.h"
#include "AudioStream_F32.h"
#include "arm_math.h"
// NOTE Changed class name to start with capital "R" RSL 7 Nov 2022
class RadioFT8Demodulator_F32 : public AudioStream_F32 {
//GUI: inputs:2, outputs:4 //this line used for automatic generation of GUI node
//GUI: shortName:FFT2048IQ
public:
RadioFT8Demodulator_F32() : AudioStream_F32(1, inputQueueArray_f32) {
}
// There is no varient for "settings," as blocks other than 128 are
// not supported FIX. <<<<<<<<<<<<<<<<<<<<<
void initialize(void) {
// Initialize 2 FIR instances (ARM DSP Math Library)
//arm_fir_init_f32 (arm_fir_instance_f32 *S, uint16_t numTaps,
// const float32_t *pCoeffs, float32_t *pState, uint32_t blockSize)
arm_fir_init_f32(&fir_inst1, 55, &firDecimate1[0], &dec1FIRWork[0], 128UL);
arm_fir_init_f32(&fir_inst2, 167, &firDecimate2[0], &dec2FIRWork[0], 128UL);
// CHECK SAMPLE RATE AND SET index <<<<<<<<<<<<<
FT8DemodInit = true;
#ifdef W5BAA_INTERFACE
gettingData = true;
#endif
}
// Following reflects that there are only 2 supported sample rates.
// IMPORTANT: This changes constants for FT8 Receive, only. It does not
// change system-wide audio sample rate. Use AudioSettings_F32.
void setSampleRate_Hz(const float32_t &fs_Hz) {
sampleRateHz = fs_Hz;
if(sampleRateHz>47900.0f && sampleRateHz<48100.0f)
{
sampleRateHz = 48000.0f;
srIndex = SR48000;
}
else if(sampleRateHz>95900.0f && sampleRateHz<96100.0f)
{
sampleRateHz = 96000.0f;
srIndex = SR96000;
}
else
{
Serial.println("Unsupported sample rate, FT8 will not receive.");\
srIndex = SR_NONE;
}
}
void decimate15(void);
bool powerAvailable(void) {
if(powAvail) return true;
return false;
}
// Read the dB power level *once* per 128 samples at 6.4kHz
float32_t powerRead(void) {
if(powAvail)
{
powAvail = false;
return powerOut;
}
else
return -200.0f;
}
#ifdef W5BAA_INTERFACE
int queueAvailable(void) { // W5BAA_INTERFACE only
uint32_t h, t;
h = head;
t = tail; //Serial.print("@queueAvailable h, t= "); Serial.print(h); Serial.print(", "); Serial.println(t);
if (h >= t)
return h - t;
return max_buffers + h - t;
}
void queueClear(void) { // W5BAA_INTERFACE only
uint32_t t;
if (userblock)
{
AudioStream::release(userblock);
userblock = NULL;
}
t = tail;
while (t != head)
{
if (++t >= max_buffers)
t = 0;
AudioStream::release(queue[t]);
}
tail = t;
}
int16_t* queueReadBuffer(void) { // W5BAA_INTERFACE only
uint32_t t;
if (userblock)
return NULL;
t = tail;
if (t == head)
return NULL;
if (++t >= max_buffers)
t = 0;
userblock = queue[t];
tail = t;
return userblock->data; // Pointer to 128 int16_t of data
}
void queueFreeBuffer(void) { // W5BAA_INTERFACE only
if (userblock == NULL)
return;
AudioStream::release(userblock);
userblock = NULL;
}
#else
// Regular 512/2048 float interface
// Returns true when output data is available.
bool available() {
if (outputFlag == true)
{
outputFlag = false; // No double returns
return true;
}
return false;
}
// Start a new 14.7 sec data gather
void startDataCollect(void) {
gettingData = true;
FFTCount = 0;
dec1Count = 0;
dec2Count = 0;
kOffset1 = 0;
kOffset2 = 0;
outputFlag = false;
current128Used1 = false;
current128Used2 = false;
FFTCount = 0;
FFTOld = 0;
block128Count = 0;
index2 = 0; // Runs 0,511 on outputs
}
// Cancel the data gather
void cancelDataCollect(void) {
gettingData = false;
// Getting started again from here is by startDataCollect()
}
bool receivingData(void) {
return gettingData;
}
// Return FFT Count, 0 = No data
// 1 to 184 = current 1024 words last returned
// The number is incremented every 80 mSec during receive
// Reset to 0 is by startDataCollect()
int getFFTCount(void) {
return FFTCount;
}
float32_t* getDataPtr(void) { // Location of input for FFT
return &data2K[0];
}
#endif
virtual void update(void);
private:
audio_block_f32_t *inputQueueArray_f32[1];
float sampleRateHz = AUDIO_SAMPLE_RATE;
int16_t srIndex = SR_NONE;
//uint16_t block_size = 128;
int16_t fcurrentArray = -1;
float32_t *p0 = NULL; // Pointers to 1024 storage
float32_t *p1 = NULL;
uint32_t ttt;
int32_t kkk;
bool FT8DemodInit = false;
bool outputFlag = false;
bool gettingData = false;
bool current128Used1 = false; // Decimation by 5
bool current128Used2 = false; // Decimation by 3
audio_block_f32_t *inputQueueArray[1];
int32_t dec1Count = 0;
int32_t dec2Count = 0;
float32_t dataIn[128];
float32_t inFIR1[128]; // Inputs for FIR1
// Working space for 55-tap FIR, 128 data points
float32_t dec1FIRWork[183];
float32_t outFIR1[128];
int kOffset1 = 0;
float32_t inFIR2[128];
// Working space for 167-tap FIR
float32_t dec2FIRWork[295];
float32_t outFIR2[128];
int kOffset2 = 0;
arm_fir_instance_f32 fir_inst1, fir_inst2;
bool powAvail = false;
float32_t powerOut = 0.0f;
float32_t outData[128]; // Storage after decimate by 3
#ifdef W5BAA_INTERFACE
static const int max_buffers = 48; // Enough for 3 FFT, i.e., plenty
audio_block_t* volatile queue[max_buffers];
audio_block_t* userblock;
volatile uint8_t head = 0;
volatile uint8_t tail = 0;
volatile uint8_t enabled = 0;
audio_block_t *block;
uint32_t h;
#else
int16_t FFTCount = 0;
int FFTOld = 0;
int16_t block128Count = 0;
int16_t index2 = 0; // Runs 0,511 on outputs
float32_t data2K[2048]; // Output time array to FFT 2048+512
#endif
// int64_t sampleCount = 0LL;
// bool sampleCountOdd = false;
/* FOR INFO ONLY
// Blackman-Harris produces a first sidelobe more than 90 dB down.
// The price is a bandwidth of about 2 bins. Very useful at times.
void useBHWindow(void) {
for (int i=0; i < 2048; i++) {
float kx = 0.00306946f; // 2*PI/2047
int ix = (float) i;
window[i] = 0.35875 -
0.48829*cosf( kx*ix) +
0.14128*cosf(2.0f*kx*ix) -
0.01168*cosf(3.0f*kx*ix);
}
}
*/
/* FIR filter designed with http://t-filter.appspot.com
* Sampling frequency = 96000 Hz
* 0 Hz - 3200 Hz ripple = 0.065 dB
* 9600 Hz - 48000 Hz att = -81.1 dB */
float32_t firDecimate1[55] = { // constexpr static
0.000037640f, 0.000248621f, 0.000535682f, 0.001017563f, 0.001647298,
0.002359693f, 0.003009942f, 0.003388980f, 0.003245855f, 0.002335195,
0.000482309f, -0.002343975f, -0.005965316f, -0.009953093f, -0.013627779f,
-0.016110493f, -0.016426909f, -0.013654288f, -0.007090645f, 0.003582981f,
0.018178902f, 0.035947300f, 0.055606527f, 0.075464728f, 0.093619230f,
0.108205411f, 0.117656340f, 0.120928651f, 0.117656340f, 0.108205411f,
0.093619230f, 0.075464728f, 0.055606527f, 0.035947300f, 0.018178902f,
0.003582981f, -0.007090645f, -0.013654288f, -0.016426909f, -0.016110493f,
-0.013627779f, -0.009953093f, -0.005965316f, -0.002343975f, 0.000482309f,
0.002335195f, 0.003245855f, 0.003388980f, 0.003009942f, 0.002359693f,
0.001647298f, 0.001017563f, 0.000535682f, 0.000248621f, 0.000037640f};
/* FIR filter designed with http://t-filter.appspot.com
* Sampling frequency = 19200 Hz
* 0 Hz - 2800 Hz ripple = 0.073 dB
* 3200 Hz - 9600 Hz att = -80.0 dB */
float32_t firDecimate2[167] = {
0.000200074f, 0.000438821f, 0.000648425f, 0.000636175f, 0.000315069f,
-0.000193500f, -0.000591064f, -0.000600896f, -0.000202482f, 0.000301473f,
0.000486276f, 0.000152104f, -0.000466930f, -0.000846593f, -0.000601871f,
0.000140985f, 0.000780434f, 0.000717692f, -0.000092419f, -0.001015260f,
-0.001218073f, -0.000407595f, 0.000820258f, 0.001400419f, 0.000712814f,
-0.000783775f, -0.001824384f, -0.001392096f, 0.000312681f, 0.001894843f,
0.001890948f, 0.000105692f, -0.002046807f, -0.002639347f, -0.000937478f,
0.001779635f, 0.003148244f, 0.001757989f, -0.001441273f, -0.003731905f,
-0.002912877f, 0.000623820f, 0.003953823f, 0.004009904f, 0.000373714f,
-0.004041709f, -0.005284541f, -0.001864153f, 0.003610036f, 0.006359221f,
0.003566059f, -0.002829631f, -0.007374880f, -0.005699305f, 0.001364774f,
0.007956768f, 0.007979137f, 0.000650460f, -0.008163693f, -0.010542154f,
-0.003518210f, 0.007604450f, 0.013094981f, 0.007160556f, -0.006233941f,
-0.015716023f, -0.011940068f, 0.003541776f, 0.018116367f, 0.018029298f,
0.000861510f, -0.020344029f, -0.026351929f, -0.008277630f, 0.022137232f,
0.038780109f, 0.021608602f, -0.023552791f, -0.062590967f, -0.053225138f,
0.024375124f, 0.148263262f, 0.262405223f, 0.308632386f, 0.262405223f,
0.148263262f, 0.024375124f, -0.053225138f, -0.062590967f, -0.023552791f,
0.021608602f, 0.038780109f, 0.022137232f, -0.008277630f, -0.026351929f,
-0.020344029f, 0.000861510f, 0.018029298f, 0.018116367f, 0.003541776f,
-0.011940068f, -0.015716023f, -0.006233941f, 0.007160556f, 0.013094981f,
0.007604450f, -0.003518210f, -0.010542154f, -0.008163693f, 0.000650460f,
0.007979137f, 0.007956768f, 0.001364774f, -0.005699305f, -0.007374880f,
-0.002829631f, 0.003566059f, 0.006359221f, 0.003610036f, -0.001864153f,
-0.005284541f, -0.004041709f, 0.000373714f, 0.004009904f, 0.003953823f,
0.000623820f, -0.002912877f, -0.003731905f, -0.001441273f, 0.001757989f,
0.003148244f, 0.001779635f, -0.000937478f, -0.002639347f, -0.002046807f,
0.000105692f, 0.001890948f, 0.001894843f, 0.000312681f, -0.001392096f,
-0.001824384f, -0.000783775f, 0.000712814f, 0.001400419f, 0.000820258f,
-0.000407595f, -0.001218073f, -0.001015260f, -0.000092419f, 0.000717692f,
0.000780434f, 0.000140985f, -0.000601871f, -0.000846593f, -0.000466930f,
0.000152104f, 0.000486276f, 0.000301473f, -0.000202482f, -0.000600896f,
-0.000591064f, -0.000193500f, 0.000315069f, 0.000636175f, 0.000648425f,
0.000438821f, 0.000200074f};
}; // End class def
// endif for Teensy 4.x only:
#endif
// endif for single read of .h file:
#endif