Add radio rfcn AnalyzePhase, Equalizer, FirGeneral, FM Det, sin_cos, Nois Blanker + example INO

pull/11/head
boblark 4 years ago
parent ae99656d29
commit 0af24d2bef
  1. 160
      AudioAnalyzePhase_F32.cpp
  2. 271
      AudioAnalyzePhase_F32.h
  3. 192
      AudioFilterEqualizer_F32.cpp
  4. 178
      AudioFilterEqualizer_F32.h
  5. 259
      AudioFilterFIRGeneral_F32.cpp
  6. 188
      AudioFilterFIRGeneral_F32.h
  7. 6
      OpenAudio_ArduinoLibrary.h
  8. 136
      RadioFMDetector_F32.cpp
  9. 277
      RadioFMDetector_F32.h
  10. 114
      examples/AudioTestAnalyzePhase_F32/AudioTestAnalyzePhase_F32.ino
  11. 58
      examples/AudioTestPeakRMS/AudioTestPeakRMS.ino
  12. 96
      examples/AudioTestSinCos/AudioTestSinCos.ino
  13. BIN
      examples/ReceiverFM/FMOutputFIR39.gif
  14. 107
      examples/ReceiverFM/ReceiverFM.ino
  15. 123
      examples/ReceiverFM/hilbert121A.h
  16. 21
      examples/ReceiverFM/hilbert19A.h
  17. 253
      examples/ReceiverFM/hilbert251A.h
  18. BIN
      examples/ReceiverPart1/Rcvr1Outputs.gnumeric
  19. 90
      examples/ReceiverPart1/ReceiverPart1.ino
  20. 123
      examples/ReceiverPart1/hilbert121A.h
  21. 21
      examples/ReceiverPart1/hilbert19A.h
  22. 253
      examples/ReceiverPart1/hilbert251A.h
  23. 177
      examples/ReceiverPart2/ReceiverPart2.ino
  24. 123
      examples/ReceiverPart2/hilbert121A.h
  25. 21
      examples/ReceiverPart2/hilbert19A.h
  26. 253
      examples/ReceiverPart2/hilbert251A.h
  27. 81
      examples/TestEqualizer1/TestEqualizer1.ino
  28. 123
      examples/TestEqualizer1/hilbert121A.h
  29. 21
      examples/TestEqualizer1/hilbert19A.h
  30. 253
      examples/TestEqualizer1/hilbert251A.h
  31. 105
      examples/TestEqualizer1Audio/TestEqualizer1Audio.ino
  32. 88
      examples/TestFIRGeneral3/TestFIRGeneral3.ino
  33. BIN
      examples/TestFIRGeneralLarge4/HP55ieeeRSL.gif
  34. BIN
      examples/TestFIRGeneralLarge4/KaiserBetaSidelobes.gif
  35. BIN
      examples/TestFIRGeneralLarge4/Test4.gnumeric
  36. 127
      examples/TestFIRGeneralLarge4/TestFIRGeneralLarge4.ino
  37. 82
      examples/TestFIRGeneralLarge5/TestFIRGeneralLarge5.ino
  38. 90
      radioNoiseBlanker_F32.cpp
  39. 126
      radioNoiseBlanker_F32.h
  40. 6
      readme.md
  41. 103
      synth_sin_cos_f32.cpp
  42. 162
      synth_sin_cos_f32.h

@ -0,0 +1,160 @@
/*
* AudioAnalyzePhase_F32.cpp
*
* 31 March 2020 Rev 8 April 2020
* Bob Larkin, in support of the library:
* Chip Audette, OpenAudio, Apr 2017
*
* * Copyright (c) 2020 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 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.
*/
/* There are two inputs, I and Q (Left and Right or 0 and 1)
* There is one output, the phase angle between the two inputs expressed in
* radians (180 degrees is Pi radians). See AudioAnalyzePhase_F32.h
* for details.
*/
#include "AudioAnalyzePhase_F32.h"
#include <math.h>
#include "mathDSP_F32.h"
void AudioAnalyzePhase_F32::update(void) {
audio_block_f32_t *block0, *block1;
uint16_t i;
float32_t mult0, mult1, min0, max0, minmax0, min1, max1, minmax1;
float32_t min_max = 1.0f; // Filled in correctly, later
mathDSP_F32 mathDSP1;
#if TEST_TIME
if (iitt++ >1000000) iitt = -10;
uint32_t t1, t2;
t1 = tElapse;
#endif
// Get first input, 0
block0 = AudioStream_F32::receiveWritable_f32(0);
if (!block0) {
if(errorPrint) Serial.println("AN_PHASE ERR: No input memory");
return;
}
// Get second input, 1
block1 = AudioStream_F32::receiveWritable_f32(1);
if (!block1){
if(errorPrint) Serial.println("AN_PHASE ERR: No input memory");
AudioStream_F32::release(block0);
return;
}
// Limiter on both inputs; can be used with narrow IIR filter
if (pdConfig & LIMITER_MASK) {
for (uint16_t j = 0; j<block0->length; j++) {
if (block0->data[j]>=0.0f)
block0->data[j] = 1.0f;
else
block0->data[j] = -1.0f;
if (block1->data[j]>=0.0f)
block1->data[j] = 1.0f;
else
block1->data[j] = -1.0f;
}
}
// If needed, find a scaling factor for the multiplier type phase det
// This would not normally be used with a limiter---they are
// both trying to solve the same problem.
if(pdConfig & SCALE_MASK) {
min0 = 1.0E6f; max0 = -1.0E6f;
min1 = 1.0E6f; max1 = -1.0E6f;
for (i=0; i < block0->length; i++){
if(block0->data[i] < min0) min0 = block0->data[i];
if(block0->data[i] > max0) max0 = block0->data[i];
if(block1->data[i] < min1) min1 = block1->data[i];
if(block1->data[i] > max1) max1 = block1->data[i];
}
minmax0 = max0 - min0; // 0 to 2
minmax1 = max1 - min1; // 0 to 2
min_max = minmax0 * minmax1; // 0 to 4
}
// multiply the two inputs to get phase plus second harmonic. Low pass
// filter and then apply ArcCos of the result to return the phase difference
// in radians. Multiply and leave result in 1
arm_mult_f32(block0->data, block1->data, block1->data, block0->length);
if (LPType == NO_LP_FILTER) {
for (i=0; i < block0->length; i++)
block0->data[i] = block1->data[i]; // Move back to block0
}
else if(LPType == IIR_LP_FILTER) {
// Now filter 1, leaving result in 0. This is 4 BiQuads
arm_biquad_cascade_df1_f32(&iir_inst, block1->data, block0->data, block0->length);
}
else { // Alternate FIR filter for FIR_LP_FILTER
arm_fir_f32(&fir_inst, block1->data, block0->data, block0->length);
}
AudioStream_F32::release(block1); // Not needed further
/* For variable, pdConfig:
* LIMITER_MASK: 0=No Limiter 1=Use limiter
* ACOS_MASK: 00=Use no acos linearizer 01=undefined
* 10=Fast, math-continuous acos() (default) 11=Accurate acos()
* SCALE_MASK: 0=No scale of multiplier 1=scale to min-max (default)
* UNITS_MASK: 0=Output in degrees 1=Output in radians (default)
*/
if(pdConfig & SCALE_MASK)
mult0 = 8.0f / min_max;
else
mult0 = 2.0f;
if(pdConfig & UNITS_MASK)
mult1 = 1.0;
else
mult1 = 57.295779f;
// Optionally, apply ArcCos() to linearize block0 output in radians * units to scale to degrees
if (pdConfig & ACOS_MASK) {
if((pdConfig & ACOS_MASK) == 0b00110) { // Accurate acosf from C-library
for (uint i = 0; i<block_size; i++) {
block0->data[i] = mult1 * acosf(mult0 * block0->data[i]);
}
}
else if ((pdConfig & ACOS_MASK) == 0b00100) { // Fast acos from polynomial
for (uint i = 0; i<block_size; i++) {
block0->data[i] = mult1 * mathDSP1.acos_f32(mult0 * block0->data[i]);
}
}
}
// Not applying linearization or scaling, at all. Works for multiplier outputs near 0 (90 deg difference)
else {
float32_t mult = 8.0f * mult0 * mult1;
for (uint i = 0; i<block_size; i++) {
block0->data[i] = mult * block0->data[i];
}
}
AudioStream_F32::transmit(block0, 0);
AudioStream_F32::release(block0);
#if TEST_TIME
t2 = tElapse;
if(iitt++ < 0) {Serial.print("At AnalyzePhase end, microseconds = "); Serial.println (t2 - t1); }
t1 = tElapse;
#endif
} // End update()

@ -0,0 +1,271 @@
/*
* AudioAnalyzePhase_F32.h
*
* 31 March 2020, Rev 8 April 2020
* Status Tested OK T3.6 and T4.0.
* Bob Larkin, in support of the library:
* Chip Audette, OpenAudio, Apr 2017
* -------------------
* There are two inputs, 0 and 1 (Left and Right)
* There is one output, the phase angle between 0 & 1 expressed in
* radians (180 degrees is Pi radians) or degrees. This is a 180-degree
* type of phase detector. See RadioIQMixer_F32 for a 360 degree type.
*
* This block can be used to measure phase between two sinusoids, and the default IIR filter is suitable for this with a cut-off
* frequency of 100 Hz. The only IIR configuration is 4-cascaded satages of BiQuad. For this, 20 coefficients must be provided
* in 4 times (b0, b1, b2, -a1, -a2) order (example below). This IIR filter inherently does not have very good
* linearity in phase vs. frequency. This can be a problem for communications systems.
* As an alternative, a linear phase (as long as coefficients are symmetrical)
* FIR filter can be set up with the begin method. The built in FIR LP filter has a cutoff frequency of 4 kHz when used
* at a 44.1 kHz sample rate. This filter uses 53 coefficients (called taps). Any FIR filter with 4 to 200 coefficients can be used
* as set up by the begin method.
*
* DEFAULTS: 100 Hz IIR LP, output is in radians, and this does *NOT* need a call to begin(). This can be changed, including
* using a FIR LP where linear phase is needed, or NO_LP_FILTER that leaves harmonics of the input frequency. Method begin()
* changes the options. For instance, to use a 60 coefficient FIR the setup() in the .INO might do
* myAnalyzePhase.begin(FIR_LP_FILTER, &myFIRCoefficients[0], 60, DEGREES_PHASE);
* If _pcoefficients is NULL, the coefficients will be left default. For instance, to use the default 100 Hz IIR filter, with degree output
* myAnalyzePhase.begin(IIR_LP_FILTER, NULL, 20, DEGREES_PHASE);
* To provide a new set of IIR coefficients (note strange coefficient order and negation for a() that CMSIS needs)
* myAnalyzePhase.begin(IIR_LP_FILTER, &myIIRCoefficients[0], 20, RADIANS_PHASE);
* In begin() the pdConfig can be set (see #defines below). The default is to use no limiter, but to measure the input levels over the
* block and use that to scale the multiplier output. This will cause successive blocks to change slightly in output level due to
* errors in level measurement, but is other wise fine. If the limiter is used, the narrow band IIR filter should also be used to
* prevent artifacts from "beats" between the sample rate and the input frequency.
*
* Three different scaling routines are available following the LP filter. These deal with the issue that the multiplier type
* of phase detector produces an output proportional to the cosine of the phase angle between the two input sine waves.
* If the inputs both have a magnitude ranging from -1.0 to 1.0, the output will be cos(phase difference). Other values of
* sine wave will multiply this by the product of the two maximum levels. The selection of "fast" or "accurate" acos() will
* make the output approximately the angle, as scaled by UNITS_MASK. The ACOS_MASK bits in pdConfig, set by begin(), selects the
* acos used. Note that if acos function is used, the output range is 0 to pi radians, i.e., 0 to 180 degrees. "Units" have no
* effect when acos90 is not being used, as that would make little sense for the (-1,1) output.
*
* Functions:
* setAnalyzePhaseConfig(const uint16_t LPType, float32_t *pCoeffs, uint16_t nCoeffs)
* setAnalyzePhaseConfig(const uint16_t LPType, float32_t *pCoeffs, uint16_t nCoeffs, uint16_t pdConfig)
* are used to chhange the output filter from the IIR default, where:
* LPType is NO_LP_FILTER, IIR_LP_FILTER, FIR_LP_FILTER to select the output filter
* pCoeffs is a pointer to filter coefficients, either IIR or FIR
* nCoeffs is the number of filter coefficients
* pdConfig is bitwise selection (default 0b1100) of
* Bit 0: 0=No Limiter (default) 1=Use limiter
* Bit 2 and 1: 00=Use no acos linearizer 01=undefined
* 10=Fast, math-continuous acos() (default) 11=Accurate acosf()
* Bit 3: 0=No scale of multiplier 1=scale to min-max (default)
* Bit 4: 0=Output in degrees 1=Output in radians (default)
* showError(uint16_t e) sets whether error printing comes from update (e=1) or not (e=0).
*
* Examples: AudioTestAnalyzePhase.ino and AudioTestSinCos.ino
*
* Some measured time data for a 128 size block, Teensy 3.6, parts of update():
* Default settings, total time 123 microseconds
* Overhead of update(), loading arrays, handling blocks, less than 2 microseconds
* Min-max calculation, 23 microseconds
* Multiplier DBMixer 8 microseconds
* IIR LPF (default filter) 57 microseconds
* 53-term FIR filter 149 microseconds
* Fast acos_32() linearizer 32 microseconds
* Accurate acosf(x) seems to vary (with x?), 150 to 350 microsecond range
*
* Measured total update() time for the min-max scaling, fast acos(), and 53-term FIR filtering
* case is 214 microseconds for Teensy 3.6 and 45 microseconds for Teensy 4.0.
*
* Copyright (c) 2020 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 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.
*/
#ifndef _analyze_phase_f32_h
#define _analyze_phase_f32_h
#define N_STAGES 4
#define NFIR_MAX 200
#define NO_LP_FILTER 0
#define IIR_LP_FILTER 1
#define FIR_LP_FILTER 2
#define RADIANS_PHASE 1.0
#define DEGREES_PHASE 57.295779
// Test the number of microseconds to execute update()
#define TEST_TIME 1
#define LIMITER_MASK 0b00001
#define ACOS_MASK 0b00110
#define SCALE_MASK 0b01000
#define UNITS_MASK 0b10000
#include "AudioStream_F32.h"
#include <math.h>
class AudioAnalyzePhase_F32 : public AudioStream_F32 {
//GUI: inputs:2, outputs:1 //this line used for automatic generation of GUI node
//GUI: shortName: AnalyzePhase
public:
// Option of AudioSettings_F32 change to block size or sample rate:
AudioAnalyzePhase_F32(void) : AudioStream_F32(2, inputQueueArray_f32) { // default block_size and sampleRate_Hz
// Initialize BiQuad IIR instance (ARM DSP Math Library)
arm_biquad_cascade_df1_init_f32(&iir_inst, N_STAGES, &iir_coeffs[0], &IIRStateF32[0]);
}
// Constructor including new block_size and/or sampleRate_Hz
AudioAnalyzePhase_F32(const AudioSettings_F32 &settings) : AudioStream_F32(2, inputQueueArray_f32) {
block_size = settings.audio_block_samples;
sampleRate_Hz = settings.sample_rate_Hz;
// Initialize BiQuad IIR instance (ARM DSP Math Library)
arm_biquad_cascade_df1_init_f32(&iir_inst, N_STAGES, &iir_coeffs[0], &IIRStateF32[0]);
}
// Set AnalyzePhaseConfig while leaving pdConfig as is
void setAnalyzePhaseConfig(const uint16_t _LPType, float32_t *_pCoeffs, uint16_t _nCoeffs) {
setAnalyzePhaseConfig( _LPType, _pCoeffs, _nCoeffs, pdConfig);
}
// Set AnalyzePhaseConfig in full generality
void setAnalyzePhaseConfig(const uint16_t _LPType, float32_t *_pCoeffs, uint16_t _nCoeffs, uint16_t _pdConfig) {
AudioNoInterrupts(); // No interrupts while changing parameters
LPType = _LPType;
if (LPType == NO_LP_FILTER) {
//Serial.println("Advice: in AnalyzePhase, for NO_LP_FILTER the output contains 2nd harmonics");
//Serial.println(" that need external filtering.");
}
else if (LPType == IIR_LP_FILTER) {
if(_pCoeffs != NULL){
pIirCoeffs = _pCoeffs;
nIirCoeffs = _nCoeffs;
}
if (nIirCoeffs != 20){
//Serial.println("Error, in AnalyzePhase, for IIR_LP_FILTER there must be 20 coefficients.");
nIirCoeffs = 20;
}
arm_biquad_cascade_df1_init_f32(&iir_inst, N_STAGES, pIirCoeffs, &IIRStateF32[0]);
}
else if (LPType==FIR_LP_FILTER) {
if(_pCoeffs != NULL){
pFirCoeffs = _pCoeffs;
nFirCoeffs = _nCoeffs;
}
if (nFirCoeffs<4 || nFirCoeffs>NFIR_MAX) { // Too many or too few
//Serial.print("Error, in AnalyzePhase, for FIR_LP_FILTER there must be >4 and <=");
//Serial.print(NFIR_MAX);
//Serial.println(" coefficients.");
//Serial.println(" Restoring default IIR Filter.");
LPType = IIR_LP_FILTER;
pIirCoeffs = &iir_coeffs[0];
nIirCoeffs = 20; // Number of coefficients 20
pdConfig = 0b11100;
LPType = IIR_LP_FILTER; // Variables were set in setup() above
}
else { //Acceptable number, so initialize it
arm_fir_init_f32(&fir_inst, nFirCoeffs, pFirCoeffs, &FIRStateF32[0], block_size);
}
}
pdConfig = _pdConfig;
AudioInterrupts();
}
void showError(uint16_t e) {
errorPrint = e;
}
void update(void);
private:
float32_t sampleRate_Hz = AUDIO_SAMPLE_RATE_EXACT;
uint16_t block_size = AUDIO_BLOCK_SAMPLES;
// Two input data pointers
audio_block_f32_t *inputQueueArray_f32[2];
// Variables controlling the configuration
uint16_t LPType = IIR_LP_FILTER; // NO_LP_FILTER, IIR_LP_FILTER or FIR_LP_FILTER
float32_t *pIirCoeffs = &iir_coeffs[0]; // Coefficients for IIR
float32_t *pFirCoeffs = &fir_coeffs[0]; // Coefficients for FIR
uint16_t nIirCoeffs = 20; // Number of coefficients 20
uint16_t nFirCoeffs = 53; // Number of coefficients <=200
uint16_t pdConfig = 0b11100; // No limiter, fast acos, scale multiplier, radians out;
// Control error printing in update(). Should never be enabled
// until all audio objects have been initialized.
// Only used as 0 or 1 now, but 16 bits are available.
uint16_t errorPrint = 0;
// *Temporary* - TEST_TIME allows measuring time in microseconds for each part of the update()
#if TEST_TIME
elapsedMicros tElapse;
int32_t iitt = 998000; // count up to a million during startup
#endif
/* FIR filter designed with http://t-filter.appspot.com
* Sampling frequency: 44100 Hz
* 0 Hz - 4000 Hz gain = 1.0, ripple = 0.101 dB
* 7000 - 22000 Hz attenuation >= 81.8 dB
* Suitable for measuring phase in communications systems with linear phase.
*/
float32_t fir_coeffs[53] = {
-0.000206064,-0.000525129,-0.00083518, -0.000774011, 2.5925E-05,
0.001614912, 0.003431897, 0.004335125, 0.003127158, -0.000566047,
-0.005566484,-0.009192163,-0.008417443,-0.001801824, 0.008839149,
0.018273049, 0.019879265, 0.009349346,-0.011696836, -0.034389317,
-0.045008839,-0.030706279, 0.013824834, 0.082060266, 0.156328996,
0.213799940, 0.235420817, 0.213799940, 0.156328996, 0.082060266,
0.013824834,-0.030706279,-0.045008839,-0.034389317, -0.011696836,
0.009349346, 0.019879265, 0.018273049, 0.008839149, -0.001801824,
-0.008417443,-0.009192163,-0.005566484,-0.000566047, 0.003127158,
0.004335125, 0.003431897, 0.001614912, 2.5925E-05, -0.000774011,
-0.000835180,-0.000525129,-0.000206064 };
// 8-pole Biquad fc=0.0025fs, -80 dB Iowa Hills
// This is roughly the narrowest that doesn't have
// artifacts from numerical errors more than about
// 0.001 radians (0.06 deg), per experiments using F32.
// b0,b1,b2,a1,a2 for each BiQuad. Start with stage 0
float32_t iir_coeffs[5 * N_STAGES]={
0.08686551007982608,
-0.1737214710369926,
0.08686551007982608,
1.9951804375779567,
-0.9951899867006161,
// and stage 1
0.20909791845765324,
-0.4181667739705088,
0.20909791845765324,
1.9965910753714984,
-0.9966201383162961,
// stage 2
0.18360046797931723,
-0.3671514768697197,
0.18360046797931723,
1.9981966389027592,
-0.998246097991674,
// stage 3
0.03079484444321144,
-0.061529427044071175,
0.03079484444321144,
1.999421284937329,
-0.9994815467796806};
// ARM DSP Math library IIR filter instance
arm_biquad_casd_df1_inst_f32 iir_inst;
// And a FIR type, as either can be used via begin()
arm_fir_instance_f32 fir_inst;
// Delay line space for the FIR
float32_t FIRStateF32[AUDIO_BLOCK_SAMPLES + NFIR_MAX];
// Delay line space for the Biquad, each arranged as {x[n-1], x[n-2], y[n-1], y[n-2]}
float32_t IIRStateF32[4 * N_STAGES];
};
#endif

