Add CESSB for zero-IF radio transmitters, new

pull/16/merge
boblark 2 years ago
parent d5427c8a68
commit 69a298f51d
  1. 1
      OpenAudio_ArduinoLibrary.h
  2. 218
      examples/CESSB_ZeroIF/CESSB_ZeroIF.ino
  3. 253
      examples/CESSB_ZeroIF/hilbert251A_Z_.h
  4. 255
      radioCESSB_Z_transmit_F32.cpp
  5. 444
      radioCESSB_Z_transmit_F32.h

@ -57,6 +57,7 @@
#include "AudioFilterEqualizer_F32.h" #include "AudioFilterEqualizer_F32.h"
#include "AudioFilterFIRGeneral_F32.h" #include "AudioFilterFIRGeneral_F32.h"
#include "radioCESSBtransmit_F32.h" #include "radioCESSBtransmit_F32.h"
#include "radioCESSB_Z_transmit_F32.h"
#include "RadioFMDetector_F32.h" #include "RadioFMDetector_F32.h"
#include "radioBFSKmodulator_F32.h" #include "radioBFSKmodulator_F32.h"
#include "radioFT8Modulator_F32.h" #include "radioFT8Modulator_F32.h"

@ -0,0 +1,218 @@
// CESSB_ZeroIF.ino
// This tests the Controlled Envelope Single Sideband generator version
// that produces a zero-IF signal 0to 3kHz or 0 to -3 kHz.
// Uses radioCESSB_Z_transmit_F32.h and .cpp. See the .h file for
// more information and references.
// Tests with voice from SD Card file and a 1 second 750 Hz tone burst.
//
// The SD card may connect to different pins, depending on the
// hardware you are using. Configure the SD card
// pins to match your hardware. It is set for T4.x Rev D PJRC
// Teensy Audio Adaptor card here.
//
// Your microSD card must have the WAV file loaded to it:
// W9GR48.WAV
// These are at
// https://github.com/chipaudette/OpenAudio_ArduinoLibrary/blob/master/utility/
//
// This example code is in the public domain.
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include "OpenAudio_ArduinoLibrary.h"
// T3.x supported sample rates: 2000, 8000, 11025, 16000, 22050, 24000, 32000, 44100, 44117, 48000,
// 88200, 88235 (44117*2), 95680, 96000, 176400, 176470, 192000
// T4.x supports any sample rate the codec will handle.
const float sample_rate_Hz = 48000.0f;
const int audio_block_samples = 128; // Always 128
AudioSettings_F32 audio_settings(sample_rate_Hz, audio_block_samples);
AudioSynthWaveformSine_F32 sine1(audio_settings);
AudioSDPlayer_F32 playWav1(audio_settings);
AudioMixer4_F32 mixer4_0;
radioCESSB_Z_transmit_F32 cessb1(audio_settings);
RadioIQMixer_F32 iqMixer1(audio_settings);
AudioMixer4_F32 mixer4_2;
AudioFilter90Deg_F32 filter90deg1(audio_settings);
RadioIQMixer_F32 iqMixer2(audio_settings);
AudioMixer4_F32 mixer4_1;
AudioOutputI2S_F32 audioOutput(audio_settings);
AudioAnalyzeFFT1024_F32 fft1;
AudioConnection_F32 patchCord0(playWav1, 0, mixer4_0, 0);
AudioConnection_F32 patchCordb(sine1, 0, mixer4_0, 1);
AudioConnection_F32 patchCordc(mixer4_0, 0, cessb1, 0);
AudioConnection_F32 patchCord1(cessb1, 0, iqMixer1, 0);
AudioConnection_F32 patchCord2(cessb1, 1, iqMixer1, 1);
AudioConnection_F32 patchCord9(iqMixer1, 0, mixer4_2, 0);
AudioConnection_F32 patchCord10(iqMixer1,1, mixer4_2, 1);
// mixer4_2 is transmitter SSB output, iqMixer2 is receiver input
AudioConnection_F32 patchCord14(mixer4_2,0, iqMixer2, 0);
AudioConnection_F32 patchCord3(iqMixer2, 0, filter90deg1, 0);
AudioConnection_F32 patchCord4(iqMixer2, 1, filter90deg1, 1);
AudioConnection_F32 patchCord7(filter90deg1, 0, mixer4_1, 0);
AudioConnection_F32 patchCord8(filter90deg1, 1, mixer4_1, 1);
AudioConnection_F32 patchCord11(mixer4_1, 0, audioOutput, 0);
AudioConnection_F32 patchCord12(mixer4_1, 0, audioOutput, 1);
AudioConnection_F32 patchCord13(mixer4_2, 0, fft1, 0);
AudioControlSGTL5000 sgtl5000_1;
// Use these with the Teensy 4.x Rev D Audio Shield (NOT for T3.x)
#define SDCARD_CS_PIN_Z 10
#define SDCARD_MOSI_PIN_Z 11
#define SDCARD_SCK_PIN_Z 13
// Filter for AudioFilter90Deg_F32 hilbert1, only for receiving the CESSB
#include "hilbert251A_Z_.h"
// wavData is a global struct, definined in AudioSDPlayer_F32.h
// This provides information about the current WAV file to this .INO
struct wavData* pCurrentWavData;
// And data about the CESSB
struct levelsZ* pLevelData;
uint32_t writeOne = 0;
uint32_t cntFFT = 0;
uint32_t ttt; // For timing test audio
uint32_t tp;
void setup() { // ********** SETUP **********
Serial.begin(9600); delay(1000);
Serial.println("*** Test CESSB Zero-IF from SD Card Voice Sample ***");
AudioMemory_F32(70, audio_settings);
pCurrentWavData = playWav1.getCurrentWavData();
sgtl5000_1.enable();
delay(500);
SPI.setMOSI(SDCARD_MOSI_PIN_Z);
SPI.setSCK(SDCARD_SCK_PIN_Z);
Serial.print("SD.begin() returns "); Serial.println(SD.begin(SDCARD_CS_PIN_Z));
// sine0.frequency(468.75f); // 2-tone generators
// sine0.amplitude(0.707107);
sine1.frequency(750.0f);
sine1.amplitude(0.707107);
// Build the CESSB SSB transmitter
// The WAV file has carefully controlled 0.707 peaks. We bring these to 1.000
mixer4_0.gain(0, 1.41421356f); // Play WAV file on
mixer4_0.gain(2, 0.0); // Sine Wave 1 off
cessb1.setSampleRate_Hz(48000.0f);
// Set input, correction, and output gains
float32_t Pre_CESSB_Gain = 1.5f; // Use to set amount of clipping, 1.0 to 2.0f, 3 is excessive
cessb1.setGains(Pre_CESSB_Gain, 1.4f, 1.0f);
cessb1.setSideband(false);
pLevelData = cessb1.getLevels(0); // Gets pointer to struct
// Generate SSB at 15 kHz from zero-IF signal of CESSB generator
iqMixer1.useTwoChannel(true);
iqMixer1.frequency(15000);
mixer4_2.gain(1, 1.0f); // 1.0f for LSB, -1.0f for USB
// Need a receiver for the SSB transmitter to let us hear the results.
iqMixer2.frequency(15000.0f);
iqMixer2.useTwoChannel(false);
filter90deg1.begin(hilbert251A, 251);
mixer4_1.gain(0, -1.0f); // LSB, + for USB
audioOutput.setGain(0.02); // <<< Output volume control
fft1.setOutputType(FFT_DBFS);
fft1.windowFunction(AudioWindowBlackmanHarris1024);
fft1.setNAverage(16);
ttt = millis(); // Time test audio
tp=millis();
}
void playFile(const char *filename) {
if(playWav1.isPlaying())
return;
Serial.println("");
Serial.print("Playing file: ");
Serial.println(filename);
playWav1.play(filename); // Start playing the file.
// A brief delay for the library read WAV info
delay(25);
Serial.print("WAV file format = "); Serial.println(pCurrentWavData->audio_format);
Serial.print("WAV number channels = "); Serial.println(pCurrentWavData->num_channels);
Serial.print("WAV File Sample Rate = "); Serial.println(pCurrentWavData->sample_rate);
Serial.print("Number of bits per Sample = "); Serial.println(pCurrentWavData->bits);
Serial.print("File length, seconds = ");
Serial.println(0.001f*(float32_t)playWav1.lengthMillis(), 3);
}
void loop() {
uint32_t tt=millis() - ttt;
if(tt < 2000)
{
// Thanks to W9GR for the test file, W9GR48.WAV. This is intended for testing
// the CESSB radio transmission system.
playFile("W9GR48.WAV");
mixer4_0.gain(0, 1.41421356f); // Play WAV file on
mixer4_0.gain(1, 0.0f); // Sine Wave 1 off
}
else if(tt > 12300 && tt<13300)
{
mixer4_0.gain(1, 0.0f); // Play WAV file off
// The following puts a 1-sec 750 Hz, full amplitude tone into the input
// .707 on the generator and 1.414 here make the peak sine wave 1.000 at the CESSB input
mixer4_0.gain(1, 1.41421356f); // Sine Wave 1 on, 750 Hz
}
else if(tt >= 13300)
{
mixer4_0.gain(0, 1.41421356f); // Play WAV file on
mixer4_0.gain(1, 0.0f); // Sine Wave 1 off
ttt = millis(); // Start again
}
delay(1);
// Un-comment the following to print out the spectrum
/*
if(fft1.available() && ++cntFFT>100 && cntFFT<102)
{
for(int kk=0; kk<512; kk++)
{
Serial.print(46.875f*(float32_t)kk); Serial.print(",");
Serial.println(fft1.read(kk));
}
}
*/
if(cessb1.levelDataCount() > 300) // Typically 300 to 3000
{
cessb1.getLevels(1); // Cause write of data to struct & reset
// Detailed Report
Serial.print(10.0f*log10f(pLevelData->pwr0));
Serial.print(" In Ave Pwr Out ");
Serial.println(10.0f*log10f(pLevelData->pwr1));
Serial.print(20.0f*log10f(pLevelData->peak0));
Serial.print(" In Peak Out ");
Serial.println(20.0f*log10f(pLevelData->peak1));
Serial.print(pLevelData->peak0, 6);
Serial.print(" In Peak Volts Out ");
Serial.println(pLevelData->peak1, 6);
Serial.print("Enhancement = ");
float32_t enhance = (10.0f*log10f(pLevelData->pwr1) - 20.0f*log10f(pLevelData->peak1)) -
(10.0f*log10f(pLevelData->pwr0) - 20.0f*log10f(pLevelData->peak0));
if(enhance<1.0f) enhance = 1.0f;
Serial.print(enhance); Serial.println(" dB");
/*
// CSV Report suitable for entering to spread sheet
// InAve, InPk, OutAve, OutPk, EnhancementdB
Serial.print(pLevelData->pwr0, 5); Serial.print(",");
Serial.print(pLevelData->peak0, 5); Serial.print(",");
Serial.print(pLevelData->pwr1, 5); Serial.print(",");
Serial.print(pLevelData->peak1, 5); Serial.print(",");
float32_t enhance = (10.0f*log10f(pLevelData->pwr1) - 20.0f*log10f(pLevelData->peak1)) -
(10.0f*log10f(pLevelData->pwr0) - 20.0f*log10f(pLevelData->peak0));
if(enhance<1.0f) enhance = 1.0f;
Serial.println(enhance);
*/
}
}

