parent
d5427c8a68
commit
69a298f51d
@ -0,0 +1,218 @@ |
|||||||
|
// CESSB_ZeroIF.ino
|
||||||
|
// This tests the Controlled Envelope Single Sideband generator version
|
||||||
|
// that produces a zero-IF signal 0to 3kHz or 0 to -3 kHz.
|
||||||
|
// Uses radioCESSB_Z_transmit_F32.h and .cpp. See the .h file for
|
||||||
|
// more information and references.
|
||||||
|
// Tests with voice from SD Card file and a 1 second 750 Hz tone burst.
|
||||||
|
//
|
||||||
|
// The SD card may connect to different pins, depending on the
|
||||||
|
// hardware you are using. Configure the SD card
|
||||||
|
// pins to match your hardware. It is set for T4.x Rev D PJRC
|
||||||
|
// Teensy Audio Adaptor card here.
|
||||||
|
//
|
||||||
|
// Your microSD card must have the WAV file loaded to it:
|
||||||
|
// W9GR48.WAV
|
||||||
|
// These are at
|
||||||
|
// https://github.com/chipaudette/OpenAudio_ArduinoLibrary/blob/master/utility/
|
||||||
|
//
|
||||||
|
// This example code is in the public domain.
|
||||||
|
|
||||||
|
#include <Audio.h> |
||||||
|
#include <Wire.h> |
||||||
|
#include <SPI.h> |
||||||
|
#include <SD.h> |
||||||
|
#include "OpenAudio_ArduinoLibrary.h" |
||||||
|
|
||||||
|
// T3.x supported sample rates: 2000, 8000, 11025, 16000, 22050, 24000, 32000, 44100, 44117, 48000,
|
||||||
|
// 88200, 88235 (44117*2), 95680, 96000, 176400, 176470, 192000
|
||||||
|
// T4.x supports any sample rate the codec will handle.
|
||||||
|
const float sample_rate_Hz = 48000.0f; |
||||||
|
const int audio_block_samples = 128; // Always 128
|
||||||
|
AudioSettings_F32 audio_settings(sample_rate_Hz, audio_block_samples); |
||||||
|
|
||||||
|
AudioSynthWaveformSine_F32 sine1(audio_settings); |
||||||
|
AudioSDPlayer_F32 playWav1(audio_settings); |
||||||
|
AudioMixer4_F32 mixer4_0; |
||||||
|
radioCESSB_Z_transmit_F32 cessb1(audio_settings); |
||||||
|
RadioIQMixer_F32 iqMixer1(audio_settings); |
||||||
|
AudioMixer4_F32 mixer4_2; |
||||||
|
AudioFilter90Deg_F32 filter90deg1(audio_settings); |
||||||
|
RadioIQMixer_F32 iqMixer2(audio_settings); |
||||||
|
AudioMixer4_F32 mixer4_1; |
||||||
|
AudioOutputI2S_F32 audioOutput(audio_settings); |
||||||
|
AudioAnalyzeFFT1024_F32 fft1; |
||||||
|
|
||||||
|
AudioConnection_F32 patchCord0(playWav1, 0, mixer4_0, 0); |
||||||
|
AudioConnection_F32 patchCordb(sine1, 0, mixer4_0, 1); |
||||||
|
AudioConnection_F32 patchCordc(mixer4_0, 0, cessb1, 0); |
||||||
|
AudioConnection_F32 patchCord1(cessb1, 0, iqMixer1, 0); |
||||||
|
AudioConnection_F32 patchCord2(cessb1, 1, iqMixer1, 1); |
||||||
|
AudioConnection_F32 patchCord9(iqMixer1, 0, mixer4_2, 0); |
||||||
|
AudioConnection_F32 patchCord10(iqMixer1,1, mixer4_2, 1); |
||||||
|
|
||||||
|
// mixer4_2 is transmitter SSB output, iqMixer2 is receiver input
|
||||||
|
AudioConnection_F32 patchCord14(mixer4_2,0, iqMixer2, 0); |
||||||
|
AudioConnection_F32 patchCord3(iqMixer2, 0, filter90deg1, 0); |
||||||
|
AudioConnection_F32 patchCord4(iqMixer2, 1, filter90deg1, 1); |
||||||
|
AudioConnection_F32 patchCord7(filter90deg1, 0, mixer4_1, 0); |
||||||
|
AudioConnection_F32 patchCord8(filter90deg1, 1, mixer4_1, 1); |
||||||
|
AudioConnection_F32 patchCord11(mixer4_1, 0, audioOutput, 0); |
||||||
|
AudioConnection_F32 patchCord12(mixer4_1, 0, audioOutput, 1); |
||||||
|
AudioConnection_F32 patchCord13(mixer4_2, 0, fft1, 0); |
||||||
|
|
||||||
|
AudioControlSGTL5000 sgtl5000_1; |
||||||
|
|
||||||
|
// Use these with the Teensy 4.x Rev D Audio Shield (NOT for T3.x)
|
||||||
|
#define SDCARD_CS_PIN_Z 10 |
||||||
|
#define SDCARD_MOSI_PIN_Z 11 |
||||||
|
#define SDCARD_SCK_PIN_Z 13 |
||||||
|
|
||||||
|
// Filter for AudioFilter90Deg_F32 hilbert1, only for receiving the CESSB
|
||||||
|
#include "hilbert251A_Z_.h" |
||||||
|
|
||||||
|
// wavData is a global struct, definined in AudioSDPlayer_F32.h
|
||||||
|
// This provides information about the current WAV file to this .INO
|
||||||
|
struct wavData* pCurrentWavData; |
||||||
|
// And data about the CESSB
|
||||||
|
struct levelsZ* pLevelData; |
||||||
|
uint32_t writeOne = 0; |
||||||
|
uint32_t cntFFT = 0; |
||||||
|
uint32_t ttt; // For timing test audio
|
||||||
|
|
||||||
|
uint32_t tp; |
||||||
|
|
||||||
|
void setup() { // ********** SETUP **********
|
||||||
|
Serial.begin(9600); delay(1000); |
||||||
|
Serial.println("*** Test CESSB Zero-IF from SD Card Voice Sample ***"); |
||||||
|
|
||||||
|
AudioMemory_F32(70, audio_settings); |
||||||
|
pCurrentWavData = playWav1.getCurrentWavData(); |
||||||
|
sgtl5000_1.enable(); |
||||||
|
delay(500); |
||||||
|
SPI.setMOSI(SDCARD_MOSI_PIN_Z); |
||||||
|
SPI.setSCK(SDCARD_SCK_PIN_Z); |
||||||
|
Serial.print("SD.begin() returns "); Serial.println(SD.begin(SDCARD_CS_PIN_Z)); |
||||||
|
|
||||||
|
// sine0.frequency(468.75f); // 2-tone generators
|
||||||
|
// sine0.amplitude(0.707107);
|
||||||
|
sine1.frequency(750.0f); |
||||||
|
sine1.amplitude(0.707107); |
||||||
|
|
||||||
|
// Build the CESSB SSB transmitter
|
||||||
|
// The WAV file has carefully controlled 0.707 peaks. We bring these to 1.000
|
||||||
|
mixer4_0.gain(0, 1.41421356f); // Play WAV file on
|
||||||
|
mixer4_0.gain(2, 0.0); // Sine Wave 1 off
|
||||||
|
|
||||||
|
cessb1.setSampleRate_Hz(48000.0f); |
||||||
|
// Set input, correction, and output gains
|
||||||
|
float32_t Pre_CESSB_Gain = 1.5f; // Use to set amount of clipping, 1.0 to 2.0f, 3 is excessive
|
||||||
|
cessb1.setGains(Pre_CESSB_Gain, 1.4f, 1.0f); |
||||||
|
cessb1.setSideband(false); |
||||||
|
pLevelData = cessb1.getLevels(0); // Gets pointer to struct
|
||||||
|
|
||||||
|
// Generate SSB at 15 kHz from zero-IF signal of CESSB generator
|
||||||
|
iqMixer1.useTwoChannel(true); |
||||||
|
iqMixer1.frequency(15000); |
||||||
|
mixer4_2.gain(1, 1.0f); // 1.0f for LSB, -1.0f for USB
|
||||||
|
|
||||||
|
// Need a receiver for the SSB transmitter to let us hear the results.
|
||||||
|
iqMixer2.frequency(15000.0f); |
||||||
|
iqMixer2.useTwoChannel(false); |
||||||
|
filter90deg1.begin(hilbert251A, 251); |
||||||
|
mixer4_1.gain(0, -1.0f); // LSB, + for USB
|
||||||
|
audioOutput.setGain(0.02); // <<< Output volume control
|
||||||
|
fft1.setOutputType(FFT_DBFS); |
||||||
|
fft1.windowFunction(AudioWindowBlackmanHarris1024); |
||||||
|
fft1.setNAverage(16); |
||||||
|
ttt = millis(); // Time test audio
|
||||||
|
tp=millis(); |
||||||
|
} |
||||||
|
|
||||||
|
void playFile(const char *filename) { |
||||||
|
if(playWav1.isPlaying()) |
||||||
|
return; |
||||||
|
Serial.println(""); |
||||||
|
Serial.print("Playing file: "); |
||||||
|
Serial.println(filename); |
||||||
|
playWav1.play(filename); // Start playing the file.
|
||||||
|
// A brief delay for the library read WAV info
|
||||||
|
delay(25); |
||||||
|
Serial.print("WAV file format = "); Serial.println(pCurrentWavData->audio_format); |
||||||
|
Serial.print("WAV number channels = "); Serial.println(pCurrentWavData->num_channels); |
||||||
|
Serial.print("WAV File Sample Rate = "); Serial.println(pCurrentWavData->sample_rate); |
||||||
|
Serial.print("Number of bits per Sample = "); Serial.println(pCurrentWavData->bits); |
||||||
|
Serial.print("File length, seconds = "); |
||||||
|
Serial.println(0.001f*(float32_t)playWav1.lengthMillis(), 3); |
||||||
|
} |
||||||
|
|
||||||
|
void loop() { |
||||||
|
uint32_t tt=millis() - ttt; |
||||||
|
if(tt < 2000) |
||||||
|
{ |
||||||
|
// Thanks to W9GR for the test file, W9GR48.WAV. This is intended for testing
|
||||||
|
// the CESSB radio transmission system.
|
||||||
|
playFile("W9GR48.WAV"); |
||||||
|
mixer4_0.gain(0, 1.41421356f); // Play WAV file on
|
||||||
|
mixer4_0.gain(1, 0.0f); // Sine Wave 1 off
|
||||||
|
} |
||||||
|
else if(tt > 12300 && tt<13300) |
||||||
|
{ |
||||||
|
mixer4_0.gain(1, 0.0f); // Play WAV file off
|
||||||
|
// The following puts a 1-sec 750 Hz, full amplitude tone into the input
|
||||||
|
// .707 on the generator and 1.414 here make the peak sine wave 1.000 at the CESSB input
|
||||||
|
mixer4_0.gain(1, 1.41421356f); // Sine Wave 1 on, 750 Hz
|
||||||
|
} |
||||||
|
else if(tt >= 13300) |
||||||
|
{ |
||||||
|
mixer4_0.gain(0, 1.41421356f); // Play WAV file on
|
||||||
|
mixer4_0.gain(1, 0.0f); // Sine Wave 1 off
|
||||||
|
ttt = millis(); // Start again
|
||||||
|
} |
||||||
|
delay(1); |
||||||
|
|
||||||
|
// Un-comment the following to print out the spectrum
|
||||||
|
/*
|
||||||
|
if(fft1.available() && ++cntFFT>100 && cntFFT<102) |
||||||
|
{ |
||||||
|
for(int kk=0; kk<512; kk++) |
||||||
|
{ |
||||||
|
Serial.print(46.875f*(float32_t)kk); Serial.print(","); |
||||||
|
Serial.println(fft1.read(kk)); |
||||||
|
} |
||||||
|
} |
||||||
|
*/ |
||||||
|
|
||||||
|
if(cessb1.levelDataCount() > 300) // Typically 300 to 3000
|
||||||
|
{ |
||||||
|
cessb1.getLevels(1); // Cause write of data to struct & reset
|
||||||
|
|
||||||
|
// Detailed Report
|
||||||
|
Serial.print(10.0f*log10f(pLevelData->pwr0)); |
||||||
|
Serial.print(" In Ave Pwr Out "); |
||||||
|
Serial.println(10.0f*log10f(pLevelData->pwr1)); |
||||||
|
Serial.print(20.0f*log10f(pLevelData->peak0)); |
||||||
|
Serial.print(" In Peak Out "); |
||||||
|
Serial.println(20.0f*log10f(pLevelData->peak1)); |
||||||
|
Serial.print(pLevelData->peak0, 6); |
||||||
|
Serial.print(" In Peak Volts Out "); |
||||||
|
Serial.println(pLevelData->peak1, 6); |
||||||
|
Serial.print("Enhancement = "); |
||||||
|
float32_t enhance = (10.0f*log10f(pLevelData->pwr1) - 20.0f*log10f(pLevelData->peak1)) - |
||||||
|
(10.0f*log10f(pLevelData->pwr0) - 20.0f*log10f(pLevelData->peak0)); |
||||||
|
if(enhance<1.0f) enhance = 1.0f; |
||||||
|
Serial.print(enhance); Serial.println(" dB"); |
||||||
|
|
||||||
|
/*
|
||||||
|
// CSV Report suitable for entering to spread sheet
|
||||||
|
// InAve, InPk, OutAve, OutPk, EnhancementdB
|
||||||
|
Serial.print(pLevelData->pwr0, 5); Serial.print(","); |
||||||
|
Serial.print(pLevelData->peak0, 5); Serial.print(","); |
||||||
|
Serial.print(pLevelData->pwr1, 5); Serial.print(","); |
||||||
|
Serial.print(pLevelData->peak1, 5); Serial.print(","); |
||||||
|
float32_t enhance = (10.0f*log10f(pLevelData->pwr1) - 20.0f*log10f(pLevelData->peak1)) - |
||||||
|
(10.0f*log10f(pLevelData->pwr0) - 20.0f*log10f(pLevelData->peak0)); |
||||||
|
if(enhance<1.0f) enhance = 1.0f; |
||||||
|
Serial.println(enhance); |
||||||
|
*/ |
||||||
|
} |
||||||
|
} |
@ -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,255 @@ |
|||||||
|
/*
|
||||||
|
* radioCESSB_Z_transmit_F32.cpp |
||||||
|
* This version of CESSB is intended for Zero-IF hardware. |
||||||
|
* |
||||||
|
* Bob Larkin, in support of the library: |
||||||
|
* Chip Audette, OpenAudio, Dec 2022 |
||||||
|
* |
||||||
|
* MIT License, Use at your own risk. |
||||||
|
* |
||||||
|
* See radioCESSB_Z_transmit_F32.h for technical info. |
||||||
|
* |
||||||
|
*/ |
||||||
|
// NOTE: 96 ksps sample rate not yet implemented
|
||||||
|
#include "radioCESSB_Z_transmit_F32.h" |
||||||
|
|
||||||
|
void radioCESSB_Z_transmit_F32::update(void) { |
||||||
|
audio_block_f32_t *blockIn, *blockOutI, *blockOutQ; |
||||||
|
|
||||||
|
// Temporary storage. At an audio sample rate of 96 ksps, the used
|
||||||
|
// space will be half of the declared space.
|
||||||
|
float32_t HilbertIn[32]; |
||||||
|
float32_t workingDataI[128]; |
||||||
|
float32_t workingDataQ[128]; |
||||||
|
float32_t delayedDataI[64]; // Allows batching of 64 data points
|
||||||
|
float32_t delayedDataQ[64]; |
||||||
|
float32_t diffI[64]; |
||||||
|
float32_t diffQ[64]; |
||||||
|
|
||||||
|
if(sampleRate!=SAMPLE_RATE_44_50 && sampleRate!=SAMPLE_RATE_88_100) |
||||||
|
return; |
||||||
|
|
||||||
|
// Get all needed resources, or return if not available.
|
||||||
|
blockIn = AudioStream_F32::receiveReadOnly_f32(); |
||||||
|
if (!blockIn) |
||||||
|
{ return; } |
||||||
|
blockOutI = AudioStream_F32::allocate_f32(); // a block for I output
|
||||||
|
if (!blockOutI) |
||||||
|
{ |
||||||
|
AudioStream_F32::release(blockIn); |
||||||
|
return; |
||||||
|
} |
||||||
|
blockOutQ = AudioStream_F32::allocate_f32(); // and for Q
|
||||||
|
if (!blockOutQ) |
||||||
|
{ |
||||||
|
AudioStream_F32::release(blockOutI); |
||||||
|
AudioStream_F32::release(blockIn); |
||||||
|
return; |
||||||
|
} |
||||||
|
// The audio input peak levels for start of CESSB are -1.0, 1.0
|
||||||
|
// when gainIn==1.0.
|
||||||
|
|
||||||
|
/* // A +/- pulse to test timing of various delays
|
||||||
|
// PULSE TEST for diagnostics only
|
||||||
|
for(int kk=0; kk<128; kk++) |
||||||
|
{ |
||||||
|
uint16_t y=(ny & 1023); |
||||||
|
// pulse max at is just starting to clip
|
||||||
|
|
||||||
|
if (y>=100 && y<115) blockIn->data[kk] = 4.189f; |
||||||
|
else if(y>=115 && y<130) blockIn->data[kk] = -4.189f; |
||||||
|
else blockIn->data[kk] = 0.0f; |
||||||
|
ny++; |
||||||
|
// Serial.println(blockIn->data[kk]);
|
||||||
|
} |
||||||
|
*/ |
||||||
|
// uint32_t ttt=micros();
|
||||||
|
|
||||||
|
// Decimate 48 ksps to 12 ksps, 128 to 32 samples
|
||||||
|
// or 96 ksps to 12 ksps, 128 to 16 samples
|
||||||
|
arm_fir_decimate_f32(&decimateInst, &(blockIn->data[0]), |
||||||
|
&HilbertIn[0], 128); |
||||||
|
// We now have nW=32 (for 48 ksps) or 16 (for 96 ksps) samples to process
|
||||||
|
|
||||||
|
// Apply the Hilbert transform FIR.
|
||||||
|
arm_fir_f32(&firInstHilbertI, &HilbertIn[0], &workingDataI[0], nW); |
||||||
|
|
||||||
|
/* ======= Sidebar: Circular 2^n length delay arrays ========
|
||||||
|
* |
||||||
|
* The length of the array, N, |
||||||
|
* must be a power of 2. For example N=2^6 = 64. The minimum |
||||||
|
* delay possible is the trivial case of 0 up to N-1. |
||||||
|
* As in C, let i be the index of the N array elements which |
||||||
|
* would range from 0 to N-1. If p is an integer, that is a power |
||||||
|
* of 2 also, with p >= n, it can serve as an index to the |
||||||
|
* delay array by "ANDing" it with (N-1). That is, |
||||||
|
* i = p & (N-1). It can be convenient if the largest |
||||||
|
* possible value of the integer p, plus 1, is an integer multiple |
||||||
|
* of the arrray size N, as then the rollover of p will not cause |
||||||
|
* a jump in i. For instance, if p is an uint8_t with a maximum |
||||||
|
* value of pmax=255, (pmax+1)/N = (255+1)/64 = 4, which is an |
||||||
|
* integer. This combination will have no problems from rollover |
||||||
|
* of p. |
||||||
|
* |
||||||
|
* The new data point is entered at index p & (N - 1). To |
||||||
|
* achieve a delay of d, the output of the delay array is taken |
||||||
|
* at index ((p-d) & (N-1)). The index is then incremented by 1. |
||||||
|
* |
||||||
|
* There are three delay lines of this construction below, starting |
||||||
|
* with delayHilbertQ |
||||||
|
* ========================================================== */ |
||||||
|
|
||||||
|
// Circular delay line for signal to align data with Hilbert FIR output
|
||||||
|
// nW (32 for 48ksps) points into and out of the delay array
|
||||||
|
for(uint16_t i=0; i<nW; i++) // Let it wrap around at 128
|
||||||
|
{ |
||||||
|
// Put data point into the delay arrays, and do LSB/USB changing
|
||||||
|
if(sidebandReverse) |
||||||
|
delayHilbertQ[indexDelayHilbertQ & 0X7F] = -HilbertIn[i]; |
||||||
|
else |
||||||
|
delayHilbertQ[indexDelayHilbertQ & 0X7F] = HilbertIn[i]; |
||||||
|
// Remove delayed data from line
|
||||||
|
workingDataQ[i] = delayHilbertQ[(indexDelayHilbertQ - 100) & 0X7F]; |
||||||
|
indexDelayHilbertQ++; |
||||||
|
} |
||||||
|
|
||||||
|
// To compensate for splitting the signal into I & Q thereby doubling
|
||||||
|
// the power, we add 0.707 factor.
|
||||||
|
float32_t gainFactor = 0.70710678f*gainIn; |
||||||
|
for(int k=0; k<nW; k++) |
||||||
|
{ |
||||||
|
workingDataI[k] *= gainFactor; |
||||||
|
workingDataQ[k] *= gainFactor; |
||||||
|
} |
||||||
|
|
||||||
|
// Mesaure input power and peak envelope, SSB before any CESSB processing
|
||||||
|
for(int k=0; k<nW; k++) |
||||||
|
{ |
||||||
|
float32_t pwrWorkingData = workingDataI[k]*workingDataI[k] + workingDataQ[k]*workingDataQ[k]; |
||||||
|
float32_t vWD = sqrtf(pwrWorkingData); // Envelope
|
||||||
|
powerSum0 += pwrWorkingData; |
||||||
|
if(vWD > maxMag0) |
||||||
|
maxMag0 = vWD; // Peak envelope
|
||||||
|
countPower0++; |
||||||
|
} |
||||||
|
|
||||||
|
// Interpolate by 2 up to 24 ksps rate
|
||||||
|
for(int k=0; k<nW; k++) // 48 ksps: 0 to 31
|
||||||
|
{ |
||||||
|
int k2 = 2*(nW - k) - 1; // 48 ksps 63 to 1
|
||||||
|
// Zero pack, working from the bottom to not overwrite
|
||||||
|
workingDataI[k2] = 0.0f; // 64 element array
|
||||||
|
workingDataI[k2-1] = workingDataI[nW-k-1]; |
||||||
|
workingDataQ[k2] = 0.0f; |
||||||
|
workingDataQ[k2-1] = workingDataQ[nW-k-1]; |
||||||
|
} |
||||||
|
|
||||||
|
// LPF with gain of 2 built into coefficients, correct for added zeros.
|
||||||
|
arm_fir_f32(&firInstInterpolate1I, workingDataI, workingDataI, nC); |
||||||
|
arm_fir_f32(&firInstInterpolate1Q, workingDataQ, workingDataQ, nC); |
||||||
|
// WorkingDataI and Q are now at 24 ksps and ready for clipping
|
||||||
|
// For input 48 ksps this produces 64 numbers
|
||||||
|
|
||||||
|
for(int kk=0; kk<nC; kk++) |
||||||
|
{ |
||||||
|
float32_t power = workingDataI[kk]*workingDataI[kk] + workingDataQ[kk]*workingDataQ[kk]; |
||||||
|
float32_t mag = sqrtf(power); |
||||||
|
if(mag > 1.0f) // This the clipping, scaled to 1.0, desired max
|
||||||
|
{ |
||||||
|
workingDataI[kk] /= mag; |
||||||
|
workingDataQ[kk] /= mag; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// clipperIn needs spectrum control, so LP filter it.
|
||||||
|
// Both BW of the signal and the sample rate have been doubled.
|
||||||
|
arm_fir_f32(&firInstClipperI, workingDataI, workingDataI, nC); |
||||||
|
arm_fir_f32(&firInstClipperQ, workingDataQ, workingDataQ, nC); |
||||||
|
// Ready to compensate for filter overshoots
|
||||||
|
for (int k=0; k<nC; k++) |
||||||
|
{ |
||||||
|
// Circular delay line for signal to align data with FIR output
|
||||||
|
// Put I & Q data points into the delay arrays
|
||||||
|
osDelayI[indexOsDelay & 0X3F] = workingDataI[k]; |
||||||
|
osDelayQ[indexOsDelay & 0X3F] = workingDataQ[k]; |
||||||
|
// Remove 64 points delayed data from line and save for later
|
||||||
|
delayedDataI[k] = osDelayI[(indexOsDelay - 63) & 0X3F]; |
||||||
|
delayedDataQ[k] = osDelayQ[(indexOsDelay - 63) & 0X3F]; |
||||||
|
indexOsDelay++; |
||||||
|
// Delay line to allow strongest envelope to be used for compensation
|
||||||
|
// We only need to look ahead 1 or behind 1, so delay line of 4 is OK.
|
||||||
|
// Enter latest envelope to delay array
|
||||||
|
osEnv[indexOsEnv & 0X03] = sqrtf( |
||||||
|
workingDataI[k]*workingDataI[k] + workingDataQ[k]*workingDataQ[k]); |
||||||
|
|
||||||
|
// look over the envelope curve to find the max
|
||||||
|
float32_t eMax = 0.0f; |
||||||
|
if(osEnv[(indexOsEnv) & 0X03] > eMax) // Data point just entered
|
||||||
|
eMax = osEnv[(indexOsEnv) & 0X03]; |
||||||
|
if(osEnv[(indexOsEnv-1) & 0X03] > eMax) // Entered one before
|
||||||
|
eMax = osEnv[(indexOsEnv-1) & 0X03]; |
||||||
|
if(osEnv[(indexOsEnv-2) & 0X03] > eMax) // Entered one before that
|
||||||
|
eMax = osEnv[(indexOsEnv-2) & 0X03]; |
||||||
|
if(eMax < 1.0f) |
||||||
|
eMax = 1.0f; // Below clipping region
|
||||||
|
indexOsEnv++; |
||||||
|
|
||||||
|
// Clip the signal to 1.0. -2 allows 1 look ahead on signal.
|
||||||
|
float32_t eCorrectedI = osDelayI[(indexOsDelay - 2) & 0X3F] / eMax; |
||||||
|
float32_t eCorrectedQ = osDelayQ[(indexOsDelay - 2) & 0X3F] / eMax; |
||||||
|
// Filtering is linear, so we only need to filter the difference between
|
||||||
|
// the signal and the clipper output. This needs less filtering, as the
|
||||||
|
// difference is many dB below the signal to begin with. Hershberger 2014
|
||||||
|
diffI[k] = osDelayI[(indexOsDelay - 2) & 0X3F] - eCorrectedI; |
||||||
|
diffQ[k] = osDelayQ[(indexOsDelay - 2) & 0X3F] - eCorrectedQ; |
||||||
|
} // End, for k=0 to 63
|
||||||
|
|
||||||
|
// Filter the differences, osFilter has 123 taps and 61 delay
|
||||||
|
arm_fir_f32(&firInstOShootI, diffI, diffI, nC); |
||||||
|
arm_fir_f32(&firInstOShootQ, diffQ, diffQ, nC); |
||||||
|
|
||||||
|
// Do the overshoot compensation
|
||||||
|
for(int k=0; k<nC; k++) |
||||||
|
{ |
||||||
|
workingDataI[k] = delayedDataI[k] - gainCompensate*diffI[k]; |
||||||
|
workingDataQ[k] = delayedDataQ[k] - gainCompensate*diffQ[k]; |
||||||
|
} |
||||||
|
|
||||||
|
// Measure average output power and peak envelope, after CESSB
|
||||||
|
// but before gainOut
|
||||||
|
for(int k=0; k<nC; k++) |
||||||
|
{ |
||||||
|
float32_t pwrOut = workingDataI[k]*workingDataI[k] + |
||||||
|
workingDataQ[k]*workingDataQ[k]; |
||||||
|
float32_t vWD = sqrtf(pwrOut); // Envelope
|
||||||
|
powerSum1 += pwrOut; |
||||||
|
if(vWD > maxMag1) |
||||||
|
maxMag1 = vWD; // Peak envelope
|
||||||
|
countPower1++; |
||||||
|
} |
||||||
|
|
||||||
|
// Finally interpolate to 48 or 96 ksps. Data is in workingDataI[k]
|
||||||
|
// and is 64 samples for audio 48 ksps.
|
||||||
|
for(int k=0; k<nC; k++) // Audio sampling at 48 ksps: 0 to 63
|
||||||
|
{ |
||||||
|
int k2 = 2*(nC - k) - 1; // 48 ksps 63 to 1
|
||||||
|
// Zero pack, working from the bottom to not overwrite
|
||||||
|
workingDataI[k2] = 0.0f; |
||||||
|
workingDataI[k2-1] = gainOut*workingDataI[nC-k-1]; // gainOut does not change CESSB
|
||||||
|
workingDataQ[k2] = 0.0f; |
||||||
|
workingDataQ[k2-1] = gainOut*workingDataQ[nC-k-1]; // ...it just scales the level
|
||||||
|
} |
||||||
|
// LPF with gain of 2 built into coefficients, correct for zeros.
|
||||||
|
arm_fir_f32(&firInstInterpolate2I, workingDataI, &blockOutI->data[0], 128); |
||||||
|
arm_fir_f32(&firInstInterpolate2Q, workingDataQ, &blockOutQ->data[0], 128); |
||||||
|
// Voltage gain from blockIn->data to here for small sine wave is 1.0
|
||||||
|
|
||||||
|
AudioStream_F32::transmit(blockOutI, 0); // send the outputs
|
||||||
|
AudioStream_F32::transmit(blockOutQ, 1); |
||||||
|
AudioStream_F32::release(blockIn); // Release the blocks
|
||||||
|
AudioStream_F32::release(blockOutI); |
||||||
|
AudioStream_F32::release(blockOutQ); |
||||||
|
|
||||||
|
jjj++; //For test printing
|
||||||
|
// Serial.println(micros() - ttt);
|
||||||
|
} // end update()
|
@ -0,0 +1,444 @@ |
|||||||
|
/*
|
||||||
|
* 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. |
||||||
|
*/ |
||||||
|
|
||||||
|
#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; |
||||||
|
} |
||||||
|
|
||||||
|
// 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; |
||||||
|
|
||||||
|
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+123-1]; |
||||||
|
arm_fir_instance_f32 firInstOShootI; |
||||||
|
float32_t pStateOShootQ[64+123-1]; |
||||||
|
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 2-bit 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://t-filter.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://t-filter.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://t-filter.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 |
Loading…
Reference in new issue