From 3f6687659ee2c71e4f9d6b5e3fb82e22e6c76899 Mon Sep 17 00:00:00 2001 From: AlexandreRouma Date: Sun, 26 Jun 2022 03:42:22 +0200 Subject: [PATCH] Prototype noise blanker --- core/src/dsp/noise_reduction/noise_blanker.h | 78 ++++++++++++++++++++ decoder_modules/radio/src/demod.h | 4 - decoder_modules/radio/src/demodulators/am.h | 3 - decoder_modules/radio/src/demodulators/cw.h | 3 - decoder_modules/radio/src/demodulators/dsb.h | 3 - decoder_modules/radio/src/demodulators/lsb.h | 3 - decoder_modules/radio/src/demodulators/nfm.h | 3 - decoder_modules/radio/src/demodulators/raw.h | 3 - decoder_modules/radio/src/demodulators/usb.h | 3 - decoder_modules/radio/src/demodulators/wfm.h | 3 - decoder_modules/radio/src/radio_module.h | 74 +++++++++++++------ misc_modules/recorder/src/main.cpp | 15 ++++ 12 files changed, 143 insertions(+), 52 deletions(-) create mode 100644 core/src/dsp/noise_reduction/noise_blanker.h diff --git a/core/src/dsp/noise_reduction/noise_blanker.h b/core/src/dsp/noise_reduction/noise_blanker.h new file mode 100644 index 00000000..bbbb89b8 --- /dev/null +++ b/core/src/dsp/noise_reduction/noise_blanker.h @@ -0,0 +1,78 @@ +#pragma once +#include "../processor.h" + +namespace dsp::noise_reduction { + class NoiseBlanker : public Processor { + using base_type = Processor; + public: + NoiseBlanker() {} + + NoiseBlanker(stream* in, double rate, double level) { init(in, rate, level); } + + void init(stream* in, double rate, double level) { + _rate = rate; + _invRate = 1.0f - _rate; + _level = level; + base_type::init(in); + } + + void setRate(double rate) { + assert(base_type::_block_init); + std::lock_guard lck(base_type::ctrlMtx); + _rate = rate; + _invRate = 1.0f - _rate; + } + + void setLevel(double level) { + assert(base_type::_block_init); + std::lock_guard lck(base_type::ctrlMtx); + _level = level; + } + + void reset() { + assert(base_type::_block_init); + std::lock_guard lck(base_type::ctrlMtx); + amp = 1.0f; + } + + inline int process(int count, complex_t* in, complex_t* out) { + for (int i = 0; i < count; i++) { + // Get signal amplitude + float inAmp = in[i].amplitude(); + + // Update average amplitude + float gain = 1.0f; + if (inAmp != 0.0f) { + amp = (amp * _invRate) + (inAmp * _rate); + float excess = inAmp / amp; + if (excess > _level) { + gain = 1.0f / excess; + } + } + + // Scale output by gain + out[i] = in[i] * gain; + } + return count; + } + + int run() { + int count = base_type::_in->read(); + if (count < 0) { return -1; } + + process(count, base_type::_in->readBuf, base_type::out.writeBuf); + + base_type::_in->flush(); + if (!base_type::out.swap(count)) { return -1; } + return count; + } + + protected: + float _rate; + float _invRate; + float _level; + + float amp = 1.0; + + }; +} \ No newline at end of file diff --git a/decoder_modules/radio/src/demod.h b/decoder_modules/radio/src/demod.h index 06247e8b..b250aefa 100644 --- a/decoder_modules/radio/src/demod.h +++ b/decoder_modules/radio/src/demod.h @@ -38,17 +38,13 @@ namespace demod { virtual double getMinBandwidth() = 0; virtual double getMaxBandwidth() = 0; virtual bool getBandwidthLocked() = 0; - virtual double getMaxAFBandwidth() = 0; virtual double getDefaultSnapInterval() = 0; virtual int getVFOReference() = 0; virtual bool getDeempAllowed() = 0; virtual bool getPostProcEnabled() = 0; virtual int getDefaultDeemphasisMode() = 0; - virtual double getAFBandwidth(double bandwidth) = 0; virtual bool getFMIFNRAllowed() = 0; virtual bool getNBAllowed() = 0; - - virtual bool getDynamicAFBandwidth() = 0; virtual dsp::stream* getOutput() = 0; }; } diff --git a/decoder_modules/radio/src/demodulators/am.h b/decoder_modules/radio/src/demodulators/am.h index 6ee21269..81d71806 100644 --- a/decoder_modules/radio/src/demodulators/am.h +++ b/decoder_modules/radio/src/demodulators/am.h @@ -79,14 +79,11 @@ namespace demod { double getMinBandwidth() { return 1000.0; } double getMaxBandwidth() { return getIFSampleRate(); } bool getBandwidthLocked() { return false; } - double getMaxAFBandwidth() { return getIFSampleRate() / 2.0; } double getDefaultSnapInterval() { return 1000.0; } int getVFOReference() { return ImGui::WaterfallVFO::REF_CENTER; } bool getDeempAllowed() { return false; } bool getPostProcEnabled() { return true; } int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; } - double getAFBandwidth(double bandwidth) { return bandwidth / 2.0; } - bool getDynamicAFBandwidth() { return true; } bool getFMIFNRAllowed() { return false; } bool getNBAllowed() { return false; } dsp::stream* getOutput() { return &demod.out; } diff --git a/decoder_modules/radio/src/demodulators/cw.h b/decoder_modules/radio/src/demodulators/cw.h index 70e5fb31..df2bdcfb 100644 --- a/decoder_modules/radio/src/demodulators/cw.h +++ b/decoder_modules/radio/src/demodulators/cw.h @@ -85,14 +85,11 @@ namespace demod { double getMinBandwidth() { return 50.0; } double getMaxBandwidth() { return 500.0; } bool getBandwidthLocked() { return false; } - double getMaxAFBandwidth() { return getIFSampleRate() / 2.0; } double getDefaultSnapInterval() { return 10.0; } int getVFOReference() { return ImGui::WaterfallVFO::REF_CENTER; } bool getDeempAllowed() { return false; } bool getPostProcEnabled() { return true; } int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; } - double getAFBandwidth(double bandwidth) { return (bandwidth / 2.0) + (float)tone; } - bool getDynamicAFBandwidth() { return true; } bool getFMIFNRAllowed() { return false; } bool getNBAllowed() { return false; } dsp::stream* getOutput() { return &demod.out; } diff --git a/decoder_modules/radio/src/demodulators/dsb.h b/decoder_modules/radio/src/demodulators/dsb.h index eb984c34..2c56565e 100644 --- a/decoder_modules/radio/src/demodulators/dsb.h +++ b/decoder_modules/radio/src/demodulators/dsb.h @@ -72,14 +72,11 @@ namespace demod { double getMinBandwidth() { return 1000.0; } double getMaxBandwidth() { return getIFSampleRate() / 2.0; } bool getBandwidthLocked() { return false; } - double getMaxAFBandwidth() { return getIFSampleRate() / 2.0; } double getDefaultSnapInterval() { return 100.0; } int getVFOReference() { return ImGui::WaterfallVFO::REF_CENTER; } bool getDeempAllowed() { return false; } bool getPostProcEnabled() { return true; } int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; } - double getAFBandwidth(double bandwidth) { return bandwidth / 2.0; } - bool getDynamicAFBandwidth() { return true; } bool getFMIFNRAllowed() { return false; } bool getNBAllowed() { return true; } dsp::stream* getOutput() { return &demod.out; } diff --git a/decoder_modules/radio/src/demodulators/lsb.h b/decoder_modules/radio/src/demodulators/lsb.h index 7b957bb6..e442a87f 100644 --- a/decoder_modules/radio/src/demodulators/lsb.h +++ b/decoder_modules/radio/src/demodulators/lsb.h @@ -72,14 +72,11 @@ namespace demod { double getMinBandwidth() { return 500.0; } double getMaxBandwidth() { return getIFSampleRate() / 2.0; } bool getBandwidthLocked() { return false; } - double getMaxAFBandwidth() { return getIFSampleRate() / 2.0; } double getDefaultSnapInterval() { return 100.0; } int getVFOReference() { return ImGui::WaterfallVFO::REF_UPPER; } bool getDeempAllowed() { return false; } bool getPostProcEnabled() { return true; } int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; } - double getAFBandwidth(double bandwidth) { return bandwidth; } - bool getDynamicAFBandwidth() { return true; } bool getFMIFNRAllowed() { return false; } bool getNBAllowed() { return true; } dsp::stream* getOutput() { return &demod.out; } diff --git a/decoder_modules/radio/src/demodulators/nfm.h b/decoder_modules/radio/src/demodulators/nfm.h index 7dda517b..d504d41c 100644 --- a/decoder_modules/radio/src/demodulators/nfm.h +++ b/decoder_modules/radio/src/demodulators/nfm.h @@ -50,14 +50,11 @@ namespace demod { double getMinBandwidth() { return 1000.0; } double getMaxBandwidth() { return getIFSampleRate(); } bool getBandwidthLocked() { return false; } - double getMaxAFBandwidth() { return getIFSampleRate() / 2.0; } double getDefaultSnapInterval() { return 2500.0; } int getVFOReference() { return ImGui::WaterfallVFO::REF_CENTER; } bool getDeempAllowed() { return true; } bool getPostProcEnabled() { return true; } int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; } - double getAFBandwidth(double bandwidth) { return bandwidth / 2.0; } - bool getDynamicAFBandwidth() { return true; } bool getFMIFNRAllowed() { return true; } bool getNBAllowed() { return false; } dsp::stream* getOutput() { return &demod.out; } diff --git a/decoder_modules/radio/src/demodulators/raw.h b/decoder_modules/radio/src/demodulators/raw.h index bab4c3bc..094494de 100644 --- a/decoder_modules/radio/src/demodulators/raw.h +++ b/decoder_modules/radio/src/demodulators/raw.h @@ -52,14 +52,11 @@ namespace demod { double getMinBandwidth() { return audioSampleRate; } double getMaxBandwidth() { return audioSampleRate; } bool getBandwidthLocked() { return true; } - double getMaxAFBandwidth() { return audioSampleRate; } double getDefaultSnapInterval() { return 2500.0; } int getVFOReference() { return ImGui::WaterfallVFO::REF_CENTER; } bool getDeempAllowed() { return false; } bool getPostProcEnabled() { return false; } int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; } - double getAFBandwidth(double bandwidth) { return bandwidth; } - bool getDynamicAFBandwidth() { return false; } bool getFMIFNRAllowed() { return false; } bool getNBAllowed() { return true; } dsp::stream* getOutput() { return &c2s.out; } diff --git a/decoder_modules/radio/src/demodulators/usb.h b/decoder_modules/radio/src/demodulators/usb.h index b285eeb6..41195ba6 100644 --- a/decoder_modules/radio/src/demodulators/usb.h +++ b/decoder_modules/radio/src/demodulators/usb.h @@ -73,14 +73,11 @@ namespace demod { double getMinBandwidth() { return 500.0; } double getMaxBandwidth() { return getIFSampleRate() / 2.0; } bool getBandwidthLocked() { return false; } - double getMaxAFBandwidth() { return getIFSampleRate() / 2.0; } double getDefaultSnapInterval() { return 100.0; } int getVFOReference() { return ImGui::WaterfallVFO::REF_LOWER; } bool getDeempAllowed() { return false; } bool getPostProcEnabled() { return true; } int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; } - double getAFBandwidth(double bandwidth) { return bandwidth; } - bool getDynamicAFBandwidth() { return true; } bool getFMIFNRAllowed() { return false; } bool getNBAllowed() { return true; } dsp::stream* getOutput() { return &demod.out; } diff --git a/decoder_modules/radio/src/demodulators/wfm.h b/decoder_modules/radio/src/demodulators/wfm.h index c3511767..7938586c 100644 --- a/decoder_modules/radio/src/demodulators/wfm.h +++ b/decoder_modules/radio/src/demodulators/wfm.h @@ -76,14 +76,11 @@ namespace demod { double getMinBandwidth() { return 50000.0; } double getMaxBandwidth() { return getIFSampleRate(); } bool getBandwidthLocked() { return false; } - double getMaxAFBandwidth() { return 16000.0; } double getDefaultSnapInterval() { return 100000.0; } int getVFOReference() { return ImGui::WaterfallVFO::REF_CENTER; } bool getDeempAllowed() { return true; } bool getPostProcEnabled() { return true; } int getDefaultDeemphasisMode() { return DEEMP_MODE_50US; } - double getAFBandwidth(double bandwidth) { return 16000.0; } - bool getDynamicAFBandwidth() { return false; } bool getFMIFNRAllowed() { return true; } bool getNBAllowed() { return false; } dsp::stream* getOutput() { return &demod.out; } diff --git a/decoder_modules/radio/src/radio_module.h b/decoder_modules/radio/src/radio_module.h index b86adb45..65057d30 100644 --- a/decoder_modules/radio/src/radio_module.h +++ b/decoder_modules/radio/src/radio_module.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -68,9 +69,11 @@ public: ifChainOutputChanged.handler = ifChainOutputChangeHandler; ifChain.init(vfo->output); + nb.init(NULL, 500.0 / 24000.0, 10.0); fmnr.init(NULL, 32); squelch.init(NULL, MIN_SQUELCH); + ifChain.addBlock(&nb, false); ifChain.addBlock(&squelch, false); ifChain.addBlock(&fmnr, false); @@ -228,6 +231,18 @@ private: } } + // Noise blanker + if (ImGui::Checkbox(("Noise blanker (W.I.P.)##_radio_nb_ena_" + _this->name).c_str(), &_this->nbEnabled)) { + _this->setNBEnabled(_this->nbEnabled); + } + if (!_this->nbEnabled && _this->enabled) { style::beginDisabled(); } + ImGui::SameLine(); + ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); + if (ImGui::SliderFloat(("##_radio_nb_lvl_" + _this->name).c_str(), &_this->nbLevel, _this->MIN_NB, _this->MAX_NB, "%.3fdB")) { + _this->setNBLevel(_this->nbLevel); + } + if (!_this->nbEnabled && _this->enabled) { style::endDisabled(); } + // Squelch if (ImGui::Checkbox(("Squelch##_radio_sqelch_ena_" + _this->name).c_str(), &_this->squelchEnabled)) { _this->setSquelchEnabled(_this->squelchEnabled); @@ -351,6 +366,7 @@ private: nbAllowed = selectedDemod->getNBAllowed(); nbEnabled = false; nbLevel = 0.0f; + double ifSamplerate = selectedDemod->getIFSampleRate(); config.acquire(); if (config.conf[name][selectedDemod->getName()].contains("bandwidth")) { bandwidth = config.conf[name][selectedDemod->getName()]["bandwidth"]; @@ -387,9 +403,6 @@ private: if (config.conf[name][selectedDemod->getName()].contains("noiseBlankerEnabled")) { nbEnabled = config.conf[name][selectedDemod->getName()]["noiseBlankerEnabled"]; } - if (config.conf[name][selectedDemod->getName()].contains("noiseBlankerEnabled")) { - nbEnabled = config.conf[name][selectedDemod->getName()]["noiseBlankerEnabled"]; - } if (config.conf[name][selectedDemod->getName()].contains("noiseBlankerLevel")) { nbLevel = config.conf[name][selectedDemod->getName()]["noiseBlankerLevel"]; } @@ -400,18 +413,23 @@ private: vfo->setBandwidthLimits(minBandwidth, maxBandwidth, selectedDemod->getBandwidthLocked()); vfo->setReference(selectedDemod->getVFOReference()); vfo->setSnapInterval(snapInterval); - vfo->setSampleRate(selectedDemod->getIFSampleRate(), bandwidth); + vfo->setSampleRate(ifSamplerate, bandwidth); } // Configure bandwidth setBandwidth(bandwidth); + // Configure noise blanker + nb.setRate(500.0 / ifSamplerate); + setNBLevel(nbLevel); + setNBEnabled(nbEnabled); + // Configure FM IF Noise Reduction setIFNRPreset((selectedDemodID == RADIO_DEMOD_NFM) ? ifnrPresets[fmIFPresetId] : IFNR_PRESET_BROADCAST); setFMIFNREnabled(FMIFNRAllowed ? FMIFNREnabled : false); // Configure squelch - squelch.setLevel(squelchLevel); + setSquelchLevel(squelchLevel); setSquelchEnabled(squelchEnabled); // Configure AF chain @@ -439,18 +457,9 @@ private: bw = std::clamp(bw, minBandwidth, maxBandwidth); bandwidth = bw; if (!selectedDemod) { return; } - float audioBW = std::min(selectedDemod->getMaxAFBandwidth(), selectedDemod->getAFBandwidth(bandwidth)); - audioBW = std::min(audioBW, audioSampleRate / 2.0); vfo->setBandwidth(bandwidth); selectedDemod->setBandwidth(bandwidth); - // // Only bother with setting the resampling setting if we're actually post processing and dynamic bw is enabled - // if (selectedDemod->getDynamicAFBandwidth() && postProcEnabled) { - // win.setCutoff(audioBW); - // win.setTransWidth(audioBW); - // resamp.block.updateWindow(&win); - // } - config.acquire(); config.conf[name][selectedDemod->getName()]["bandwidth"] = bandwidth; config.release(true); @@ -469,17 +478,10 @@ private: vfo->setSampleRate(selectedDemod->getIFSampleRate(), bandwidth); return; } - float audioBW = std::min(selectedDemod->getMaxAFBandwidth(), selectedDemod->getAFBandwidth(bandwidth)); - audioBW = std::min(audioBW, audioSampleRate / 2.0); afChain.stop(); - // // Configure resampler - // resamp.block.setOutSampleRate(audioSampleRate); - // win.setSampleRate(selectedDemod->getAFSampleRate() * resamp.block.getInterpolation()); - // win.setCutoff(audioBW); - // win.setTransWidth(audioBW); - // resamp.block.updateWindow(&win); + // Configure resampler resamp.setOutSamplerate(audioSampleRate); // Configure deemphasis sample rate @@ -501,6 +503,27 @@ private: config.release(true); } + void setNBEnabled(bool enable) { + nbEnabled = enable; + if (!selectedDemod) { return; } + ifChain.setBlockEnabled(&nb, nbEnabled, [=](dsp::stream* out){ selectedDemod->setInput(out); }); + + // Save config + config.acquire(); + config.conf[name][selectedDemod->getName()]["noiseBlankerEnabled"] = nbEnabled; + config.release(true); + } + + void setNBLevel(float level) { + nbLevel = std::clamp(level, MIN_NB, MAX_NB); + nb.setLevel(nbLevel); + + // Save config + config.acquire(); + config.conf[name][selectedDemod->getName()]["noiseBlankerLevel"] = nbLevel; + config.release(true); + } + void setSquelchEnabled(bool enable) { squelchEnabled = enable; if (!selectedDemod) { return; } @@ -623,6 +646,7 @@ private: // IF chain dsp::chain ifChain; + dsp::noise_reduction::NoiseBlanker nb; dsp::noise_reduction::FMIF fmnr; dsp::noise_reduction::Squelch squelch; @@ -663,9 +687,11 @@ private: float notchWidth = 500; bool nbAllowed; - bool nbEnabled; - float nbLevel = -100.0f; + bool nbEnabled = false; + float nbLevel = 10.0f; + const double MIN_NB = 1.0; + const double MAX_NB = 10.0; const double MIN_SQUELCH = -100.0; const double MAX_SQUELCH = 0.0; diff --git a/misc_modules/recorder/src/main.cpp b/misc_modules/recorder/src/main.cpp index 5bb58e39..c95387d4 100644 --- a/misc_modules/recorder/src/main.cpp +++ b/misc_modules/recorder/src/main.cpp @@ -67,11 +67,15 @@ public: if (!config.conf[name].contains("audioVolume")) { config.conf[name]["audioVolume"] = 1.0; } + if (!config.conf[name].contains("ignoreSilence")) { + config.conf[name]["ignoreSilence"] = false; + } recMode = config.conf[name]["mode"]; folderSelect.setPath(config.conf[name]["recPath"]); selectedStreamName = config.conf[name]["audioStream"]; audioVolume = config.conf[name]["audioVolume"]; + ignoreSilence = config.conf[name]["ignoreSilence"]; config.release(created); // Init audio path @@ -291,6 +295,12 @@ private: } ImGui::PopItemWidth(); + if (ImGui::Checkbox(CONCAT("Ignore silence##_recorder_ing_silence_", name), &ignoreSilence)) { + config.acquire(); + config.conf[name]["ignoreSilence"] = ignoreSilence; + config.release(true); + } + if (!folderSelect.pathIsValid() || selectedStreamName == "") { style::beginDisabled(); } if (!recording) { if (ImGui::Button(CONCAT("Record##_recorder_rec_", name), ImVec2(menuColumnWidth, 0))) { @@ -314,6 +324,9 @@ private: static void _audioHandler(dsp::stereo_t* data, int count, void* ctx) { RecorderModule* _this = (RecorderModule*)ctx; + if (_this->ignoreSilence && data[0].l == 0.0f && data[0].r == 0.0f) { + return; + } volk_32f_s32f_convert_16i(_this->wavSampleBuf, (float*)data, 32767.0f, count * 2); _this->audioWriter->writeSamples(_this->wavSampleBuf, count * 2 * sizeof(int16_t)); _this->samplesWritten += count; @@ -514,6 +527,8 @@ private: EventHandler streamRegisteredHandler; EventHandler streamUnregisterHandler; EventHandler streamUnregisteredHandler; + + bool ignoreSilence = false; }; struct RecorderContext_t {