@ -0,0 +1,253 @@
// Following is 251 term Hilbert FIR filter
float32_t hilbert251A[]={
0.0000003255,
0.0000000000,
0.0000030702,
0.0000000000,
0.0000089286,
0.0000000000,
0.0000183061,
0.0000000000,
0.0000316287,
0.0000000000,
0.0000493436,
0.0000000000,
0.0000719193,
0.0000000000,
0.0000998451,
0.0000000000,
0.0001336320,
0.0000000000,
0.0001738120,
0.0000000000,
0.0002209393,
0.0000000000,
0.0002755899,
0.0000000000,
0.0003383625,
0.0000000000,
0.0004098790,
0.0000000000,
0.0004907853,
0.0000000000,
0.0005817525,
0.0000000000,
0.0006834782,
0.0000000000,
0.0007966881,
0.0000000000,
0.0009221383,
0.0000000000,
0.0010606178,
0.0000000000,
0.0012129515,
0.0000000000,
0.0013800041,
0.0000000000,
0.0015626848,
0.0000000000,
0.0017619529,
0.0000000000,
0.0019788241,
0.0000000000,
0.0022143787,
0.0000000000,
0.0024697715,
0.0000000000,
0.0027462425,
0.0000000000,
0.0030451312,
0.0000000000,
0.0033678928,
0.0000000000,
0.0037161183,
0.0000000000,
0.0040915578,
0.0000000000,
0.0044961498,
0.0000000000,
0.0049320558,
0.0000000000,
0.0054017033,
0.0000000000,
0.0059078375,
0.0000000000,
0.0064535860,
0.0000000000,
0.0070425380,
0.0000000000,
0.0076788436,
0.0000000000,
0.0083673390,
0.0000000000,
0.0091137048,
0.0000000000,
0.0099246683,
0.0000000000,
0.0108082660,
0.0000000000,
0.0117741868,
0.0000000000,
0.0128342256,
0.0000000000,
0.0140028938,
0.0000000000,
0.0152982506,
0.0000000000,
0.0167430570,
0.0000000000,
0.0183664064,
0.0000000000,
0.0202060801,
0.0000000000,
0.0223120327,
0.0000000000,
0.0247516963,
0.0000000000,
0.0276183140,
0.0000000000,
0.0310445375,
0.0000000000,
0.0352256211,
0.0000000000,
0.0404611696,
0.0000000000,
0.0472354231,
0.0000000000,
0.0563851215,
0.0000000000,
0.0694911881,
0.0000000000,
0.0899418673,
0.0000000000,
0.1265473875,
0.0000000000,
0.2116132716,
0.0000000000,
0.6358933477,
0.0000000000,
-0.6358933478,
0.0000000000,
-0.2116132717,
0.0000000000,
-0.1265473876,
0.0000000000,
-0.0899418674,
0.0000000000,
-0.0694911882,
0.0000000000,
-0.0563851216,
0.0000000000,
-0.0472354232,
0.0000000000,
-0.0404611697,
0.0000000000,
-0.0352256212,
0.0000000000,
-0.0310445376,
0.0000000000,
-0.0276183141,
0.0000000000,
-0.0247516964,
0.0000000000,
-0.0223120328,
0.0000000000,
-0.0202060802,
0.0000000000,
-0.0183664065,
0.0000000000,
-0.0167430571,
0.0000000000,
-0.0152982507,
0.0000000000,
-0.0140028939,
0.0000000000,
-0.0128342257,
0.0000000000,
-0.0117741869,
0.0000000000,
-0.0108082661,
0.0000000000,
-0.0099246684,
0.0000000000,
-0.0091137049,
0.0000000000,
-0.0083673391,
0.0000000000,
-0.0076788437,
0.0000000000,
-0.0070425381,
0.0000000000,
-0.0064535861,
0.0000000000,
-0.0059078376,
0.0000000000,
-0.0054017034,
0.0000000000,
-0.0049320559,
0.0000000000,
-0.0044961499,
0.0000000000,
-0.0040915579,
0.0000000000,
-0.0037161184,
0.0000000000,
-0.0033678929,
0.0000000000,
-0.0030451313,
0.0000000000,
-0.0027462426,
0.0000000000,
-0.0024697716,
0.0000000000,
-0.0022143788,
0.0000000000,
-0.0019788242,
0.0000000000,
-0.0017619530,
0.0000000000,
-0.0015626849,
0.0000000000,
-0.0013800042,
0.0000000000,
-0.0012129516,
0.0000000000,
-0.0010606179,
0.0000000000,
-0.0009221384,
0.0000000000,
-0.0007966882,
0.0000000000,
-0.0006834783,
0.0000000000,
-0.0005817526,
0.0000000000,
-0.0004907854,
0.0000000000,
-0.0004098791,
0.0000000000,
-0.0003383626,
0.0000000000,
-0.0002755900,
0.0000000000,
-0.0002209394,
0.0000000000,
-0.0001738121,
0.0000000000,
-0.0001336321,
0.0000000000,
-0.0000998452,
0.0000000000,
-0.0000719194,
0.0000000000,
-0.0000493437,
0.0000000000,
-0.0000316288,
0.0000000000,
-0.0000183062,
0.0000000000,
-0.0000089287,
0.0000000000,
-0.0000030703,
0.0000000000,
-0.0000003256};

