From 42419879eee050f6cd4ca3afad17c3960c6420b2 Mon Sep 17 00:00:00 2001 From: boblark Date: Tue, 25 Jan 2022 11:17:50 -0800 Subject: [PATCH] Added analyze_CTCSS sub-audible tone detector --- OpenAudio_ArduinoLibrary.h | 1 + analyze_CTCSS_F32.cpp | 121 +++++++++ analyze_CTCSS_F32.h | 505 +++++++++++++++++++++++++++++++++++++ keywords.txt | 122 ++++++++- 4 files changed, 748 insertions(+), 1 deletion(-) create mode 100644 analyze_CTCSS_F32.cpp create mode 100644 analyze_CTCSS_F32.h diff --git a/OpenAudio_ArduinoLibrary.h b/OpenAudio_ArduinoLibrary.h index 1ecbf07..151d0fb 100644 --- a/OpenAudio_ArduinoLibrary.h +++ b/OpenAudio_ArduinoLibrary.h @@ -27,6 +27,7 @@ #include "synth_waveform_F32.h" #include "synth_whitenoise_f32.h" #include "synth_GaussianWhiteNoise_F32.h" +#include "analyze_CTCSS_F32.h" #include "analyze_fft1024_F32.h" #include "analyze_fft256_iq_F32.h" #include "analyze_fft1024_iq_F32.h" diff --git a/analyze_CTCSS_F32.cpp b/analyze_CTCSS_F32.cpp new file mode 100644 index 0000000..7811201 --- /dev/null +++ b/analyze_CTCSS_F32.cpp @@ -0,0 +1,121 @@ +/* + * analyze_CTCSS_F32.cpp Converted to float from PJRC Teensy Audio Library + * MIT License on changed portions + * Bob Larkin March 2021 + * + * Some parts of this cpp came from: Audio Library for Teensy 3.X + * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com + * + * Development of this audio library was funded by PJRC.COM, LLC by sales of + * Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop + * open source software by purchasing Teensy or other PJRC products. + * + * 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. + */ +/* From Pavel Rajmic (2021). Generalized Goertzel algorithm + * https://www.mathworks.com/matlabcentral/fileexchange/35103-generalized-goertzel-algorithm + * MATLAB Central File Exchange. Retrieved December 18, 2021. + */ + +#include +#include "analyze_CTCSS_F32.h" + +void analyze_CTCSS_F32::update(void) { + audio_block_f32_t *block; + float gs0=0.0; + + block = receiveReadOnly_f32(); + if (!block) return; + if (!gEnabled) { + release(block); + return; + } + + // This process is working with frequencies of 254 Hz or less. Thus + // it is beneficial to decimate before processing. The chosen + // decimation rate is 16. For example, if the basic sample rate is 44.1 kHz, + // the decimated rate is 44100/16=2756.25 Hz Before decimation, we + // low pass filter to prevent alias problems. Returns 128 pts in block. + arm_biquad_cascade_df1_f32(&iir_lpf_inst, block->data, + block->data, block->length); + + // And decimate, using the first sample, giving 128/16=8 samples to + // be processed per block. + // Create a small data block, normally 8, d16a[]. + for(int i=0; i<8; i++) + d16a[i] = *(block->data + 16*i); // Decimated sample, only every 16 + + // Filter down to 67-254Hz band, leaving result in d16a[]; + arm_biquad_cascade_df1_f32(&iir_bpf_inst, d16a, d16a, 8); + + // For reference measurement, only, null out the tone, creating d16b[]. + // This d16b signal path is not used for the Goertzel. + arm_biquad_cascade_df1_f32(&iir_nulls_inst, d16a, d16b, 8); + + for(int i=0; i<8; i++) + { + if(gCount++ < gLength) // Main Goertzel calculation + { + // Use decimated sample, only every 16 + gs0 = d16a[i] + gCoefficient*gs1 - gs2; + gs2 = gs1; + gs1 = gs0; + // Do reference channel power measurement of the 67 to 254 Hz + // band, including nulls at tone frequency. + powerSum += d16b[i]*d16b[i]; + powerSumCount++; + } + else // The last, special, calculation + { + gs0 = gCoefficient*gs1 - gs2; // Generalized non-integer freq Goertzel + out1 = gs0 - gs1*ccRe; + out2 = - gs1*ccIm; + // At this point the phase is still in need of correction. But we + // only use amplitude so, leave the phase as is. + powerTone = 4.0f*(out1*out1 + out2*out2)/((float)gLength*(float)gLength); + // 2.0f*sqrtf(powerTone)/(float)gLength; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + gs1 = 0.0; // Initialize to start new measurement + gs2 = 0.0; + gCount = 0; + // The 2.04 factor makes the reference for sine waves the same as the Goertzel + powerRef = 2.04f*powerSum/((float)powerSumCount); + powerSum = 0.0f; + powerSumCount = 0; + new_output = true; + } + } + // If the CTCSS tone is detected, the output is the original data block. + // If silenced, zeros are returned. + if(powerTone>threshAbs && powerTone>threshRel*powerRef) + transmit(block); + else + { + for(int i = 0; i<128; i++) + *(block->data + i) = 0.0; + transmit(block); + } + release(block); + } + +analyze_CTCSS_F32::operator bool() { + float pThresh; + pThresh = ((float)gLength)*threshAbs; + pThresh *= pThresh; + return (powerTone >= pThresh); + } diff --git a/analyze_CTCSS_F32.h b/analyze_CTCSS_F32.h new file mode 100644 index 0000000..a5e5216 --- /dev/null +++ b/analyze_CTCSS_F32.h @@ -0,0 +1,505 @@ +/* + * analyze_CTCSS_F32.h For the OpenAudio_TeensyArduino library + * (floating point audio). + * MIT License on changed portions + * Bob Larkin March 2021 + * + * This is specific for the CTCSS tone system + * with tones in the 67.0 to 254.1 Hz range. + * + * Parts of this are based on analyze_toneDetect: Audio Library for Teensy 3.X + * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com + * + * Development of this audio library was funded by PJRC.COM, LLC by sales of + * Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop + * open source software by purchasing Teensy or other PJRC products. + * + * 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. + */ + +/* The tone frequency is set by the function frequency(float freq, float tMeas). + * The parameter freq is in Hz. The parameter tMeas is optional and is the + * measuring time for the Goertzel algorithm, measured in milliseconds. Sort + * tMeas, say 100, give fast response, but poor selectivity to adjacent tone + * frequencies. Long values, say 500 to 1000, are sluggish and for NBFM systems + * tend to omit initial words. The default for tMeas is 300 milliseconds. + * + * CTCSS ranges from 67.0 to 254.1 Hz + (Actual CTCSS use seems to be 77.0 to 203.5 Hz) + This object can be used on any frequency in the range. + But, the standard CTCSS channel numbers, designations and + frequencies, in Hz, are: + 1 XZ 67 25 5A 156.7 + 39 WZ 69.3 40 159.8 + 2 XA 71.9 26 5B 162.2 + 3 WA 74.4 41 165.5 + 4 XB 77 27 6Z 167.9 + 5 WB 79.7 42 171.3 + 6 YZ 82.5 28 6A 173.8 + 7 YA 85.4 43 177.3 + 8 YB 88.5 29 6B 179.9 + 9 ZZ 91.5 44 183.5 + 10 ZA 94.8 30 7Z 186.2 + 11 ZB 97.4 45 189.9 + 12 1Z 100 31 7A 192.8 + 13 1A 103.5 46 196.6 + 14 1B 107.2 47 199.5 + 15 2Z 110.9 32 M1 203.5 + 16 2A 114.8 48 8Z 206.5 + 17 2B 118.8 33 M2 210.7 + 18 3Z 123 34 M3 218.1 + 19 3A 127.3 35 M4 225.7 + 20 3B 131.8 49 9Z 229.1 + 21 4Z 136.5 36 M5 233.6 + 22 4A 141.3 37 M6 241.8 + 23 4B 146.2 38 M7 250.3 + 24 5Z 151.4 50 0Z 254.1 + */ + +/* FUTURE (Let's see this all working without windowing, first): + * This CTCSS detector has an optional windowing function. The default is + * no windowing, but this leaves the first Goertzel filter + * sideband at about -13 dB relative to the tone frequency response. See + * setGWindow(float *pWinCoeff) below. The caller supplied window function is + * an array of NWINDOW floats corresponding to half of the window + * function, starting with the outside (non-center) coefficient. The number + * of coefficients depends on the sample rate of the ADC and the amount + * time to make a detector measurement. The following might be used in an + * .ino to set the size of the half array. This is in pre-compiler lingo + * as it needs to be installed at compile time, not run time: + * #define SAMPLE_RATE 44117.0f + * #define DETECTOR_TIME 300.0f + * #define NWINDOW (uint16_t)( 0.5 + SAMPLE_RATE * DETECTOR_TIME / 32000.0f ) + * See example/ToneDetect2. Note that DETECTOR_TIME is in milliseconds. + */ + +/* This class can be used to see if a sub-audible tone is present with resulting + * action coming through the .ino. This uses available() and readTonePresent(). + * Alternatively, the threshold(s) can be set + * and the comparison results used to gate the input signal through to other + * audio blocks. Done this way, the audio gating is done without + * any .ino processing. + * + * Note - Both the Goertzel tone output and the reference band outputs are in + * "power." This is convenient and meaningful for the CTCSS detector, but + * different than the Teensy library tone detectors, which take the square + * root of the power for output. + * + * Two outputs are available, TonePower and RefPower. TonePower is the output + * of the Goertzel sharply tuned filter and is the conventional CTCSS output + * that is compared to a threshold. RefPower measures the power in the entire + * 67 to 254 Hz sub-audible band, except at the tone frequency. This is a measure + * of both FM quieting and also the presence of a tone away from the TonePower + * frequency. By comparing TonePower to RefPower several dB of increased + * sensitivity is achieved for threshold setting. + * + * Thresholds are used in two ways. FirstThey can be read by the .ino + * with readTonePresent(what) where "what" allows an absolute threshod comparison + * on Tone Power, a relative comparison between Tone Power and RefPower, + * or both. The relative comparison is the preferred form for many applicatins. + * Parameter "what" can be isAbsThreshold, isRelThreshold or isBothThreshold. + * + * The second method of threshold useage is to gate the input of the block to + * output 0 of the block. This happens as part of the class update and requires + * no .ino action. The control comes from the function + * thresholds(levelAbs, levelRel). Setting either levelbs or levelRel to zero + * removes that measure from the gating process. So, a typical function call + * would be detCTCSS.thresholds(0.0f, 0.4f); + * + * This class uses decimation-by-16 to improve the performance of the low-frequency + * filtering and to reduce processing load. The filter parameters of the decimation + * LPF can be used at any sampling rate from 44 to 100 kHz. Other rates will need + * a new design for the LPF. See the discussion below for "iirLpfCoeffs" showing + * how to design such a filter. + * + * The bandpass filter to cover 67 to 254 Hz is predesigned for sample rates near + * 44, 48, 96 or 100 kHz. The discussion below "BPF iir Coefficients" has + * information for designing BPF for other sample rates. The new design is entered + * via the function setCTCSS_BP(float *filterCoeffs). + * + * Each update of an 128-input block takes about 42 uSec on a Teensy 3.6. + * + * Note - Pieces of this support block sizes other than 128, but not all. + * Work needs to be done to implement and test variable block size. The same is + * true for sample rates outside the 44.1 to 100 kHz range. + */ + +#ifndef analyze_CTCSS_F32_h_ +#define analyze_CTCSS_F32_h_ + +#include "Arduino.h" +#include "AudioStream_F32.h" + +class analyze_CTCSS_F32 : public AudioStream_F32 +{ +//GUI: inputs:1, outputs:1 //this line used for automatic generation of GUI node +//GUI: shortName:DetCTCSS +public: + analyze_CTCSS_F32(void) + : AudioStream_F32(1, inputQueueArray_f32) { + sample_rate_Hz = AUDIO_SAMPLE_RATE; + block_size = AUDIO_BLOCK_SAMPLES; + initCTCSS(); + } + // Option of AudioSettings_F32 change to block size and/or sample rate: + analyze_CTCSS_F32(const AudioSettings_F32 &settings) : AudioStream_F32(1,inputQueueArray_f32){ + sample_rate_Hz = settings.sample_rate_Hz; + block_size = settings.audio_block_samples; + initCTCSS(); + } + + void initCTCSS(void) + { + frequency(103.5f, 300.0f); // Defaults + setNotchX4(103.5f); + // Initialize BiQuad instance (ARM DSP Math Library) + //https://www.keil.com/pack/doc/CMSIS/DSP/html/group__BiquadCascadeDF1.html + arm_biquad_cascade_df1_init_f32(&iir_lpf_inst, 2, &iirLpfCoeffs[0], &StateLpf32[0]); + arm_biquad_cascade_df1_init_f32(&iir_bpf_inst, 4, iirBpfCoeffs, &StateBpf32[0]); + for (int i=0; i<8; i++) + { + d16a[i] = 0.0f; + d16b[i] = 0.0f; + } + arm_biquad_cascade_df1_init_f32(&iir_nulls_inst, 4, &iirNullsCoeffs[0], &StateNullsF32[0]); + thresholds(0.1f, 1.0f); + gEnabled = true; + setCTCSS_BP(NULL); // May be invalid if Sample rate is not provided for here. + } + + // Set frequency and length of Goertzel in Hz and milliseconds. + void frequency(float freq) { // Defaults to 300 mSec + frequency(freq, 300.0f); + } + + void frequency(float freq, float tMeas) { + // e.g. freq=103.5, tMeas=250, sample_rate_Hz = 44100, gLength=689 + gLength = (uint16_t)(0.5 + sample_rate_Hz*tMeas/(1000.0f*16)); // reinstated + // Now set the notch filters for the four-biquad notch filters + setNotchX4(freq); + // and the sub-audible general BP filter + setCTCSS_BP(NULL); + // e.g., freq=103.5, sample_rate_Hz=44100, gCoefficient=2*cos(0.23594)=1.944634 + gCoefficient = 2.0f*cosf(6.28318530718f*16.0f*freq/sample_rate_Hz); + // Another constant for ending calculation is exp(-j 2*pi*16*freq*(glength-1)/sample_rate_Hz) + ccRe = cos(6.28318530718f*16.0f*freq/sample_rate_Hz); + ccIm = -sin(6.28318530718f*16.0f*freq/sample_rate_Hz); + } + + bool available(void) { + __disable_irq(); + bool flag = new_output; + if(flag) + new_output = false; // Prevent multiple reads of same data + __enable_irq(); + return flag; + } + + float readTonePower(void) { return powerTone; } + + float readRefPower(void) { return powerRef; } + + const uint16_t isAbsThreshold=1; + const uint16_t isRelThreshold=2; + const uint16_t isBothThreshold=3; + bool readTonePresent(uint16_t what) { + if(what == isAbsThreshold) + return (powerTone>threshAbs); + else if(what == isRelThreshold) + return (powerTone>threshRel*powerRef); + else if(what == isBothThreshold) + return (powerTone>threshAbs && powerTone>threshRel*powerRef); + else return false; // Something was wrong about call + } + + void thresholds(float levelAbs, float levelRel) { + // The absolute threshold is compared with tonePower + if (levelAbs < 0.000001f) threshAbs = 0.000001f; + else threshAbs = levelAbs; + + // The relative threshold is multiples of refPower + if (levelRel < 0.000001f) threshRel = 0.000001f; + else threshRel = levelRel; + } + + operator bool(); // true if at or above threshold, false if below + + virtual void update(void); + + /* The bandpass filter covers 67 to 254 Hz to allow a comparison + * measurement for the tone. This changes with sample rate. Four + * precomputed filters are provided for 44.1, 48, 96 and 100 kHz. + * Supply a NULL for the filterCoeffs to use these 4. + * filterCoeffs points to an array of 20 floats that are IIR + * coefficients for four BiQuad Stages. To use caller supplied + * filter coefficients, call with a pointer to that 4x BiQuad filter at + * filterCoeffs. + */ + float* setCTCSS_BP(float *filterCoeffs) + { + if(filterCoeffs == NULL) + { + if(sample_rate_Hz>43900.0f && sample_rate_Hz<44200.0f) + iirBpfCoeffs = coeff32bp44; + else if(sample_rate_Hz>47900.0f && sample_rate_Hz<48100.0f) + iirBpfCoeffs = coeff32bp48; + else if(sample_rate_Hz>95800.0f && sample_rate_Hz<96200.0f) + iirBpfCoeffs = coeff32bp96; + else if(sample_rate_Hz>99800.0f && sample_rate_Hz<100200.0f) + iirBpfCoeffs = coeff32bp100; + else return NULL; + } + else iirBpfCoeffs = filterCoeffs; // Caller supplied + return iirBpfCoeffs; + } + + /* To use a windowing fuction to change the Goertzel sidelobe levels, + * in the .ino compute half the window function and put that in an array. + * Call this function with a pointer to that array. To stop windowing, + * call this function with NULL as an argument. + * / Windowing not yet implemented <<<------------------- + void setGWindow(float *pWinCoeff) + { + windowCoeff = pWinCoeff; // Point to the caller supplied window function + if(pWinCoeff != NULL) + nWindow = (uint16_t)(0.5f + sample_rate_Hz*tMeas/(2.0f*1000.0f*16.0f)); + else + nWindow = 0; + } + */ + +private: + int16_t count16 = 0; unsigned long cnt = 0UL; + float ccRe=0.0f; + float ccIm=0.0f; + float freq = 103.5f; + float tMeas = 300.0f; + float d16a[8]; // Small data blocks, after decimation + float d16b[8]; + float gCoefficient = 1.9f; // Goertzel algorithm coefficient + float gs1, gs2; // Goertzel algorithm state + float out1, out2; // Goertzel algorithm state output + float powerTone; + float powerRef; + uint16_t gLength; // number of samples to analyze + uint16_t gCount; // how many left to analyze + float powerSum = 0.0f; + uint16_t powerSumCount = 0; + float threshAbs = 0.1f; // absolute threshold, 0.01 to 0.99 + float threshRel = 1.0f; // noise relative threshold + bool gEnabled = false; + volatile bool new_output = false; + audio_block_f32_t *inputQueueArray_f32[1]; + float sample_rate_Hz = AUDIO_SAMPLE_RATE; + uint16_t block_size = 128; + float iirNullsCoeffs[20]; + float *iirBpfCoeffs; + // windowCoeff needs to store half of values (symmetry) for up to + // 0.25 second at 100KHz sample rate, or 0.25*0.5*100000/16=782 pts. + // This is extravagant and needs to be in caller area to allow + // frugality when there is no window at all. + // float *windowCoeff; <<- Windowing is not currently implemented + // uint16_t nWindow = 0; <<- ditto + /* Info - The structure from arm_biquad_casd_df1_inst_f32 instance consists of + * uint32_t numStages; + * const float32_t *pCoeffs; //Points to the array of coefficients, length 5*numStages. + * float32_t *pState; //Points to the array of state variables, length 4*numStages. + * We have 3 IIR's: + */ + arm_biquad_casd_df1_inst_f32 iir_lpf_inst; + arm_biquad_casd_df1_inst_f32 iir_bpf_inst; + arm_biquad_casd_df1_inst_f32 iir_nulls_inst; + + /* Each Biquad stage has 4 state variables x[n-1], x[n-2], y[n-1], and y[n-2]. + * See https://www.keil.com/pack/doc/CMSIS/DSP/html/group__BiquadCascadeDF1.html + * The state variables are arranged in the pState array as: + * {x[n-1], x[n-2], y[n-1], y[n-2]} + * The 4 state variables for stage 1 are first, then the 4 state variables for stage 2, + * and so on. The state array has a total length of 4*numStages values. + * The state variables are updated after each block of data is processed; the + * coefficients are untouched. + */ + float StateLpf32[2*4]; + float StateBpf32[4*4]; + float StateNullsF32[4*4]; + + /* + * LPF for decimation before CTCSS tone detector. + * Pick cutoff frequency to pass 254 Hz with fs=44.1kHz + * or fc=2*254/44100=0.01152. This passes highest tone + * frequency at the lowest sample rate. Octave code: + * FSample = 44100; + * halfFSample = 0.5 * FSample + * [z,p,g] = ellip(4,0.1,60,254/halfFSample); + * sos = zp2sos(z,p,g); + * sos1 = sos(1:1,:) % 1st row + * sos2 = sos(2:2,:) % 2nd row + * % This puts the stop band at 0.02*fs or for fs=100 KHz + * % at 2 kHz. This, in turn, supports an 4 kHz + * % sampling rate after decimation. Thus a single LPF design + * % can be used for sample rates from 44.1 to 100 kHz. + * % See notes below on how to convert Octave output to coefficients: + */ + float iirLpfCoeffs[10] = // See lp1ForCtcss.gif + {9.937058251573265e-04f, -1.898847656031816e-03f, 9.937058251573265e-04f, + 1.953233139252097e+00f, -9.540781328043288e-01f, + 1.000000000000000e+00f, -1.983872743032985e+00f, 9.999999999999998e-01f, + 1.980584507653506e+00f, -9.822943824311938e-01f}; + + /* There is a band pass filter that is used after decimation to limit the + * spectrum that is seen by the CTCSS detector to (67, 254) Hz. This + * provides the entire CTCSS band to compare against to see if any other + * tones are present. It also restricts the amount of voice spectrum + * presented to the CTCSS detector. This filter is sample rate dependent + * this routine allows for four different "built-in" rates. + * + * Sample LPF Decimation CTCSS BPF + * Rate Fco Ratio Samp Rate Name + * ------- ------ -------- -------- ----- + * 44.1kHz 254Hz 16 2756.2Hz BP44 + * 48.0kHz 276Hz 16 3000.0Hz BP48 + * 96.0kHz 553Hz 16 6000.0Hz BP96 + * 100kHz 576Hz 16 6250.0Hz BP100 + * + * The following allow computing a bandpass filter for any sample rate in + * the 44 to 100 kHz range (gnu octave script). + * +% ===================== BPF iir Coefficients ============================= +% This GNU Octave m script will generate the BPF iir coefficients +% for other sample rates than the four listed here. It will probably +% run under Matlab, but that is untested. +% + format long e + FSample = 6250.0; + halfFSample = 0.5 * FSample + [z,p,g] = ellip(4,0.1,60,[67 254]/halfFSample); + sos = zp2sos(z,p,g); + sos1 = sos(1:1,:) % 1st row + sos2 = sos(2:2,:) % 2nd row + sos3 = sos(3:3,:) % 3rd row + sos4 = sos(4:4,:) % 4th row +% +% sos are the "Second Order State" (BiQuad) consisting of 4 sets of 6 numbers. +% These are numerator and denominator in form of the Open Audio Matlab +% IIR function setFilterCoeff_Matlab(float32_t b[], float32_t a[]) +% in AudioFilterIIR_F32.h of +% https://github.com/chipaudette/OpenAudio_ArduinoLibrary/blob/master/AudioFilterIIR_F32.h +% +% sosi, i=1 to 4, are each 6 double precision numbers, a0, a1, a2, +% b0, b1, b2. To use these with the ARM library, +% arm_biquad_cascade_df1_f32, two changes are needed. The fourth +% number is a0 that is always 1.000 and for the arm routine, it is omitted. +% The fifth and sixth numbers are -a1 and -a2, so they need to have their +% signs reversed. So, for example, for the 44.1 kHz sample rate: +% sos1 = +% 4.386558177463118e-03 4.835083945591431e-03 4.386558177463117e-03 +% 1.000000000000000e+00 -1.503280453219001e+00 8.498498588788136e-01% +% sos2 = +% 1.000000000000000e+00 -4.234328155653108e-01 1.000000000000000e+00 +% 1.000000000000000e+00 -1.524529304401012e+00 6.838502689975459e-01 +% sos3 = +% 1.000000000000000e+00 -1.996808089693871e+00 9.999999999999998e-01 +% 1.000000000000000e+00 -1.797650895821358e+00 8.356641238925192e-01 +% sos4 = +% 1.000000000000000e+00 -1.999398698654737e+00 1.000000000000000e+00 +% 1.000000000000000e+00 -1.941833848024410e+00 9.614543316508065e-01 +% and the resulting arm coefficient array, including all 4 biquads, is +% coeff32BP44[20] = { +% 4.386558177463118e-03, 4.835083945591431e-03, 4.386558177463117e-03, +% 1.503280453219001e+00, -8.498498588788136e-01, +% 1.000000000000000e+00, -4.234328155653108e-01, 1.000000000000000e+00, +% 1.524529304401012e+00, -6.838502689975459e-01, +% 1.000000000000000e+00, -1.996808089693871e+00, 9.999999999999998e-01, +% 1.797650895821358e+00, -8.356641238925192e-01, +% 1.000000000000000e+00, -1.999398698654737e+00, 1.000000000000000e+00, +% 1.941833848024410e+00, -9.614543316508065e-01}; +% +*/ + // The four predefined bandpass filters: + // BP44 Decimated fSample = 2756.2 Hz + float coeff32bp44[20] = { + 4.386558177463118e-03f, 4.835083945591431e-03f, 4.386558177463117e-03f, + 1.503280453219001e+00f, -8.498498588788136e-01f, + 1.000000000000000e+00f, -4.234328155653108e-01f, 1.000000000000000e+00f, + 1.524529304401012e+00f, -6.838502689975459e-01f, + 1.000000000000000e+00f, -1.996808089693871e+00f, 9.999999999999998e-01f, + 1.797650895821358e+00f, -8.356641238925192e-01f, + 1.000000000000000e+00f, -1.999398698654737e+00f, 1.000000000000000e+00f, + 1.941833848024410e+00f, -9.614543316508065e-01f}; + + // BP48 Decimated fSample = 3000 Hz + float coeff32bp48[20] = { + 3.764439090680418e-03f, 3.650362666517592e-03f, 3.764439090680417e-03f, + 1.000000000000000e+00f, -5.926113222413726e-01f, 1.000000000000000e+00f, + 1.569400123464342e+00f, -7.055760636246757e-01f, + 1.000000000000000e+00f, -1.997300534603388e+00f, 9.999999999999998e-01f, + 1.816002936969042e+00f, -8.483092827984080e-01f, + 1.000000000000000e+00f, -1.999491318923814e+00f, 1.000000000000000e+00f, + 1.947984087804752e+00f, -9.645809301964445e-01f}; + + // BP96 Decimated fSample = 6000 Hz + float coeff32bp96[20] = { + 1.554430361573611e-03f, 5.765630035510897e-04f, 1.554430361573611e-03f, + 1.803990601116592e+00f, -8.406882700444890e-01f, + 1.000000000000000e+00f, -1.541034615722940e+00f, 9.999999999999998e-01f, + 1.846550880066576e+00f, -9.250235034256622e-01f, + 1.000000000000000e+00f, -1.999319784384852e+00f, 9.999999999999998e-01f, + 1.913429616511321e+00f, -9.218266406823739e-01f, + 1.000000000000000e+00f, -1.999871669777634e+00f, 1.000000000000000e+00f, + 1.978038062492293e+00f, -9.822341038950552e-01f}; + + // BP100 Decimated fSample = 6250 Hz + float coeff32bp100[20] = { + 1.504180063396739e-03f, -6.773089598829345e-04f, 1.504180063396738e-03f, + 1.812635069874353e+00f, -8.465624856328144e-01f, + 1.000000000000000e+00f, -1.573630872142354e+00f, 1.000000000000000e+00f, + 1.855377871833802e+00f, -9.278512110118003e-01f, + 1.000000000000000e+00f, -1.999372984381045e+00f, 9.999999999999998e-01f, + 1.917101697268557e+00f, -9.248525551899550e-01f, + 1.000000000000000e+00f, -1.999881702874664e+00f, 1.000000000000000e+00f, + 1.979072537983060e+00f, -9.829412184208248e-01f}; + + /* Designs a four-notch filter to null out a tone. At 100 Hz the + * null width of the four staggered tones is about 2Hz wide. + * iirNullsCoeffs is a pointer to an array of 20 floats. The nulls + * filter is always designed here and there is no need for + * caller-designed filters. + */ + void setNotchX4(float frequency) + { + float kf[] = {0.9955f, 0.9982f, 1.0018f, 1.0045f}; // Offset ratios + float Q = 60.0f; + float w0, sinW0, alpha, cosW0, scale; + for (int ii = 0; ii<4; ii++) // Over 4 cascaded BiQuad filters + { + w0 = kf[ii]*frequency*(2.0f*3.141592654f*16.0f / sample_rate_Hz); + sinW0 = sin(w0); + alpha = sinW0 / (Q * 2.0f); + cosW0 = cos(w0); + scale = 1.0f / (1.0f+alpha); + /* b0 */ iirNullsCoeffs[5*ii + 0] = scale; + /* b1 */ iirNullsCoeffs[5*ii + 1] = -2.0f * cosW0 * scale; + /* b2 */ iirNullsCoeffs[5*ii + 2] = scale; + /* a0 always 1.0f */ + /* a1 */ iirNullsCoeffs[5*ii + 3] = 2.0f * cosW0 * scale; + /* a2 */ iirNullsCoeffs[5*ii + 4] = -(1.0f - alpha) * scale; + } + } + }; +#endif diff --git a/keywords.txt b/keywords.txt index de3c08a..ca0b8d4 100644 --- a/keywords.txt +++ b/keywords.txt @@ -7,6 +7,72 @@ audio_block_f32_t KEYWORD1 AudioStream_F32 KEYWORD1 AudioConnection_F32 KEYWORD1 +AudioAnalyzeFFT256_IQ_F32 KEYWORD1 +windowFunction KEYWORD2 +getData KEYWORD2 +getWindow KEYWORD2 +putWindow KEYWORD2 +setOutputType KEYWORD2 +setNAverage KEYWORD2 +setXAxis KEYWORD2 + +AudioAnalyzeFFT1024_F32 KEYWORD1 +AudioAnalyzeFFT2048_F32 KEYWORD1 +AudioAnalyzeFFT4096_F32 KEYWORD1 + +AudioAnalyzePeak_F32 KEYWORD1 +readPeakToPeak KEYWORD2 + +AudioAnalyzePhase_F32 KEYWORD1 +setAnalyzePhaseConfig + +AudioAnalyzeToneDetect_F32 KEYWORD1 +threshold KEYWORD2 +set_params KEYWORD2 + +AudioAnalyzeRMS_F32 KEYWORD1 +showError KEYWORD1 + +AudioCalcEnvelope_F32 KEYWORD1 +smooth_env KEYWORD2 +setAttackRelease_msec KEYWORD2 +setDefaultValues KEYWORD2 +setSampleRate_Hz KEYWORD2 +resetStates KEYWORD2 +getCurrentLevel KEYWORD2 + +AudioCalcGainDecWDRC_F32 KEYWORD1 +calcGainFromEnvelope KEYWORD2 +WDRC_circuit_gain KEYWORD2 +setDefaultValues KEYWORD2 +setParams_from_CHA_WDRC KEYWORD2 +setGain_dB KEYWORD2 +incrementGain_dB KEYWORD2 +getCurrentGain KEYWORD2 +getCurrentGain_dB KEYWORD2 +setDecimationFactor KEYWORD2 +setMaxdB KEYWORD2 +getMaxdB KEYWORD2 +setKneeExpansion_dBSPL KEYWORD2 +getKneeExpansion_dBSPL KEYWORD2 +setExpansionCompRatio KEYWORD2 +getExpansionCompRatio KEYWORD2 +setKneeCompressor_dBSPL KEYWORD2 +getKneeCompressor_dBSPL KEYWORD2 +setCompRatio KEYWORD2 +getCompRatio KEYWORD2 +setKneeLimiter_dBSPL KEYWORD2 +getKneeLimiter_dBSPL KEYWORD2 +undb KEYWORD2 +db2 KEYWORD2 +log2f_approx KEYWORD2 + +AudioCalcGainWDRC_F32 KEYWORD1 + +AudioConfigFIRFilterBank_F32 KEYWORD1 +createFilterCoeff KEYWORD2 +computeLogSpacedCornerFreqs KEYWORD2 + AudioControlTLV320AIC3206 KEYWORD1 inputSelect KEYWORD2 setMicBias KEYWORD2 @@ -15,7 +81,8 @@ volume_dB KEYWORD2 setInputGain_dB KEYWORD2 AudioControlSGTL5000_Extended KEYWORD1 -micBiasEnable KEYWORD2 +micBiasEnable KEYWORD2 +micBiasDisable KEYWORD2 AudioConvert_I16toF32 KEYWORD1 AudioConvert_F32toI16 KEYWORD1 @@ -23,7 +90,28 @@ AudioConvert_F32toI16 KEYWORD1 AudioEffectGain_F32 KEYWORD1 setGain_dB KEYWORD2 +analyze_CTCSS_F32 KEYWORD1 +initCTCSS KEYWORD2 +readTonePower KEYWORD2 +readRefPower KEYWORD2 +readTonePresent KEYWORD2 +thresholds KEYWORD2 +setCTCSS_BP KEYWORD2 + +AudioFilterBiquad_F32 KEYWORD1 +doClassInit KEYWORD2 +setCoefficients KEYWORD2 +setBlockDC KEYWORD2 +setFilterCoeff_Matlab KEYWORD2 +setLowpass KEYWORD2 +setHighpass KEYWORD2 +setBandpass KEYWORD2 +setNotch KEYWORD2 + AudioFilterFIR_F32 KEYWORD1 +setSampleRate_Hz KEYWORD2 +setBlockLength KEYWORD2 + AudioEffectIIR_F32 KEYWORD1 AudioEffectCompressor_F32 KEYWORD1 AudioInputI2S_F32 KEYWORD1 @@ -31,13 +119,32 @@ AudioMemory_F32 KEYWORD1 AudioMemoryUsage_F32 KEYWORD1 AudioMemoryUsageMax_F32 KEYWORD1 AudioMemoryUsageMaxReset_F32 KEYWORD1 + AudioMixer4_F32 KEYWORD1 +gain KEYWORD2 + AudioMultiply_F32 KEYWORD1 AudioOutputI2S_F32 KEYWORD1 AudioPlayQueue_F32 KEYWORD1 + AudioRecordQueue_F32 KEYWORD1 +begin KEYWORD1 +clear KEYWORD1 +available KEYWORD1 +readBuffer KEYWORD1 +freeBuffer KEYWORD1 +end KEYWORD1 + +AudioSynthGaussian_F32 KEYWORD1 + AudioSynthNoiseWhite_F32 KEYWORD1 AudioSynthNoisePink_F32 KEYWORD1 + +AudioSynthSineCosine_F32 KEYWORD1 +phase_r KEYWORD2 +phaseS_C_r KEYWORD2 +simple KEYWORD2 + AudioSynthWaveform_F32 KEYWORD1 @@ -46,4 +153,17 @@ frequency KEYWORD2 phase KEYWORD2 amplitude KEYWORD2 +RadioFMDetector_F32 KEYWORD1 +filterIQ KEYWORD2 +filterOut KEYWORD2 +setSquelchFilter KEYWORD2 +getSquelchLevel KEYWORD2 +setSquelchDecay KEYWORD2 +setSquelchThreshold KEYWORD2 +showError KEYWORD2 +returnInitializeFMError KEYWORD2 +radioModulatedGenerator_F32 KEYWORD1 +setFMScale KEYWORD2 +amplitudeQI KEYWORD2 +doModulation_AM_PM_FM KEYWORD2