You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('') and can be up to 35 characters long.
459 lines
21 KiB
459 lines
21 KiB
/*


* 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 zeroIF radios where the finite carrier balance


* of the hardware mixers produces the midband 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://githubwikisee.page/m/df8oe/UHSDR/wiki/ControlledEnvelopeSingleSidebandCESSB


* 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


* lowpassfiltered 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:


* 1Hershberger, D.L. (2014): Controlled Envelope Single Sideband. QEX


* November/December 2014 pp313.


* http://www.arrl.org/files/file/QEX_Next_Issue/2014/NovDec_2014/Hershberger_QEX_11_14.pdf


* 2Hershberger, D.L. (2016a): External Processing for Controlled


* Envelope Single Sideband.  QEX January/February 2016 pp912.


* http://www.arrl.org/files/file/QEX_Next_Issue/2016/January_February_2016/Hershberger_QEX_1_16.pdf


* 3Hershberger, D.L. (2016b): Understanding Controlled Envelope Single


* Sideband.  QST February 2016 pp3036.


* 4Forum discussion on CESSB on the FlexRadio forum,


* https://community.flexradio.com/discussion/6432965/cessbquestions


*


* 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 nonlinear phase filtering,


* such as IIR. Minimize any linearphase filtering such as FIR.


* Such activities enhance the overshoots and defeat the purpose of CESSB.


*/




#ifndef _radioCESSB_Z_transmit_f32_h


#define _radioCESSB_Z_transmit_f32_h




#include "Arduino.h"


#include "AudioStream_F32.h"


#include "arm_math.h"


#include "mathDSP_F32.h"




#define SAMPLE_RATE_0 0


#define SAMPLE_RATE_44_50 1


#define SAMPLE_RATE_88_100 2




#ifndef M_PI


#define M_PI 3.141592653589793f


#endif




#ifndef M_PI_2


#define M_PI_2 1.570796326794897f


#endif




#ifndef M_TWOPI


#define M_TWOPI (M_PI * 2.0f)


#endif




// For the average power and peak voltage readings, global


struct levelsZ {


float32_t pwr0;


float32_t peak0;


float32_t pwr1;


float32_t peak1;


uint32_t countP; // Number of averaged samples for pwr0.


};




