/* * radioCESSB_Z_transmit_F32.h * * This is a modification of the CESSB algorithm to output SSB at zero carrier * instead of 1350 Hz as the Weaver modulation produces. This allows * transmission with zero-IF radios where the finite carrier balance * of the hardware mixers produces the mid-band tone.The basic change * is to use the phasing method in place of the Weaver method. However, * all filters needed to be changed and are at the bottom of this file. * The 12 and 24 ksps sample rates of the radioCESSB_transmit_F32 class * are continued here as they were more than adequate for the Weaver method. * The sine/cosine oscillator is not needed here and has been removed. * * * 18 Jan 2023 (c) copyright Bob Larkin * But with With much credit to: * Chip Audette (OpenAudio) * and of course, to PJRC for the Teensy and Teensy Audio Library * * The development of the Controlled Envelope Single Side Band (CESSB) * was done by Dave Hershberger, W9GR. Many thanks to Dave. * The following description is mostly taken * from Frank, DD4WH and is on line at the GNU Radio site, ref: * https://github-wiki-see.page/m/df8oe/UHSDR/wiki/Controlled-Envelope-Single-Sideband-CESSB * and has been revised by Bob L. to reflect the phasing method change. * * Controlled Envelope Single Sideband is an invention by Dave Hershberger * W9GR with the aim to "allow your rig to output more average power while * keeping peak envelope power PEP the same". The increase in perceived * loudness can be up to 4dB without any audible increase in distortion * and without making you sound "processed" (Hershberger 2014, 2016b). * * The principle to achieve this is relatively simple. The process * involves only audio baseband processing which can be done digitally in * software without the need for modifications in the hardware or messing * with the RF output of your rig. * * Controlled Envelope Single Sideband can be produced using three * processing blocks making up a complete CESSB system: * 1. An SSB modulator. This is implemented as the phasing method to allow * minimum (12 kHz) decimated sample rate with the output of I & Q * signals (a complex SSB signal). * 2. A baseband envelope clipper. This takes the modulus of the I & Q * signals (also called the magnitude), which is sqrt(I * I + Q * Q) * and divides the I & Q signals by the modulus, IF the signal is * larger than 1.0. If not, the signal remains untouched. After * clipping, the signal is lowpass filtered with a linear phase FIR * low pass filter with a stopband frequency of 3.0kHz * 3. An overshoot controller . This does something similar as the * envelope clipper: Again, the modulus is calculated (but now on * the basis of the current and two preceding and two subsequent * samples). If the signals modulus is larger than 1 (clipping), * the signals I and Q are divided by the maximum of 1 or of * (1.9 * signal). That means the clipping is overcompensated by 1.9 * [the phasing method seems to perform best with 1.4*signal] * which leads to a much better suppression of the overshoots from * the first two stages. Finally, the resulting signal is again * lowpass-filtered with a linear phase FIR filter with stopband * frequency of 3.0khz * * It is important that the sample rate is high enough so that the higher * frequency components of the output of the modulator, clipper and * overshoot controller do not alias back into the desired signal. Also * all the filters should be linear phase filters (FIR, not IIR). * * This CESSB system can reduce the overshoot of the SSB modulator from * 61% to 1.3%, meaning about 2.5 times higher perceived SSB output power * (Hershberger 2014). * * References: * 1-Hershberger, D.L. (2014): Controlled Envelope Single Sideband. QEX * November/December 2014 pp3-13. * http://www.arrl.org/files/file/QEX_Next_Issue/2014/Nov-Dec_2014/Hershberger_QEX_11_14.pdf * 2-Hershberger, D.L. (2016a): External Processing for Controlled * Envelope Single Sideband. - QEX January/February 2016 pp9-12. * http://www.arrl.org/files/file/QEX_Next_Issue/2016/January_February_2016/Hershberger_QEX_1_16.pdf * 3-Hershberger, D.L. (2016b): Understanding Controlled Envelope Single * Sideband. - QST February 2016 pp30-36. * 4-Forum discussion on CESSB on the Flex-Radio forum, * https://community.flexradio.com/discussion/6432965/cessb-questions * * Status: Experimental * * Inputs: 0 is voice audio input * Outputs: 0 is I 1 is Q * * Functions, available during operation: * void frequency(float32_t fr) Sets LO frequency Hz * * void setSampleRate_Hz(float32_t fs_Hz) Allows dynamic sample rate change for this function * * struct levels* getLevels(int what) { * what = 0 returns a pointer to struct levels before data is ready * what = 1 returns a pointer to struct levels * * uint32_t levelDataCount() return countPower0 * * void setGains(float32_t gIn, float32_t gCompensate, float32_t gOut) * * Time: T3.6 For an update of a 128 sample block, estimated 700 microseconds * T4.0 For an update of a 128 sample block, measured 211 microseconds * These times are for a 48 ksps rate. * * NOTE: Do NOT follow this block with any non-linear phase filtering, * such as IIR. Minimize any linear-phase filtering such as FIR. * Such activities enhance the overshoots and defeat the purpose of CESSB. */ // Rev 14Oct24 Added on/off via cessbProcessing. Tnx KF5N. #ifndef _radioCESSB_Z_transmit_f32_h #define _radioCESSB_Z_transmit_f32_h #include "Arduino.h" #include "AudioStream_F32.h" #include "arm_math.h" #include "mathDSP_F32.h" #define SAMPLE_RATE_0 0 #define SAMPLE_RATE_44_50 1 #define SAMPLE_RATE_88_100 2 #ifndef M_PI #define M_PI 3.141592653589793f #endif #ifndef M_PI_2 #define M_PI_2 1.570796326794897f #endif #ifndef M_TWOPI #define M_TWOPI (M_PI * 2.0f) #endif // For the average power and peak voltage readings, global struct levelsZ { float32_t pwr0; float32_t peak0; float32_t pwr1; float32_t peak1; uint32_t countP; // Number of averaged samples for pwr0. }; class radioCESSB_Z_transmit_F32 : public AudioStream_F32 { //GUI: inputs:1, outputs:2 //this line used for automatic generation of GUI node //GUI: shortName:CESSBTransmit //this line used for automatic generation of GUI node public: radioCESSB_Z_transmit_F32(void) : AudioStream_F32(1, inputQueueArray_f32) { setSampleRate_Hz(AUDIO_SAMPLE_RATE); //uses default AUDIO_SAMPLE_RATE from AudioStream.h //setBlockLength(128); Always default 128 } radioCESSB_Z_transmit_F32(const AudioSettings_F32 &settings) : AudioStream_F32(1, inputQueueArray_f32) { setSampleRate_Hz(settings.sample_rate_Hz); //setBlockLength(128); Always default 128 } // A "setter" and "getter" methods. If cessbProcessing==false, CESSB processing is bypassed. // This is intended for digital modes. Greg KF5N August 16 2024 void setProcessing(bool cessbActive) { cessbProcessing = cessbActive; } bool getProcessing(void) { return cessbProcessing; } // Sample rate starts at default 44.1 ksps. That will work. Filters // are designed for 48 and 96 ksps, however. This is a *required* // function at setup(). void setSampleRate_Hz(const float fs_Hz) { sample_rate_Hz = fs_Hz; if(sample_rate_Hz>44000.0f && sample_rate_Hz<50100.0f) { // Design point is 48 ksps sampleRate = SAMPLE_RATE_44_50; nW = 32; nC = 64; countLevelMax = 37; // About 0.1 sec for 48 ksps inverseMaxCount = 1.0f/(float32_t)countLevelMax; arm_fir_decimate_init_f32(&decimateInst, 65, 4, (float32_t*)decimateFilter48, &pStateDecimate[0], 128); arm_fir_init_f32(&firInstHilbertI, 201, (float32_t*)hilbert201_130Hz12000Hz, &pStateHilbertI[0], nW); arm_fir_init_f32(&firInstInterpolate1I, 23, (float32_t*)interpolateFilter1, &pStateInterpolate1I[0], nC); arm_fir_init_f32(&firInstInterpolate1Q, 23, (float32_t*)interpolateFilter1, &pStateInterpolate1Q[0], nC); arm_fir_init_f32(&firInstClipperI, 123, (float32_t*)clipperOut, &pStateClipperI[0], nC); arm_fir_init_f32(&firInstClipperQ, 123, (float32_t*)clipperOut, &pStateClipperQ[0], nC); arm_fir_init_f32(&firInstOShootI, 123, (float32_t*)clipperOut, &pStateOShootI[0], nC); arm_fir_init_f32(&firInstOShootQ, 123, (float32_t*)clipperOut, &pStateOShootQ[0], nC); arm_fir_init_f32(&firInstInterpolate2I, 23, (float32_t*)interpolateFilter1, &pStateInterpolate2I[0], nC); arm_fir_init_f32(&firInstInterpolate2Q, 23, (float32_t*)interpolateFilter1, &pStateInterpolate2Q[0], nC); } else if(sample_rate_Hz>88000.0f && sample_rate_Hz<100100.0f) { // GET THINGS WORKING AT SAMPLE_RATE_44_50 FIRST AND THEN FIX UP 96 ksps // Design point is 96 ksps /* sampleRate = SAMPLE_RATE_88_100; //<<<<<<<<<<<<<<<<<<<<<