@ -0,0 +1,192 @@
/* AudioFilterEqualizer_F32.cpp
*
* Bob Larkin, W7PUA 8 May 2020
*
* 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.
*/
#include "AudioFilterEqualizer_F32.h"
void AudioFilterEqualizer_F32::update(void) {
audio_block_f32_t *block, *block_new;
#if TEST_TIME_EQ
if (iitt++ >1000000) iitt = -10;
uint32_t t1, t2;
t1 = tElapse;
#endif
block = AudioStream_F32::receiveReadOnly_f32();
if (!block) return;
// If there's no coefficient table, give up.
if (cf32f == NULL) {
AudioStream_F32::release(block);
return;
}
block_new = AudioStream_F32::allocate_f32(); // get a block for the FIR output
if (block_new) {
//apply the FIR
arm_fir_f32(&fir_inst, block->data, block_new->data, block->length);
AudioStream_F32::transmit(block_new); // send the FIR output
AudioStream_F32::release(block_new);
}
AudioStream_F32::release(block);
#if TEST_TIME_EQ
t2 = tElapse;
if(iitt++ < 0) {Serial.print("At AnalyzePhase end, microseconds = "); Serial.println (t2 - t1); }
t1 = tElapse;
#endif
}
/* equalizerNew() calculates the Equalizer FIR filter coefficients. Works from:
* uint16_t equalizerNew(uint16_t _nBands, float32_t *feq, float32_t *adb,
uint16_t _nFIR, float32_t *_cf32f, float32_t kdb)
* nBands Number of equalizer bands
* feq Pointer to array feq[] of nBands breakpoint frequencies, fractions of sample rate, Hz
* adb Pointer to array aeq[] of nBands levels, in dB, for the feq[] defined frequency bands
* nFIR The number of FIR coefficients (taps) used in the equalzer
* cf32f Pointer to an array of float to hold FIR coefficients
* kdb A parameter that trades off sidelobe levels for sharpness of band transition.
* kdb=30 sharp cutoff, poor sidelobes
* kdb=60 slow cutoff, low sidelobes
*
* The arrays, feq[], aeq[] and cf32f[] are supplied by the calling .INO
*
* Returns: 0 if successful, or an error code if not.
* Errors: 1 = Too many bands, 50 max
* 2 = sidelobe level out of range, must be > 0
* 3 = nFIR out of range
*
* Note - This function runs at setup time, and there is no need to fret about
* processor speed. Likewise, local arrays are created on the stack and are
* available for other use when this function closes.
*/
uint16_t AudioFilterEqualizer_F32::equalizerNew(uint16_t _nBands, float32_t *feq, float32_t *adb,
uint16_t _nFIR, float32_t *_cf32f, float32_t kdb) {
uint16_t i, j;
uint16_t nHalfFIR;
float32_t beta, kbes;
float32_t q, xj2, scaleXj2, WindowWt;
float32_t fNorm[50]; // Normalized to the sampling frequency
float32_t aVolts[50]; // Convert from dB to "quasi-Volts"
mathDSP_F32 mathEqualizer; // For Bessel function
// Make private copies
cf32f = _cf32f;
nFIR = _nFIR;
nBands = _nBands;
// Check range of nFIR
if (nFIR<5 || nFIR>EQUALIZER_MAX_COEFFS)
return ERR_EQ_NFIR;
// The number of FIR coefficients needs to be odd
if (2*(nFIR/2) == nFIR)
nFIR -= 1; // We just won't use the last element of the array
nHalfFIR = (nFIR - 1)/2; // If nFIR=199, nHalfFIR=99
for (int kk = 0; kk<nFIR; kk++) // To be sure, zero the coefficients
cf32f[kk] = 0.0f;
// Convert dB to Voltage ratios, frequencies to fractions of sampling freq
if(nBands <2 || nBands>50) return ERR_EQ_BANDS;
for (i=0; i<nBands; i++) {
aVolts[i]=powf(10.0, (0.05*adb[i]));
fNorm[i]=feq[i]/sample_rate_Hz;
}
/* Find FIR coefficients, the Fourier transform of the frequency
* response. This is done by dividing the response into a sequence
* of nBands rectangular frequency blocks, each of a different level.
* We can precalculate the Fourier transform for each rectangular band.
* The linearity of the Fourier transform allows us to sum the transforms
* of the individual blocks to get pre-windowed coefficients. As follows
*
* Numbering example for nFIR==199:
* Subscript 0 to 98 is 99 taps; 100 to 198 is 99 taps; 99+1+99=199 taps
* The center coef ( for nFIR=199 taps, nHalfFIR=99 ) is a
* special case that comes from sin(0)/0 and treated first:
*/
cf32f[nHalfFIR] = 2.0f*(aVolts[0]*fNorm[0]); // Coefficient "99"
for(i=1; i<nBands; i++) {
cf32f[nHalfFIR] += 2.0f*aVolts[i]*(fNorm[i]-fNorm[i-1]);
}
for (j=1; j<=nHalfFIR; j++) { // Coefficients "100 to 198"
q = MF_PI*(float32_t)j;
// First, deal with the zero frequency end band that is "low-pass."
cf32f[j+nHalfFIR] = aVolts[0]*sinf(fNorm[0]*2.0*q)/q;
// and then the rest of the bands that have low and high frequencies
for(i=1; i<nBands; i++)
cf32f[j+nHalfFIR] += aVolts[i]*( (sinf(fNorm[i]*2.0*q)/q) - (sinf(fNorm[i-1]*2.0*q)/q) );
}
/* At this point, the cf32f[] coefficients are simply truncated sin(x)/x shapes, creating
* very high sidelobe responses. To reduce the sidelobes, a windowing function is applied.
* This has the side affect of increasing the rate of cutoff for sharp frequency changes.
* The only windowing function available here is that of James Kaiser. This has a number
* of desirable features. The tradeoff of sidelobe level versus cutoff rate is variable.
* We specify it in terms of kdb, the highest sidelobe, in dB, next to a sharp cutoff. For
* calculating the windowing vector, we need a parameter beta, found as follows:
*/
if (kdb<0) return ERR_EQ_SIDELOBES;
if (kdb>50)
beta = 0.1102*(kdb-8.7);
else if (kdb>20.96 && kdb<=50.0)
beta = 0.58417*powf((kdb-20.96), 0.4) + 0.07886*(kdb-20.96);
else
beta=0.0;
// Note: i0f is the floating point in & out zero'th order Bessel function (see mathDSP_F32.h)
kbes = 1.0f / mathEqualizer.i0f(beta); // An additional derived parameter used in loop
// Apply the Kaiser window
scaleXj2 = 2.0f/(float32_t)nFIR;
scaleXj2 *= scaleXj2;
for (j=0; j<=nHalfFIR; j++) { // For 199 Taps, this is 0 to 99
xj2 = (int16_t)(0.5f+(float32_t)j);
xj2 = scaleXj2*xj2*xj2;
WindowWt=kbes*(mathEqualizer.i0f(beta*sqrt(1.0-xj2)));
cf32f[nHalfFIR + j] *= WindowWt; // Apply the Kaiser window to upper half
cf32f[nHalfFIR - j] = cf32f[nHalfFIR +j]; // and create the lower half
}
// And fill in the members of fir_inst
arm_fir_init_f32(&fir_inst, nFIR, (float32_t *)cf32f, &StateF32[0], (uint32_t)block_size);
return 0;
}
/* Calculate response in dB. Leave nFreq point result in array rdb[] supplied
* by the calling .INO See Parks and Burris, "Digital Filter Design," p27 (Type 1).
*/
void AudioFilterEqualizer_F32::getResponse(uint16_t nFreq, float32_t *rdb) {
uint16_t i, j;
float32_t bt;
float32_t piOnNfreq;
uint16_t nHalfFIR;
nHalfFIR = (nFIR - 1)/2;
piOnNfreq = MF_PI / (float32_t)nFreq;
for (i=0; i<nFreq; i++) {
bt = cf32f[nHalfFIR];//bt = 0.5f*cf32f[nHalfFIR]; // Center coefficient
for (j=0; j<nHalfFIR; j++) // Add in the others twice, as they are symmetric
bt += 2.0f*cf32f[j]*cosf(piOnNfreq*(float32_t)((nHalfFIR-j)*i));
rdb[i] = 20.0f*log10f(fabsf(bt)); // Convert to dB
}
}

@ -0,0 +1,178 @@
/*
* AudioFilterEqualizer_F32
*
* Created: Bob Larkin W7PUA 8 May 2020
*
* This is a direct translation of the receiver audio equalizer written
* by this author for the open-source DSP-10 radio in 1999. See
* http://www.janbob.com/electron/dsp10/dsp10.htm and
* http://www.janbob.com/electron/dsp10/uhf3_35a.zip
*
* Credit and thanks to PJRC, Paul Stoffregen and Teensy products for the audio
* system and library that this is built upon as well as the float32
* work of Chip Audette embodied in the OpenAudio_ArduinoLibrary. Many thanks
* for the library structures and wonderful Teensy products.
*
* This equalizer is specified by an array of 'nBands' frequency bands
* each of of arbitrary frequency span. The first band always starts at
* 0.0 Hz, and that value is not entered. Each band is specified by the upper
* frequency limit to the band.
* The last band always ends at half of the sample frequency, which for 44117 Hz
* sample frequency would be 22058.5. Each band is specified by its upper
* frequency in an .INO supplied array feq[]. The dB level of that band is
* specified by a value, in dB, arranged in an .INO supplied array
* aeq[]. Thus a trivial bass/treble control might look like:
* nBands = 3;
* feq[] = {300.0, 1500.0, 22058.5};
* float32_t bass = -2.5; // in dB, relative to anything
* float32_t treble = 6.0;
* aeq[] = {bass, 0.0, treble};
*
* It may be obvious that this equalizer is a more general case of the common
* functions such as low-pass, band-pass, notch, etc. For instance, a pair
* of band pass filters would look like:
* nBands = 5;
* feq[] = {500.0, 700.0, 2000.0, 2200.0, 22058.5};
* aeq[] = {-100.0, 0.0, -100.0, 2.0, -100.0};
* where we added 2 dB of gain to the 2200 to 2400 Hz filter, relative to the 500
* to 700 Hz band.
*
* An octave band equalizer is made by starting at some low frequency, say 40 Hz for the
* first band. The lowest frequency band will be from 0.0 Hz up to that first frequency.
* Next multiply the first frequency by 2, creating in our example, a band from 40.0
* to 80 Hz. This is continued until the last frequency is about 22058 Hz.
* This works out to require 10 bands, as follows:
* nBands = 10;
* feq[] = { 40.0, 80.0, 160.0, 320.0, 640.0, 1280.0, 2560.0, 5120.0, 10240.0, 22058.5};
* aeq[] = { 5.0, 4.0, 2.0, -3.0, -4.0, -1.0, 3.0, 6.0, 3.0, 0.5 };
*
* For a "half octave" equalizer, multiply each upper band limit by the square root of 2 = 1.414
* to get the next band limit. For that case, feq[] would start with a sequence
* like 40, 56.56, 80.00, 113.1, 160.0, ... for a total of about 20 bands.
*
* How well all of this is achieved depends on the number of FIR coefficients
* being used. In the Teensy 3.6 / 4.0 the resourses allow a hefty number,
* say 201, of coefficients to be used without stealing all the processor time
* (see Timing, below). The coefficient and FIR memory is sized for a maximum of
* 250 coefficients, but can be recompiled for bigger with the define FIR_MAX_COEFFS.
* To simplify calculations, the number of FIR coefficients should be odd. If not
* odd, the number will be reduced by one, quietly.
*
* If you try to make the bands too narrow for the number of FIR coeffficients,
* the approximation to the desired curve becomes poor. This can all be evaluated
* by the function getResponse(nPoints, pResponse) which fills an .INO-supplied array
* pResponse[nPoints] with the frequency response of the equalizer in dB. The nPoints
* are spread evenly between 0.0 and half of the sample frequency.
*
* Initialization is a 2-step process. This makes it practical to change equalizer
* levels on-the-fly. The constructor starts up with a 4-tap FIR setup for direct
* pass through. Then the setup() in the .INO can specify the equalizer.
* The newEqualizer() function has several parameters, the number of equalizer bands,
* the frequencies of the bands, and the sidelobe level. All of these can be changed
* dynamically. This function can be changed dynamically, but it may be desireable to
* mute the audio during the change to prevent clicks.
*
* This 16-bit integer version adjusts the maximum coefficient size to scale16 in the calls
* to both equalizerNew() and getResponse(). Broadband equalizers can work with full-scale
* 32767.0f sorts of levels, where narrow band filtering may need smaller values to
* prevent overload. Experiment and check carefully. Use lower values if there are doubts.
*
* For a pass-through function, something like this (which can be intermixed with fancy equalizers):
* float32_t fBand[] = {10000.0f, 22058.5f};
* float32_t dbBand[] = {0.0f, 0.0f};
* equalize1.equalizerNew(2, &fBand[0], &dbBand[0], 4, &equalizeCoeffs[0], 30.0f, 32767.0f);
*
* Measured timing of update() for a 128 sample block, Teensy 3.6:
* Fixed time 13 microseconds
* Per FIR Coefficient time 2.5 microseconds
* Total for 199 FIR Coefficients = 505 microseconds (17.4% of 44117 Hz available time)
*
* Per FIR Coefficient, Teensy 4.0, 0.44 microseconds
*
* Copyright (c) 2020 Bob Larkin
* Any snippets of code from PJRC or Chip Audette used here brings with it
* the associated license.
*
* In addition, work here is covered by MIT LIcense:
*
* 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.
*/
#ifndef _filter_equalizer_f32_h
#define _filter_equalizer_f32_h
#include "Arduino.h"
#include "AudioStream_F32.h"
#include "arm_math.h"
#include "mathDSP_F32.h"
#ifndef MF_PI
#define MF_PI 3.1415926f
#endif
// Temporary timing test
#define TEST_TIME_EQ 0
#define EQUALIZER_MAX_COEFFS 251
#define ERR_EQ_BANDS 1
#define ERR_EQ_SIDELOBES 2
#define ERR_EQ_NFIR 3
class AudioFilterEqualizer_F32 : public AudioStream_F32
{
//GUI: inputs:1, outputs:1 //this line used for automatic generation of GUI node
//GUI: shortName:filter_Equalizer
public:
AudioFilterEqualizer_F32(void): AudioStream_F32(1,inputQueueArray) {
// Initialize FIR instance (ARM DSP Math Library) with default simple passthrough FIR
arm_fir_init_f32(&fir_inst, nFIR, (float32_t *)cf32f, &StateF32[0], (uint32_t)block_size);
}
AudioFilterEqualizer_F32(const AudioSettings_F32 &settings): AudioStream_F32(1,inputQueueArray) {
block_size = settings.audio_block_samples;
sample_rate_Hz = settings.sample_rate_Hz;
arm_fir_init_f32(&fir_inst, nFIR, (float32_t *)cf32f, &StateF32[0], (uint32_t)block_size);
}
uint16_t equalizerNew(uint16_t _nBands, float32_t *feq, float32_t *adb,
uint16_t _nFIR, float32_t *_cf32f, float32_t kdb);
void getResponse(uint16_t nFreq, float32_t *rdb);
void update(void);
private:
audio_block_f32_t *inputQueueArray[1];
uint16_t block_size = AUDIO_BLOCK_SAMPLES;
float32_t firStart[4] = {0.0, 1.0, 0.0, 0.0}; // Initialize to passthrough
float32_t* cf32f = firStart; // pointer to current coefficients
uint16_t nFIR = 4; // Number of coefficients
uint16_t nBands = 2;
float32_t sample_rate_Hz = AUDIO_SAMPLE_RATE;
// *Temporary* - TEST_TIME allows measuring time in microseconds for each part of the update()
#if TEST_TIME_EQ
elapsedMicros tElapse;
int32_t iitt = 999000; // count up to a million during startup
#endif
// ARM DSP Math library filter instance
arm_fir_instance_f32 fir_inst;
float32_t StateF32[AUDIO_BLOCK_SAMPLES + EQUALIZER_MAX_COEFFS]; // max, max
};
#endif

