/* * radioVoiceClipper_F32.h * * 12 March 2023 (c) copyright Bob Larkin * But with With much credit to: * Chip Audette (OpenAudio) * and of course, to PJRC for the Teensy and Teensy Audio Library * * The development of this Voice Clipper was by Bob Larkin, W7PUA, based * entirely on ideas and suggestions from Dave Hershberger, W9GR. * Many thanks to Dave. Note that this clipper is is a "real variable" * version of the Single Sideband CESSB clipper. See the companion * radioCESSBtransmit_F32.h class which uses all the same principles. * * The input signal is a voice (or tones) that will, in general, have * been compressed in amplitude, keeping the maximum amplitude close to * 1.0 peak-to-center. For this class, clipping occurs for any input * greater than 1/gainIn where gainIn comes from the public function * setGains(). Normally gainIn has a value around 1.5 and so clipping occurs * for inputs above peak levels of 2/3=0.667. For this level of gaiIn, * there will be about 3 dB of increase in the average power of the voice * but still minimal perception of "over-processing." * * Internally the audio is clipped at the higher levels and the resulting * out-of-band distion is low pass filtered. Next, the overshoot that * occurs with the filter is removed by measuring the overshoot, low-pass * filtering the overshoot and subtracting it off. All this requires * care with the timing as all of the filtering steps involve delays. * * The compressor2 class in this F32 library is intended to precede this * class. * * NOTE: Do NOT follow this block with any non-linear phase filtering, * such as IIR. Minimize any linear-phase filtering such as FIR. * Such activities enhance the overshoots and defeat the purpose of clipping. * * An important note: This clipper is suitable for voice modes, such as * AM or NBFM. Do not use this clipper ahead of a single sideband * transmitter. That is what the CESSB class is for. * * The following reference has information on CESSB, in detail, as well * as on the use of clippers, similar to this one, in broadcast work: * Hershberger, D.L. (2014): Controlled Envelope Single Sideband. QEX * November/December 2014 pp3-13. * http://www.arrl.org/files/file/QEX_Next_Issue/2014/Nov-Dec_2014/Hershberger_QEX_11_14.pdf * * Status: Experimental * * Inputs: 0 is voice audio input * Outputs: 0 is clipped voice. * * Functions, available during operation: * void setSampleRate_Hz(float32_t fs_Hz) Allows dynamic sample rate change. * * struct levels* getLevels(int what) { * what = 0 returns a pointer to struct levels before data is ready * what = 1 returns a pointer to struct levels * * uint32_t levelDataCount() return countPower0 * * void setGains(float32_t gIn, float32_t gCompensate, float32_t gOut) * * Time: T3.6 For an update of a 128 sample block, estimated microseconds * T4.0 For an update of a 128 sample block, measured microseconds * These times are for a 48 ksps rate. * * NOTE: Do NOT follow this block with any non-linear phase filtering, * such as IIR. Minimize any linear-phase filtering such as FIR. * Such activities enhance the overshoots and defeat the purpose of clipping. */ #ifndef _radioVoiceClipper_f32_h #define _radioVoiceClipper_f32_h #include "Arduino.h" #include "AudioStream_F32.h" #include "arm_math.h" #include "mathDSP_F32.h" #define VC_SAMPLE_RATE_0 0 #define VC_SAMPLE_RATE_11_12 1 #define VC_SAMPLE_RATE_44_50 2 #define VC_SAMPLE_RATE_88_100 3 #ifndef M_PI #define M_PI 3.141592653589793f #endif #ifndef M_PI_2 #define M_PI_2 1.570796326794897f #endif #ifndef M_TWOPI #define M_TWOPI (M_PI * 2.0f) #endif // For the average power and peak voltage readings, global struct levelClipper { float32_t pwr0; float32_t peak0; float32_t pwr1; float32_t peak1; uint32_t countP; // Number of averaged samples for pwr0. }; class radioVoiceClipper_F32 : public AudioStream_F32 { //GUI: inputs:1, outputs:2 //this line used for automatic generation of GUI node //GUI: shortName:CESSBTransmit //this line used for automatic generation of GUI node public: radioVoiceClipper_F32(void) : AudioStream_F32(1, inputQueueArray_f32) { setSampleRate_Hz(AUDIO_SAMPLE_RATE); //uses default AUDIO_SAMPLE_RATE from AudioStream.h //setBlockLength(128); Always default 128 } radioVoiceClipper_F32(const AudioSettings_F32 &settings) : AudioStream_F32(1, inputQueueArray_f32) { setSampleRate_Hz(settings.sample_rate_Hz); //setBlockLength(128); Always default 128 } // Sample rate starts at default 44.1 ksps. That will work. Filters // are designed for 48 and 96 ksps, however. This is a *required* // function at setup(). void setSampleRate_Hz(const float _fs_Hz) { sample_rate_Hz = _fs_Hz; if(sample_rate_Hz>10900.0f && sample_rate_Hz<12600.0f) { // Design point is 12 ksps. No initial decimation. Interpolate // to 24 ksps for clipping and then decimate back to 12 at the end. sampleRate = VC_SAMPLE_RATE_11_12; nW = 128; nC = 256; countLevelMax = 10; // About 0.1 sec for 12 ksps inverseMaxCount = 1.0f/(float32_t)countLevelMax; arm_fir_init_f32(&firInstInterpolate1I, 23, (float32_t*)interpolateFilter1, &pStateInterpolate1I[0], nC); arm_fir_init_f32(&firInstClipperI, 123, (float32_t*)clipperOut, &pStateClipperI[0], nC); arm_fir_init_f32(&firInstOShootI, 123, (float32_t*)clipperOut, &pStateOShootI[0], nC); } else if(sample_rate_Hz>43900.0f && sample_rate_Hz<50100.0f) { // Design point is 48 ksps sampleRate = VC_SAMPLE_RATE_44_50; nW = 32; nC = 64; countLevelMax = 37; // About 0.1 sec for 48 ksps inverseMaxCount = 1.0f/(float32_t)countLevelMax; arm_fir_decimate_init_f32(&decimateInst, 65, 4, (float32_t*)decimateFilter48, &pStateDecimate[0], 128); arm_fir_init_f32(&firInstInterpolate1I, 23, (float32_t*)interpolateFilter1, &pStateInterpolate1I[0], nC); arm_fir_init_f32(&firInstClipperI, 123, (float32_t*)clipperOut, &pStateClipperI[0], nC); arm_fir_init_f32(&firInstOShootI, 123, (float32_t*)clipperOut, &pStateOShootI[0], nC); arm_fir_init_f32(&firInstInterpolate2I, 23, (float32_t*)interpolateFilter1, &pStateInterpolate2I[0], nC); } else if(sample_rate_Hz>88000.0f && sample_rate_Hz<100100.0f) { // GET THINGS WORKING AT VC_SAMPLE_RATE_44_50 FIRST AND THEN FIX UP 96 ksps // Design point is 96 ksps /* sampleRate = VC_SAMPLE_RATE_88_100; //<<<<<<<<<<<<<<<<<<<<<