class radioCESSB_Z_transmit_F32 : public AudioStream_F32 {


//GUI: inputs:1, outputs:2 //this line used for automatic generation of GUI node


//GUI: shortName:CESSBTransmit //this line used for automatic generation of GUI node


public:


radioCESSB_Z_transmit_F32(void) :


AudioStream_F32(1, inputQueueArray_f32)


{


setSampleRate_Hz(AUDIO_SAMPLE_RATE);


//uses default AUDIO_SAMPLE_RATE from AudioStream.h


//setBlockLength(128); Always default 128


}




radioCESSB_Z_transmit_F32(const AudioSettings_F32 &settings) :


AudioStream_F32(1, inputQueueArray_f32)


{


setSampleRate_Hz(settings.sample_rate_Hz);


//setBlockLength(128); Always default 128


}




// Sample rate starts at default 44.1 ksps. That will work. Filters


// are designed for 48 and 96 ksps, however. This is a *required*


// function at setup().


void setSampleRate_Hz(const float fs_Hz) {


sample_rate_Hz = fs_Hz;


if(sample_rate_Hz>44000.0f && sample_rate_Hz<50100.0f)


{


// Design point is 48 ksps


sampleRate = SAMPLE_RATE_44_50;


nW = 32;


nC = 64;


countLevelMax = 37; // About 0.1 sec for 48 ksps


inverseMaxCount = 1.0f/(float32_t)countLevelMax;




arm_fir_decimate_init_f32(&decimateInst, 65, 4,


(float32_t*)decimateFilter48, &pStateDecimate[0], 128);




arm_fir_init_f32(&firInstHilbertI, 201, (float32_t*)hilbert201_130Hz12000Hz,


&pStateHilbertI[0], nW);




arm_fir_init_f32(&firInstInterpolate1I, 23, (float32_t*)interpolateFilter1,


&pStateInterpolate1I[0], nC);


arm_fir_init_f32(&firInstInterpolate1Q, 23, (float32_t*)interpolateFilter1,


&pStateInterpolate1Q[0], nC);




arm_fir_init_f32(&firInstClipperI, 123, (float32_t*)clipperOut,


&pStateClipperI[0], nC);


arm_fir_init_f32(&firInstClipperQ, 123, (float32_t*)clipperOut,


&pStateClipperQ[0], nC);




arm_fir_init_f32(&firInstOShootI, 123, (float32_t*)clipperOut,


&pStateOShootI[0], nC);


arm_fir_init_f32(&firInstOShootQ, 123, (float32_t*)clipperOut,


&pStateOShootQ[0], nC);




arm_fir_init_f32(&firInstInterpolate2I, 23, (float32_t*)interpolateFilter1,


&pStateInterpolate2I[0], nC);


arm_fir_init_f32(&firInstInterpolate2Q, 23, (float32_t*)interpolateFilter1,


&pStateInterpolate2Q[0], nC);


}


else if(sample_rate_Hz>88000.0f && sample_rate_Hz<100100.0f)


{


// GET THINGS WORKING AT SAMPLE_RATE_44_50 FIRST AND THEN FIX UP 96 ksps


// Design point is 96 ksps


/* sampleRate = SAMPLE_RATE_88_100; //<<<<<<<<<<<<<<<<<<<<<<FIXUP


nW = 16;


nC = 32;


countLevelMax = 75; // About 0.1 sec for 96 ksps


inverseMaxCount = 1.0f/(float32_t)countLevelMax;


arm_fir_decimate_init_f32 (&decimateInst, 55, 4,


(float32_t*)decimateFilter48, pStateDecimate, 128);


arm_fir_init_f32(&firInstClipper, 199, basebandFilter,


&StateFirClipperF32[0], 128);


*/


}


else


{


// Unsupported sample rate


sampleRate = SAMPLE_RATE_0;


nW = 1;


nC = 1;


}


newLevelDataReady = false;


}




struct levelsZ* getLevels(int what) {


if(what != 0) // 0 leaves a way to get pointer before data is ready


{


levelData.pwr0 = powerSum0/((float32_t)countPower0);


levelData.peak0 = maxMag0;


levelData.pwr1 = powerSum1/(float32_t)countPower1;


levelData.peak1 = maxMag1;


levelData.countP = countPower0;




// Automatic reset for next set of readings


powerSum0 = 0.0f;


maxMag0 = 1.0f;


powerSum1 = 0.0f;


maxMag1 = 1.0f;


countPower0 = 0;


countPower1 = 0;


}


return &levelData;


}




uint32_t levelDataCount(void) {


return countPower0; // Input count, out may be different


}




void setGains(float32_t gIn, float32_t gCompensate, float32_t gOut)


{


gainIn = gIn;


gainCompensate = gCompensate;


gainOut = gOut;


}




// Small corrections at the output end of this object can patch up hardware flaws.


// _gI should be close to 1.0, _gXIQ and _gXQI should be close to 0.0.


void setIQCorrections(bool _useCor, float32_t _gI, float32_t _gXIQ, float32_t _gXQI)


{


useIQCorrection = _useCor;


gainI = _gI;


crossIQ = _gXIQ;


crossQI = _gXQI;


}




// The LSB/USB selection depends on the processing of the IQ signals


// inside this class. It may get flipped with later processing.


void setSideband(bool _sbReverse)


{


sidebandReverse = _sbReverse;


}




virtual void update(void);




private:


void sincos_Z_(float32_t ph);


struct levelsZ levelData;


audio_block_f32_t *inputQueueArray_f32[1];


uint32_t jjj = 0; // Used for diagnostic printing




// Input/Output is at 48 or 96 ksps. Hilbert generation is at 12 ksps.


// Clipping and overshoot processing is at 24 ksps.


// Next line is to indicate that setSampleRateHz() has not executed


int sampleRate = SAMPLE_RATE_0;


float32_t sample_rate_Hz = AUDIO_SAMPLE_RATE; // 44.1 ksps


int16_t nW = 32; // 32 or 16


int16_t nC = 64; // 64 or 32


uint16_t block_length = 128;


bool sidebandReverse = false;




bool useIQCorrection = false;


float32_t gainI = 1.0f;


float32_t crossIQ = 0.0f;


float32_t crossQI = 0.0f;




float32_t pStateDecimate[128 + 65  1]; // Goes with CMSIS decimate function


arm_fir_decimate_instance_f32 decimateInst;




float32_t pStateHilbertI[32 + 201  1];


arm_fir_instance_f32 firInstHilbertI;




float32_t pStateInterpolate1I[64 + 23  1]; // For interpolate 12 to 24 ksps


arm_fir_instance_f32 firInstInterpolate1I;


float32_t pStateInterpolate1Q[64 + 23  1];


arm_fir_instance_f32 firInstInterpolate1Q;






float32_t pStateClipperI[64 + 123  1]; // Goes with Clipper filter


arm_fir_instance_f32 firInstClipperI; // at 24 ksps


float32_t pStateClipperQ[64 + 123  1];


arm_fir_instance_f32 firInstClipperQ;






float32_t pStateOShootI[64+1231];


arm_fir_instance_f32 firInstOShootI;


float32_t pStateOShootQ[64+1231];


arm_fir_instance_f32 firInstOShootQ;




float32_t pStateInterpolate2I[128 + 23  1]; // For interpolate 12 to 24 ksps


arm_fir_instance_f32 firInstInterpolate2I;


float32_t pStateInterpolate2Q[128 + 23  1];


arm_fir_instance_f32 firInstInterpolate2Q;




// float32_t sn, cs;


float32_t gainIn = 1.0f;


float32_t gainCompensate = 1.4f;


float32_t gainOut = 1.0f; // Does not change CESSB, here for convenience to set transmit power




float32_t delayHilbertQ[128];


uint16_t indexDelayHilbertQ = 0;




// A tiny delay to allow negative time for the previous path


float32_t osEnv[4];


uint16_t indexOsEnv = 4; // 0 to 3 by using a 2bit mask




// We need a delay for overshoot remove to account for the FIR


// filter in the correction path. Some where around 128 taps works


// but if we make the delay exactly 2^6=64 the delay line is simple


// resulting in a FIR size of 2*64+1=129 taps.


float32_t osDelayI[64];


float32_t osDelayQ[64];


uint16_t indexOsDelay = 64;




// RMS and Peak variable for monitoring levels and changes to the


// Peak to RMS ratio. These are temporary storage. Data is


// transferred by global levelData struct at the top of this file.


float32_t powerSum0 = 0.0f;


float32_t maxMag0 = 1.0f;


float32_t powerSum1 = 0.0f;


float32_t maxMag1 = 1.0f;


uint32_t countPower0 = 0;


uint32_t countPower1 = 0;




bool newLevelDataReady = false;


int countLevel = 0;


int countLevelMax = 37; // About 0.1 sec for 48 ksps


float32_t inverseMaxCount = 1.0f/(float32_t)countLevelMax;




/* Input filter for decimate by 4:


* FIR filter designed with http://tfilter.appspot.com


* Sampling frequency: 48000 Hz


* 0 Hz  3000 Hz ripple = 0.075 dB


* 6000 Hz  24000 Hz atten = 95.93 dB */


const float32_t decimateFilter48[65] = {


0.00004685f, 0.00016629f, 0.00038974f, 0.00073279f, 0.00113663f, 0.00148721f,


0.00159057f, 0.00125129f, 0.00032821f,0.00114283f,0.00289782f,0.00441933f,


0.00505118f,0.00418143f,0.00151748f, 0.00268876f, 0.00751487f, 0.01147689f,


0.01286243f, 0.01027735f, 0.00323528f,0.00737003f,0.01913035f,0.02842381f,


0.03117447f,0.02390063f,0.00480378f, 0.02544011f, 0.06344286f, 0.10357132f,


0.13904464f, 0.16342506f, 0.17210799f, 0.16342506f, 0.13904464f, 0.10357132f,


0.06344286f, 0.02544011f,0.00480378f,0.02390063f,0.03117447f,0.02842381f,


0.01913035f,0.00737003f, 0.00323528f, 0.01027735f, 0.01286243f, 0.01147689f,


0.00751487f, 0.00268876f,0.00151748f,0.00418143f,0.00505118f,0.00441933f,


0.00289782f,0.00114283f, 0.00032821f, 0.00125129f, 0.00159057f, 0.00148721f,


0.00113663f, 0.00073279f, 0.00038974f, 0.00016629f, 0.00004685};




/* 90 degree Hilbert filter


* FIR filter designed Iowa Hills suite  Thank you.


* Sampling frequency: 12000 Hz


* 130 Hz  5870 Hz ripple = 0.0036 dB */


const float32_t hilbert201_130Hz12000Hz[201] = {


0.000000000f, 0.000081360f, 0.000000000f, 0.000114966f, 0.000000000f, 0.000155734f,


0.000000000f, 0.000204564f, 0.000000000f, 0.000262417f, 0.000000000f, 0.000330320f,


0.000000000f, 0.000409359f, 0.000000000f, 0.000500689f, 0.000000000f, 0.000605532f,


0.000000000f, 0.000725179f, 0.000000000f, 0.000860994f, 0.000000000f, 0.001014419f,


0.000000000f, 0.001186978f, 0.000000000f, 0.001380282f, 0.000000000f, 0.001596041f,


0.000000000f, 0.001836068f, 0.000000000f, 0.002102298f, 0.000000000f, 0.002396800f,


0.000000000f, 0.002721798f, 0.000000000f, 0.003079696f, 0.000000000f, 0.003473107f,


0.000000000f, 0.003904895f, 0.000000000f, 0.004378221f, 0.000000000f, 0.004896603f,


0.000000000f, 0.005463995f, 0.000000000f, 0.006084876f, 0.000000000f, 0.006764381f,


0.000000000f, 0.007508449f, 0.000000000f, 0.008324026f, 0.000000000f, 0.009219325f,


0.000000000f, 0.010204165f, 0.000000000f, 0.011290428f, 0.000000000f, 0.012492662f,


0.000000000f, 0.013828919f, 0.000000000f, 0.015321902f, 0.000000000f, 0.017000603f,


0.000000000f, 0.018902655f, 0.000000000f, 0.021077827f, 0.000000000f, 0.023593325f,


0.000000000f, 0.026542141f, 0.000000000f, 0.030056654f, 0.000000000f, 0.034331851f,


0.000000000f, 0.039667098f, 0.000000000f, 0.046546491f, 0.000000000f, 0.055806835f,


0.000000000f, 0.069029606f, 0.000000000f, 0.089604827f, 0.000000000f, 0.126348239f,


0.000000000f, 0.211587134f, 0.000000000f, 0.636276105f, 0.000000000f,0.636276105f,


0.000000000f,0.211587134f, 0.000000000f,0.126348239f, 0.000000000f,0.089604827f,


0.000000000f,0.069029606f, 0.000000000f,0.055806835f, 0.000000000f,0.046546491f,


0.000000000f,0.039667098f, 0.000000000f,0.034331851f, 0.000000000f,0.030056654f,


0.000000000f,0.026542141f, 0.000000000f,0.023593325f, 0.000000000f,0.021077827f,


0.000000000f,0.018902655f, 0.000000000f,0.017000603f, 0.000000000f,0.015321902f,


0.000000000f,0.013828919f, 0.000000000f,0.012492662f, 0.000000000f,0.011290428f,


0.000000000f,0.010204165f, 0.000000000f,0.009219325f, 0.000000000f,0.008324026f,


0.000000000f,0.007508449f, 0.000000000f,0.006764381f, 0.000000000f,0.006084876f,


0.000000000f,0.005463995f, 0.000000000f,0.004896603f, 0.000000000f,0.004378221f,


0.000000000f,0.003904895f, 0.000000000f,0.003473107f, 0.000000000f,0.003079696f,


0.000000000f,0.002721798f, 0.000000000f,0.002396800f, 0.000000000f,0.002102298f,


0.000000000f,0.001836068f, 0.000000000f,0.001596041f, 0.000000000f,0.001380282f,


0.000000000f,0.001186978f, 0.000000000f,0.001014419f, 0.000000000f,0.000860994f,


0.000000000f,0.000725179f, 0.000000000f,0.000605532f, 0.000000000f,0.000500689f,


0.000000000f,0.000409359f, 0.000000000f,0.000330320f, 0.000000000f,0.000262417f,


0.000000000f,0.000204564f, 0.000000000f,0.000155734f, 0.000000000f,0.000114966f,


0.000000000f,0.000081360f, 0.000000000};




/* Filter for outputs of clipper


* Use also overshoot corrector, but might be able to use less terms.


* FIR filter designed with http://tfilter.appspot.com


* Sample frequency: 24000 Hz


* 0 Hz  2800 Hz ripple = 0.14 dB


* 3200 Hz  12000 Hz atten = 40.51 dB */


const float32_t clipperOut[123] = {


0.003947255f, 0.001759588f, 0.002221444f, 0.002407244f, 0.001833343f, 0.000524622f,


0.000946260f,0.001768428f,0.001395297f, 0.000055916f, 0.001779024f, 0.002694998f,


0.002099736f, 0.000157764f,0.002092190f,0.003282801f,0.002542927f,0.000116969f,


0.002694319f, 0.004153363f, 0.003197589f, 0.000143560f,0.003346600f,0.005148200f,


0.003947437f,0.000152425f, 0.004166345f, 0.006378882f, 0.004871469f, 0.000164557f,


0.005173898f,0.007896395f,0.006014470f,0.000173552f, 0.006447615f, 0.009828080f,


0.007480359f, 0.000184482f,0.008116957f,0.012379161f,0.009436712f,0.000194737f,


0.010412610f, 0.015941971f, 0.012213107f, 0.000200845f,0.013823966f,0.021360759f,


0.016552097f,0.000205707f, 0.019544260f, 0.030836344f, 0.024523278f, 0.000211298f,


0.031509151f,0.052450055f,0.044811840f,0.000214078f, 0.074661107f, 0.158953216f,


0.225159581f, 0.250214862f, 0.225159581f, 0.158953216f, 0.074661107f,0.000214078f,


0.044811840f,0.052450055f,0.031509151f, 0.000211298f, 0.024523278f, 0.030836344f,


0.019544260f,0.000205707f,0.016552097f,0.021360759f,0.013823966f, 0.000200845f,


0.012213107f, 0.015941971f, 0.010412610f,0.000194737f,0.009436712f,0.012379161f,


0.008116957f, 0.000184482f, 0.007480359f, 0.009828080f, 0.006447615f,0.000173552f,


0.006014470f,0.007896395f,0.005173898f, 0.000164557f, 0.004871469f, 0.006378882f,


0.004166345f,0.000152425f,0.003947437f,0.005148200f,0.003346600f, 0.000143560f,


0.003197589f, 0.004153363f, 0.002694319f,0.000116969f,0.002542927f,0.003282801f,


0.002092190f, 0.000157764f, 0.002099736f, 0.002694998f, 0.001779024f, 0.000055916f,


0.001395297f,0.001768428f,0.000946260f, 0.000524622f, 0.001833343f, 0.002407244f,


0.002221444f, 0.001759588f,0.003947255f};




/* FIR filter designed with http://tfilter.appspot.com


* Sampling frequency: 24000 sps


* 0 Hz  3000 Hz gain = 2 ripple = 0.11 dB


* 6000 Hz  12000 Hz atten = 62.4 dB


* (At Sampling Frequency=48ksps, double all frequency values) */


const float32_t interpolateFilter1[23] = {


0.00413402f,0.01306124f,0.01106321f, 0.01383359f, 0.04386756f, 0.02731837f,


0.05470066f,0.12407408f,0.04389386f, 0.23355907f, 0.56707488f, 0.71763165f,


0.56707488f, 0.23355907f,0.04389386f,0.12407408f,0.05470066f, 0.02731837f,


0.04386756f, 0.01383359f,0.01106321f,0.01306124f,0.00413402};




}; // end Class


#endif