@ -0,0 +1,259 @@
/* AudioFilterFIRGeneralr_F32.cpp
*
* Bob Larkin, W7PUA 20 May 2020 (c)
*
* 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.
*/
/* A math note - The design of the FIR filter from the frequency response requires
* taking a discrete Fourier transform. The calculation of the achieved
* frequency response requires another Fourier transform. Good news is
* that this is arithmetically simple, because of
* working with real (not complex) inputs and requiring the FIR be linear
* phase. One of a number of references is C.S. Burris and T. W. Parks,
* "Digital Filter Design." That book also has excellent sections on
* Chebychev error (Parks-McClellan-Rabiner) FIR filters and IIR Filters.
*/
#include "AudioFilterFIRGeneral_F32.h"
void AudioFilterFIRGeneral_F32::update(void) {
audio_block_f32_t *blockIn, *blockOut;
#if TEST_TIME_FIRG
if (iitt++ >1000000) iitt = -10;
uint32_t t1, t2;
t1 = tElapse;
#endif
blockIn = AudioStream_F32::receiveReadOnly_f32();
if (!blockIn) return;
// If there's no coefficient table, give up.
if (cf32f == NULL) {
AudioStream_F32::release(blockIn);
return;
}
blockOut = AudioStream_F32::allocate_f32(); // get a block for the FIR output
if (blockOut) {
// The FIR update
arm_fir_f32(&fir_inst, blockIn->data, blockOut->data, blockIn->length);
AudioStream_F32::transmit(blockOut); // send the FIR output
AudioStream_F32::release(blockOut);
}
AudioStream_F32::release(blockIn);
#if TEST_TIME_FIRG
t2 = tElapse;
if(iitt++ < 0) {Serial.print("At FIRGeneral end, microseconds = "); Serial.println (t2 - t1); // }
Serial.print("numtaps = "); Serial.println (fir_inst.numTaps);
}
t1 = tElapse;
#endif
}
/* FIRGeneralNew() calculates the generalFIR filter coefficients. Works from:
* adb Pointer to nFIR/2 array adb[] of levels, in dB
* nFIR The number of FIR coefficients (taps) used
* cf32f Pointer to an array of float to hold nFIR FIR coefficients
* kdb A parameter that trades off sidelobe levels for sharpness of band transition.
* kdb=30 sharp cutoff, poor sidelobes
* kdb=60 slow cutoff, low sidelobes
* pStateArray Pointer to 128+nFIR array of floats, used here briefly and then
* passed to the FIR routine as working storage
*
* The arrays, adb[], cf32f[] and pStateArray[] are supplied by the calling .INO
*
* Returns: 0 if successful, or an error code if not.
* Errors: 1 = NU
* 2 = sidelobe level out of range, must be > 0
* 3 = nFIR out of range
*
* Note - This function runs at setup time, so slowness is not an issue
*/
uint16_t AudioFilterFIRGeneral_F32::FIRGeneralNew(
float32_t *adb, uint16_t _nFIR, float32_t *_cf32f, float32_t kdb,
float32_t *pStateArray) {
uint16_t i, k, n;
uint16_t nHalfFIR;
float32_t beta, kbes;
float32_t num, xn2, scaleXn2, WindowWt;
float32_t M; // Burris and Parks, (2.16)
mathDSP_F32 mathEqualizer; // For Bessel function
bool even;
cf32f = _cf32f; // Make pivate copies
nFIR = _nFIR;
// Check range of nFIR
if (nFIR<4)
return ERR_FIRGEN_NFIR;
M = 0.5f*(float32_t)(nFIR - 1);
// The number of FIR coefficients is even or odd
if (2*(nFIR/2) == nFIR) {
even = true;
nHalfFIR = nFIR/2;
}
else {
even = false;
nHalfFIR = (nFIR - 1)/2;
}
for (i=0; i<nFIR; i++) // To be sure, zero the coefficients
cf32f[i] = 0.0f;
for(i=0; i<(nFIR+AUDIO_BLOCK_SAMPLES); i++) // And the storage
pStateArray[i] = 0.0f;
// Convert dB to "Voltage" ratios, frequencies to fractions of sampling freq
// Borrow pStateArray, as it has not yet gone to ARM math
for(i=0; i<=nHalfFIR; i++)
pStateArray[i] = powf(10.0, 0.05f*adb[i]);
/* Find FIR coefficients, the Fourier transform of the frequency
* response. This general frequency description has half as many
* frequency dB points as FIR coefficients. If nFIR is odd,
* the number of frequency points is nHalfFIR = (nFIR - 1)/2.
*
* Numbering example for nFIR==21:
* Odd nFIR: Subscript 0 to 9 is 10 taps; 11 to 20 is 10 taps; 10+1+10=21 taps
*
* Numbering example for nFIR==20:
* Even nFIR: Subscript 0 to 9 is 10 taps; 10 to 19 is 10 taps; 10+10=20 taps
*/
float32_t oneOnNFIR = 1.0f / (float32_t)nFIR;
if(even) {
for(n=0; n<nHalfFIR; n++) { // Over half of even nFIR FIR coeffs
cf32f[n] = pStateArray[0]; // DC term
for(k=1; k<nHalfFIR; k++) { // Over all frequencies, half of nFIR
num = (M - (float32_t)n) * (float32_t)k;
cf32f[n] += 2.0f*pStateArray[k]*cosf(MF_TWOPI*num*oneOnNFIR);
}
cf32f[n] *= oneOnNFIR; // Divide by nFIR
}
}
else { // nFIR is odd
for(n=0; n<=nHalfFIR; n++) { // Over half of FIR coeffs, nFIR odd
cf32f[n] = pStateArray[0];
for(k=1; k<=nHalfFIR; k++) {
num = (float32_t)((nHalfFIR - n)*k);
cf32f[n] += 2.0f*pStateArray[k]*cosf(MF_TWOPI*num*oneOnNFIR);
}
cf32f[n] *= oneOnNFIR;
}
}
/* At this point, the cf32f[] coefficients are simply truncated, creating
* high sidelobe responses. To reduce the sidelobes, a windowing function is applied.
* This has the side affect of increasing the rate of cutoff for sharp frequency transition.
* The only windowing function available here is that of James Kaiser. This has a number
* of desirable features. The sidelobes drop off as the frequency away from a transition.
* Also, the tradeoff of sidelobe level versus cutoff rate is variable.
* Here we specify it in terms of kdb, the highest sidelobe, in dB, next to a sharp cutoff. For
* calculating the windowing vector, we need a parameter beta, found as follows:
*/
if (kdb<0.0f) return ERR_FIRGEN_SIDELOBES;
if (kdb < 20.0f)
beta = 0.0;
else
beta = -2.17+0.17153*kdb-0.0002841*kdb*kdb; // Within a dB or so
// Note: i0f is the fp zero'th order modified Bessel function (see mathDSP_F32.h)
kbes = 1.0f / mathEqualizer.i0f(beta); // An additional derived parameter used in loop
scaleXn2 = 4.0f / ( ((float32_t)nFIR - 1.0f)*((float32_t)nFIR - 1.0f) ); // Needed for even & odd
if (even) {
// nHalfFIR = nFIR/2; for nFIR even
for (n=0; n<nHalfFIR; n++) { // For 20 Taps, this is 0 to 9
xn2 = 0.5f+(float32_t)n;
xn2 = scaleXn2*xn2*xn2;
WindowWt=kbes*(mathEqualizer.i0f(beta*sqrt(1.0-xn2)));
if(kdb > 0.1f) // kdb==0.0 means no window
cf32f[nHalfFIR - n - 1] *= WindowWt; // Apply window, reverse subscripts
cf32f[nHalfFIR + n] = cf32f[nHalfFIR - n - 1]; // and create the upper half
}
}
else { // nFIR is odd, nHalfFIR = (nFIR - 1)/2
for (n=0; n<=nHalfFIR; n++) { // For 21 Taps, this is 0 to 10, including center
xn2 = (int16_t)(0.5f+(float32_t)n);
xn2 = scaleXn2*xn2*xn2;
WindowWt=kbes*(mathEqualizer.i0f(beta*sqrt(1.0-xn2)));
if(kdb > 0.1f)
cf32f[nHalfFIR - n] *= WindowWt;
// 21 taps, for n=0, nHalfFIR-n = 10, for n=1 nHalfFIR-n=9, for n=nHalfFIR, nHalfFIR-n=0
cf32f[nHalfFIR + n] = cf32f[nHalfFIR - n]; // and create upper half (rewrite center is OK)
}
}
// And finally, fill in the members of fir_inst given in update() to the ARM FIR routine.
AudioNoInterrupts();
arm_fir_init_f32(&fir_inst, nFIR, (float32_t *)cf32f, &pStateArray[0], (uint32_t)block_size);
AudioInterrupts();
return 0;
}
// FIRGeneralLoad() allows an array of nFIR FIR coefficients to be loaded. They come from an .INO
// supplied array. Also, pStateArray[] is .INO supplied and must be (block_size + nFIR) in size.
uint16_t AudioFilterFIRGeneral_F32::LoadCoeffs(uint16_t _nFIR, float32_t *_cf32f, float32_t *pStateArray) {
nFIR = _nFIR;
cf32f = _cf32f;
if (nFIR<4) // Check range of nFIR
return ERR_FIRGEN_NFIR;
for(int i=0; i<(nFIR+AUDIO_BLOCK_SAMPLES); i++) // Zero, to be sure
pStateArray[i] = 0.0f;
AudioNoInterrupts();
arm_fir_init_f32(&fir_inst, nFIR, &cf32f[0], &pStateArray[0], (uint32_t)block_size);
AudioInterrupts();
return 0;
}
/* Calculate frequency response in dB. Leave nFreq point result in array rdb[] supplied
* by the calling .INO See B&P p27 (Type 1 and 2). Be aware that if nFIR*nFreq is big,
* like 100,000 or more, this will take a while to calcuulate all the cosf(). Normally,
* this is not an issue as this is an infrequent calculation.
* This function assumes that the phase of the FIR is linear with frequency, i.e.,
* the coefficients are symmetrical about the middle. Otherwise, doubling of values,
* as is done here, is not valid.
*/
void AudioFilterFIRGeneral_F32::getResponse(uint16_t nFreq, float32_t *rdb) {
uint16_t i, n;
float32_t bt;
float32_t piOnNfreq;
uint16_t nHalfFIR;
float32_t M;
piOnNfreq = MF_PI / (float32_t)nFreq;
// The number of FIR coefficients, even or odd?
if (2*(nFIR/2) == nFIR) { // it is even
nHalfFIR = nFIR/2;
M = 0.5f*(float32_t)(nFIR - 1);
for (i=0; i<nFreq; i++) {
bt = 0.0;
for (n=0; n<nHalfFIR; n++) // Add in terms twice, as they are symmetric
bt += 2.0f*cf32f[n]*cosf(piOnNfreq*((M-(float32_t)n)*(float32_t)i));
rdb[i] = 20.0f*log10f(fabsf(bt)); // Convert to dB
}
}
else { // it is odd
nHalfFIR = (nFIR - 1)/2;
for (i=0; i<nFreq; i++) {
bt = cf32f[nHalfFIR]; // Center coefficient
for (n=0; n<nHalfFIR; n++) // Add in the others twice, as they are symmetric
bt += 2.0f*cf32f[n]*cosf(piOnNfreq*(float32_t)((nHalfFIR-n)*i));
rdb[i] = 20.0f*log10f(fabsf(bt));
}
}
}

@ -0,0 +1,188 @@
/*
* AudioFilterFIRGeneral_F32
*
* Created: Bob Larkin W7PUA 20 May 2020
*
* Credit and thanks to PJRC, Paul Stoffregen and Teensy products for the audio
* system and library that this is built upon as well as the float32
* work of Chip Audette embodied in the OpenAudio_ArduinoLibrary. Many thanks
* for the library structures and wonderful Teensy products.
*
* There are enough different FIR filter Audio blocks to need a summary. Here goes:
*
* AudioFilterFIR (Teensy Audio Library by PJRC) handles 16-bit integer data,
* and a maximum of 200 FIR coefficients, even only. (taps). For Teensy Audio.
* AudioFilterFIR_F32 (OpenAudio_ArduinoLibrary by Chip Audette) handles 32-bit floating point
* data and a maximum of 200 taps. Can be used with a mix of Teensy Audio
* and 32-bit OpenAudio_Arduino blocks.
* AudioFilterFIRGeneral_F32 (This block) handles 32-bit floating point
* data and very large number of taps, even or odd. Can be used with a mix of Teensy Audio
* and 32-bit OpenAudio_Arduino blocks. This includes the design of an
* arbitrary frequency response using Kaiser windows.
* AudioFilterFIRGeneral_I16 Same as this block, but data is 16-bit integer and
* the number of taps must be even.
* AudioFilterEqualizer_F32 handles 32-bit floating point data and 250 maximum taps
* even or odd. Can be used with a mix of Teensy Audio
* and 32-bit OpenAudio_Arduino blocks. This includes the design of an
* arbitrary frequency response using multiple flat response steps. A Kaiser windows
* is used.
* AudioFilterEqualizer_I16 handles 16-bit integer data and 250 maximum taps,
* even only.
*
* FIR filters suffer from needing considerable computation of the multiply-and-add
* sort. This limits the number of taps that can be used, but less so as time goes by.
* In particular, the Teensy 4.x, if it *did nothing but* FIR calculations, could
* use about 6000 taps inmonaural, which is a huge number. But, this also
* suggests that if the filtering task is an important function of a project,
* using, say 2000 taps is practical.
*
* FIR filters can be (and are here) implemented to have symmetrical coefficients. This
* results in constant delay at all frequencies (linear phase). For some applications this can
* be an important feature. Sometimes it is suggested that the FIR should not be
* used because of the latency it creates. Note that if constant delay is needed, the FIR
* implementation does this with minimum latency.
*
* For this block, AudioFilterFIRGeneral_F32, memory storage for the FIR
* coefficiients as well as working storage for the ARM FIR routine is provided
* by the calling .INO. This allows large FIR sizes without always tying up a
* big memory block.
*
* This block normally calculates the FIR coefficients using a Fourier transform
* of the desired amplitude response and a Kaiser window. This flexability requires
* the calling .INO to provide an array of response levels, in relative dB,
* that is half the length of the number of FIR taps. An example of this entry is a
* 300 point low-pass filter with a cutoff at 10% of the 44.1 kHz sample frequency:
* for(int i=0; i<150; i++) {
* if (i<30) dbA[i] = 0.0f;
* else dbA[i] = -140.0f;
* }
* firg1.FIRGeneralNew(&dbA[0], 300, &equalizeCoeffs[0], 50.0f, &workSpace[0]);
*
* As an alternate to inputting the response function, the FIR coefficients can be
* entered directly using LoadCoeffs(nFIR, cf32f, *pStateArray). This is a very quick
* operation as only pointers to coefficients are involved. Several filters can be
* stored in arrays and switched quickly this way. If this is done, pStateArray[]
* as initially setup should be large enough for all filters. There will be "clicks"
* associated with filter changes and these may need to be muted.
*
* How well the desired response is achieved depends on the number of FIR coefficients
* being used. As noted above, for some applications it may be desired to use
* large numbers of taps. The achieved response can be evaluated
* by the function getResponse(nPoints, pResponse) which fills an .INO-supplied array
* pResponse[nPoints] with the frequency response of the equalizer in dB. The nPoints
* are spread evenly between 0.0 and half of the sample frequency.
*
* Initialization is a 2-step process. This makes it practical to change equalizer
* levels on-the-fly. The constructor starts up with a 4-tap FIR setup for direct
* pass through. Then the setup() in the .INO can specify the equalizer.
* The newEqualizer() function has several parameters, the number of equalizer bands,
* the frequencies of the bands, and the sidelobe level. All of these can be changed
* dynamically. This function can be changed dynamically, but it may be desireable to
* mute the audio during the change to prevent clicks.
*
* Measured timing of update() for L or R 128 sample block, Teensy 3.6:
* Fixed time 13 microseconds
* Per FIR Coefficient time 2.5 microseconds (about 51E6 multiply and accumulates/sec)
* Total for 199 FIR Coefficients = 505 microseconds (17.4% of 44117 Hz available time)
* Total for stereo is twice those numbers.
* Measured timing of update() for L or R 128 sample block, Teensy 4.0:
* Fixed time 1.4 microseconds
* Per FIR Coefficient time 0.44 microseconds (about 290E6 multiply and accumulate/sec)
* Total for 199 FIR Coefficients = 90 microseconds (3.1% of 44117 Hz available time)
* Total for stereo is twice those numbers
* Measured for FIRGeneralNew(), T4.0, to design a 4000 tap FIR is 14 sec. This goes
* with the square of the number of taps.
* Measured for getResponse() for nFIR=4000 and nFreq=5000, T4.0, is about a minute.
*
* Functions for the AudioFilterFIRGeneral_F32 object are
* FIRGeneralNew(*adb, nFIR, cf32f, kdb, *pStateArray); // to design and use an adb[]
* frequency response.
* LoadCoeffs(nFIR, cf32f, *pStateArray); // To directly load FIR coefficients cf32f[]
* getResponse(nFreq, *rdb); // To obtain the amplitude response in dB, rdb[]
*
* Status: Tested T3.6, T4.0 No known bugs
*
* Examples: TestFIRGeneralLarge4.ino TestFIRGeneralLarge5.ino
*
* Copyright (c) 2020 Bob Larkin
* Any snippets of code from PJRC or Chip Audette used here brings with it
* the associated license.
*
* In addition, work here is covered by MIT LIcense:
*
* 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.
*/
#ifndef _filter_FIR_general_f32_h
#define _filter_FIR_general_f32_h
#include "Arduino.h"
#include "AudioStream_F32.h"
#include "arm_math.h"
#include "mathDSP_F32.h"
#ifndef MF_PI
#define MF_PI 3.1415926f
#endif
// Temporary timing test
#define TEST_TIME_FIRG 0
#define ERR_FIRGEN_BANDS 1
#define ERR_FIRGEN_SIDELOBES 2
#define ERR_FIRGEN_NFIR 3
class AudioFilterFIRGeneral_F32 : public AudioStream_F32
{
//GUI: inputs:1, outputs:1 //this line used for automatic generation of GUI node
//GUI: shortName:filter_Equalizer
public:
AudioFilterFIRGeneral_F32(void): AudioStream_F32(1,inputQueueArray) {
// Initialize FIR instance (ARM DSP Math Library) with default simple passthrough FIR
arm_fir_init_f32(&fir_inst, nFIR, (float32_t *)cf32f, &StateF32[0], (uint32_t)block_size);
}
AudioFilterFIRGeneral_F32(const AudioSettings_F32 &settings): AudioStream_F32(1,inputQueueArray) {
block_size = settings.audio_block_samples;
sample_rate_Hz = settings.sample_rate_Hz;
arm_fir_init_f32(&fir_inst, nFIR, (float32_t *)cf32f, &StateF32[0], (uint32_t)block_size);
}
uint16_t FIRGeneralNew(float32_t *adb, uint16_t _nFIR, float32_t *_cf32f, float32_t kdb, float32_t *pStateArray);
uint16_t LoadCoeffs(uint16_t _nFIR, float32_t *_cf32f, float32_t *pStateArray);
void getResponse(uint16_t nFreq, float32_t *rdb);
void update(void);
private:
audio_block_f32_t *inputQueueArray[1];
uint16_t block_size = AUDIO_BLOCK_SAMPLES;
float32_t firStart[4] = {0.0, 1.0, 0.0, 0.0}; // Initialize to passthrough
float32_t* cf32f = firStart; // pointer to current coefficients
uint16_t nFIR = 4; // Number of coefficients
float32_t sample_rate_Hz = AUDIO_SAMPLE_RATE;
// *Temporary* - TEST_TIME allows measuring time in microseconds for each part of the update()
#if TEST_TIME_FIRG
elapsedMicros tElapse;
int32_t iitt = 999000; // count up to a million during startup
#endif
// ARM DSP Math library filter instance
arm_fir_instance_f32 fir_inst;
float32_t StateF32[AUDIO_BLOCK_SAMPLES + 4]; // FIR_GENERAL_MAX_COEFFS]; // max, max
};
#endif

@ -35,3 +35,9 @@
#include "AudioEffectDelay_OA_F32.h"
#include "RadioIQMixer_F32.h"
#include "AudioFilter90Deg_F32.h"
#include "AudioAnalyzePhase_F32.h"
#include "AudioFilterEqualizer_F32.h"
#include "AudioFilterFIRGeneral_F32.h"
#include "RadioFMDetector_F32.h"
#include "radioNoiseBlanker_F32.h"
#include "synth_sin_cos_f32.h"

