diff --git a/examples/TestNoiseBlanker2/TestNB.gnumeric b/examples/TestNoiseBlanker2/TestNB.gnumeric new file mode 100644 index 0000000..0a05361 Binary files /dev/null and b/examples/TestNoiseBlanker2/TestNB.gnumeric differ diff --git a/examples/TestNoiseBlanker2/TestNB128.gif b/examples/TestNoiseBlanker2/TestNB128.gif new file mode 100644 index 0000000..72928de Binary files /dev/null and b/examples/TestNoiseBlanker2/TestNB128.gif differ diff --git a/examples/TestNoiseBlanker2/TestNoiseBlanker2.ino b/examples/TestNoiseBlanker2/TestNoiseBlanker2.ino new file mode 100644 index 0000000..a5cf055 --- /dev/null +++ b/examples/TestNoiseBlanker2/TestNoiseBlanker2.ino @@ -0,0 +1,90 @@ +/* TestNoiseBlanker.ino Bob Larkin 20 May 2020 + * + * Sine wave plus noise spike test of radioNoiseBlanker_F32 + * Feeds input to both 0 and 1 channels (I & Q or L & R). + * The 0 channel controls the noise blanking, but both paths are + * noise-blanked. The function useTwoChannels(true) enables the + * second path. The default case is to use only the 0 path for everything. + */ + +#include "AudioStream_F32.h" +#include "OpenAudio_ArduinoLibrary.h" +#include "Arduino.h" +#include "Audio.h" + +#include "radioNoiseBlanker_F32.h" + +AudioInputI2S_F32 i2sIn1; +//AudioSynthGaussian_F32 gwn1; +AudioPlayQueue_F32 playq1; +radioNoiseBlanker_F32 nb1; +AudioRecordQueue_F32 queue1; + +AudioConnection_F32 pcord0(playq1, 0, nb1, 0); +AudioConnection_F32 pcord1(playq1, 0, nb1, 1); + +// The next two should be identical data outputs. Pick ONLY ONE at a time +//AudioConnection_F32 pcord2(nb1, 0, queue1, 0); +AudioConnection_F32 pcord2(nb1, 1, queue1, 0); + +float32_t *pin; +float32_t dt1[128]; +float32_t *pq1, *pd1; +int i; + +// Fake noise pulses +float32_t pulse[] = +{0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.9f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.9f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.9f, + 0.9f, 0.9f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -0.9f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}; + +void setup(void) { + AudioMemory_F32(25); + Serial.begin(300); delay(1000); + Serial.println("*** Test Noise Blanker ***"); + + // setNoiseBlanker(float threshold, uint16_t nAnticipation, uint16_t nDecay) + nb1.setNoiseBlanker(4.0f, 2, 3); + + //nb1.showError(1); + nb1.useTwoChannel(true); // true enables a path trrough the "1" side + nb1.enable(true); + + while(pin == NULL) + pin = playq1.getBuffer(); + Serial.println("Input to NB:"); + for(int k=0; k<128; k++) { // Signal and noise + pin[k] = pulse[k] + 0.1*sinf(0.6*(float32_t)k); + Serial.println(pin[k], 6); + } + playq1.playBuffer(); // Put 128 data into stream + queue1.begin(); + i = 0; +} + +void loop(void) { + // Collect 128 samples and output to Serial + if (queue1.available() >= 1) { // See if it has arrived + pq1 = queue1.readBuffer(); + pd1 = &dt1[0]; + for(int k=0; k<128; k++) { + *pd1++ = *pq1++; + } + i=1; // data into dt1[] + queue1.freeBuffer(); + queue1.end(); // No more data to queue1 + } + if(i == 1) { + // Printout 128 samples of the gated signal. + Serial.println("128 NB Output Samples: "); + for (int k=0; k<128; k++) { + Serial.println (dt1[k], 9); + } + i = 2; + } +} diff --git a/examples/TestNoiseBlanker2/Voice1.wav b/examples/TestNoiseBlanker2/Voice1.wav new file mode 100644 index 0000000..743d8b7 Binary files /dev/null and b/examples/TestNoiseBlanker2/Voice1.wav differ diff --git a/examples/TestNoiseBlanker2/Voice1_I16.wav b/examples/TestNoiseBlanker2/Voice1_I16.wav new file mode 100644 index 0000000..429b9c1 Binary files /dev/null and b/examples/TestNoiseBlanker2/Voice1_I16.wav differ diff --git a/radioNoiseBlanker_F32.cpp b/radioNoiseBlanker_F32.cpp index f415e64..70a2a72 100644 --- a/radioNoiseBlanker_F32.cpp +++ b/radioNoiseBlanker_F32.cpp @@ -12,43 +12,75 @@ #include "radioNoiseBlanker_F32.h" void radioNoiseBlanker_F32::update(void) { - audio_block_f32_t *blockIn, *blockOut=NULL; + audio_block_f32_t *blockIn0=NULL; + audio_block_f32_t *blockOut0=NULL; + audio_block_f32_t *blockIn1=NULL; + audio_block_f32_t *blockOut1=NULL; uint16_t i; float32_t absSignal; // Get input block // <data + // for noise pulses. Then in same loop, move data to output buffer blockOut0->data // based on whether gate is open or not. for(i=0; idata[i]; // ith data - delayData[(i+in_index) & delayBufferMask] = datai; // Put ith data to circular delay buffer + float32_t datai0 = blockIn0->data[i]; // ith data + delayData0[(i+in_index) & delayBufferMask] = datai0; // Put ith data to circular delay buffer + if(twoChannel) { + float32_t datai1 = blockIn1->data[i]; + delayData1[(i+in_index) & delayBufferMask] = datai1; + } - absSignal = fabsf(datai); // Rectified I-F - runningSum += fabsf(datai); // Update by adding one rectified point - runningSum -= delayData[(i + in_index - RUNNING_SUM_SIZE) & delayBufferMask]; // & subtract one + // All control comes from the 0 input (not the 1 input) + absSignal = fabsf(datai0); // Rectified I-F + runningSum += absSignal; // Update by adding one rectified point + runningSum -= delayData0[(i + in_index - RUNNING_SUM_SIZE) & delayBufferMask]; // & subtract one pulseTime++; // This keeps track of leading and trailing delays of the gate pulse if (absSignal > (threshold * runningSum)) { // A noise pulse event @@ -74,15 +106,26 @@ void radioNoiseBlanker_F32::update(void) { } } // Ready to enter I-F data to output, offset in time by "nAnticipation" - if (pulseTime == -9999) - blockOut->data[i] = delayData[(256 + i - nAnticipation) & delayBufferMask]; // Need 256?? - else // -nAnticipation < pulseTime < nDecay i.e., blanked out - blockOut->data[i] = 0.0f; + if (pulseTime == -9999) { + blockOut0->data[i] = delayData0[(256 + i - nAnticipation) & delayBufferMask]; + if(twoChannel) + blockOut1->data[i] = delayData1[(256 + i - nAnticipation) & delayBufferMask]; + } + else { // -nAnticipation < pulseTime < nDecay i.e., blanked out + blockOut0->data[i] = 0.0f; + if(twoChannel) + blockOut1->data[i] = 0.0f; + } } // End of loop point by point over input 128 data points - AudioStream_F32::release (blockIn); - AudioStream_F32::transmit(blockOut, 0); // send the delayed or blanked data - AudioStream_F32::release (blockOut); + AudioStream_F32::release (blockIn0); + AudioStream_F32::transmit(blockOut0, 0); // send the delayed or blanked data + AudioStream_F32::release (blockOut0); + if(twoChannel) { + AudioStream_F32::release (blockIn1); + AudioStream_F32::transmit(blockOut1, 1); // send second "Q" channel + AudioStream_F32::release (blockOut1); + } // Update pointer in_index to delay line for next 128 update in_index = (in_index + block_size) & delayBufferMask; diff --git a/radioNoiseBlanker_F32.h b/radioNoiseBlanker_F32.h index b0a0fbf..bbe49ac 100644 --- a/radioNoiseBlanker_F32.h +++ b/radioNoiseBlanker_F32.h @@ -51,6 +51,9 @@ * Examples: * TestNoiseBlanker1.ino 128 data for plotting and examination * Time: Update() of 128 samples 32 microseconds +* +* Allow two channels, for I-Q receivers. Input 0 operates gate. +* Paths 0 & 1 are gated. 31 March 2021. Bob L. */ #ifndef _radio_noise_blanker_f32_h @@ -62,20 +65,25 @@ #define RUNNING_SUM_SIZE 125 class radioNoiseBlanker_F32 : public AudioStream_F32 { -//GUI: inputs:1, outputs:1 //this line used for automatic generation of GUI node +//GUI: inputs:2, outputs:2 //this line used for automatic generation of GUI node //GUI: shortName: NoiseBlanker public: // Option of AudioSettings_F32 change to block size (no sample rate dependent variables here): - radioNoiseBlanker_F32(void) : AudioStream_F32(1, inputQueueArray_f32) { + radioNoiseBlanker_F32(void) : AudioStream_F32(2, inputQueueArray_f32) { block_size = AUDIO_BLOCK_SAMPLES; } - radioNoiseBlanker_F32(const AudioSettings_F32 &settings) : AudioStream_F32(1, inputQueueArray_f32) { + radioNoiseBlanker_F32(const AudioSettings_F32 &settings) : AudioStream_F32(2, inputQueueArray_f32) { block_size = settings.audio_block_samples; } void enable(bool _runNB) { runNB = _runNB; } + + // Channel 0 gates both Channel 0 & 1 + void useTwoChannel(bool _2Ch) { + twoChannel = _2Ch; + } void setNoiseBlanker(float32_t _threshold, uint16_t _nAnticipation, uint16_t _nDecay) { if (_threshold < 0.0) threshold = 0.0; @@ -99,22 +107,23 @@ public: private: uint16_t block_size = AUDIO_BLOCK_SAMPLES; // Input data pointers - audio_block_f32_t *inputQueueArray_f32[1]; + audio_block_f32_t *inputQueueArray_f32[2]; // Control error printing in update() 0=No print uint16_t errorPrint = 0; - // To look ahead we need a delay of up to 128 samples. Too much removes good data. + // To look ahead we need a delay of up to 256 samples. Too much removes good data. // Too little enters noise pulse data to the output. This can be a simple circular // buffer if we make the buffer a power of 2 in length and binary-truncate the index. // Choose 2^8 = 256. - float32_t delayData[256]; // The circular delay line + float32_t delayData0[256]; // The circular delay lines + float32_t delayData1[256]; uint16_t in_index = 0; // Pointer to next block update entry // And a mask to make the circular buffer limit to a power of 2 uint16_t delayBufferMask = 0X00FF; // Three variables to allow .INO control of Noise Blanker - float32_t threshold = 1.0E6f; // Start disabled + float32_t threshold = 1.0E3f; // Start disabled uint16_t nAnticipation = 5; uint16_t nDecay = 8; @@ -122,5 +131,6 @@ private: bool gateOn = true; // Signals going through NB float32_t runningSum = 0.0; bool runNB = false; + bool twoChannel = false; // Activates 2 channels for I-Q. }; #endif