@ -0,0 +1,255 @@
/*
* radioCESSB_Z_transmit_F32.cpp
* This version of CESSB is intended for Zero-IF hardware.
*
* Bob Larkin, in support of the library:
* Chip Audette, OpenAudio, Dec 2022
*
* MIT License, Use at your own risk.
*
* See radioCESSB_Z_transmit_F32.h for technical info.
*
*/
// NOTE: 96 ksps sample rate not yet implemented
#include "radioCESSB_Z_transmit_F32.h"
void radioCESSB_Z_transmit_F32::update(void) {
audio_block_f32_t *blockIn, *blockOutI, *blockOutQ;
// Temporary storage. At an audio sample rate of 96 ksps, the used
// space will be half of the declared space.
float32_t HilbertIn[32];
float32_t workingDataI[128];
float32_t workingDataQ[128];
float32_t delayedDataI[64]; // Allows batching of 64 data points
float32_t delayedDataQ[64];
float32_t diffI[64];
float32_t diffQ[64];
if(sampleRate!=SAMPLE_RATE_44_50 && sampleRate!=SAMPLE_RATE_88_100)
return;
// Get all needed resources, or return if not available.
blockIn = AudioStream_F32::receiveReadOnly_f32();
if (!blockIn)
{ return; }
blockOutI = AudioStream_F32::allocate_f32(); // a block for I output
if (!blockOutI)
{
AudioStream_F32::release(blockIn);
return;
}
blockOutQ = AudioStream_F32::allocate_f32(); // and for Q
if (!blockOutQ)
{
AudioStream_F32::release(blockOutI);
AudioStream_F32::release(blockIn);
return;
}
// The audio input peak levels for start of CESSB are -1.0, 1.0
// when gainIn==1.0.
/* // A +/- pulse to test timing of various delays
// PULSE TEST for diagnostics only
for(int kk=0; kk<128; kk++)
{
uint16_t y=(ny & 1023);
// pulse max at is just starting to clip
if (y>=100 && y<115) blockIn->data[kk] = 4.189f;
else if(y>=115 && y<130) blockIn->data[kk] = -4.189f;
else blockIn->data[kk] = 0.0f;
ny++;
// Serial.println(blockIn->data[kk]);
}
*/
// uint32_t ttt=micros();
// Decimate 48 ksps to 12 ksps, 128 to 32 samples
// or 96 ksps to 12 ksps, 128 to 16 samples
arm_fir_decimate_f32(&decimateInst, &(blockIn->data[0]),
&HilbertIn[0], 128);
// We now have nW=32 (for 48 ksps) or 16 (for 96 ksps) samples to process
// Apply the Hilbert transform FIR.
arm_fir_f32(&firInstHilbertI, &HilbertIn[0], &workingDataI[0], nW);
/* ======= Sidebar: Circular 2^n length delay arrays ========
*
* The length of the array, N,
* must be a power of 2. For example N=2^6 = 64. The minimum
* delay possible is the trivial case of 0 up to N-1.
* As in C, let i be the index of the N array elements which
* would range from 0 to N-1. If p is an integer, that is a power
* of 2 also, with p >= n, it can serve as an index to the
* delay array by "ANDing" it with (N-1). That is,
* i = p & (N-1). It can be convenient if the largest
* possible value of the integer p, plus 1, is an integer multiple
* of the arrray size N, as then the rollover of p will not cause
* a jump in i. For instance, if p is an uint8_t with a maximum
* value of pmax=255, (pmax+1)/N = (255+1)/64 = 4, which is an
* integer. This combination will have no problems from rollover
* of p.
*
* The new data point is entered at index p & (N - 1). To
* achieve a delay of d, the output of the delay array is taken
* at index ((p-d) & (N-1)). The index is then incremented by 1.
*
* There are three delay lines of this construction below, starting
* with delayHilbertQ
* ========================================================== */
// Circular delay line for signal to align data with Hilbert FIR output
// nW (32 for 48ksps) points into and out of the delay array
for(uint16_t i=0; i<nW; i++) // Let it wrap around at 128
{
// Put data point into the delay arrays, and do LSB/USB changing
if(sidebandReverse)
delayHilbertQ[indexDelayHilbertQ & 0X7F] = -HilbertIn[i];
else
delayHilbertQ[indexDelayHilbertQ & 0X7F] = HilbertIn[i];
// Remove delayed data from line
workingDataQ[i] = delayHilbertQ[(indexDelayHilbertQ - 100) & 0X7F];
indexDelayHilbertQ++;
}
// To compensate for splitting the signal into I & Q thereby doubling
// the power, we add 0.707 factor.
float32_t gainFactor = 0.70710678f*gainIn;
for(int k=0; k<nW; k++)
{
workingDataI[k] *= gainFactor;
workingDataQ[k] *= gainFactor;
}
// Mesaure input power and peak envelope, SSB before any CESSB processing
for(int k=0; k<nW; k++)
{
float32_t pwrWorkingData = workingDataI[k]*workingDataI[k] + workingDataQ[k]*workingDataQ[k];
float32_t vWD = sqrtf(pwrWorkingData); // Envelope
powerSum0 += pwrWorkingData;
if(vWD > maxMag0)
maxMag0 = vWD; // Peak envelope
countPower0++;
}
// Interpolate by 2 up to 24 ksps rate
for(int k=0; k<nW; k++) // 48 ksps: 0 to 31
{
int k2 = 2*(nW - k) - 1; // 48 ksps 63 to 1
// Zero pack, working from the bottom to not overwrite
workingDataI[k2] = 0.0f; // 64 element array
workingDataI[k2-1] = workingDataI[nW-k-1];
workingDataQ[k2] = 0.0f;
workingDataQ[k2-1] = workingDataQ[nW-k-1];
}
// LPF with gain of 2 built into coefficients, correct for added zeros.
arm_fir_f32(&firInstInterpolate1I, workingDataI, workingDataI, nC);
arm_fir_f32(&firInstInterpolate1Q, workingDataQ, workingDataQ, nC);
// WorkingDataI and Q are now at 24 ksps and ready for clipping
// For input 48 ksps this produces 64 numbers
for(int kk=0; kk<nC; kk++)
{
float32_t power = workingDataI[kk]*workingDataI[kk] + workingDataQ[kk]*workingDataQ[kk];
float32_t mag = sqrtf(power);
if(mag > 1.0f) // This the clipping, scaled to 1.0, desired max
{
workingDataI[kk] /= mag;
workingDataQ[kk] /= mag;
}
}
// clipperIn needs spectrum control, so LP filter it.
// Both BW of the signal and the sample rate have been doubled.
arm_fir_f32(&firInstClipperI, workingDataI, workingDataI, nC);
arm_fir_f32(&firInstClipperQ, workingDataQ, workingDataQ, nC);
// Ready to compensate for filter overshoots
for (int k=0; k<nC; k++)
{
// Circular delay line for signal to align data with FIR output
// Put I & Q data points into the delay arrays
osDelayI[indexOsDelay & 0X3F] = workingDataI[k];
osDelayQ[indexOsDelay & 0X3F] = workingDataQ[k];
// Remove 64 points delayed data from line and save for later
delayedDataI[k] = osDelayI[(indexOsDelay - 63) & 0X3F];
delayedDataQ[k] = osDelayQ[(indexOsDelay - 63) & 0X3F];
indexOsDelay++;
// Delay line to allow strongest envelope to be used for compensation
// We only need to look ahead 1 or behind 1, so delay line of 4 is OK.
// Enter latest envelope to delay array
osEnv[indexOsEnv & 0X03] = sqrtf(
workingDataI[k]*workingDataI[k] + workingDataQ[k]*workingDataQ[k]);
// look over the envelope curve to find the max
float32_t eMax = 0.0f;
if(osEnv[(indexOsEnv) & 0X03] > eMax) // Data point just entered
eMax = osEnv[(indexOsEnv) & 0X03];
if(osEnv[(indexOsEnv-1) & 0X03] > eMax) // Entered one before
eMax = osEnv[(indexOsEnv-1) & 0X03];
if(osEnv[(indexOsEnv-2) & 0X03] > eMax) // Entered one before that
eMax = osEnv[(indexOsEnv-2) & 0X03];
if(eMax < 1.0f)
eMax = 1.0f; // Below clipping region
indexOsEnv++;
// Clip the signal to 1.0. -2 allows 1 look ahead on signal.
float32_t eCorrectedI = osDelayI[(indexOsDelay - 2) & 0X3F] / eMax;
float32_t eCorrectedQ = osDelayQ[(indexOsDelay - 2) & 0X3F] / eMax;
// Filtering is linear, so we only need to filter the difference between
// the signal and the clipper output. This needs less filtering, as the
// difference is many dB below the signal to begin with. Hershberger 2014
diffI[k] = osDelayI[(indexOsDelay - 2) & 0X3F] - eCorrectedI;
diffQ[k] = osDelayQ[(indexOsDelay - 2) & 0X3F] - eCorrectedQ;
} // End, for k=0 to 63
// Filter the differences, osFilter has 123 taps and 61 delay
arm_fir_f32(&firInstOShootI, diffI, diffI, nC);
arm_fir_f32(&firInstOShootQ, diffQ, diffQ, nC);
// Do the overshoot compensation
for(int k=0; k<nC; k++)
{
workingDataI[k] = delayedDataI[k] - gainCompensate*diffI[k];
workingDataQ[k] = delayedDataQ[k] - gainCompensate*diffQ[k];
}
// Measure average output power and peak envelope, after CESSB
// but before gainOut
for(int k=0; k<nC; k++)
{
float32_t pwrOut = workingDataI[k]*workingDataI[k] +
workingDataQ[k]*workingDataQ[k];
float32_t vWD = sqrtf(pwrOut); // Envelope
powerSum1 += pwrOut;
if(vWD > maxMag1)
maxMag1 = vWD; // Peak envelope
countPower1++;
}
// Finally interpolate to 48 or 96 ksps. Data is in workingDataI[k]
// and is 64 samples for audio 48 ksps.
for(int k=0; k<nC; k++) // Audio sampling at 48 ksps: 0 to 63
{
int k2 = 2*(nC - k) - 1; // 48 ksps 63 to 1
// Zero pack, working from the bottom to not overwrite
workingDataI[k2] = 0.0f;
workingDataI[k2-1] = gainOut*workingDataI[nC-k-1]; // gainOut does not change CESSB
workingDataQ[k2] = 0.0f;
workingDataQ[k2-1] = gainOut*workingDataQ[nC-k-1]; // ...it just scales the level
}
// LPF with gain of 2 built into coefficients, correct for zeros.
arm_fir_f32(&firInstInterpolate2I, workingDataI, &blockOutI->data[0], 128);
arm_fir_f32(&firInstInterpolate2Q, workingDataQ, &blockOutQ->data[0], 128);
// Voltage gain from blockIn->data to here for small sine wave is 1.0
AudioStream_F32::transmit(blockOutI, 0); // send the outputs
AudioStream_F32::transmit(blockOutQ, 1);
AudioStream_F32::release(blockIn); // Release the blocks
AudioStream_F32::release(blockOutI);
AudioStream_F32::release(blockOutQ);
jjj++; //For test printing
// Serial.println(micros() - ttt);
} // end update()