@ -0,0 +1,136 @@
/*
* RadioFMDetector_F32.cpp
*
* 22 March 2020
* Bob Larkin, in support of the library:
* Chip Audette, OpenAudio, Apr 2017
* -------------------
*
* Copyright (c) 2020 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 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.
*
* See RadioFMDetector_F32.h for usage details
*/
#include "RadioFMDetector_F32.h"
// 513 values of the sine wave in a float array:
#include "sinTable512_f32.h"
// ==== UPDATE ====
void RadioFMDetector_F32::update(void) {
audio_block_f32_t *blockIn, *blockOut=NULL;
uint16_t i, index_sine;
float32_t deltaPhase, a, b, dtemp1, dtemp2;
float32_t v_i[128]; // max size
float32_t v_q[128];
mathDSP_F32 mathDSP1; // Math support functions
#if TEST_TIME_FM
if (iitt++ >1000000) iitt = -10;
uint32_t t1, t2;
t1 = tElapse;
#endif
// Get input to FM Detector block
blockIn = AudioStream_F32::receiveWritable_f32(0);
if (!blockIn) {
if(errorPrintFM) Serial.println("FMDET-ERR: No input memory");
return;
}
// If there's no coefficient table, give up.
if (fir_IQ_Coeffs == NULL) {
if(errorPrintFM) Serial.println("FMDET-ERR: No IQ FIR Coefficients");
AudioStream_F32::release(blockIn);
return;
}
if (fir_Out_Coeffs == NULL) {
if(errorPrintFM) Serial.println("FMDET-ERR: No Out FIR Coefficients");
AudioStream_F32::release(blockIn);
return;
}
// Try to get a block for the FM output
blockOut = AudioStream_F32::allocate_f32();
if (!blockOut){ // Didn't have any
if(errorPrintFM) Serial.println("FMDET-ERR: No Output Memory");
AudioStream_F32::release(blockIn);
return;
}
// Generate sine and cosine of center frequency and double-balance mix
// these with the input signal to produce an intermediate result
// saved as v_i[] and v_q[]
for (i=0; i < block_size; i++) {
phaseS += phaseIncrement;
if (phaseS > 512.0f)
phaseS -= 512.0f;
index_sine = (uint16_t) phaseS;
deltaPhase = phaseS -(float32_t) index_sine;
/* Read two nearest values of input value from the sin table */
a = sinTable512_f32[index_sine];
b = sinTable512_f32[index_sine+1];
// Linear interpolation and multiplying (DBMixer) with input
v_i[i] = blockIn->data[i] * (a + 0.001953125*(b-a)*deltaPhase);
/* Repeat for cosine by adding 90 degrees phase */
index_sine = (index_sine + 128) & 0x01ff;
/* Read two nearest values of input value from the sin table */
a = sinTable512_f32[index_sine];
b = sinTable512_f32[index_sine+1];
/* deltaPhase will be the same as used for sin */
v_q[i] = blockIn->data[i] * (a + 0.001953125*(b-a)*deltaPhase);
}
// Do I FIR and Q FIR. We can borrow blockIn and blockOut at this point
//void arm_fir_f32( const arm_fir_instance_f32* S, float32_t* pSrc, float32_t* pDst, uint32_t blockSize)
arm_fir_f32(&FMDet_I_inst, v_i, blockIn->data, (uint32_t)blockIn->length);
arm_fir_f32(&FMDet_Q_inst, v_q, blockOut->data, (uint32_t)blockOut->length);
// Do ATAN2, differentiation and de-emphasis in single loop
for(i=0; i<block_size; i++) { // y x
dtemp1 = mathDSP1.fastAtan2((float)blockOut->data[i], (float)blockIn->data[i]);
// Apply differentiator by subtracting last value of atan2
if(dtemp1>MF_PI_2 && diffLast<-MF_PI_2) // Probably a wrap around
dtemp2 = dtemp1 - diffLast - MF_TWOPI;
else if(dtemp1<-MF_PI_2 && diffLast >MF_PI_2) // Probably a reverse wrap around
dtemp2 = dtemp1 - diffLast + MF_TWOPI;
else
dtemp2 = dtemp1 - diffLast; // Differentiate
diffLast = dtemp1; // Ready for next time through loop
// Data point is now dtemp2. Apply single pole de-emphasis LPF, in place
dLast = Kdem * dtemp2 + OneMinusKdem * dLast;
blockIn->data[i] = dLast; // and save to an array
}
// Do output FIR filter. Data now in blockIn.
arm_fir_f32(&FMDet_Out_inst, blockIn->data, blockOut->data, (uint32_t)blockIn->length);
AudioStream_F32::release(blockIn);
// Transmit the data
AudioStream_F32::transmit(blockOut, 0);
AudioStream_F32::release(blockOut);
#if TEST_TIME_FM
t2 = tElapse;
if(iitt++ < 0) {Serial.print("At end of FM Det "); Serial.println (t2 - t1); }
t1 = tElapse;
#endif
}

@ -0,0 +1,277 @@
/*
* RadioFMDetector_F32
* 22 March 2020 Bob Larkin
* With much credit to:
* Chip Audette (OpenAudio) Feb 2017
* Building from AudioFilterFIR from Teensy Audio Library
* (AudioFilterFIR credited to Pete (El Supremo))
* and of course, to PJRC for the Teensy and Teensy Audio Library
*
* Copyright (c) 2020 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 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.
*/
/* This consists of a single input at some frequency, such as 10 to 20 kHz and
* an output, such as 0 to 5 kHz. The output level is linearly dependent on the
* frequency of the input sine wave frequency, i.e., an it is an FM detector.
* The input needs to be band limited below the lower frequency side of the
* input, typically 10 kHz. This is not part of this block.
*
* NOTE: Due to the sample frequencies we are working with, like 44.1 kHz, this
* detector cannot handle full FM broadcast bandwidths. It is suitable for
* NBFM as used in communications, marine radio, ham radio, etc.
*
* The output can be FIR filtered using default parameters,
* or using coefficients from an array. A separate single pole de-emphasis filer
* is included that again can be programmed.
*
* Internally, the detector uses a pair of mixers (multipliers) that generate the
* in-phase and quadrature inputs to a atan2 type of phase detector. These
* mixers have two output signals at the difference (desired) and sum (undesired)
* frequencies. The high frequency sum signal can be filtered (For a 15 kHz center,
* with an input band of 10 to 20 kHz the sum signal will be from 25 to 35 kHz that
* wraps around the 22 kHz half-sample point to produce 19 to 9 kHz. This needs to
* be removed before the atan2. A pair of FIR filters, using FIR_IQ_Coeffs
* are used. These are again programmable and default to a 29-tap LPF with
* a 5 kHz cutoff.
*
* Status: Tested static, tested with FM modulated Fluke 6061B.
* An input of about 60 microvolts to the SGTL5000 gave 12 dB SINAD.
* The output sounded good. Tested T3.6 and T4.0. No known bugs
*
* Output: Float, sensitivity is 2*pi*(f - fCenter)*sample_rate_Hz
* For 44117Hz samplerate, this is 0.000142421 per Hz
*
* Accuracy: The function used is precise. However, the approximations, such
* fastAtan2, slightly limit the accuracy. A 200 point sample of a
* 14 kHz input had an average error of 0.03 Hz
* and a standard deviation of 0.81 Hz.
* The largest errors in this sample were about +/- 1.7 Hz. This is
* with the default filters.
*
* Functions:
* frequency(float fCenter ) sets the center frequency in Hz, default 15000.
*
* filterOut(float *firCoeffs, uint nFIR, float Kdem) sets output filtering where:
* float32_t* firCoeffs is an array of coefficients
* uint nFIR is the number of coefficients
* float32_t Kdem is the de-emphasis frequency factor, where
* Kdem = 1/(0.5+(tau*fsample)) and tau is the de-emphasis
* time constant, typically 0.0005 second and fsample is
* the sample frequency, typically 44117.
*
* filterIQ(float *fir_IQ_Coeffs, uint nFIR_IQ) sets output filtering where:
* float32_t* fir_IQ_Coeffs is an array of coefficients
* uint nFIR_IQ is the number of coefficients, max 60
*
* setSampleRate_Hz(float32_t _sampleRate_Hz) allows dynamic changing of
* the sample rate (experimental as of May 2020).
*
* returnInitializeFMError() Returns the initialization errors.
* B0001 (value 1) is an error in the IQ FIR Coefficients or quantity.
* B0010 (value 2) is an error in the Output FIR Coefficients or quantity.
* B0100 (value 4) is an error in the de-emphasis constant
* B1000 (value 8) is center frequency above half-sample frequency.
* All for debug.
*
* showError(uint16_t e) Turns error printing in the update function on (e=1)
* or off (e=0). For debug only.
*
* Time: For T3.6, an update of a 128 sample block, 370 microseconds, or
* 2.9 microseconds per data point.
* For T4.0, 87 microseconds, or 0.68 microseconds per data point.
*
* Error checking: See functions setSampleRate_Hz() and returnInitializeFMError()
* above.
*/
#ifndef _radioFMDetector_f32_h
#define _radioFMDetector_f32_h
#include "mathDSP_F32.h"
#include "AudioStream_F32.h"
#include "arm_math.h"
#define MAX_FIR_IQ_COEFFS 100
#define MAX_FIR_OUT_COEFFS 120
#define TEST_TIME_FM 0
class RadioFMDetector_F32 : public AudioStream_F32 {
//GUI: inputs:1, outputs:1 //this line used for automatic generation of GUI node
//GUI: shortName: FMDetector
public:
// Default block size and sample rate:
RadioFMDetector_F32(void) : AudioStream_F32(1, inputQueueArray_f32) {
initializeFM();
}
// Option of AudioSettings_F32 change to block size and/or sample rate:
RadioFMDetector_F32(const AudioSettings_F32 &settings) : AudioStream_F32(1, inputQueueArray_f32) {
sampleRate_Hz = settings.sample_rate_Hz;
block_size = settings.audio_block_samples;
initializeFM();
}
// Provide for changing input center frequency, in Hz
void frequency(float32_t _fCenter) {
fCenter = _fCenter;
phaseIncrement = 512.0f * fCenter / sampleRate_Hz;
}
// Provide for user FIR for I and Q signals to user supplied array
void filterIQ(float32_t* _fir_IQ_Coeffs, int _nFIR_IQ) {
if( fir_IQ_Coeffs==NULL || nFIR_IQ<4 || nFIR_IQ>MAX_FIR_IQ_COEFFS ) {
initializeFMErrors |= 1;
return;
}
fir_IQ_Coeffs = _fir_IQ_Coeffs;
nFIR_IQ = _nFIR_IQ;
initializeFM();
}
// Provide for changing to user FIR for detector output, (and user de-emphasis)
void filterOut(float32_t *_fir_Out_Coeffs, int _nFIR_Out, float32_t _Kdem) {
if( _fir_Out_Coeffs==NULL || _nFIR_Out<4 || _nFIR_Out>MAX_FIR_OUT_COEFFS) {
initializeFMErrors |= 2;
return;
}
if( _Kdem<0.0001 || _Kdem>1.0 ) {
initializeFMErrors |= 4;
return;
}
fir_Out_Coeffs = _fir_Out_Coeffs;
nFIR_Out = _nFIR_Out;
Kdem = _Kdem;
OneMinusKdem = 1.0f - Kdem;
initializeFM();
}
void setSampleRate_Hz(float32_t _sampleRate_Hz) {
if (fCenter > _sampleRate_Hz/2.0f) { // Check freq range
initializeFMErrors |= 8;
return;
}
sampleRate_Hz = _sampleRate_Hz;
// update phase increment for new frequency
phaseIncrement = 512.0f * fCenter / sampleRate_Hz;
}
void showError(uint16_t e) {
errorPrintFM = e;
}
uint16_t returnInitializeFMError(void) {
return initializeFMErrors;
}
void update(void);
private:
// One input data pointer
audio_block_f32_t *inputQueueArray_f32[1];
float32_t fCenter = 15000.0f;
float32_t phaseS = 0.0f;
float32_t phaseS_C = 128.00f;
float32_t phaseIncrement = 512.0f*15000.0f/AUDIO_SAMPLE_RATE_EXACT;
float32_t sampleRate_Hz = AUDIO_SAMPLE_RATE_EXACT;
uint16_t block_size = AUDIO_BLOCK_SAMPLES;
// De-emphasis constant
float32_t Kdem = 0.045334f;
float32_t OneMinusKdem = 0.954666f;
// Save last data point of atan2 for differentiator
float32_t diffLast = 0.0f;
// Save last data point for next update of de-emphasis filter
float32_t dLast = 0.0f;
// Control error printing in update(), normally off
uint16_t errorPrintFM = 0;
// Monitor constructor errors
uint16_t initializeFMErrors = 0;
uint16_t nFIR_IQ = 29;
float32_t* fir_IQ_Coeffs = fir_IQ29;
uint16_t nFIR_Out = 39;
float32_t* fir_Out_Coeffs = fir_Out39;
#if TEST_TIME_FM
elapsedMicros tElapse;
int32_t iitt = 999000; // count up to a million during startup
#endif
// ARM CMSIS FIR filter instances and State vectors, sized for max, max
arm_fir_instance_f32 FMDet_I_inst;
float32_t State_I_F32[AUDIO_BLOCK_SAMPLES + MAX_FIR_IQ_COEFFS]; // 228
arm_fir_instance_f32 FMDet_Q_inst;
float32_t State_Q_F32[AUDIO_BLOCK_SAMPLES + MAX_FIR_IQ_COEFFS]; // 248
arm_fir_instance_f32 FMDet_Out_inst;
float32_t State_Out_F32[AUDIO_BLOCK_SAMPLES + MAX_FIR_OUT_COEFFS];
// Initialize the FM Detector, part of setting up and changing parameters
void initializeFM(void) {
if (fir_IQ_Coeffs && nFIR_IQ <= MAX_FIR_IQ_COEFFS) {
/* the instance setup call
* void arm_fir_init_f32(
* arm_fir_instance_f32* S, points to instance of floating-point FIR filter structure.
* uint16_t numTaps, Number of filter coefficients in the filter.
* float32_t* pCoeffs, points to the filter coefficients buffer.
* float32_t* pState, points to the state buffer.
* uint32_t blockSize) Number of samples that are processed per call.
*/
arm_fir_init_f32(&FMDet_I_inst, nFIR_IQ, (float32_t*)fir_IQ_Coeffs, &State_I_F32[0], (uint32_t)block_size);
arm_fir_init_f32(&FMDet_Q_inst, nFIR_IQ, (float32_t*)fir_IQ_Coeffs, &State_Q_F32[0], (uint32_t)block_size);
}
else initializeFMErrors |= B0001;
if (fir_Out_Coeffs && nFIR_Out <= MAX_FIR_OUT_COEFFS) {
arm_fir_init_f32(&FMDet_Out_inst, nFIR_Out, (float32_t*)fir_Out_Coeffs, &State_Out_F32[0], (uint32_t)block_size);
}
else initializeFMErrors |= B0010;
dLast = 0.0;
}
/* FIR filter designed with http://t-filter.appspot.com
* fs = 44100 Hz, < 5kHz ripple 0.29 dB, >9 kHz, -62 dB, 29 taps
*/
float32_t fir_IQ29[29] = {
-0.000970689f, -0.004690292f, -0.008256345f, -0.007565650f,
0.001524420f, 0.015435011f, 0.021920240f, 0.008211937f,
-0.024286413f, -0.052184700f, -0.040532507f, 0.031248107f,
0.146902412f, 0.255179564f, 0.299445269f, 0.255179564f,
0.146902412f, 0.031248107f, -0.040532507f, -0.052184700f,
-0.024286413f, 0.008211937f, 0.021920240f, 0.015435011f,
0.001524420f, -0.007565650f, -0.008256345f, -0.004690292f,
-0.000970689f};
/* FIR filter designed with http://t-filter.appspot.com
* fs = 44100 Hz, < 3kHz ripple 0.36 dB, >6 kHz, -60 dB, 39 taps
* Corrected to give DC gain = 1.00
*/
float32_t fir_Out39[39] = {
-0.0008908477f, -0.0008401274f, -0.0001837353f, 0.0017556005f,
0.0049353322f, 0.0084952916f, 0.0107668722f, 0.0097441685f,
0.0039877576f, -0.0063455016f, -0.0188069300f, -0.0287453055f,
-0.0303831521f, -0.0186809770f, 0.0085931270f, 0.0493875744f,
0.0971742012f, 0.1423015880f, 0.1745838382f, 0.1863024485f,
0.1745838382f, 0.1423015880f, 0.0971742012f, 0.0493875744f,
0.0085931270f, -0.0186809770f, -0.0303831521f, -0.0287453055f,
-0.0188069300f, -0.0063455016f, 0.0039877576f, 0.0097441685f,
0.0107668722f, 0.0084952916f, 0.0049353322f, 0.0017556005f,
-0.0001837353f, -0.0008401274f, -0.0008908477f };
};
#endif

