mirror of
				https://github.com/AlexandreRouma/SDRPlusPlus.git
				synced 2025-10-31 00:48:11 +01:00 
			
		
		
		
	Compare commits
	
		
			2 Commits
		
	
	
		
			debug
			...
			noise_redu
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 87da47f53d | ||
|  | 75050347de | 
| @@ -9,7 +9,7 @@ namespace dsp::convert { | ||||
|  | ||||
|         StereoToMono(stream<stereo_t>* in) { base_type::init(in); } | ||||
|          | ||||
|         inline int process(int count, const stereo_t* in, float* out) { | ||||
|         static inline int process(int count, const stereo_t* in, float* out) { | ||||
|             for (int i = 0; i < count; i++) { | ||||
|                 out[i] = (in[i].l + in[i].r) / 2.0f; | ||||
|             } | ||||
|   | ||||
							
								
								
									
										183
									
								
								core/src/dsp/noise_reduction/audio.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								core/src/dsp/noise_reduction/audio.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,183 @@ | ||||
| #pragma once | ||||
| #include "../processor.h" | ||||
| #include "../window/nuttall.h" | ||||
| #include <fftw3.h> | ||||
| #include "../convert/stereo_to_mono.h" | ||||
|  | ||||
| namespace dsp::noise_reduction { | ||||
|     class Audio : public Processor<stereo_t, stereo_t> { | ||||
|         using base_type = Processor<stereo_t, stereo_t>; | ||||
|     public: | ||||
|         Audio() {} | ||||
|  | ||||
|         Audio(stream<stereo_t>* in, int bins) { init(in, bins); } | ||||
|  | ||||
|         ~Audio() { | ||||
|             if (!base_type::_block_init) { return; } | ||||
|             base_type::stop(); | ||||
|             destroyBuffers(); | ||||
|         } | ||||
|  | ||||
|         void init(stream<stereo_t>* in, int bins) { | ||||
|             _bins = bins; | ||||
|             complexBins = (bins / 2) + 1; | ||||
|             normFactor = 1.0f / (float)_bins; | ||||
|             initBuffers(); | ||||
|             base_type::init(in); | ||||
|         } | ||||
|  | ||||
|         void setBins(int bins) { | ||||
|             assert(base_type::_block_init); | ||||
|             std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); | ||||
|             base_type::tempStop(); | ||||
|             _bins = bins; | ||||
|             complexBins = (bins / 2) + 1; | ||||
|             normFactor = 1.0f / (float)_bins; | ||||
|             destroyBuffers(); | ||||
|             initBuffers(); | ||||
|             base_type::tempStart(); | ||||
|         } | ||||
|  | ||||
|         void setLevel(float level) { | ||||
|             _level = powf(10.0f, level * 0.1f); | ||||
|         } | ||||
|  | ||||
|         void reset() { | ||||
|             assert(base_type::_block_init); | ||||
|             std::lock_guard<std::recursive_mutex> lck(base_type::ctrlMtx); | ||||
|             base_type::tempStop(); | ||||
|             buffer::clear(buffer, _bins - 1); | ||||
|             buffer::clear(backFFTIn, _bins); | ||||
|             buffer::clear(noisePrint, _bins); | ||||
|             base_type::tempStart(); | ||||
|         } | ||||
|  | ||||
|         int process(int count, const stereo_t* in, stereo_t* out) { | ||||
|             // Write new input data to buffer | ||||
|             convert::StereoToMono::process(count, in, bufferStart); | ||||
|              | ||||
|             // Iterate the FFT | ||||
|             for (int i = 0; i < count; i++) { | ||||
|                 // Apply windows | ||||
|                 volk_32f_x2_multiply_32f(forwFFTIn, &buffer[i], fftWin, _bins); | ||||
|  | ||||
|                 // Do forward FFT | ||||
|                 fftwf_execute(forwardPlan); | ||||
|  | ||||
|                 // Get bin amplitude and square to get power | ||||
|                 volk_32fc_magnitude_32f(ampBuf, (lv_32fc_t*)forwFFTOut, complexBins); | ||||
|  | ||||
|                 // Update noise print using a running average | ||||
|                 volk_32f_s32f_multiply_32f(scaledAmps, ampBuf, alpha, complexBins); | ||||
|                 volk_32f_s32f_multiply_32f(noisePrint, noisePrint, beta, complexBins); | ||||
|                 volk_32f_x2_add_32f(noisePrint, noisePrint, scaledAmps, complexBins); | ||||
|  | ||||
|                 // Clamp amplitudes | ||||
|                 volk_32f_x2_max_32f(ampBuf, ampBuf, noisePrint, complexBins); | ||||
|  | ||||
|                 // Compute Wiener (funny) filter | ||||
|                 volk_32f_x2_subtract_32f(scaledAmps, ampBuf, noisePrint, complexBins); | ||||
|                 volk_32f_x2_divide_32f(scaledAmps, scaledAmps, ampBuf, complexBins); | ||||
|  | ||||
|                 // Apply wiener filter to bins | ||||
|                 volk_32fc_32f_multiply_32fc((lv_32fc_t*)backFFTIn, (lv_32fc_t*)forwFFTOut, scaledAmps, complexBins); | ||||
|  | ||||
|                 // Do reverse FFT and get first element | ||||
|                 fftwf_execute(backwardPlan); | ||||
|                 out[i].l = backFFTOut[_bins / 2]; | ||||
|                 out[i].r = backFFTOut[_bins / 2]; | ||||
|             } | ||||
|  | ||||
|             // Correct amplitude | ||||
|             volk_32f_s32f_multiply_32f((float*)out, (float*)out, normFactor, count*2); | ||||
|  | ||||
|             // Move buffer buffer | ||||
|             memmove(buffer, &buffer[count], (_bins - 1) * sizeof(float)); | ||||
|  | ||||
|             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); | ||||
|  | ||||
|             // Swap if some data was generated | ||||
|             base_type::_in->flush(); | ||||
|             if (!base_type::out.swap(count)) { return -1; } | ||||
|             return count; | ||||
|         } | ||||
|  | ||||
|     protected: | ||||
|         void initBuffers() { | ||||
|             // Allocate FFT buffers | ||||
|             forwFFTIn = (float*)fftwf_malloc(_bins * sizeof(float)); | ||||
|             forwFFTOut = (complex_t*)fftwf_malloc(_bins * sizeof(complex_t)); | ||||
|             backFFTIn = (complex_t*)fftwf_malloc(_bins * sizeof(complex_t)); | ||||
|             backFFTOut = (float*)fftwf_malloc(_bins * sizeof(float)); | ||||
|  | ||||
|             // Allocate and clear delay buffer | ||||
|             buffer = buffer::alloc<float>(STREAM_BUFFER_SIZE + 64000); | ||||
|             bufferStart = &buffer[_bins - 1]; | ||||
|             buffer::clear(buffer, _bins - 1); | ||||
|  | ||||
|             // Clear backward FFT input | ||||
|             buffer::clear(backFFTIn, _bins); | ||||
|  | ||||
|             // Allocate amplitude buffer | ||||
|             ampBuf = buffer::alloc<float>(_bins); | ||||
|             scaledAmps = buffer::alloc<float>(_bins); | ||||
|             noisePrint = buffer::alloc<float>(_bins); | ||||
|             buffer::clear(noisePrint, _bins); | ||||
|  | ||||
|             // Allocate and generate Window | ||||
|             fftWin = buffer::alloc<float>(_bins); | ||||
|             for (int i = 0; i < _bins; i++) { fftWin[i] = window::nuttall(i, _bins - 1); } | ||||
|  | ||||
|             // Plan FFTs | ||||
|             forwardPlan = fftwf_plan_dft_r2c_1d(_bins, forwFFTIn, (fftwf_complex*)forwFFTOut, FFTW_ESTIMATE); | ||||
|             backwardPlan = fftwf_plan_dft_c2r_1d(_bins, (fftwf_complex*)backFFTIn, backFFTOut, FFTW_ESTIMATE); | ||||
|         } | ||||
|  | ||||
|         void destroyBuffers() { | ||||
|             fftwf_destroy_plan(forwardPlan); | ||||
|             fftwf_destroy_plan(backwardPlan); | ||||
|             fftwf_free(forwFFTIn); | ||||
|             fftwf_free(forwFFTOut); | ||||
|             fftwf_free(backFFTIn); | ||||
|             fftwf_free(backFFTOut); | ||||
|             buffer::free(buffer); | ||||
|             buffer::free(ampBuf); | ||||
|             buffer::free(scaledAmps); | ||||
|             buffer::free(noisePrint); | ||||
|             buffer::free(fftWin); | ||||
|         } | ||||
|  | ||||
|         float _level = 0.0f; | ||||
|  | ||||
|         float* forwFFTIn; | ||||
|         complex_t* forwFFTOut; | ||||
|         complex_t* backFFTIn; | ||||
|         float* backFFTOut; | ||||
|  | ||||
|         fftwf_plan forwardPlan; | ||||
|         fftwf_plan backwardPlan; | ||||
|  | ||||
|         float* buffer; | ||||
|         float* bufferStart; | ||||
|  | ||||
|         float* fftWin; | ||||
|  | ||||
|         float* ampBuf; | ||||
|         float* scaledAmps; | ||||
|         float* noisePrint; | ||||
|  | ||||
|         int _bins; | ||||
|         int complexBins; | ||||
|         float normFactor = 1.0f; | ||||
|  | ||||
|         float alpha = 0.0001f; | ||||
|         float beta = 0.9999f; | ||||
|     };  | ||||
| } | ||||
| @@ -37,21 +37,17 @@ namespace dsp::noise_reduction { | ||||
|  | ||||
|         inline int process(int count, complex_t* in, complex_t* out) { | ||||
|             for (int i = 0; i < count; i++) { | ||||
|                 // Get signal amplitude | ||||
|                 // Get signal amplitude and pass value if null | ||||
|                 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; | ||||
|                     } | ||||
|                 if (!inAmp) { | ||||
|                     out[i] = in[i]; | ||||
|                 } | ||||
|                  | ||||
|                 // Scale output by gain | ||||
|                 out[i] = in[i] * gain; | ||||
|  | ||||
|                 // Update running average of amplitude | ||||
|                 amp = (_rate*inAmp) + (_invRate*amp); | ||||
|  | ||||
|                 // Null out if spike (Note: ideally, it should try to guess the real data) | ||||
|                 out[i] = (inAmp > _level*amp) ? complex_t{0.0f,0.0f} : in[i]; | ||||
|             } | ||||
|             return count; | ||||
|         } | ||||
|   | ||||
| @@ -45,6 +45,7 @@ namespace demod { | ||||
|         virtual int getDefaultDeemphasisMode() = 0; | ||||
|         virtual bool getFMIFNRAllowed() = 0; | ||||
|         virtual bool getNBAllowed() = 0; | ||||
|         virtual bool getAFNRAllowed() = 0; | ||||
|         virtual dsp::stream<dsp::stereo_t>* getOutput() = 0; | ||||
|     }; | ||||
| } | ||||
|   | ||||
| @@ -86,6 +86,7 @@ namespace demod { | ||||
|         int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; } | ||||
|         bool getFMIFNRAllowed() { return false; } | ||||
|         bool getNBAllowed() { return false; } | ||||
|         bool getAFNRAllowed() { return false; } | ||||
|         dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; } | ||||
|  | ||||
|     private: | ||||
|   | ||||
| @@ -92,6 +92,7 @@ namespace demod { | ||||
|         int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; } | ||||
|         bool getFMIFNRAllowed() { return false; } | ||||
|         bool getNBAllowed() { return false; } | ||||
|         bool getAFNRAllowed() { return false; } | ||||
|         dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; } | ||||
|  | ||||
|     private: | ||||
|   | ||||
| @@ -79,6 +79,7 @@ namespace demod { | ||||
|         int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; } | ||||
|         bool getFMIFNRAllowed() { return false; } | ||||
|         bool getNBAllowed() { return true; } | ||||
|         bool getAFNRAllowed() { return false; } | ||||
|         dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; } | ||||
|  | ||||
|     private: | ||||
|   | ||||
| @@ -79,6 +79,7 @@ namespace demod { | ||||
|         int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; } | ||||
|         bool getFMIFNRAllowed() { return false; } | ||||
|         bool getNBAllowed() { return true; } | ||||
|         bool getAFNRAllowed() { return false; } | ||||
|         dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; } | ||||
|  | ||||
|     private: | ||||
|   | ||||
| @@ -75,6 +75,7 @@ namespace demod { | ||||
|         int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; } | ||||
|         bool getFMIFNRAllowed() { return true; } | ||||
|         bool getNBAllowed() { return false; } | ||||
|         bool getAFNRAllowed() { return false; } | ||||
|         dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; } | ||||
|  | ||||
|     private: | ||||
|   | ||||
| @@ -59,6 +59,7 @@ namespace demod { | ||||
|         int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; } | ||||
|         bool getFMIFNRAllowed() { return false; } | ||||
|         bool getNBAllowed() { return true; } | ||||
|         bool getAFNRAllowed() { return false; } | ||||
|         dsp::stream<dsp::stereo_t>* getOutput() { return &c2s.out; } | ||||
|  | ||||
|     private: | ||||
|   | ||||
| @@ -80,6 +80,7 @@ namespace demod { | ||||
|         int getDefaultDeemphasisMode() { return DEEMP_MODE_NONE; } | ||||
|         bool getFMIFNRAllowed() { return false; } | ||||
|         bool getNBAllowed() { return true; } | ||||
|         bool getAFNRAllowed() { return true; } | ||||
|         dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; } | ||||
|  | ||||
|     private: | ||||
|   | ||||
| @@ -130,6 +130,7 @@ namespace demod { | ||||
|         int getDefaultDeemphasisMode() { return DEEMP_MODE_50US; } | ||||
|         bool getFMIFNRAllowed() { return true; } | ||||
|         bool getNBAllowed() { return false; } | ||||
|         bool getAFNRAllowed() { return false; } | ||||
|         dsp::stream<dsp::stereo_t>* getOutput() { return &demod.out; } | ||||
|  | ||||
|         // ============= DEDICATED FUNCTIONS ============= | ||||
|   | ||||
| @@ -9,6 +9,7 @@ | ||||
| #include <dsp/noise_reduction/noise_blanker.h> | ||||
| #include <dsp/noise_reduction/fm_if.h> | ||||
| #include <dsp/noise_reduction/squelch.h> | ||||
| #include <dsp/noise_reduction/audio.h> | ||||
| #include <dsp/multirate/rational_resampler.h> | ||||
| #include <dsp/filter/deephasis.h> | ||||
| #include <core.h> | ||||
| @@ -83,9 +84,11 @@ public: | ||||
|  | ||||
|         resamp.init(NULL, 250000.0, 48000.0); | ||||
|         deemp.init(NULL, 50e-6, 48000.0); | ||||
|         afNR.init(NULL, 1024); | ||||
|  | ||||
|         afChain.addBlock(&resamp, true); | ||||
|         afChain.addBlock(&deemp, false); | ||||
|         afChain.addBlock(&afNR, false); | ||||
|  | ||||
|         // Initialize the sink | ||||
|         srChangeHandler.ctx = this; | ||||
| @@ -247,6 +250,12 @@ private: | ||||
|             if (!_this->nbEnabled && _this->enabled) { style::endDisabled(); } | ||||
|         } | ||||
|          | ||||
|         // Noise reduction | ||||
|         if (_this->afNRAllowed) { | ||||
|             if (ImGui::Checkbox(("Audio Noise Reduction##_radio_afnr_ena_" + _this->name).c_str(), &_this->afNREnabled)) { | ||||
|                 _this->setAFNREnabled(_this->afNREnabled); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Squelch | ||||
|         if (ImGui::Checkbox(("Squelch##_radio_sqelch_ena_" + _this->name).c_str(), &_this->squelchEnabled)) { | ||||
| @@ -370,6 +379,8 @@ private: | ||||
|         fmIFPresetId = ifnrPresets.valueId(IFNR_PRESET_VOICE); | ||||
|         nbAllowed = selectedDemod->getNBAllowed(); | ||||
|         nbEnabled = false; | ||||
|         afNRAllowed = selectedDemod->getAFNRAllowed(); | ||||
|         afNREnabled = false; | ||||
|         nbLevel = 0.0f; | ||||
|         double ifSamplerate = selectedDemod->getIFSampleRate(); | ||||
|         config.acquire(); | ||||
| @@ -411,6 +422,9 @@ private: | ||||
|         if (config.conf[name][selectedDemod->getName()].contains("noiseBlankerLevel")) { | ||||
|             nbLevel = config.conf[name][selectedDemod->getName()]["noiseBlankerLevel"]; | ||||
|         } | ||||
|         if (config.conf[name][selectedDemod->getName()].contains("audioNoiseReductionEnabled")) { | ||||
|             nbEnabled = config.conf[name][selectedDemod->getName()]["audioNoiseReductionEnabled"]; | ||||
|         } | ||||
|         config.release(); | ||||
|  | ||||
|         // Configure VFO | ||||
| @@ -446,7 +460,10 @@ private: | ||||
|             afChain.enableBlock(&resamp, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); }); | ||||
|  | ||||
|             // Configure deemphasis | ||||
|             setDeemphasisMode(deempModes[deempId]); | ||||
|             setDeemphasisMode(deempAllowed ? deempModes[deempId] : DEEMP_MODE_NONE); | ||||
|  | ||||
|             // Configure AF NR | ||||
|             setAFNREnabled(afNRAllowed && afNREnabled); | ||||
|         } | ||||
|         else { | ||||
|             // Disable everything if post processing is disabled | ||||
| @@ -508,6 +525,17 @@ private: | ||||
|         config.release(true); | ||||
|     } | ||||
|  | ||||
|     void setAFNREnabled(bool enable) { | ||||
|         afNREnabled = enable; | ||||
|         if (!postProcEnabled || !selectedDemod) { return; } | ||||
|         afChain.setBlockEnabled(&afNR, afNREnabled, [=](dsp::stream<dsp::stereo_t>* out){ stream.setInput(out); }); | ||||
|  | ||||
|         // Save config | ||||
|         config.acquire(); | ||||
|         config.conf[name][selectedDemod->getName()]["audioNoiseReductionEnabled"] = nbEnabled; | ||||
|         config.release(true); | ||||
|     } | ||||
|  | ||||
|     void setNBEnabled(bool enable) { | ||||
|         nbEnabled = enable; | ||||
|         if (!selectedDemod) { return; } | ||||
| @@ -660,6 +688,7 @@ private: | ||||
|     dsp::chain<dsp::stereo_t> afChain; | ||||
|     dsp::multirate::RationalResampler<dsp::stereo_t> resamp; | ||||
|     dsp::filter::Deemphasis<dsp::stereo_t> deemp; | ||||
|     dsp::noise_reduction::Audio afNR; | ||||
|  | ||||
|     SinkManager::Stream stream; | ||||
|  | ||||
| @@ -683,6 +712,9 @@ private: | ||||
|     int deempId = 0; | ||||
|     bool deempAllowed; | ||||
|  | ||||
|     bool afNREnabled = false; | ||||
|     bool afNRAllowed; | ||||
|  | ||||
|     bool FMIFNRAllowed; | ||||
|     bool FMIFNREnabled = false; | ||||
|     int fmIFPresetId; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user