@ -0,0 +1,444 @@
/*
* radioCESSB_Z_transmit_F32.h
*
* This is a modification of the CESSB algorithm to output SSB at zero carrier
* instead of 1350 Hz as the Weaver modulation produces. This allows
* transmission with zero-IF radios where the finite carrier balance
* of the hardware mixers produces the mid-band tone.The basic change
* is to use the phasing method in place of the Weaver method. However,
* all filters needed to be changed and are at the bottom of this file.
* The 12 and 24 ksps sample rates of the radioCESSB_transmit_F32 class
* are continued here as they were more than adequate for the Weaver method.
* The sine/cosine oscillator is not needed here and has been removed.
*
*
* 18 Jan 2023 (c) copyright Bob Larkin
* But with With much credit to:
* Chip Audette (OpenAudio)
* and of course, to PJRC for the Teensy and Teensy Audio Library
*
* The development of the Controlled Envelope Single Side Band (CESSB)
* was done by Dave Hershberger, W9GR. Many thanks to Dave.
* The following description is mostly taken
* from Frank, DD4WH and is on line at the GNU Radio site, ref:
* https://github-wiki-see.page/m/df8oe/UHSDR/wiki/Controlled-Envelope-Single-Sideband-CESSB
* and has been revised by Bob L. to reflect the phasing method change.
*
* Controlled Envelope Single Sideband is an invention by Dave Hershberger
* W9GR with the aim to "allow your rig to output more average power while
* keeping peak envelope power PEP the same". The increase in perceived
* loudness can be up to 4dB without any audible increase in distortion
* and without making you sound "processed" (Hershberger 2014, 2016b).
*
* The principle to achieve this is relatively simple. The process
* involves only audio baseband processing which can be done digitally in
* software without the need for modifications in the hardware or messing
* with the RF output of your rig.
*
* Controlled Envelope Single Sideband can be produced using three
* processing blocks making up a complete CESSB system:
* 1. An SSB modulator. This is implemented as the phasing method to allow
* minimum (12 kHz) decimated sample rate with the output of I & Q
* signals (a complex SSB signal).
* 2. A baseband envelope clipper. This takes the modulus of the I & Q
* signals (also called the magnitude), which is sqrt(I * I + Q * Q)
* and divides the I & Q signals by the modulus, IF the signal is
* larger than 1.0. If not, the signal remains untouched. After
* clipping, the signal is lowpass filtered with a linear phase FIR
* low pass filter with a stopband frequency of 3.0kHz
* 3. An overshoot controller . This does something similar as the
* envelope clipper: Again, the modulus is calculated (but now on
* the basis of the current and two preceding and two subsequent
* samples). If the signals modulus is larger than 1 (clipping),
* the signals I and Q are divided by the maximum of 1 or of
* (1.9 * signal). That means the clipping is overcompensated by 1.9
* [the phasing method seems to perform best with 1.4*signal]
* which leads to a much better suppression of the overshoots from
* the first two stages. Finally, the resulting signal is again
* lowpass-filtered with a linear phase FIR filter with stopband
* frequency of 3.0khz
*
* It is important that the sample rate is high enough so that the higher
* frequency components of the output of the modulator, clipper and
* overshoot controller do not alias back into the desired signal. Also
* all the filters should be linear phase filters (FIR, not IIR).
*
* This CESSB system can reduce the overshoot of the SSB modulator from
* 61% to 1.3%, meaning about 2.5 times higher perceived SSB output power
* (Hershberger 2014).
*
* References:
* 1-Hershberger, D.L. (2014): Controlled Envelope Single Sideband. QEX
* November/December 2014 pp3-13.
* http://www.arrl.org/files/file/QEX_Next_Issue/2014/Nov-Dec_2014/Hershberger_QEX_11_14.pdf
* 2-Hershberger, D.L. (2016a): External Processing for Controlled
* Envelope Single Sideband. - QEX January/February 2016 pp9-12.
* http://www.arrl.org/files/file/QEX_Next_Issue/2016/January_February_2016/Hershberger_QEX_1_16.pdf
* 3-Hershberger, D.L. (2016b): Understanding Controlled Envelope Single
* Sideband. - QST February 2016 pp30-36.
* 4-Forum discussion on CESSB on the Flex-Radio forum,
* https://community.flexradio.com/discussion/6432965/cessb-questions
*
* Status: Experimental
*
* Inputs: 0 is voice audio input
* Outputs: 0 is I 1 is Q
*
* Functions, available during operation:
* void frequency(float32_t fr) Sets LO frequency Hz
*
* void setSampleRate_Hz(float32_t fs_Hz) Allows dynamic sample rate change for this function
*
* struct levels* getLevels(int what) {
* what = 0 returns a pointer to struct levels before data is ready
* what = 1 returns a pointer to struct levels
*
* uint32_t levelDataCount() return countPower0
*
* void setGains(float32_t gIn, float32_t gCompensate, float32_t gOut)
*
* Time: T3.6 For an update of a 128 sample block, estimated 700 microseconds
* T4.0 For an update of a 128 sample block, measured 211 microseconds
* These times are for a 48 ksps rate.
*
* NOTE: Do NOT follow this block with any non-linear phase filtering,
* such as IIR. Minimize any linear-phase filtering such as FIR.
* Such activities enhance the overshoots and defeat the purpose of CESSB.
*/
#ifndef _radioCESSB_Z_transmit_f32_h
#define _radioCESSB_Z_transmit_f32_h
#include "Arduino.h"
#include "AudioStream_F32.h"
#include "arm_math.h"
#include "mathDSP_F32.h"
#define SAMPLE_RATE_0 0
#define SAMPLE_RATE_44_50 1
#define SAMPLE_RATE_88_100 2
#ifndef M_PI
#define M_PI 3.141592653589793f
#endif
#ifndef M_PI_2
#define M_PI_2 1.570796326794897f
#endif
#ifndef M_TWOPI
#define M_TWOPI (M_PI * 2.0f)
#endif
// For the average power and peak voltage readings, global
struct levelsZ {
float32_t pwr0;
float32_t peak0;
float32_t pwr1;
float32_t peak1;
uint32_t countP; // Number of averaged samples for pwr0.
};
class radioCESSB_Z_transmit_F32 : public AudioStream_F32 {
//GUI: inputs:1, outputs:2 //this line used for automatic generation of GUI node
//GUI: shortName:CESSBTransmit //this line used for automatic generation of GUI node
public:
radioCESSB_Z_transmit_F32(void) :
AudioStream_F32(1, inputQueueArray_f32)
{
setSampleRate_Hz(AUDIO_SAMPLE_RATE);
//uses default AUDIO_SAMPLE_RATE from AudioStream.h
//setBlockLength(128); Always default 128
}
radioCESSB_Z_transmit_F32(const AudioSettings_F32 &settings) :
AudioStream_F32(1, inputQueueArray_f32)
{
setSampleRate_Hz(settings.sample_rate_Hz);
//setBlockLength(128); Always default 128
}
// Sample rate starts at default 44.1 ksps. That will work. Filters
// are designed for 48 and 96 ksps, however. This is a *required*
// function at setup().
void setSampleRate_Hz(const float fs_Hz) {
sample_rate_Hz = fs_Hz;
if(sample_rate_Hz>44000.0f && sample_rate_Hz<50100.0f)
{
// Design point is 48 ksps
sampleRate = SAMPLE_RATE_44_50;
nW = 32;
nC = 64;
countLevelMax = 37; // About 0.1 sec for 48 ksps
inverseMaxCount = 1.0f/(float32_t)countLevelMax;
arm_fir_decimate_init_f32(&decimateInst, 65, 4,
(float32_t*)decimateFilter48, &pStateDecimate[0], 128);
arm_fir_init_f32(&firInstHilbertI, 201, (float32_t*)hilbert201_130Hz12000Hz,
&pStateHilbertI[0], nW);
arm_fir_init_f32(&firInstInterpolate1I, 23, (float32_t*)interpolateFilter1,
&pStateInterpolate1I[0], nC);
arm_fir_init_f32(&firInstInterpolate1Q, 23, (float32_t*)interpolateFilter1,
&pStateInterpolate1Q[0], nC);
arm_fir_init_f32(&firInstClipperI, 123, (float32_t*)clipperOut,
&pStateClipperI[0], nC);
arm_fir_init_f32(&firInstClipperQ, 123, (float32_t*)clipperOut,
&pStateClipperQ[0], nC);
arm_fir_init_f32(&firInstOShootI, 123, (float32_t*)clipperOut,
&pStateOShootI[0], nC);
arm_fir_init_f32(&firInstOShootQ, 123, (float32_t*)clipperOut,
&pStateOShootQ[0], nC);
arm_fir_init_f32(&firInstInterpolate2I, 23, (float32_t*)interpolateFilter1,
&pStateInterpolate2I[0], nC);
arm_fir_init_f32(&firInstInterpolate2Q, 23, (float32_t*)interpolateFilter1,
&pStateInterpolate2Q[0], nC);
}
else if(sample_rate_Hz>88000.0f && sample_rate_Hz<100100.0f)
{
// GET THINGS WORKING AT SAMPLE_RATE_44_50 FIRST AND THEN FIX UP 96 ksps
// Design point is 96 ksps
/* sampleRate = SAMPLE_RATE_88_100; //<<<<<<<<<<<<<<<<<<<<<<FIXUP
nW = 16;
nC = 32;
countLevelMax = 75; // About 0.1 sec for 96 ksps
inverseMaxCount = 1.0f/(float32_t)countLevelMax;
arm_fir_decimate_init_f32 (&decimateInst, 55, 4,
(float32_t*)decimateFilter48, pStateDecimate, 128);
arm_fir_init_f32(&firInstClipper, 199, basebandFilter,
&StateFirClipperF32[0], 128);
*/
}
else
{
// Unsupported sample rate
sampleRate = SAMPLE_RATE_0;
nW = 1;
nC = 1;
}
newLevelDataReady = false;
}
struct levelsZ* getLevels(int what) {
if(what != 0) // 0 leaves a way to get pointer before data is ready
{
levelData.pwr0 = powerSum0/((float32_t)countPower0);
levelData.peak0 = maxMag0;
levelData.pwr1 = powerSum1/(float32_t)countPower1;
levelData.peak1 = maxMag1;
levelData.countP = countPower0;
// Automatic reset for next set of readings
powerSum0 = 0.0f;
maxMag0 = -1.0f;
powerSum1 = 0.0f;
maxMag1 = -1.0f;
countPower0 = 0;
countPower1 = 0;
}
return &levelData;
}
uint32_t levelDataCount(void) {
return countPower0; // Input count, out may be different
}
void setGains(float32_t gIn, float32_t gCompensate, float32_t gOut)
{
gainIn = gIn;
gainCompensate = gCompensate;
gainOut = gOut;
}
// The LSB/USB selection depends on the processing of the IQ signals
// inside this class. It may get flipped with later processing.
void setSideband(bool _sbReverse)
{
sidebandReverse = _sbReverse;
}
virtual void update(void);
private:
void sincos_Z_(float32_t ph);
struct levelsZ levelData;
audio_block_f32_t *inputQueueArray_f32[1];
uint32_t jjj = 0; // Used for diagnostic printing
// Input/Output is at 48 or 96 ksps. Hilbert generation is at 12 ksps.
// Clipping and overshoot processing is at 24 ksps.
// Next line is to indicate that setSampleRateHz() has not executed
int sampleRate = SAMPLE_RATE_0;
float32_t sample_rate_Hz = AUDIO_SAMPLE_RATE; // 44.1 ksps
int16_t nW = 32; // 32 or 16
int16_t nC = 64; // 64 or 32
uint16_t block_length = 128;
bool sidebandReverse = false;
float32_t pStateDecimate[128 + 65 - 1]; // Goes with CMSIS decimate function
arm_fir_decimate_instance_f32 decimateInst;
float32_t pStateHilbertI[32 + 201 - 1];
arm_fir_instance_f32 firInstHilbertI;
float32_t pStateInterpolate1I[64 + 23 - 1]; // For interpolate 12 to 24 ksps
arm_fir_instance_f32 firInstInterpolate1I;
float32_t pStateInterpolate1Q[64 + 23 - 1];
arm_fir_instance_f32 firInstInterpolate1Q;
float32_t pStateClipperI[64 + 123 - 1]; // Goes with Clipper filter
arm_fir_instance_f32 firInstClipperI; // at 24 ksps
float32_t pStateClipperQ[64 + 123 - 1];
arm_fir_instance_f32 firInstClipperQ;
float32_t pStateOShootI[64+123-1];
arm_fir_instance_f32 firInstOShootI;
float32_t pStateOShootQ[64+123-1];
arm_fir_instance_f32 firInstOShootQ;
float32_t pStateInterpolate2I[128 + 23 - 1]; // For interpolate 12 to 24 ksps
arm_fir_instance_f32 firInstInterpolate2I;
float32_t pStateInterpolate2Q[128 + 23 - 1];
arm_fir_instance_f32 firInstInterpolate2Q;
// float32_t sn, cs;
float32_t gainIn = 1.0f;
float32_t gainCompensate = 1.4f;
float32_t gainOut = 1.0f; // Does not change CESSB, here for convenience to set transmit power
float32_t delayHilbertQ[128];
uint16_t indexDelayHilbertQ = 0;
// A tiny delay to allow negative time for the previous path
float32_t osEnv[4];
uint16_t indexOsEnv = 4; // 0 to 3 by using a 2-bit mask
// We need a delay for overshoot remove to account for the FIR
// filter in the correction path. Some where around 128 taps works
// but if we make the delay exactly 2^6=64 the delay line is simple
// resulting in a FIR size of 2*64+1=129 taps.
float32_t osDelayI[64];
float32_t osDelayQ[64];
uint16_t indexOsDelay = 64;
// RMS and Peak variable for monitoring levels and changes to the
// Peak to RMS ratio. These are temporary storage. Data is
// transferred by global levelData struct at the top of this file.
float32_t powerSum0 = 0.0f;
float32_t maxMag0 = -1.0f;
float32_t powerSum1 = 0.0f;
float32_t maxMag1 = -1.0f;
uint32_t countPower0 = 0;
uint32_t countPower1 = 0;
bool newLevelDataReady = false;
int countLevel = 0;
int countLevelMax = 37; // About 0.1 sec for 48 ksps
float32_t inverseMaxCount = 1.0f/(float32_t)countLevelMax;
/* Input filter for decimate by 4:
* FIR filter designed with http://t-filter.appspot.com
* Sampling frequency: 48000 Hz
* 0 Hz - 3000 Hz ripple = 0.075 dB
* 6000 Hz - 24000 Hz atten = -95.93 dB */
const float32_t decimateFilter48[65] = {
0.00004685f, 0.00016629f, 0.00038974f, 0.00073279f, 0.00113663f, 0.00148721f,
0.00159057f, 0.00125129f, 0.00032821f,-0.00114283f,-0.00289782f,-0.00441933f,
-0.00505118f,-0.00418143f,-0.00151748f, 0.00268876f, 0.00751487f, 0.01147689f,
0.01286243f, 0.01027735f, 0.00323528f,-0.00737003f,-0.01913035f,-0.02842381f,
-0.03117447f,-0.02390063f,-0.00480378f, 0.02544011f, 0.06344286f, 0.10357132f,
0.13904464f, 0.16342506f, 0.17210799f, 0.16342506f, 0.13904464f, 0.10357132f,
0.06344286f, 0.02544011f,-0.00480378f,-0.02390063f,-0.03117447f,-0.02842381f,
-0.01913035f,-0.00737003f, 0.00323528f, 0.01027735f, 0.01286243f, 0.01147689f,
0.00751487f, 0.00268876f,-0.00151748f,-0.00418143f,-0.00505118f,-0.00441933f,
-0.00289782f,-0.00114283f, 0.00032821f, 0.00125129f, 0.00159057f, 0.00148721f,
0.00113663f, 0.00073279f, 0.00038974f, 0.00016629f, 0.00004685};
/* 90 degree Hilbert filter
* FIR filter designed Iowa Hills suite - Thank you.
* Sampling frequency: 12000 Hz
* 130 Hz - 5870 Hz ripple = 0.0036 dB */
const float32_t hilbert201_130Hz12000Hz[201] = {
0.000000000f, 0.000081360f, 0.000000000f, 0.000114966f, 0.000000000f, 0.000155734f,
0.000000000f, 0.000204564f, 0.000000000f, 0.000262417f, 0.000000000f, 0.000330320f,
0.000000000f, 0.000409359f, 0.000000000f, 0.000500689f, 0.000000000f, 0.000605532f,
0.000000000f, 0.000725179f, 0.000000000f, 0.000860994f, 0.000000000f, 0.001014419f,
0.000000000f, 0.001186978f, 0.000000000f, 0.001380282f, 0.000000000f, 0.001596041f,
0.000000000f, 0.001836068f, 0.000000000f, 0.002102298f, 0.000000000f, 0.002396800f,
0.000000000f, 0.002721798f, 0.000000000f, 0.003079696f, 0.000000000f, 0.003473107f,
0.000000000f, 0.003904895f, 0.000000000f, 0.004378221f, 0.000000000f, 0.004896603f,
0.000000000f, 0.005463995f, 0.000000000f, 0.006084876f, 0.000000000f, 0.006764381f,
0.000000000f, 0.007508449f, 0.000000000f, 0.008324026f, 0.000000000f, 0.009219325f,
0.000000000f, 0.010204165f, 0.000000000f, 0.011290428f, 0.000000000f, 0.012492662f,
0.000000000f, 0.013828919f, 0.000000000f, 0.015321902f, 0.000000000f, 0.017000603f,
0.000000000f, 0.018902655f, 0.000000000f, 0.021077827f, 0.000000000f, 0.023593325f,
0.000000000f, 0.026542141f, 0.000000000f, 0.030056654f, 0.000000000f, 0.034331851f,
0.000000000f, 0.039667098f, 0.000000000f, 0.046546491f, 0.000000000f, 0.055806835f,
0.000000000f, 0.069029606f, 0.000000000f, 0.089604827f, 0.000000000f, 0.126348239f,
0.000000000f, 0.211587134f, 0.000000000f, 0.636276105f, 0.000000000f,-0.636276105f,
0.000000000f,-0.211587134f, 0.000000000f,-0.126348239f, 0.000000000f,-0.089604827f,
0.000000000f,-0.069029606f, 0.000000000f,-0.055806835f, 0.000000000f,-0.046546491f,
0.000000000f,-0.039667098f, 0.000000000f,-0.034331851f, 0.000000000f,-0.030056654f,
0.000000000f,-0.026542141f, 0.000000000f,-0.023593325f, 0.000000000f,-0.021077827f,
0.000000000f,-0.018902655f, 0.000000000f,-0.017000603f, 0.000000000f,-0.015321902f,
0.000000000f,-0.013828919f, 0.000000000f,-0.012492662f, 0.000000000f,-0.011290428f,
0.000000000f,-0.010204165f, 0.000000000f,-0.009219325f, 0.000000000f,-0.008324026f,
0.000000000f,-0.007508449f, 0.000000000f,-0.006764381f, 0.000000000f,-0.006084876f,
0.000000000f,-0.005463995f, 0.000000000f,-0.004896603f, 0.000000000f,-0.004378221f,
0.000000000f,-0.003904895f, 0.000000000f,-0.003473107f, 0.000000000f,-0.003079696f,
0.000000000f,-0.002721798f, 0.000000000f,-0.002396800f, 0.000000000f,-0.002102298f,
0.000000000f,-0.001836068f, 0.000000000f,-0.001596041f, 0.000000000f,-0.001380282f,
0.000000000f,-0.001186978f, 0.000000000f,-0.001014419f, 0.000000000f,-0.000860994f,
0.000000000f,-0.000725179f, 0.000000000f,-0.000605532f, 0.000000000f,-0.000500689f,
0.000000000f,-0.000409359f, 0.000000000f,-0.000330320f, 0.000000000f,-0.000262417f,
0.000000000f,-0.000204564f, 0.000000000f,-0.000155734f, 0.000000000f,-0.000114966f,
0.000000000f,-0.000081360f, 0.000000000};
/* Filter for outputs of clipper
* Use also overshoot corrector, but might be able to use less terms.
* FIR filter designed with http://t-filter.appspot.com
* Sample frequency: 24000 Hz
* 0 Hz - 2800 Hz ripple = 0.14 dB
* 3200 Hz - 12000 Hz atten = 40.51 dB */
const float32_t clipperOut[123] = {
-0.003947255f, 0.001759588f, 0.002221444f, 0.002407244f, 0.001833343f, 0.000524622f,
-0.000946260f,-0.001768428f,-0.001395297f, 0.000055916f, 0.001779024f, 0.002694998f,
0.002099736f, 0.000157764f,-0.002092190f,-0.003282801f,-0.002542927f,-0.000116969f,
0.002694319f, 0.004153363f, 0.003197589f, 0.000143560f,-0.003346600f,-0.005148200f,
-0.003947437f,-0.000152425f, 0.004166345f, 0.006378882f, 0.004871469f, 0.000164557f,
-0.005173898f,-0.007896395f,-0.006014470f,-0.000173552f, 0.006447615f, 0.009828080f,
0.007480359f, 0.000184482f,-0.008116957f,-0.012379161f,-0.009436712f,-0.000194737f,
0.010412610f, 0.015941971f, 0.012213107f, 0.000200845f,-0.013823966f,-0.021360759f,
-0.016552097f,-0.000205707f, 0.019544260f, 0.030836344f, 0.024523278f, 0.000211298f,
-0.031509151f,-0.052450055f,-0.044811840f,-0.000214078f, 0.074661107f, 0.158953216f,
0.225159581f, 0.250214862f, 0.225159581f, 0.158953216f, 0.074661107f,-0.000214078f,
-0.044811840f,-0.052450055f,-0.031509151f, 0.000211298f, 0.024523278f, 0.030836344f,
0.019544260f,-0.000205707f,-0.016552097f,-0.021360759f,-0.013823966f, 0.000200845f,
0.012213107f, 0.015941971f, 0.010412610f,-0.000194737f,-0.009436712f,-0.012379161f,
-0.008116957f, 0.000184482f, 0.007480359f, 0.009828080f, 0.006447615f,-0.000173552f,
-0.006014470f,-0.007896395f,-0.005173898f, 0.000164557f, 0.004871469f, 0.006378882f,
0.004166345f,-0.000152425f,-0.003947437f,-0.005148200f,-0.003346600f, 0.000143560f,
0.003197589f, 0.004153363f, 0.002694319f,-0.000116969f,-0.002542927f,-0.003282801f,
-0.002092190f, 0.000157764f, 0.002099736f, 0.002694998f, 0.001779024f, 0.000055916f,
-0.001395297f,-0.001768428f,-0.000946260f, 0.000524622f, 0.001833343f, 0.002407244f,
0.002221444f, 0.001759588f,-0.003947255f};
/* FIR filter designed with http://t-filter.appspot.com
* Sampling frequency: 24000 sps
* 0 Hz - 3000 Hz gain = 2 ripple = 0.11 dB
* 6000 Hz - 12000 Hz atten = -62.4 dB
* (At Sampling Frequency=48ksps, double all frequency values) */
const float32_t interpolateFilter1[23] = {
-0.00413402f,-0.01306124f,-0.01106321f, 0.01383359f, 0.04386756f, 0.02731837f,
-0.05470066f,-0.12407408f,-0.04389386f, 0.23355907f, 0.56707488f, 0.71763165f,
0.56707488f, 0.23355907f,-0.04389386f,-0.12407408f,-0.05470066f, 0.02731837f,
0.04386756f, 0.01383359f,-0.01106321f,-0.01306124f,-0.00413402};
}; // end Class
#endif
Loading…
Cancel
Save