@ -0,0 +1,114 @@
/* Test AudioAnalyzePhase_F32.cpp RSL 7 April 2020
* Generates 2 sine waves of different phase, but same frequency
* and measures the phase difference.
*/
//Include files
#include <AudioAnalyzePhase_F32.h>
//#include <AudioStream_F32.h>
//#include <AudioStream.h>
//#include <Arduino.h>
#include <Audio.h>
#include <OpenAudio_ArduinoLibrary.h>
//#include <math.h>
//Create audio objects
// Input object creates stream, even though not used. I16 object to allow T4.x.
AudioInputI2S audioInI2S1;
AudioConvert_I16toF32 cnvt1;
// And the objects for the phase measurement:
AudioSynthWaveformSine_F32 sine1;
AudioSynthWaveformSine_F32 sine2;
AudioAnalyzePhase_F32 phase1;
AudioRecordQueue_F32 queue1_F;
AudioConnection patchCord1(audioInI2S1, 0, cnvt1, 0);
AudioConnection_F32 patchCord3(sine1, 0, phase1, 0);
AudioConnection_F32 patchCord4(sine2, 0, phase1, 1);
AudioConnection_F32 patchCord5(phase1, 0, queue1_F, 0);
AudioControlSGTL5000 sgtl5000_1;
#define NBLOCKS 8
float dt1[128*NBLOCKS];
float *pq, *pd;
int k, i;
// ==================== SETUP() ============================================
void setup(void) {
//Start the USB serial link (to enable debugging)
Serial.begin(300); delay(500);
AudioMemory(2); // Allocate Int16 audio data blocks
AudioMemory_F32(15); // Allocate Float32 audio data blocks
sgtl5000_1.enable();
AudioNoInterrupts();
sine1.amplitude(1.0);
sine1.frequency(12345);
sine1.phase(0.0);
sine2.amplitude(1.0);
sine2.frequency(12345);
// The next call sets the phase difference
sine2.phase(60.0); // This phase reationship will measure +60 degrees
AudioInterrupts();
// The next argument can be set to 1 to show update() errors. But, it will show false
// errors before the audiostream is up and running.
phase1.showError(0); // For diagnostics
/* For variable pdConfig (default 0b1100):
* Bit 0: 0=No Limiter (default) 1=Use limiter
* Bit 2 and 1: 00=Use no acos linearizer 01=undefined
* 10=Fast, math-continuous acos() (default) 11=Accurate acos()
* Bit 3: 0=No scale of multiplier 1=scale to min-max (default)
* Bit 4: 0=Output in degrees 1=Output in radians (default)
*
* Uncomment one of the next 4 examples, or leave all 4 commented
* out and get the default settings for begin(), using about
* 123 microseconds for 128 block size.
* Times tu are time spent in update() on T3.6 for full 128 point block.
*/
//#1 - This uses min-max scaling, fast acos(), and FIR filtering
phase1.setAnalyzePhaseConfig(FIR_LP_FILTER, NULL, 53, 0b01100); // tu = 213 microseconds
//#2 - This uses min-max scaling and the Accurate acos()
// phase1.setAnalyzePhaseConfig(FIR_LP_FILTER, NULL, 53, 0b01110); // tu <= 531 microseconds
//#3 - This uses min-max scaling, IIR Filter and the no acos() linearization
// phase1.setAnalyzePhaseConfig(IIR_LP_FILTER, NULL, 53, 0b01000); // tu = 96 microseconds
//#4 - This uses no scaling (use two magnitude 1.0 sine waves),
// and the no acos() linearization. No LP filtering
// phase1.setAnalyzePhaseConfig(NO_LP_FILTER, NULL, 20, 0b10000); // tu = 28 uSec
i = 0; k=0;
queue1_F.begin();
Serial.println("Setup complete.");
};
void loop(void) {
// Collect 128xNBLOCKS samples and output to Serial
// This "if" will be active for i on (0, NBLOCKS-1)
if (queue1_F.available() >= 1 && i>=0 && i<NBLOCKS) // See if 128 words are available
{
pq = queue1_F.readBuffer();
pd = &dt1[128*i++];
for(k=0; k<128; k++)
{ if(i==2 && k==5) Serial.println(dt1[259]);
*pd++ = *pq++; // Save 128 words in dt1[]
}
queue1_F.freeBuffer();
// If 128*NBLOCKS words are saved, send them out Serial
if(i == NBLOCKS)
{
i = NBLOCKS + 1; // Should stop all this
queue1_F.end(); // No more data to queue1
Serial.println("128 x NBLOCKS Data Points:");
for (k=0; k<128*NBLOCKS; k++)
Serial.println (dt1[k],6);
Serial.println();
}
}
}

@ -0,0 +1,58 @@
/* AudioTestPeakRMS.ino Bob Larkin 2 May 2020
*
* Generates sine wave and measures RMS, Peak and Peak
* to Peak values.
* NOTE: This is for the floating point _F32 versions of
* AnalyzePeak and AnalyzeRMS *NOT* the Teensy Audio Library
* versions with 16-bit Fixed point data blocks.
*/
#include "Audio.h"
#include <OpenAudio_ArduinoLibrary.h>
#include "analyze_peak_f32.h"
#include "analyze_rms_f32.h"
AudioInputI2S i2s1;
AudioSynthWaveformSine_F32 sine1;
AudioAnalyzeRMS_F32 rms1;
AudioAnalyzePeak_F32 peak1;
AudioConnection_F32 patchCord1(sine1, 0, rms1, 0);
AudioConnection_F32 patchCord2(sine1, 0, peak1, 0);
uint16_t n = 0;
void setup(void) {
AudioMemory(5); //allocate Int16 audio data blocks
AudioMemory_F32(5); //allocate Float32 audio data blocks
Serial.begin(300); delay(1000);
// Default amlitude +/- 1.0
sine1.frequency(1000.0);
// Set next to 0 to suppress print errors in update()
rms1.showError(1);
peak1.showError(1);
}
void loop(void) {
if (n & 1) {
while(!rms1.available() ) ; //Wait
Serial.print("RMS value = ");
Serial.println(rms1.read(), 7 );
while(!peak1.available() ) ; //Wait
Serial.print("Peak value = ");
Serial.println(peak1.read(), 7 );
}
else {
while(!rms1.available() ) ; //Wait
Serial.print("RMS value = ");
Serial.println(rms1.read(), 7 );
while(!peak1.available() ) ; //Wait
Serial.print("Peak to peak value = ");
Serial.println(peak1.readPeakToPeak(), 7 );
}
n++;
// The RMS and Peak data collection runs during
// delay() because of hardware interrupts
delay(1000);
}

@ -0,0 +1,96 @@
/* AudioTestSinCos.ino Bob Larkin 19 April 2020
* Generates 1024 samples of Sin and Cos from the
* AudioSynthSinCos_F32. Samples sent to the USB
* Serial.print. Also, the sine and cosine signals are
* connected to the AudioAnalyzePhase_32 and a third
* Queue to Serial.print the, close to 90 degree,
* phase difference.
*/
#include "Audio.h"
#include <OpenAudio_ArduinoLibrary.h>
#include "DSP_TeensyAudio_F32.h"
#define NBLOCKS 8
// Necessary foraudio stream
AudioInputI2S i2s1;
AudioConvert_I16toF32 convert;
AudioConnection pcI16(i2s1, 0, convert, 0);
// And the experiment
AudioSynthSineCosine_F32 sincos1;
AudioRecordQueue_F32 queue1;
AudioRecordQueue_F32 queue2;
AudioRecordQueue_F32 queue3;
AudioAnalyzePhase_F32 phase1;
AudioConnection_F32 patchCord1(sincos1, 0, queue1, 0);
AudioConnection_F32 patchCord2(sincos1, 1, queue2, 0);
AudioConnection_F32 patchCord3(sincos1, 0, phase1, 0);
AudioConnection_F32 patchCord4(sincos1, 1, phase1, 1);
AudioConnection_F32 patchCord5(phase1, 0, queue3, 0);
float dt1[128*NBLOCKS]; // Place to save sin
float dt2[128*NBLOCKS]; // and cos
float dt3[128*NBLOCKS]; // and phase angle
float *pq1, *pd1, *pq2, *pd2, *pd3, *pq3;
int i, k;
void setup(void) {
AudioMemory(5); //allocate Int16 audio data blocks
AudioMemory_F32(20); //allocate Float32 audio data blocks
Serial.begin(300); delay(1000);
// simple() is not needed here, as it is default unless amplitude or
// phaseS_C_r is changed. But, it can be used to restore simple after
// involking changes. Here it is just a sample of usage.
sincos1.simple(true);
// Default amlitude +/- 1.0
sincos1.frequency(1212.345);
// If either or both of the following two are uncommented, the
// detailed, slower, sincos will be used:
// sincos1.amplitude(0.9);
// sincos1.phaseS_C_r(120.00*M_PI/180.0); // Test non-90 degree, like 120 deg
// Set next to 1 to print errors in update(), or 0 for no print. For debug only
phase1.showError(0);
queue1.begin();
queue2.begin();
queue3.begin();
i = 0; k=0;
}
void loop(void) {
// Collect 128xNBLOCKS samples and output to Serial
// This "if" will be active for i on (0, NBLOCKS-1)
if (queue1.available() >= 1 && queue2.available()
&& queue3.available() && i>=0 && i<NBLOCKS) {
pq1 = queue1.readBuffer();
pd1 = &dt1[128*i];
pq2 = queue2.readBuffer();
pd2 = &dt2[128*i];
pq3 = queue3.readBuffer();
pd3 = &dt3[128*i++];
for(k=0; k<128; k++) {
*pd1++ = *pq1++; // Save 128 words in dt1[]
*pd2++ = *pq2++;
*pd3++ = *pq3++;
}
queue1.freeBuffer();
queue2.freeBuffer();
queue3.freeBuffer();
}
if(i == NBLOCKS) { // Wait for all NBLOCKS
i = NBLOCKS + 1; // Should stop data collection
queue1.end(); // No more data to queue1
queue2.end(); // No more data to queue2
Serial.println("1024 Sine, Cosine, Phase Data Points:");
for (k=0; k<128*NBLOCKS; k++) {
Serial.print (dt1[k],7);
Serial.print (",");
Serial.print (dt2[k],7);
Serial.print (",");
Serial.println(dt3[k],7);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

@ -0,0 +1,107 @@
/* ReceiverFM.ino Bob Larkin 26 April 2020
* This is a simple test of introducing a sine wave to the
* FM Detector and taking 512 samples of the output. It is
* a static test with a fixed frequency for test and so
* the output DC value and noise can be tested. Note that the 512
* samples include the startup transient, so the first 300,
* or so, points should be ignored in seeing the DC value.
*
* Change the value of sine1.frequency to see the DC output change.
* See FMReceiver2.ino for testing with real AC modulation.
*/
#include "Audio.h"
#include <OpenAudio_ArduinoLibrary.h>
// Uncomment the lines "SINE" for internally generated sine wave.
// Uncomment the lines "ADC" to use the SGTL5000 Teensy audio adaptor
// AudioInputI2S_F32 i2sIn; // ADC
AudioSynthWaveformSine_F32 sine1; // SINE
RadioFMDetector_F32 fmDet1;
AudioRecordQueue_F32 queue1;
AudioOutputI2S_F32 i2sOut; // Leave in for timing
// AudioControlSGTL5000 sgtl5000_1; // ADC
// AudioConnection_F32 connect0(i2sIn, 0, fmDet1, 0); // ADC
AudioConnection_F32 connect0(sine1, 0, fmDet1, 0); // SINE
// AudioConnection_F32 connect1(sine1, 0, queue1, 0);
AudioConnection_F32 connect3(fmDet1, 0, i2sOut, 0);
AudioConnection_F32 connect5(fmDet1, 0, queue1, 0);
float dt1[512]; // Place to save output
float *pq1, *pd1;
uint16_t k;
int i;
void setup(void) {
AudioMemory(5);
AudioMemory_F32(5);
Serial.begin(300); delay(1000); // Any rate is OK
Serial.println("Serial Started");
// sgtl5000_1.enable(); // ADC
// sgtl5000_1.inputSelect(AUDIO_INPUT_LINEIN); //ADC
sine1.frequency(14000.0); // SINE
// The FM detector has error checking during object construction
// when Serial.print is not available. See RadioFMDetector_F32.h:
Serial.print("FM Initialization errors: ");
Serial.println( fmDet1.returnInitializeFMError() );
// The following enables error checking inside of the "ubdate()"
// Output goes to the Serial (USB) Monitor. Normally, this is quiet.
fmDet1.showError(1);
queue1.begin();
i = 0; k=0;
}
void loop(void) {
// Collect 512 samples and output to Serial
// This "if" will be active for i = 0,1,2,3
if (queue1.available() >= 1) {
if( i>=0 && i<4) {
pq1 = queue1.readBuffer();
pd1 = &dt1[i*128];
for(k=0; k<128; k++) {
*pd1++ = *pq1++;
}
queue1.freeBuffer();
if(i++==3) {
i=4; // Only collect 4 blocks
queue1.end(); // No more data to queue1
}
}
else {
queue1.freeBuffer();
}
}
// We have 512 data samples. Serial.print them
if(i == 4) {
Serial.println("512 Time in seconds and FM Output samples:");
for (k=0; k<512; k++) {
Serial.print (0.000022667*(float32_t)k, 6); Serial.print (",");
Serial.println (dt1[k],7);
}
i = 5;
}
if(i==5) {
i = 6;
Serial.print("CPU: Percent Usage, Max: ");
Serial.print(AudioProcessorUsage());
Serial.print(", ");
Serial.println(AudioProcessorUsageMax());
Serial.print("Int16 Memory Usage, Max: ");
Serial.print(AudioMemoryUsage());
Serial.print(", ");
Serial.println(AudioMemoryUsageMax());
Serial.print("Float Memory Usage, Max: ");
Serial.print(AudioMemoryUsage_F32());
Serial.print(", ");
Serial.println(AudioMemoryUsageMax_F32());
Serial.println();
}
}

@ -0,0 +1,123 @@
// Following is 121 term Hilbert FIR filter
float32_t hilbert121A[121] = {
0.000000000000000000,
0.000773378567767513,
0.000000000000000000,
0.001046207887980644,
0.000000000000000000,
0.001368896533613985,
0.000000000000000000,
0.001746769975247667,
0.000000000000000000,
0.002185555845922462,
0.000000000000000000,
0.002691457154069645,
0.000000000000000000,
0.003271251311125927,
0.000000000000000000,
0.003932423233774751,
0.000000000000000000,
0.004683343721596901,
0.000000000000000000,
0.005533508538632429,
0.000000000000000000,
0.006493859804516438,
0.000000000000000000,
0.007577220484233372,
0.000000000000000000,
0.008798886675905997,
0.000000000000000000,
0.010177443901536392,
0.000000000000000000,
0.011735907609641917,
0.000000000000000000,
0.013503343224246872,
0.000000000000000000,
0.015517212970554440,
0.000000000000000000,
0.017826854793349920,
0.000000000000000000,
0.020498780519188083,
0.000000000000000000,
0.023625003856774591,
0.000000000000000000,
0.027336628208641155,
0.000000000000000000,
0.031827023036304102,
0.000000000000000000,
0.037393534868609392,
0.000000000000000000,
0.044517689704988733,
0.000000000000000000,
0.054032871748808158,
0.000000000000000000,
0.067515548043274365,
0.000000000000000000,
0.088347125250410385,
0.000000000000000000,
0.125324201622410869,
0.000000000000000000,
0.210709715079613419,
0.000000000000000000,
0.634897508268964295,
0.000000000000000000,
-0.634897508268964295,
0.000000000000000000,
-0.210709715079613419,
0.000000000000000000,
-0.125324201622410869,
0.000000000000000000,
-0.088347125250410385,
0.000000000000000000,
-0.067515548043274365,
0.000000000000000000,
-0.054032871748808158,
0.000000000000000000,
-0.044517689704988733,
0.000000000000000000,
-0.037393534868609392,
0.000000000000000000,
-0.031827023036304102,
0.000000000000000000,
-0.027336628208641155,
0.000000000000000000,
-0.023625003856774591,
0.000000000000000000,
-0.020498780519188083,
0.000000000000000000,
-0.017826854793349920,
0.000000000000000000,
-0.015517212970554440,
0.000000000000000000,
-0.013503343224246872,
0.000000000000000000,
-0.011735907609641917,
0.000000000000000000,
-0.010177443901536392,
0.000000000000000000,
-0.008798886675905997,
0.000000000000000000,
-0.007577220484233372,
0.000000000000000000,
-0.006493859804516438,
0.000000000000000000,
-0.005533508538632429,
0.000000000000000000,
-0.004683343721596901,
0.000000000000000000,
-0.003932423233774751,
0.000000000000000000,
-0.003271251311125927,
0.000000000000000000,
-0.002691457154069645,
0.000000000000000000,
-0.002185555845922462,
0.000000000000000000,
-0.001746769975247667,
0.000000000000000000,
-0.001368896533613985,
0.000000000000000000,
-0.001046207887980644,
0.000000000000000000,
-0.000773378567767513,
0.000000000000000000};

@ -0,0 +1,21 @@
// Following is a 19 term Hilbert FIR filter
float hilbert19A[]={
0.003006666677728199,
0.000000000000000000,
0.017439390086760032,
0.000000000000000000,
0.058235158985840196,
0.000000000000000000,
0.161905323559397157,
0.000000000000000000,
0.617838316334015092,
0.000000000000000000,
-0.617838316334015092,
0.000000000000000000,
-0.161905323559397157,
0.000000000000000000,
-0.058235158985840196,
0.000000000000000000,
-0.017439390086760032,
0.000000000000000000,
-0.003006666677728199};

@ -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,90 @@
/* ReceiverPart1.ino Bob Larkin 20 April 2020
* This is "Hello World" of Radio design. It can
* receive a Lower Sideband signal, but that is it.
* To test the floating point radio receiver blocks:
* AudioFilter90Deg_F32
* RadioIQMixer_F32
*/
#include "Audio.h"
#include <OpenAudio_ArduinoLibrary.h>
#include "DSP_TeensyAudio_F32.h"
AudioInputI2S i2s1;
AudioSynthWaveformSine_F32 sine1; // Test signal
RadioIQMixer_F32 iqmixer1;
AudioFilter90Deg_F32 hilbert1;
// NOTE: Use AudioMixer4_F32 *from Tympan*
AudioMixer4_F32 sum1;
AudioRecordQueue_F32 queue1; // The LSB output
AudioRecordQueue_F32 queue2; // The test sine wave
AudioConnection_F32 patchCord0(sine1, 0, iqmixer1, 0);
AudioConnection_F32 patchCord2(sine1, 0, iqmixer1, 1);
AudioConnection_F32 patchCord4(iqmixer1, 0, hilbert1, 0);
AudioConnection_F32 patchCord5(iqmixer1, 1, hilbert1, 1);
AudioConnection_F32 patchCord6(hilbert1, 0, sum1, 0);
AudioConnection_F32 patchCord7(hilbert1, 1, sum1, 1);
AudioConnection_F32 patchCord8(sum1, 0, queue1, 0);
AudioConnection_F32 patchCord9(sine1, 0, queue2, 0);
// Pick one of these
#include "hilbert19A.h"
#include "hilbert121A.h"
#include "hilbert251A.h"
float dt1[128];
float dt2[128];
float *pq1, *pd1, *pq2, *pd2;
int i, k;
void setup(void) {
AudioMemory(5);
AudioMemory_F32(10);
Serial.begin(300); delay(1000);
sine1.frequency(14000.0);
iqmixer1.frequency(16000.0);
// Pick one of the three, the 251 does not have
// enough samples here to show the full build-up.
// hilbert1.begin(hilbert19A, 19);
// hilbert1.begin(hilbert121A, 121);
hilbert1.begin(hilbert121A, 121);
sum1.gain(0, 1.0); // Leave at 1.0
sum1.gain(1, -1.0); // -1 for LSB out
iqmixer1.showError(1); // Prints update() errors
hilbert1.showError(1);
queue1.begin();
queue2.begin();
i = 0; k=0;
}
void loop(void) {
// Collect 128 samples and output to Serial
// This "if" will be active for i == 0
if (queue1.available() >= 1 && i==0) {
pq1 = queue1.readBuffer();
pd1 = &dt1[0];
pq2 = queue2.readBuffer();
pd2 = &dt2[0];
for(k=0; k<128; k++) {
*pd1++ = *pq1++;
*pd2++ = *pq2++;
}
i=1; // Only collect 1 block
Serial.print("Maximum F32 memory usage at loop:");
Serial.println( AudioMemoryUsageMax_F32() );
queue1.freeBuffer();
queue2.freeBuffer();
queue1.end(); // No more data to queue1
queue2.end(); // No more data to queue2
}
if(i == 1) {
Serial.println("128 Samples: Time (sec), LSB Out, Sine Wave In");
for (k=0; k<128; k++) {
Serial.print (0.000022667*(float32_t)k, 6); Serial.print (",");
Serial.print (dt1[k],7); Serial.print (",");
Serial.println (dt2[k],7);
}
i = 2;
}
}

@ -0,0 +1,123 @@
// Following is 121 term Hilbert FIR filter
float32_t hilbert121A[121] = {
0.000000000000000000,
0.000773378567767513,
0.000000000000000000,
0.001046207887980644,
0.000000000000000000,
0.001368896533613985,
0.000000000000000000,
0.001746769975247667,
0.000000000000000000,
0.002185555845922462,
0.000000000000000000,
0.002691457154069645,
0.000000000000000000,
0.003271251311125927,
0.000000000000000000,
0.003932423233774751,
0.000000000000000000,
0.004683343721596901,
0.000000000000000000,
0.005533508538632429,
0.000000000000000000,
0.006493859804516438,
0.000000000000000000,
0.007577220484233372,
0.000000000000000000,
0.008798886675905997,
0.000000000000000000,
0.010177443901536392,
0.000000000000000000,
0.011735907609641917,
0.000000000000000000,
0.013503343224246872,
0.000000000000000000,
0.015517212970554440,
0.000000000000000000,
0.017826854793349920,
0.000000000000000000,
0.020498780519188083,
0.000000000000000000,
0.023625003856774591,
0.000000000000000000,
0.027336628208641155,
0.000000000000000000,
0.031827023036304102,
0.000000000000000000,
0.037393534868609392,
0.000000000000000000,
0.044517689704988733,
0.000000000000000000,
0.054032871748808158,
0.000000000000000000,
0.067515548043274365,
0.000000000000000000,
0.088347125250410385,
0.000000000000000000,
0.125324201622410869,
0.000000000000000000,
0.210709715079613419,
0.000000000000000000,
0.634897508268964295,
0.000000000000000000,
-0.634897508268964295,
0.000000000000000000,
-0.210709715079613419,
0.000000000000000000,
-0.125324201622410869,
0.000000000000000000,
-0.088347125250410385,
0.000000000000000000,
-0.067515548043274365,
0.000000000000000000,
-0.054032871748808158,
0.000000000000000000,
-0.044517689704988733,
0.000000000000000000,
-0.037393534868609392,
0.000000000000000000,
-0.031827023036304102,
0.000000000000000000,
-0.027336628208641155,
0.000000000000000000,
-0.023625003856774591,
0.000000000000000000,
-0.020498780519188083,
0.000000000000000000,
-0.017826854793349920,
0.000000000000000000,
-0.015517212970554440,
0.000000000000000000,
-0.013503343224246872,
0.000000000000000000,
-0.011735907609641917,
0.000000000000000000,
-0.010177443901536392,
0.000000000000000000,
-0.008798886675905997,
0.000000000000000000,
-0.007577220484233372,
0.000000000000000000,
-0.006493859804516438,
0.000000000000000000,
-0.005533508538632429,
0.000000000000000000,
-0.004683343721596901,
0.000000000000000000,
-0.003932423233774751,
0.000000000000000000,
-0.003271251311125927,
0.000000000000000000,
-0.002691457154069645,
0.000000000000000000,
-0.002185555845922462,
0.000000000000000000,
-0.001746769975247667,
0.000000000000000000,
-0.001368896533613985,
0.000000000000000000,
-0.001046207887980644,
0.000000000000000000,
-0.000773378567767513,
0.000000000000000000};

@ -0,0 +1,21 @@
// Following is a 19 term Hilbert FIR filter
float hilbert19A[]={
0.003006666677728199,
0.000000000000000000,
0.017439390086760032,
0.000000000000000000,
0.058235158985840196,
0.000000000000000000,
0.161905323559397157,
0.000000000000000000,
0.617838316334015092,
0.000000000000000000,
-0.617838316334015092,
0.000000000000000000,
-0.161905323559397157,
0.000000000000000000,
-0.058235158985840196,
0.000000000000000000,
-0.017439390086760032,
0.000000000000000000,
-0.003006666677728199};

@ -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,177 @@
/* ReceiverPart2.ino Bob Larkin 29 April 2020
* This is a simple SP radio design. It can receive 2 modes,
* Single Sideband (SSB) and Narrow Band FM (NBFM). SSB
* breaks into Lower Sidband (LSB) and Upper Sideband (USB).
* It gets even better in that AM can be received on either
* LSB or USB by tuning to the AM carrier frequency.
*
* The signal path is switched between SSB and FM by a Chip
* Audette AudioSwitch4_F32 switch block. This keeps resources
* from being used in blocks that are not needed.
*
* We are restricted to receiving in the 8 to 22 kHz range,
* so to use this on the air would require frequency conversion
* hardware.
*
* Details on the blocks are part of the include .h files for the
* blocks and in the INO files
* TestFM.ino
* ReceiverPart1.ino
*
* Input and Output is via the Teensy Audio Adaptor that uses
* a SGTL5000 CODEC,
*
* See the individual .h files for each block for more details.
*
* Measured peak-to-peak levels, using AudioAnalyzePeak_F32,
* all done at overload point for ADC:
* At ADC (i2sIn), max, 2.0
* At IQ Mixer out, max, 2.0
* At 90 deg Phase out, 2.0
* At Sum out 2.0
* At LPF FIR Out 2.0
*
* FM Det gives 0.50 out for about 5.6 kHz p-p deviation
*
* T3.6 Processor load, measured: 16% for NBFM
* 30% for LSB or USB 29 tap LPF
* T4.0 Processor load, measured: 4.3% for NBFM
* 6.5% for LSB or USB 29 tap LPF
*/
#include "Audio.h"
#include <OpenAudio_ArduinoLibrary.h>
#include "DSP_TeensyAudio_F32.h"
// ********* Mini Control Panel *********
// Set mode and gain here and re-compile
// Here is the mode switch
#define LSB 1
#define USB 2
#define NBFM 3
uint16_t mode = NBFM; // <--Select mode
int gainControlDB = 0; // Set SSB gain in dB. 0 dB is a gain of 1.0
// *****************************************
// To work with T4.0 the I2S routine outputs 16-bit integer (I16). Then
// use Audette I16 to F32 convert. Same below for output, in reverse.
AudioInputI2S i2sIn;
AudioConvert_I16toF32 cnvrt1;
AudioSwitch4_F32 switch1; // Select SSB or FM
RadioIQMixer_F32 iqmixer1;
AudioFilter90Deg_F32 hilbert1;
AudioMixer4_F32 sum1; // Summing node for the SSB receiver
AudioFilterFIR_F32 fir1; // Low Pass Filter to frequency limit the SSB
RadioFMDetector_F32 fmdet1; // NBFM from 10 to 20 kHz
AudioMixer4_F32 sum2; // SSB and NBFM rejoin here
AudioConvert_F32toI16 cnvrt2; // Left
AudioConvert_F32toI16 cnvrt3; // Right
AudioOutputI2S i2sOut;
AudioAnalyzePeak_F32 peak1;
AudioControlSGTL5000 sgtl5000_1;
AudioConnection conI16_1(i2sIn, 0, cnvrt1, 0); // ADC
AudioConnection_F32 connect0(cnvrt1, 0, switch1, 0); // Analog to Digital
AudioConnection_F32 connect1(switch1, 0, iqmixer1, 0); // SSB Input
AudioConnection_F32 connect2(switch1, 0, iqmixer1, 1); // SSB Input
AudioConnection_F32 connect3(switch1, 1, fmdet1, 0); // FM input
AudioConnection_F32 connect4(iqmixer1, 0, hilbert1, 0); // Broadband 90 deg phase
AudioConnection_F32 connect5(iqmixer1, 1, hilbert1, 1);
AudioConnection_F32 connect6(hilbert1, 0, sum1, 0); // Sideband select
AudioConnection_F32 connect7(hilbert1, 1, sum1, 1);
AudioConnection_F32 connect8(sum1, 0, fir1, 0); // Limit audio BW
AudioConnection_F32 connect9(fir1, 0, sum2, 0); // Output of SSB
AudioConnection_F32 connectA(fmdet1, 0, sum2, 1); // Output of FM
AudioConnection_F32 connectC(sum2, 0, cnvrt2, 0); // Out to the CODEC left
AudioConnection_F32 connectD(sum2, 0, cnvrt3, 0); // and right
AudioConnection_F32 connectE(sum2, 0, peak1, 0);
AudioConnection conI16_2(cnvrt2, 0, i2sOut, 0); // DAC
AudioConnection conI16_3(cnvrt3, 0, i2sOut, 1); // DAC
// Filter for AudioFilter90Deg_F32 hilbert1
#include "hilbert251A.h"
/* FIR filter designed with http://t-filter.appspot.com
* fs = 44100 Hz, < 5kHz ripple 0.29 dB, >9 kHz, -62 dB, 29 taps
*/
float32_t fir_IQ29[29] = {
-0.000970689f, -0.004690292f, -0.008256345f, -0.007565650f,
0.001524420f, 0.015435011f, 0.021920240f, 0.008211937f,
-0.024286413f, -0.052184700f, -0.040532507f, 0.031248107f,
0.146902412f, 0.255179564f, 0.299445269f, 0.255179564f,
0.146902412f, 0.031248107f, -0.040532507f, -0.052184700f,
-0.024286413f, 0.008211937f, 0.021920240f, 0.015435011f,
0.001524420f, -0.007565650f, -0.008256345f, -0.004690292f,
-0.000970689f};
void setup(void) {
float32_t vGain;
AudioMemory(5);
AudioMemory_F32(5);
Serial.begin(300); delay(1000);
// Enable the audio shield, select input, and enable output
sgtl5000_1.enable(); // ADC
sgtl5000_1.inputSelect(AUDIO_INPUT_LINEIN);
// The switch is single pole 4 position, numbered (0, 3) 0=SSB, 1 = FM
if(mode==LSB || mode==USB){ switch1.setChannel(0); Serial.println("SSB"); }
else if(mode==NBFM) { switch1.setChannel(1); Serial.println("NBFM"); }
iqmixer1.frequency(15000.0); // LO Frequency, typically 10 to 15 kHz
hilbert1.begin(hilbert251A, 251); // Set the Hilbert transform FIR filter
sum1.gain(0, 1.0f); // Leave set at 1.0
if(mode==LSB) sum1.gain(1, -1.0f); // -1 for LSB out
else if(mode==USB) sum1.gain(1, 1.0f); // +1 for USB and for NBFM, we don't care
// The FM detector has error checking during object construction
// when Serial.print is not available. See RadioFMDetector_F32.h:
Serial.print("FM Initialization errors: ");
Serial.println( fmdet1.returnInitializeFMError() );
// See RadioFMDetector_F32.h for information on functions for modifying the
// FM Detector. Default values are used here, starting with a 15 kHz center frequency.
fir1.begin(fir_IQ29, 29); // 5 kHz bandwidth set for radio in SSB
// The gainControlDB goes in 1 dB steps. Convert here to a voltage ratio
vGain = powf(10.0f, ((float32_t)gainControlDB)/20.0 );
// And apply that ratio to the output summing block. Gain here for SSB only
sum2.gain(0, vGain); Serial.print("vGain = "); Serial.println(vGain, 4);
sum2.gain(1, 1.0f); // FM gain
// The following enable error checking inside of the blocks indicated.
// Output goes to the Serial (USB) Monitor. Use for debug.
//hilbert1.showError(1); // Should show input error when in FM
//fmdet1.showError(1); // Should show input error when in LSB or USB
}
void loop(void) {
// Here is where the adjustment of the volume control could go.
// And anything else that needs regular attention, other
// than the audio stream.
if (peak1.available() ) {Serial.print("P-P ="); Serial.println(peak1.readPeakToPeak(), 6);}
else Serial.println("Peak-Peak not available");
Serial.print("CPU: Percent Usage, Max: ");
Serial.print(AudioProcessorUsage());
Serial.print(", ");
Serial.print(AudioProcessorUsageMax());
Serial.print(" ");
Serial.print("Int16 Memory: ");
Serial.print(AudioMemoryUsage());
Serial.print(", ");
Serial.print(AudioMemoryUsageMax());
Serial.print(" ");
Serial.print("Float Memory: ");
Serial.print(AudioMemoryUsage_F32());
Serial.print(", ");
Serial.println(AudioMemoryUsageMax_F32());
Serial.println();
delay(1000);
}

@ -0,0 +1,123 @@
// Following is 121 term Hilbert FIR filter
float32_t hilbert121A[121] = {
0.000000000000000000,
0.000773378567767513,
0.000000000000000000,
0.001046207887980644,
0.000000000000000000,
0.001368896533613985,
0.000000000000000000,
0.001746769975247667,
0.000000000000000000,
0.002185555845922462,
0.000000000000000000,
0.002691457154069645,
0.000000000000000000,
0.003271251311125927,
0.000000000000000000,
0.003932423233774751,
0.000000000000000000,
0.004683343721596901,
0.000000000000000000,
0.005533508538632429,
0.000000000000000000,
0.006493859804516438,
0.000000000000000000,
0.007577220484233372,
0.000000000000000000,
0.008798886675905997,
0.000000000000000000,
0.010177443901536392,
0.000000000000000000,
0.011735907609641917,
0.000000000000000000,
0.013503343224246872,
0.000000000000000000,
0.015517212970554440,
0.000000000000000000,
0.017826854793349920,
0.000000000000000000,
0.020498780519188083,
0.000000000000000000,
0.023625003856774591,
0.000000000000000000,
0.027336628208641155,
0.000000000000000000,
0.031827023036304102,
0.000000000000000000,
0.037393534868609392,
0.000000000000000000,
0.044517689704988733,
0.000000000000000000,
0.054032871748808158,
0.000000000000000000,
0.067515548043274365,
0.000000000000000000,
0.088347125250410385,
0.000000000000000000,
0.125324201622410869,
0.000000000000000000,
0.210709715079613419,
0.000000000000000000,
0.634897508268964295,
0.000000000000000000,
-0.634897508268964295,
0.000000000000000000,
-0.210709715079613419,
0.000000000000000000,
-0.125324201622410869,
0.000000000000000000,
-0.088347125250410385,
0.000000000000000000,
-0.067515548043274365,
0.000000000000000000,
-0.054032871748808158,
0.000000000000000000,
-0.044517689704988733,
0.000000000000000000,
-0.037393534868609392,
0.000000000000000000,
-0.031827023036304102,
0.000000000000000000,
-0.027336628208641155,
0.000000000000000000,
-0.023625003856774591,
0.000000000000000000,
-0.020498780519188083,
0.000000000000000000,
-0.017826854793349920,
0.000000000000000000,
-0.015517212970554440,
0.000000000000000000,
-0.013503343224246872,
0.000000000000000000,
-0.011735907609641917,
0.000000000000000000,
-0.010177443901536392,
0.000000000000000000,
-0.008798886675905997,
0.000000000000000000,
-0.007577220484233372,
0.000000000000000000,
-0.006493859804516438,
0.000000000000000000,
-0.005533508538632429,
0.000000000000000000,
-0.004683343721596901,
0.000000000000000000,
-0.003932423233774751,
0.000000000000000000,
-0.003271251311125927,
0.000000000000000000,
-0.002691457154069645,
0.000000000000000000,
-0.002185555845922462,
0.000000000000000000,
-0.001746769975247667,
0.000000000000000000,
-0.001368896533613985,
0.000000000000000000,
-0.001046207887980644,
0.000000000000000000,
-0.000773378567767513,
0.000000000000000000};

@ -0,0 +1,21 @@
// Following is a 19 term Hilbert FIR filter
float hilbert19A[]={
0.003006666677728199,
0.000000000000000000,
0.017439390086760032,
0.000000000000000000,
0.058235158985840196,
0.000000000000000000,
0.161905323559397157,
0.000000000000000000,
0.617838316334015092,
0.000000000000000000,
-0.617838316334015092,
0.000000000000000000,
-0.161905323559397157,
0.000000000000000000,
-0.058235158985840196,
0.000000000000000000,
-0.017439390086760032,
0.000000000000000000,
-0.003006666677728199};

@ -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,81 @@
/* TestEqualizer1.ino Bob Larkin 10 May 2020
* This is a test of the Filter Equalizer for Teensy Audio.
* This version is for the Chip Audette _F32 Library.
*/
#include "Audio.h"
#include "OpenAudio_ArduinoLibrary.h"
#include "DSP_TeensyAudio_F32.h"
AudioInputI2S i2s1; // Start interrupts
AudioSynthWaveformSine_F32 sine1; // Test signal
AudioFilterEqualizer_F32 equalize1;
AudioRecordQueue_F32 queue1; // The LSB output
AudioConnection_F32 patchCord1(sine1, 0, equalize1, 0);
AudioConnection_F32 patchCord2(equalize1, 0, queue1, 0);
//nBands = 10 This octave band equalizer is set strangely to demonstrate the Equalizer
float fBand[] = { 40.0, 80.0, 160.0, 320.0, 640.0, 1280.0, 2560.0, 5120.0, 10240.0, 22058.5};
float dbBand[] = {10.0, 2.0, -2.0, -5.0, -2.0, -4.0, -20.0, 6.0, 10.0, -100};
float equalizeCoeffs[249];
float dt1[128];
float *pq1, *pd1;
int i, k;
float32_t dBResponse[500]; // Show lots of detail
void setup(void) {
AudioMemory(5);
AudioMemory_F32(10);
Serial.begin(300); delay(1000);
Serial.println("*** Test Audio Equalizer ***");
sine1.frequency(1000.0f);
// Initialize the equalizer with 10 bands, 199 FIR coefficients and -65 dB sidelobes
uint16_t eq = equalize1.equalizerNew(10, &fBand[0], &dbBand[0], 199, &equalizeCoeffs[0], 65);
if (eq == ERR_EQ_BANDS)
Serial.println("Equalizer failed: Invalid number of frequency bands.");
else if (eq == ERR_EQ_SIDELOBES)
Serial.println("Equalizer failed: Invalid sidelobe specification.");
else if (eq == ERR_EQ_NFIR)
Serial.println("Equalizer failed: Invalid number of FIR coefficients.");
else
Serial.println("Equalizer initialized successfully.");
// Get frequency response in dB for 500 points, uniformly spaced from 0 to 21058 Hz
equalize1.getResponse(500, dBResponse);
Serial.println("Freq Hz, Response dB");
for(int m=0; m<500; m++) { // Print the response in Hz, dB, suitable for a spread sheet
Serial.print((float32_t)m * 22058.5f / 500.0f);
Serial.print(",");
Serial.println(dBResponse[m]);
}
i = -10; k=0;
}
void loop(void) {
if(i<0) i++; // Get past startup
if(i==0) queue1.begin();
// Collect 128 samples and output to Serial
// This "if" will be active for i == 0
if (queue1.available() >= 1 && i >= 0) {
pq1 = queue1.readBuffer();
pd1 = &dt1[0];
for(k=0; k<128; k++) {
*pd1++ = *pq1++;
}
i=1; // Only collect 1 block
Serial.print("Maximum F32 memory usage at loop:");
Serial.println( AudioMemoryUsageMax_F32() );
queue1.freeBuffer();
queue1.end(); // No more data to queue1
}
if(i == 1) {
// Uncomment the next 3 lines to printout a sample of the sine wave.
/*Serial.println("128 Samples: ");
for (k=0; k<128; k++) {
Serial.println (dt1[k],7); */
}
i = 2;
delay(2);
}

@ -0,0 +1,123 @@
// Following is 121 term Hilbert FIR filter
float32_t hilbert121A[121] = {
0.000000000000000000,
0.000773378567767513,
0.000000000000000000,
0.001046207887980644,
0.000000000000000000,
0.001368896533613985,
0.000000000000000000,
0.001746769975247667,
0.000000000000000000,
0.002185555845922462,
0.000000000000000000,
0.002691457154069645,
0.000000000000000000,
0.003271251311125927,
0.000000000000000000,
0.003932423233774751,
0.000000000000000000,
0.004683343721596901,
0.000000000000000000,
0.005533508538632429,
0.000000000000000000,
0.006493859804516438,
0.000000000000000000,
0.007577220484233372,
0.000000000000000000,
0.008798886675905997,
0.000000000000000000,
0.010177443901536392,
0.000000000000000000,
0.011735907609641917,
0.000000000000000000,
0.013503343224246872,
0.000000000000000000,
0.015517212970554440,
0.000000000000000000,
0.017826854793349920,
0.000000000000000000,
0.020498780519188083,
0.000000000000000000,
0.023625003856774591,
0.000000000000000000,
0.027336628208641155,
0.000000000000000000,
0.031827023036304102,
0.000000000000000000,
0.037393534868609392,
0.000000000000000000,
0.044517689704988733,
0.000000000000000000,
0.054032871748808158,
0.000000000000000000,
0.067515548043274365,
0.000000000000000000,
0.088347125250410385,
0.000000000000000000,
0.125324201622410869,
0.000000000000000000,
0.210709715079613419,
0.000000000000000000,
0.634897508268964295,
0.000000000000000000,
-0.634897508268964295,
0.000000000000000000,
-0.210709715079613419,
0.000000000000000000,
-0.125324201622410869,
0.000000000000000000,
-0.088347125250410385,
0.000000000000000000,
-0.067515548043274365,
0.000000000000000000,
-0.054032871748808158,
0.000000000000000000,
-0.044517689704988733,
0.000000000000000000,
-0.037393534868609392,
0.000000000000000000,
-0.031827023036304102,
0.000000000000000000,
-0.027336628208641155,
0.000000000000000000,
-0.023625003856774591,
0.000000000000000000,
-0.020498780519188083,
0.000000000000000000,
-0.017826854793349920,
0.000000000000000000,
-0.015517212970554440,
0.000000000000000000,
-0.013503343224246872,
0.000000000000000000,
-0.011735907609641917,
0.000000000000000000,
-0.010177443901536392,
0.000000000000000000,
-0.008798886675905997,
0.000000000000000000,
-0.007577220484233372,
0.000000000000000000,
-0.006493859804516438,
0.000000000000000000,
-0.005533508538632429,
0.000000000000000000,
-0.004683343721596901,
0.000000000000000000,
-0.003932423233774751,
0.000000000000000000,
-0.003271251311125927,
0.000000000000000000,
-0.002691457154069645,
0.000000000000000000,
-0.002185555845922462,
0.000000000000000000,
-0.001746769975247667,
0.000000000000000000,
-0.001368896533613985,
0.000000000000000000,
-0.001046207887980644,
0.000000000000000000,
-0.000773378567767513,
0.000000000000000000};

@ -0,0 +1,21 @@
// Following is a 19 term Hilbert FIR filter
float hilbert19A[]={
0.003006666677728199,
0.000000000000000000,
0.017439390086760032,
0.000000000000000000,
0.058235158985840196,
0.000000000000000000,
0.161905323559397157,
0.000000000000000000,
0.617838316334015092,
0.000000000000000000,
-0.617838316334015092,
0.000000000000000000,
-0.161905323559397157,
0.000000000000000000,
-0.058235158985840196,
0.000000000000000000,
-0.017439390086760032,
0.000000000000000000,
-0.003006666677728199};

@ -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,105 @@
/* TestEqualizer1Audio.ino Bob Larkin 18 May 2020
* This is a test of the Filter Equalizer using the Audette OpenAudio_ArduinoLibrary.
* Runs two different equalizers, switching every 5 seconds to demonstrate
* dynamic filter changing.
*/
#include "Audio.h"
#include "OpenAudio_ArduinoLibrary.h"
#include "DSP_TeensyAudio_F32.h"
#include <Audio.h>
// Signals from ADC to Equalizer to DAC using Teensy Audio Adaptor
AudioInputI2S i2sIn;
AudioConvert_I16toF32 convertItoF1;
AudioConvert_I16toF32 convertItoF2;
AudioFilterEqualizer_F32 equalize1;
AudioFilterEqualizer_F32 equalize2;
AudioConvert_F32toI16 convertFtoI1;
AudioConvert_F32toI16 convertFtoI2;
AudioOutputI2S i2sOut;
AudioConnection patchCord1(i2sIn, 0, convertItoF1, 0);
AudioConnection patchCord3(i2sIn, 1, convertItoF2, 0);
AudioConnection_F32 patchCord5(convertItoF1, 0, equalize1, 0);
AudioConnection_F32 patchCord7(convertItoF2, 0, equalize2, 0);
AudioConnection_F32 patchCord9(equalize1, 0, convertFtoI1, 0);
AudioConnection_F32 patchCordB(equalize2, 0, convertFtoI2, 0);
AudioConnection patchCordD(convertFtoI1, 0, i2sOut, 0);
AudioConnection patchCordF(convertFtoI2, 0, i2sOut, 1);
AudioControlSGTL5000 codec;
// Some sort of octave band equalizer as a one alternative, 10 bands
float32_t fBand1[] = { 40.0, 80.0, 160.0, 320.0, 640.0, 1280.0, 2560.0, 5120.0, 10240.0, 22058.5};
float32_t dbBand1[] = {10.0, 2.0, -2.0, -5.0, -2.0, -4.0, -10.0, 6.0, 10.0, -100};
// To contrast, put a strange bandpass filter as an alternative, 5 bands
float32_t fBand2[] = { 300.0, 500.0, 800.0, 1000.0, 22058.5};
float32_t dbBand2[] = {-60.0, 0.0, -20.0, 0.0, -60.0};
float32_t equalizeCoeffs[200];
int16_t k = 0;
void setup(void) {
AudioMemory(5);
AudioMemory_F32(10);
Serial.begin(300); delay(1000);
Serial.println("*** Test Audio Equalizer ***");
codec.enable();
codec.inputSelect(AUDIO_INPUT_LINEIN);
codec.adcHighPassFilterDisable(); // necessary to suppress noise
codec.lineInLevel(2);
codec.volume(0.8);
// Initialize the equalizer with 10 bands, fBand1[] 199 FIR coefficients
// -65 dB sidelobes, 16384 Max coefficient value
uint16_t eq = equalize1.equalizerNew(10, &fBand1[0], &dbBand1[0], 30, &equalizeCoeffs[0], 60.0);
if (eq == ERR_EQ_BANDS)
Serial.println("Equalizer failed: Invalid number of frequency bands.");
else if (eq == ERR_EQ_SIDELOBES)
Serial.println("Equalizer failed: Invalid sidelobe specification.");
else if (eq == ERR_EQ_NFIR)
Serial.println("Equalizer failed: Invalid number of FIR coefficients.");
else
Serial.println("Equalizer initialized successfully.");
eq = equalize2.equalizerNew(10, &fBand1[0], &dbBand1[0], 30, &equalizeCoeffs[0], 60.0);
if (eq == ERR_EQ_BANDS)
Serial.println("Equalizer failed: Invalid number of frequency bands.");
else if (eq == ERR_EQ_SIDELOBES)
Serial.println("Equalizer failed: Invalid sidelobe specification.");
else if (eq == ERR_EQ_NFIR)
Serial.println("Equalizer failed: Invalid number of FIR coefficients.");
else
Serial.println("Equalizer initialized successfully.");
// for (k=0; k<200; k++) Serial.println(equalizeCoeffs[k]);
}
void loop(void) {
#if 0 // Change between two filters every 10 seconds.
// To run with just the 10-band equalizer, comment out the entire loop with "/* ... */"
if(k == 0) {
k = 1;
equalize1.equalizerNew(10, &fBand1[0], &dbBand1[0], 200, &equalizeCoeffs[0], 65.0);
equalize2.equalizerNew(10, &fBand1[0], &dbBand1[0], 200, &equalizeCoeffs[0], 65.0);
}
else { // Swap back and forth
k = 0;
equalize1.equalizerNew(5, &fBand2[0], &dbBand2[0], 100, &equalizeCoeffs[0], 40.0);
equalize2.equalizerNew(5, &fBand2[0], &dbBand2[0], 100, &equalizeCoeffs[0], 40.0);
}
#endif
delay(10000); // Interrupts will keep the audio going
Serial.print("Proc = ");
Serial.print(AudioProcessorUsage());
Serial.print(" (");
Serial.print(AudioProcessorUsageMax());
Serial.print("), Mem = ");
Serial.print(AudioMemoryUsage());
Serial.print(" (");
Serial.print(AudioMemoryUsageMax());
Serial.println(")");
AudioProcessorUsageMaxReset();
AudioMemoryUsageMaxReset();
}

@ -0,0 +1,88 @@
/* TestEqualizer3General.ino Bob Larkin 22 May 2020
* This is a test of the Filter Equalizer for Teensy Audio.
* This version is for the Chip Audette _F32 Library.
* See TestEqualizer2.ino for the int16 version.
*/
#include "Audio.h"
#include "OpenAudio_ArduinoLibrary.h"
#include "DSP_TeensyAudio_F32.h"
AudioInputI2S i2s1;
AudioSynthWaveformSine_F32 sine1; // Test signal
AudioFilterFIRGeneral_F32 firg1;
AudioRecordQueue_F32 queue1; // The LSB output
AudioConnection_F32 patchCord1(sine1, 0, firg1, 0);
AudioConnection_F32 patchCord2(firg1, 0, queue1, 0);
float dbA[51];
float equalizeCoeffs[51];
float dt1[128];
float *pq1, *pd1;
int i, k;
float32_t dBResponse[500]; // Show lots of detail
void setup(void) {
AudioMemory(5);
AudioMemory_F32(10);
Serial.begin(300); delay(1000);
Serial.println("*** Test FIR General routine for F32 ***");
sine1.frequency(1000.0f);
// This simple case corresponds to Burris and Parks, "Digital Filter Design."
// Example 3.1when sidelobes are set to 0.0.
for (i=0; i<6; i++) dbA[i] = 0.0f;
for (i=6; i<11; i++) dbA[i] = -120.0f;
// Initialize the FIR with 21 points, 21 FIR coefficients and -0 dB sidelobes
uint16_t eq = firg1.FIRGeneralNew(&dbA[0], 21, &equalizeCoeffs[0], 0.0);
if (eq == ERR_EQ_BANDS)
Serial.println("FIR General failed: Invalid number of frequency points.");
else if (eq == ERR_EQ_SIDELOBES)
Serial.println("FIR General failed: Invalid sidelobe specification.");
else if (eq == ERR_EQ_NFIR)
Serial.println("FIR General failed: Invalid number of FIR coefficients.");
else
Serial.println("FIR General initialized successfully.");
Serial.println("FIR Coeffs");
for(i=0; i<21; i++) Serial.println(equalizeCoeffs[i], 7);
Serial.println("");
// Get frequency response in dB for 200 points, uniformly spaced from 0 to 21058 Hz
firg1.getResponse(200, dBResponse);
Serial.println("Freq Hz, Response dB");
for(int m=0; m<200; m++) { // Print the response in Hz, dB, suitable for a spread sheet
Serial.print((float32_t)m * 22058.5f / 200.0f);
Serial.print(",");
Serial.println(dBResponse[m]);
}
i = -10; k=0;
}
void loop(void) {
if(i<0) i++; // Get past startup
if(i==0) queue1.begin();
// Collect 128 samples and output to Serial
// This "if" will be active for i == 0
if (queue1.available() >= 1 && i >= 0) {
pq1 = queue1.readBuffer();
pd1 = &dt1[0];
for(k=0; k<128; k++) {
*pd1++ = *pq1++;
}
i=1; // Only collect 1 block
queue1.freeBuffer();
queue1.end(); // No more data to queue1
}
if(i == 1) {
// Uncomment the next 3 lines to printout a sample of the sine wave.
Serial.println("128 Samples: ");
for (k=0; k<128; k++)
Serial.println (dt1[k],7);
i = 2;
}
delay(2);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

@ -0,0 +1,127 @@
/* TestFIRGeneralLarge4.ino Bob Larkin 24 May 2020
* Test the generation of FIR filters and obtaining their
* responses. See TestFIRGeneralLarge5.ino to run the filters.
* This is a test of the AudioFilterFIR_F32 for Teensy Audio.
*/
#include "Audio.h"
#include "OpenAudio_ArduinoLibrary.h"
#include "DSP_TeensyAudio_F32.h"
AudioInputI2S i2s1; // Creates timing
AudioFilterFIRGeneral_F32 firg1;
float32_t dbA[2000];
float32_t workSpace[4128];
float32_t equalizeCoeffs[4000];
int i, k;
float32_t dBResponse[5000]; // Extreme size, can show lots of detail!
// Following is based on Rabiner, McGonegal and Paul, FWFIR test case, but has
// had the windowing effect divided out to show their basic filter response.
float32_t hpCoeff[55]={
-0.003643388f, -0.007195844f, 0.012732424f, -0.007796006f, -0.004276372f,
0.013760401f, -0.012262941f, 0.000000304f, 0.013553423f, -0.016818445f,
0.005786355f, 0.011693321f, -0.021220577f, 0.013364335f, 0.007566100f,
-0.025227439f, 0.023410856f, -0.000000305f, -0.028612852f, 0.037841329f,
-0.014052131f, -0.031182658f, 0.063661835f, -0.046774701f, -0.032787389f,
0.151364865f, -0.257518316f, 0.300000700f, -0.257518316f, 0.151364865f,
-0.032787389f, -0.046774701f, 0.063661835f, -0.031182658f, -0.014052131f,
0.037841329f, -0.028612852f, -0.000000305f, 0.023410856f, -0.025227439f,
0.007566100f, 0.013364335f, -0.021220577f, 0.011693321f, 0.005786355f,
-0.016818445f, 0.013553423f, 0.000000304f, -0.012262941f, 0.013760401f,
-0.004276372f, -0.007796006f, 0.012732424f, -0.007195844f, -0.003643388};
void setup(void) {
uint16_t nFIR = 4;
float32_t sidelobes = 45;
AudioMemory(5);
AudioMemory_F32(10);
Serial.begin(300); delay(1000);
Serial.println("*** Test Response of FIR General routine for F32 ***");
// Un-comment one of the following, or use the direct Coefficient load that follows
// Test case for odd nFIR from Burris and Parks Example 3.1
nFIR = 21; sidelobes = 0.0; // No window
dbA[0]=0.0; dbA[1]=0.0; dbA[2]=0.0; dbA[3]=0.0; dbA[4]=0.0; dbA[5]=0.0;
dbA[6]=-140.0; dbA[7]=-140.0; dbA[8]=-140.0; dbA[9]=-140.0; dbA[10]=-140.0;
// Test case for even nFIR from Burris and Parks Example 3.2
// nFIR = 20; sidelobes = 0.0;
// dbA[0]=0.0; dbA[1]=0.0; dbA[2]=0.0; dbA[3]=0.0; dbA[4]=-120.0; dbA[5]=-120.0;
// dbA[6]=-120.0; dbA[7]=-120.0; dbA[8]=-120.0; dbA[9]=-120.0;
// Test case for odd nFIR from Burris and Parks Example 3.1 with Kaiser Window
// nFIR = 21; sidelobes = 45.0f;
// dbA[0]=0.0; dbA[1]=0.0; dbA[2]=0.0; dbA[3]=0.0; dbA[4]=0.0; dbA[5]=0.0;
// dbA[6]=-140.0; dbA[7]=-140.0; dbA[8]=-140.0; dbA[9]=-140.0; dbA[10]=-140.0f;
// Test case for even nFIR from Burris and Parks Example 3.2
// nFIR = 20; sidelobes = 45.0f;
// dbA[0]=0.0; dbA[1]=0.0; dbA[2]=0.0; dbA[3]=0.0; dbA[4]=0.0; dbA[5]=-120.0;
// dbA[6]=-120.0; dbA[7]=-120.0; dbA[8]=-120.0; dbA[9]=-120.0;
// Rabiner, McGonegal and Paul, FWFIR test case, HP Kaiser SL=60, fco=0.35
// nFIR = 55; sidelobes = 60.0;
// for(i=0; i<28; i++) {
// if (i<20) dbA[i] = -120.0f;
// else dbA[i] = 0.0f;
// }
// The next 3 examples are tests of extremely large FIR arrays and only for Teensy 4
// Generate a response curve for our "octave" equalizer, tried with 30 dB sidelobes
// nFIR = 3999; sidelobes = 30.0f;
// dbA[0] = 12.0f; // Avoid log(0)
// for(i=1; i<2000; i++)
// dbA[i] = 12.0f*cosf(10.0f*log10f(11.02925f*(float32_t)i));
// Generate a slowly changing response, up 12 dB at bass and treble and -12 dB
// for mid-frequencies. Scaled to "octaves" by the form cos(log(f))
// nFIR = 3999; sidelobes = 30.0f;
// dbA[0] = 12.0f;
// dbA[1] = 12.0f;
// for(i=2; i<2000; i++)
// dbA[i] = -12.0f*cosf(2.2f*log10f(11.02925f*(float32_t)i));
// Narrow band "CW" filter, tried with 60 dB sidelobes (60 to 80 or 70 to 72
// nFIR = 3999; sidelobes = 60;
// for(i=0; i<2000; i++) {
// if (i<70 || i>72) dbA[i] = -120.0f;
// else dbA[i] = 0.0f;
// }
// Initialize the FIR filter design and run
uint16_t eq = firg1.FIRGeneralNew(&dbA[0], nFIR, &equalizeCoeffs[0], sidelobes, &workSpace[0]);
if (eq == ERR_EQ_BANDS)
Serial.println("FIR General failed: Invalid number of frequency points.");
else if (eq == ERR_EQ_SIDELOBES)
Serial.println("FIR General failed: Invalid sidelobe specification.");
else if (eq == ERR_EQ_NFIR)
Serial.println("FIR General failed: Invalid number of FIR coefficients.");
else
Serial.println("FIR General initialized successfully.");
// Load (if not commented) non-windowed HP filter coefficients, test case:
// firg1.LoadCoeffs(55, hpCoeff, workSpace); // Direct load of FIR coefficients, already calculated
Serial.println("FIR Coeffs");
for(i=0; i<nFIR; i++)
Serial.println(equalizeCoeffs[i], 7);
Serial.println("");
// Get frequency response in dB for 5000 points, uniformly spaced from 0 to 22058 Hz
// This is chosen at 5000 for test. Normally, a smaller value,
// perhaps 500, would be adequate.
firg1.getResponse(5000, dBResponse);
Serial.println("Freq Hz, Response dB");
for(int m=0; m<5000; m++) { // Print the response in Hz, dB, suitable for a spread sheet
// Uncomment/comment next 3 for print/no print
Serial.print((float32_t)m * 22058.5f / 5000.0f);
Serial.print(",");
Serial.println(dBResponse[m]);
}
}
void loop(void) {
}

@ -0,0 +1,82 @@
/* TestFIRGeneralLarge5.ino Bob Larkin 24 May 2020
* This is a test of the FIRGeneralLarge block for Teensy Audio,
* that has two new elements:
* * The FIR filter is specified by an arbitrary frequency response
* * The number of FIR taps is not restricted in number or in even/odd.
*
* This test program passes a signal from the SGTL5000 CODEC through
* the FIR filter and back out through the CODEC.
* This version is for the Chip Audette _F32 Library. A separate version
* with adjustments, is for the Teensy Audio Library with I16 data types
* called AudioFilterFIRGeneral_I16.
*/
#include "Audio.h"
#include "OpenAudio_ArduinoLibrary.h"
#include "DSP_TeensyAudio_F32.h"
// Input as I16 and convert to be Teensy 4 compatible
AudioInputI2S i2sIn;
AudioConvert_I16toF32 convertItoF1;
AudioFilterFIRGeneral_F32 firg1;
AudioConvert_F32toI16 convertFtoI1;
AudioOutputI2S i2sOut;
AudioConnection patchCord1(i2sIn, 0, convertItoF1, 0);
AudioConnection_F32 patchCord5(convertItoF1, 0, firg1, 0);
AudioConnection_F32 patchCord9(firg1, 0, convertFtoI1, 0);
AudioConnection patchCordD(convertFtoI1, 0, i2sOut, 0);
AudioControlSGTL5000 codec;
float32_t dbA[2000];
float32_t workSpace[4128];
float32_t equalizeCoeffs[4000];
int i;
void setup(void) {
uint16_t nFIR = 4;
float32_t sidelobes = 45.0f;
AudioMemory(5);
AudioMemory_F32(10);
Serial.begin(300); delay(1000);
Serial.println("*** Test FIR General routine for F32 ***");
codec.enable();
codec.inputSelect(AUDIO_INPUT_LINEIN);
codec.adcHighPassFilterDisable();
codec.lineInLevel(2);
codec.volume(0.8);
// Un-comment one of the following, or use the direct Coefficient load that follows
// Test case for odd nFIR from Burris and Parks Example 3.1 with Kaiser Window
// nFIR = 21; sidelobes = 45.0f;
// dbA[0]=0.0; dbA[1]=0.0; dbA[2]=0.0; dbA[3]=0.0; dbA[4]=0.0; dbA[5]=0.0;
// dbA[6]=-140.0; dbA[7]=-140.0; dbA[8]=-140.0; dbA[9]=-140.0; dbA[10]=-140.0;
// Test case for even nFIR from Burris and Parks Example 3.2
// nFIR = 20; sidelobes = 45.0f;
// dbA[0]=0.0; dbA[1]=0.0; dbA[2]=0.0; dbA[3]=0.0; dbA[4]=0.0; dbA[5]=-120.0;
// dbA[6]=-120.0; dbA[7]=-120.0; dbA[8]=-120.0; dbA[9]=-120.0;
// The next example tests extremely large FIR arrays and *only for Teensy 4*
// Narrow band "CW" filter, tried with 60 dB sidelobes (60 to 80 or 70 to 72
nFIR = 3999; sidelobes = 60;
for(i=0; i<2000; i++) {
if (i<70 || i>72) dbA[i] = -120.0f;
else dbA[i] = 0.0f;
}
// Initialize the FIR filter design and run
uint16_t eq = firg1.FIRGeneralNew(&dbA[0], nFIR, &equalizeCoeffs[0], sidelobes, &workSpace[0]);
if (eq == ERR_EQ_BANDS)
Serial.println("FIR General failed: Invalid number of frequency points.");
else if (eq == ERR_EQ_SIDELOBES)
Serial.println("FIR General failed: Invalid sidelobe specification.");
else if (eq == ERR_EQ_NFIR)
Serial.println("FIR General failed: Invalid number of FIR coefficients.");
else
Serial.println("FIR General initialized successfully.");
}
void loop(void) {
}

@ -0,0 +1,90 @@
/*
* radioNoiseBlanker_F32.cpp
*
* 22 March 2020
* Bob Larkin, in support of the library:
* Chip Audette, OpenAudio, Apr 2017
* -------------------
*
* MIT License, Use at your own risk.
*/
#include "radioNoiseBlanker_F32.h"
void radioNoiseBlanker_F32::update(void) {
audio_block_f32_t *blockIn, *blockOut=NULL;
uint16_t i;
float32_t absSignal;
// Get input block // <<Writable??
blockIn = AudioStream_F32::receiveWritable_f32(0);
if (!blockIn) {
if(errorPrint) Serial.println("NB-ERR: No input memory");
return;
}
// Are we noise blanking?
if(! runNB) {
AudioStream_F32::transmit(blockIn, 0); // send the delayed or blanked data
AudioStream_F32::release (blockIn);
return;
}
// Get a block for the output
blockOut = AudioStream_F32::allocate_f32();
if (!blockOut){ // Didn't have any
if(errorPrint) Serial.println("NB-ERR: No output memory");
AudioStream_F32::release(blockIn);
return;
}
// delayData[] always represents 256 points of I-F data. It is pre-gate and includes noise pulses.
// Go through new data, point i at a time, entering to delay line, looking
// for noise pulses. Then in same loop, move data to output buffer blockOut->data
// based on whether gate is open or not.
for(i=0; i<block_size; i++) {
float32_t datai = blockIn->data[i]; // ith data
delayData[(i+in_index) & delayBufferMask] = datai; // Put ith data to circular delay buffer
absSignal = fabsf(datai); // Rectified I-F
runningSum += fabsf(datai); // Update by adding one rectified point
runningSum -= delayData[(i + in_index - RUNNING_SUM_SIZE) & delayBufferMask]; // & subtract one
pulseTime++; // This keeps track of leading and trailing delays of the gate pulse
if (absSignal > (threshold * runningSum)) { // A noise pulse event
if (gateOn == true) { // Signals are flowing, this is beginning of noise pulse event
gateOn = false;
pulseTime = -nAnticipation;
}
else { // gateOn==false, we are already in a noise pulse event
if (pulseTime > 0) { // Waiting for pulse event to end
pulseTime = 0; // Keep waiting
}
}
}
else { // Noise pulse is below threshold
if (gateOn == true) { // Signals are flowing normally
pulseTime = -9999;
}
else { // gateOn==false, we are already in a noise pulse event
if(pulseTime >= nDecay) { // End of a pulse event, turn gate on
gateOn = true;
pulseTime = -9999;
}
}
}
// Ready to enter I-F data to output, offset in time by "nAnticipation"
if (pulseTime == -9999)
blockOut->data[i] = delayData[(256 + i - nAnticipation) & delayBufferMask]; // Need 256??
else // -nAnticipation < pulseTime < nDecay i.e., blanked out
blockOut->data[i] = 0.0f;
} // End of loop point by point over input 128 data points
AudioStream_F32::release (blockIn);
AudioStream_F32::transmit(blockOut, 0); // send the delayed or blanked data
AudioStream_F32::release (blockOut);
// Update pointer in_index to delay line for next 128 update
in_index = (in_index + block_size) & delayBufferMask;
}

@ -0,0 +1,126 @@
/* radioNoiseBlanker_F32.h
* 15 May 2020 Bob Larkin
*
* Thanks to PJRC and Pau Stoffregen for the Teensy processor, Teensyduino
* and Teensy Audio. Thanks to Chip Audette for the F32 extension
* work. Alll of that makes this possible. Bob
*
* Copyright (c) 2020 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.
*/
/*
* An I-F signal comes in with occassional noise pulses. This block watches for
* the pulses and turns off the I-F while the pulse exists. In order to
* be as smart as possible this looks ahead by a number of samples, nAnticipation.
* Likewise, the I-F is left off for nDecay samples after the pulse ends.
* Various methods could be to be used to "turn off" the i-f,
* including replacement with waveforms.
* As of this initial write, zeros are used in the waveform.
*
* A threshold can be adjusted via setNB(). This is compared with the
* average rectified voltage being received. If this is too small, like 1.5
* or 2.0, we will be loosing data by blanking. If we set it too high, like 20.0,
* we will not blank noise pulses. Experiments will find a god setting.
* With a sine wave input and no impulse noise, the average rectified signal
* should be about 0.637. To catch the top of that requires a threshold of
* 1/0.637 = 1.57. That would seem to be a very minimal threshold setting.
*
* Status: Tested on computer data
* Functions:
* setNB( ... ); // See below
* showError(e); // e=1 means to print update() error messages
* enable(runNB); // When runNB=false the signals bypasses the update() processing
* Examples:
* TestNoiseBlanker1.ino 128 data for plotting and examination
* Time: Update() of 128 samples 32 microseconds
*/
#ifndef _radio_noise_blanker_f32_h
#define _radio_noise_blanker_f32_h
#include "AudioStream_F32.h"
#include "arm_math.h"
#define RUNNING_SUM_SIZE 125
class radioNoiseBlanker_F32 : public AudioStream_F32 {
//GUI: inputs:1, outputs:1 //this line used for automatic generation of GUI node
//GUI: shortName: NoiseBlanker
public:
// Option of AudioSettings_F32 change to block size (no sample rate dependent variables here):
radioNoiseBlanker_F32(void) : AudioStream_F32(1, inputQueueArray_f32) {
block_size = AUDIO_BLOCK_SAMPLES;
}
radioNoiseBlanker_F32(const AudioSettings_F32 &settings) : AudioStream_F32(1, inputQueueArray_f32) {
block_size = settings.audio_block_samples;
}
void enable(bool _runNB) {
runNB = _runNB;
}
void setNoiseBlanker(float32_t _threshold, uint16_t _nAnticipation, uint16_t _nDecay) {
if (_threshold < 0.0) threshold = 0.0;
else threshold = _threshold/(float32_t)RUNNING_SUM_SIZE;
if(_nAnticipation < 1) nAnticipation = 1;
else if (_nAnticipation >125) nAnticipation = 125;
else nAnticipation = _nAnticipation;
if (_nDecay < 1) nDecay = 1;
else if (_nDecay > 10) nDecay = 10; // Is this handled OK at end of block?
else nDecay = _nDecay;
}
void showError(uint16_t e) { // This is for debug
errorPrint = e;
}
void update(void);
private:
uint16_t block_size = AUDIO_BLOCK_SAMPLES;
// Input data pointers
audio_block_f32_t *inputQueueArray_f32[1];
// Control error printing in update() 0=No print
uint16_t errorPrint = 0;
// To look ahead we need a delay of up to 128 samples. Too much removes good data.
// Too little enters noise pulse data to the output. This can be a simple circular
// buffer if we make the buffer a power of 2 in length and binary-truncate the index.
// Choose 2^8 = 256.
float32_t delayData[256]; // The circular delay line
uint16_t in_index = 0; // Pointer to next block update entry
// And a mask to make the circular buffer limit to a power of 2
uint16_t delayBufferMask = 0X00FF;
// Three variables to allow .INO control of Noise Blanker
float32_t threshold = 1.0E6f; // Start disabled
uint16_t nAnticipation = 5;
uint16_t nDecay = 8;
int16_t pulseTime = -9999; // Tracks nAnticipation and nDecay
bool gateOn = true; // Signals going through NB
float32_t runningSum = 0.0;
bool runNB = false;
};
#endif

@ -25,6 +25,12 @@ OpenAudio Library for Teensy
20-Moved in radio function RadioIQMixer_F32.
21-Added Example INO, FineFreqShift_OA.ino with Hilbert shifter and delay stereo.
22-Brought in support stuff mathDSP_F32 and sinTable512_F32.h.
23-Brought in AudioAnalyzePhase_F32 and Example
24-Brought in AudioFilterEqualyzer_F32 and 2 Example
25-Brought in AudioFilterFIRGeneral_F32 and 2 Example INOs
26-Brought in RadioFMDetector_F32 and Example
27-Brought in synth_sin_cos_F32 and test example
28-Brought in RadioNoiseBlanker_F32 and Example
**Purpose**: The purpose of this library is to build upon the [Teensy Audio Library](http://www.pjrc.com/teensy/td_libs_Audio.html) to enable new functionality for real-time audio processing.

@ -0,0 +1,103 @@
/* synth_sin_cos_f32.cpp
*
* SynthSinCos_F32 Bob Larkin April 17, 2020
*
* Based on Chip Audette's OpenAudio sine(), that was
* Modeled on: AudioSynthWaveformSine from Teensy Audio Library
*
* Purpose: Create sine and cosine wave of given amplitude, frequency
* and phase. Outputs in float32_t floating point.
* Routines are from the arm CMSIS library and use a 512 point lookup
* table with linear interpolation to achieve float accuracy limits.
*
* Copyright (c) 2020 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 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.
*/
#include "synth_sin_cos_f32.h"
// 513 values of the sine wave in a float array:
#include "sinTable512_f32.h"
void AudioSynthSineCosine_F32::update(void) {
audio_block_f32_t *blockS, *blockC;
uint16_t index, i;
float32_t a, b, deltaPhase, phaseC;
blockS = AudioStream_F32::allocate_f32(); // Output blocks
if (!blockS) return;
blockC = AudioStream_F32::allocate_f32();
if (!blockC) {
AudioStream_F32::release(blockS);
return;
}
// doSimple has amplitude (-1, 1) and sin/cos differ by 90.00 degrees.
if (doSimple) {
for (i=0; i < block_length; i++) {
phaseS += phaseIncrement;
if (phaseS > 512.0f)
phaseS -= 512.0f;
index = (uint16_t) phaseS;
deltaPhase = phaseS -(float32_t) index;
/* Read two nearest values of input value from the sin table */
a = sinTable512_f32[index];
b = sinTable512_f32[index+1];
blockS->data[i] = a + 0.001953125*(b-a)*deltaPhase; /* Linear interpolation process */
/* Repeat for cosine by adding 90 degrees phase */
index = (index + 128) & 0x01ff;
/* Read two nearest values of input value from the sin table */
a = sinTable512_f32[index];
b = sinTable512_f32[index+1];
/* deltaPhase will be the same as used for sin */
blockC->data[i] = a + 0.001953125*(b-a)*deltaPhase; /* Linear interpolation process */
}
}
else { // Do a more flexible update, i.e., not doSimple
for (i=0; i < block_length; i++) {
phaseS += phaseIncrement;
if (phaseS > 512.0f) phaseS -= 512.0f;
index = (uint16_t) phaseS;
deltaPhase = phaseS -(float32_t) index;
/* Read two nearest values of input value from the sin table */
a = sinTable512_f32[index];
b = sinTable512_f32[index+1];
blockS->data[i] = amplitude_pk * (a + 0.001953125*(b-a)*deltaPhase); /* Linear interpolation process */
/* Shift forward phaseS_C and get cos. First, the calculation of index of the table */
phaseC = phaseS + phaseS_C;
if (phaseC > 512.0f) phaseC -= 512.0f;
index = (uint16_t) phaseC;
deltaPhase = phaseC -(float32_t) index;
/* Read two nearest values of input value from the sin table */
a = sinTable512_f32[index];
b = sinTable512_f32[index+1];
blockC->data[i] = amplitude_pk * (a + 0.001953125*(b-a)*deltaPhase); /* Linear interpolation process */
}
}
AudioStream_F32::transmit(blockS, 0);
AudioStream_F32::release (blockS);
AudioStream_F32::transmit(blockC, 1);
AudioStream_F32::release (blockC);
return;
}

@ -0,0 +1,162 @@
/* synth_sin_cos_f32.h
* AudioSynthSineCosine_F32
*
* Status: Checked for function and accuracy. 19 April 2020
*
* Created: Bob Larkin 15 April 2020
*
* Based on Chip Audette's OpenAudio sine(), that was
* Modeled on: AudioSynthWaveformSine from Teensy Audio Library
*
* Purpose: Create sine and cosine wave of given amplitude, frequency
* and phase. Outputs are audio_block_f32_t blocks of float32_t.
* Routines are from the arm CMSIS library and use a 512 point lookup
* table with linear interpolation to achieve float accuracy limits.
*
* This provides for setting the phase of the sine, setting the difference
* in phase between the sine and cosine and setting the
* (-amplitude, amplitude) range. If these are at the default values,
* called doSimple, the caluclation is faster.
* For doSimple either true or false, the frequency can be changed
* at will using the frequency() method.
*
* Defaults:
* Frequency: 1000.0 Hz
* Phase of Sine: 0.0 radians (0.0 deg)
* Phase of Cosine: pi/2 radians (90.0 deg) ahead of Sine
* Amplitude: -1.0 to 1.0
*
* Time: T3.6 update() block of 128 with doSimple is 36 microseconds
* Same using flexible doSimple=false is 49 microseconds
* T4.0 update() block of 128 with doSimple is 16 microseconds
* Same using flexible doSimple=false is 24 microseconds
*
* Copyright (c) 2020 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 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.
*/
#ifndef synth_sin_cos_f32_h_
#define synth_sin_cos_f32_h_
#include "AudioStream_F32.h"
#include "arm_math.h"
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
#ifndef M_PI_2
#define M_PI_2 1.57079632679489661923
#endif
#ifndef M_TWOPI
#define M_TWOPI (M_PI * 2.0)
#endif
#define MF2_PI 6.2831853f
class AudioSynthSineCosine_F32 : public AudioStream_F32 {
//GUI: inputs:0, outputs:2 //this line used for automatic generation of GUI node
//GUI: shortName:SineCosine //this line used for automatic generation of GUI node
public:
AudioSynthSineCosine_F32(void) : AudioStream_F32(0, NULL) { } //uses default AUDIO_SAMPLE_RATE from AudioStream.h
AudioSynthSineCosine_F32(const AudioSettings_F32 &settings) : AudioStream_F32(0, NULL) {
setSampleRate_Hz(settings.sample_rate_Hz);
setBlockLength(settings.audio_block_samples);
}
void frequency(float32_t fr) { // Frequency in Hz
freq = fr;
if (freq < 0.0f) freq = 0.0f;
else if (freq > sample_rate_Hz/2.0f) freq = sample_rate_Hz/2.0f;
phaseIncrement = 512.0f * freq / sample_rate_Hz;
}
/* Externally, phase comes in the range (0,2*M_PI) keeping with C math functions
* Internally, the full circle is represented as (0.0, 512.0). This is
* convenient for finding the entry to the sine table.
*/
void phase_r(float32_t a) {
while (a < 0.0f) a += MF2_PI;
while (a > MF2_PI) a -= MF2_PI;
phaseS = 512.0f * a / MF2_PI;
doSimple = false;
return;
}
// phaseS_C_r is the number of radians that the cosine output leads the
// sine output. The default is M_PI_2 = pi/2 = 1.57079633 radians,
// corresponding to 90.00 degrees cosine leading sine.
void phaseS_C_r(float32_t a) {
while (a < 0.0f) a += MF2_PI;
while (a > MF2_PI) a -= MF2_PI;
// Internally a full circle is 512.00 of phase
phaseS_C = 512.0f * a / MF2_PI;
doSimple = false;
return;
}
// The amplitude, a, is the peak, as in zero-to-peak. This produces outputs
// ranging from -a to +a. Both outputs are the same amplitude.
void amplitude(float32_t a) {
amplitude_pk = a;
doSimple = false;
return;
}
// Speed up calculations by setting phaseS_C=90deg, amplitude=1
// Note, s=true will override any setting of phaseS_C_r or amplitude.
void simple(bool s) {
doSimple = s;
if(doSimple) {
phaseS_C = 128.0f;
amplitude_pk = 1.0f;
}
return;
}
void setSampleRate_Hz(float32_t fs_Hz) {
// Check freq range
if (freq > sample_rate_Hz/2.0f) freq = sample_rate_Hz/2.f;
// update phase increment for new frequency
phaseIncrement = 512.0f * freq / fs_Hz;
}
void setBlockLength(uint16_t bl) {
if(bl > 128) bl = 128;
block_length = bl;
}
virtual void update(void);
private:
float32_t freq = 1000.0f;
float32_t phaseS = 0.0f;
float32_t phaseS_C = 128.00;
float32_t amplitude_pk = 1.0f;
float32_t sample_rate_Hz = AUDIO_SAMPLE_RATE;
float32_t phaseIncrement = 512.0f * freq /sample_rate_Hz;
uint16_t block_length = 128;
// if only freq() is used, the complexities of phase, phaseS_C,
// and amplitude are not used, speeding up the sin and cos:
bool doSimple = true;
};
#endif
Loading…
Cancel
Save