From 02a73de41be5436eff27b587062a1c759044d691 Mon Sep 17 00:00:00 2001 From: f4exb Date: Sat, 29 Jun 2024 20:03:13 +0200 Subject: [PATCH] WDSP: added ANB and NOB blocks (noise blanker) --- wdsp/CMakeLists.txt | 4 + wdsp/RXA.cpp | 58 +++++ wdsp/RXA.hpp | 10 + wdsp/anb.cpp | 282 +++++++++++++++++++++ wdsp/anb.hpp | 109 ++++++++ wdsp/nob.cpp | 600 ++++++++++++++++++++++++++++++++++++++++++++ wdsp/nob.hpp | 129 ++++++++++ 7 files changed, 1192 insertions(+) create mode 100644 wdsp/anb.cpp create mode 100644 wdsp/anb.hpp create mode 100644 wdsp/nob.cpp create mode 100644 wdsp/nob.hpp diff --git a/wdsp/CMakeLists.txt b/wdsp/CMakeLists.txt index 3f09f5c39..18feb71dd 100644 --- a/wdsp/CMakeLists.txt +++ b/wdsp/CMakeLists.txt @@ -5,6 +5,7 @@ set(wdsp_SOURCES amd.cpp ammod.cpp amsq.cpp + anb.cpp anf.cpp anr.cpp bandpass.cpp @@ -34,6 +35,7 @@ set(wdsp_SOURCES meter.cpp meterlog10.cpp nbp.cpp + nob.cpp osctrl.cpp patchpanel.cpp resample.cpp @@ -54,6 +56,7 @@ set(wdsp_HEADERS amd.hpp ammod.hpp amsq.hpp + anb.hpp anf.hpp anr.hpp bandpass.hpp @@ -85,6 +88,7 @@ set(wdsp_HEADERS meter.hpp meterlog10.hpp nbp.hpp + nob.hpp osctrl.hpp patchpanel.hpp resample.hpp diff --git a/wdsp/RXA.cpp b/wdsp/RXA.cpp index d43c63d95..9ad735363 100644 --- a/wdsp/RXA.cpp +++ b/wdsp/RXA.cpp @@ -52,6 +52,8 @@ warren@wpratt.com #include "iir.hpp" #include "firmin.hpp" #include "wcpAGC.hpp" +#include "anb.hpp" +#include "nob.hpp" namespace WDSP { @@ -85,6 +87,36 @@ RXA* RXA::create_rxa ( rxa->midbuff = new float[2 * rxa->dsp_size * 2]; // (float *) malloc0 (2 * ch.dsp_size * sizeof (complex)); memset(rxa->meter, 0, sizeof(float)*RXA_METERTYPE_LAST); + // Noise blanker (ANB or "NB") + rxa->anb.p = ANB::create_anb( + 0, // run + rxa->dsp_insize, // input buffer size + rxa->inbuff, // pointer to input buffer + rxa->inbuff, // pointer to output buffer + rxa->in_rate, // samplerate + 0.0001, // tau + 0.0001, // hang time + 0.0001, // advance time + 0.05, // back tau + 30.0 // thershold + ); + // Noise blanker (NOB or "NB2") + rxa->nob.p = NOB::create_nob( + 0, // run + rxa->dsp_insize, // input buffer size + rxa->inbuff, // pointer to input buffer + rxa->inbuff, // pointer to output buffer + rxa->in_rate, // samplerate + 0, // mode (zero) + 0.0001, // advance slew time + 0.0001, // advance time + 0.0001, // hang slew time + 0.0001, // hang time + 0.025, // max_imp_seq_time: + 0.05, // back tau + 30 + ); + // Ftequency shifter - shift to select a slice of spectrum rxa->shift.p = SHIFT::create_shift ( 1, // run @@ -571,6 +603,8 @@ void RXA::destroy_rxa (RXA *rxa) GEN::destroy_gen (rxa->gen0.p); RESAMPLE::destroy_resample (rxa->rsmpin.p); SHIFT::destroy_shift (rxa->shift.p); + ANB::destroy_anb(rxa->anb.p); + NOB::destroy_nob(rxa->nob.p); delete[] (rxa->midbuff); delete[] (rxa->outbuff); delete[] (rxa->inbuff); @@ -609,10 +643,14 @@ void RXA::flush_rxa (RXA *rxa) SSQL::flush_ssql (rxa->ssql.p); PANEL::flush_panel (rxa->panel.p); RESAMPLE::flush_resample (rxa->rsmpout.p); + ANB::flush_anb (rxa->anb.p); + NOB::flush_nob(rxa->nob.p); } void RXA::xrxa (RXA *rxa) { + ANB::xanb (rxa->anb.p); + NOB::xnob (rxa->nob.p); SHIFT::xshift (rxa->shift.p); RESAMPLE::xresample (rxa->rsmpin.p); GEN::xgen (rxa->gen0.p); @@ -661,6 +699,14 @@ void RXA::setInputSamplerate (RXA *rxa, int in_rate) // buffers delete[] (rxa->inbuff); rxa->inbuff = new float[1 * rxa->dsp_insize * 2]; // (float *)malloc0(1 * ch.dsp_insize * sizeof(complex)); + // anb + ANB::setBuffers_anb(rxa->anb.p, rxa->inbuff, rxa->inbuff); + ANB::setSize_anb(rxa->anb.p, rxa->dsp_insize); + ANB::setSamplerate_anb(rxa->anb.p, rxa->in_rate); + // nob + NOB::setBuffers_nob(rxa->nob.p, rxa->inbuff, rxa->inbuff); + NOB::setSize_nob(rxa->nob.p, rxa->dsp_insize); + NOB::setSamplerate_nob(rxa->nob.p, rxa->in_rate); // shift SHIFT::setBuffers_shift (rxa->shift.p, rxa->inbuff, rxa->inbuff); SHIFT::setSize_shift (rxa->shift.p, rxa->dsp_insize); @@ -708,6 +754,12 @@ void RXA::setDSPSamplerate (RXA *rxa, int dsp_rate) rxa->inbuff = new float[1 * rxa->dsp_insize * 2]; // (float *)malloc0(1 * rxa->dsp_insize * sizeof(complex)); delete[] (rxa->outbuff); rxa->outbuff = new float[1 * rxa->dsp_outsize * 2]; // (float *)malloc0(1 * rxa->dsp_outsize * sizeof(complex)); + // anb + ANB::setBuffers_anb (rxa->anb.p, rxa->inbuff, rxa->inbuff); + ANB::setSize_anb(rxa->anb.p, rxa->dsp_insize); + // nob + NOB::setBuffers_nob(rxa->nob.p, rxa->inbuff, rxa->inbuff); + NOB::setSize_nob(rxa->nob.p, rxa->dsp_insize); // shift SHIFT::setBuffers_shift (rxa->shift.p, rxa->inbuff, rxa->inbuff); SHIFT::setSize_shift (rxa->shift.p, rxa->dsp_insize); @@ -767,6 +819,12 @@ void RXA::setDSPBuffsize (RXA *rxa, int dsp_size) rxa->midbuff = new float[2 * rxa->dsp_size * 2]; // (float *)malloc0(2 * rxa->dsp_size * sizeof(complex)); delete[] (rxa->outbuff); rxa->outbuff = new float[1 * rxa->dsp_outsize * 2]; // (float *)malloc0(1 * rxa->dsp_outsize * sizeof(complex)); + // anb + ANB::setBuffers_anb (rxa->anb.p, rxa->inbuff, rxa->inbuff); + ANB::setSize_anb (rxa->anb.p, rxa->dsp_insize); + // nob + NOB::setBuffers_nob(rxa->nob.p, rxa->inbuff, rxa->inbuff); + NOB::setSize_nob(rxa->nob.p, rxa->dsp_insize); // shift SHIFT::setBuffers_shift (rxa->shift.p, rxa->inbuff, rxa->inbuff); SHIFT::setSize_shift (rxa->shift.p, rxa->dsp_insize); diff --git a/wdsp/RXA.hpp b/wdsp/RXA.hpp index a7111d0f0..03c2c1875 100644 --- a/wdsp/RXA.hpp +++ b/wdsp/RXA.hpp @@ -63,6 +63,8 @@ class PANEL; class SIPHON; class CBL; class SSQL; +class ANB; +class NOB; class BufferProbe; class WDSP_API RXA : public Unit @@ -203,6 +205,14 @@ public: { SSQL *p; } ssql; + struct + { + ANB *p; + } anb; + struct + { + NOB *p; + } nob; static RXA* create_rxa ( int in_rate, // input samplerate diff --git a/wdsp/anb.cpp b/wdsp/anb.cpp new file mode 100644 index 000000000..ee453f42e --- /dev/null +++ b/wdsp/anb.cpp @@ -0,0 +1,282 @@ +/* anb.h + +This file is part of a program that implements a Software-Defined Radio. + +Copyright (C) 2013, 2014 Warren Pratt, NR0V +Copyright (C) 2024 Edouard Griffiths, F4EXB Adapted to SDRangel + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +The author can be reached by email at + +warren@wpratt.com + +*/ + +#include "comm.hpp" +#include "anb.hpp" +#include "RXA.hpp" + +#define MAX_TAU (0.002) // maximum transition time, signal<->zero +#define MAX_ADVTIME (0.002) // maximum deadtime (zero output) in advance of detected noise +#define MAX_SAMPLERATE (1536000) + +namespace WDSP { + +void ANB::initBlanker(ANB *a) +{ + int i; + a->trans_count = (int)(a->tau * a->samplerate); + if (a->trans_count < 2) a->trans_count = 2; + a->hang_count = (int)(a->hangtime * a->samplerate); + a->adv_count = (int)(a->advtime * a->samplerate); + a->count = 0; + a->in_idx = a->trans_count + a->adv_count; + a->out_idx = 0; + a->coef = PI / a->trans_count; + a->state = 0; + a->avg = 1.0; + a->power = 1.0; + a->backmult = exp(-1.0 / (a->samplerate * a->backtau)); + a->ombackmult = 1.0 - a->backmult; + for (i = 0; i <= a->trans_count; i++) + a->wave[i] = 0.5 * cos(i * a->coef); + memset(a->dline, 0, a->dline_size * sizeof(wcomplex)); +} + +ANB* ANB::create_anb ( + int run, + int buffsize, + float* in, + float* out, + double samplerate, + double tau, + double hangtime, + double advtime, + double backtau, + double threshold +) +{ + ANB *a; + a = new ANB; + a->run = run; + a->buffsize = buffsize; + a->in = in; + a->out = out; + a->samplerate = samplerate; + a->tau = tau; + a->hangtime = hangtime; + a->advtime = advtime; + a->backtau = backtau; + a->threshold = threshold; + a->wave = new float[((int)(MAX_SAMPLERATE * MAX_TAU) + 1)]; + a->dline_size = (int)((MAX_TAU + MAX_ADVTIME) * MAX_SAMPLERATE) + 1; + a->dline = new float[a->dline_size * 2]; + initBlanker(a); + a->legacy = new float[2048 * 2]; /////////////// legacy interface - remove + return a; +} + +void ANB::destroy_anb (ANB *a) +{ + delete[] (a->legacy); /////////////// legacy interface - remove + delete[] (a->dline); + delete[] (a->wave); + delete (a); +} + +void ANB::flush_anb (ANB *a) +{ + a->cs_update.lock(); + initBlanker (a); + a->cs_update.unlock(); +} + +void ANB::xanb (ANB *a) +{ + double scale; + double mag; + int i; + if (a->run) + { + a->cs_update.lock(); + for (i = 0; i < a->buffsize; i++) + { + mag = sqrt(a->in[2 * i + 0] * a->in[2 * i + 0] + a->in[2 * i + 1] * a->in[2 * i + 1]); + a->avg = a->backmult * a->avg + a->ombackmult * mag; + a->dline[2 * a->in_idx + 0] = a->in[2 * i + 0]; + a->dline[2 * a->in_idx + 1] = a->in[2 * i + 1]; + if (mag > (a->avg * a->threshold)) + a->count = a->trans_count + a->adv_count; + + switch (a->state) + { + case 0: + a->out[2 * i + 0] = a->dline[2 * a->out_idx + 0]; + a->out[2 * i + 1] = a->dline[2 * a->out_idx + 1]; + if (a->count > 0) + { + a->state = 1; + a->dtime = 0; + a->power = 1.0; + } + break; + case 1: + scale = a->power * (0.5 + a->wave[a->dtime]); + a->out[2 * i + 0] = a->dline[2 * a->out_idx + 0] * scale; + a->out[2 * i + 1] = a->dline[2 * a->out_idx + 1] * scale; + if (++a->dtime > a->trans_count) + { + a->state = 2; + a->atime = 0; + } + break; + case 2: + a->out[2 * i + 0] = 0.0; + a->out[2 * i + 1] = 0.0; + if (++a->atime > a->adv_count) + a->state = 3; + break; + case 3: + if (a->count > 0) + a->htime = -a->count; + + a->out[2 * i + 0] = 0.0; + a->out[2 * i + 1] = 0.0; + if (++a->htime > a->hang_count) + { + a->state = 4; + a->itime = 0; + } + break; + case 4: + scale = 0.5 - a->wave[a->itime]; + a->out[2 * i + 0] = a->dline[2 * a->out_idx + 0] * scale; + a->out[2 * i + 1] = a->dline[2 * a->out_idx + 1] * scale; + if (a->count > 0) + { + a->state = 1; + a->dtime = 0; + a->power = scale; + } + else if (++a->itime > a->trans_count) + a->state = 0; + break; + } + if (a->count > 0) a->count--; + if (++a->in_idx == a->dline_size) a->in_idx = 0; + if (++a->out_idx == a->dline_size) a->out_idx = 0; + } + a->cs_update.unlock(); + } + else if (a->in != a->out) + memcpy (a->out, a->in, a->buffsize * sizeof (wcomplex)); +} + +void ANB::setBuffers_anb (ANB *a, float* in, float* out) +{ + a->in = in; + a->out = out; +} + +void ANB::setSamplerate_anb (ANB *a, int rate) +{ + a->samplerate = rate; + initBlanker (a); +} + +void ANB::setSize_anb (ANB *a, int size) +{ + a->buffsize = size; + initBlanker (a); +} + +/******************************************************************************************************** +* * +* RXA PROPERTIES * +* * +********************************************************************************************************/ + +void ANB::SetRXAANBRun (RXA& rxa, int run) +{ + ANB *a = rxa.anb.p; + a->cs_update.lock(); + a->run = run; + a->cs_update.unlock(); +} + +void ANB::SetRXAANBBuffsize (RXA& rxa, int size) +{ + ANB *a = rxa.anb.p; + a->cs_update.lock(); + a->buffsize = size; + a->cs_update.unlock(); +} + +void ANB::SetRXAANBSamplerate (RXA& rxa, int rate) +{ + ANB *a = rxa.anb.p; + a->cs_update.lock(); + a->samplerate = (double) rate; + initBlanker (a); + a->cs_update.unlock(); +} + +void ANB::SetRXAANBTau (RXA& rxa, double tau) +{ + ANB *a = rxa.anb.p; + a->cs_update.lock(); + a->tau = tau; + initBlanker (a); + a->cs_update.unlock(); +} + +void ANB::SetRXAANBHangtime (RXA& rxa, double time) +{ + ANB *a = rxa.anb.p; + a->cs_update.lock(); + a->hangtime = time; + initBlanker (a); + a->cs_update.unlock(); +} + +void ANB::SetRXAANBAdvtime (RXA& rxa, double time) +{ + ANB *a = rxa.anb.p; + a->cs_update.lock(); + a->advtime = time; + initBlanker (a); + a->cs_update.unlock(); +} + +void ANB::SetRXAANBBacktau (RXA& rxa, double tau) +{ + ANB *a = rxa.anb.p; + a->cs_update.lock(); + a->backtau = tau; + initBlanker (a); + a->cs_update.unlock(); +} + +void ANB::SetRXAANBThreshold (RXA& rxa, double thresh) +{ + ANB *a = rxa.anb.p; + a->cs_update.lock(); + a->threshold = thresh; + a->cs_update.unlock(); +} + +} + diff --git a/wdsp/anb.hpp b/wdsp/anb.hpp new file mode 100644 index 000000000..94d71cd90 --- /dev/null +++ b/wdsp/anb.hpp @@ -0,0 +1,109 @@ +/* anb.h + +This file is part of a program that implements a Software-Defined Radio. + +Copyright (C) 2013, 2014 Warren Pratt, NR0V +Copyright (C) 2024 Edouard Griffiths, F4EXB Adapted to SDRangel + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +The author can be reached by email at + +warren@wpratt.com + +*/ + +#ifndef wdsp_anb_h +#define wdsp_anb_h + +#include + +namespace WDSP { + +class RXA; + +class ANB +{ +public: + int run; + int buffsize; // size of input/output buffer + float* in; // input buffer + float* out; // output buffer + int dline_size; // length of delay line which is 'double dline[length][2]' + float *dline; // pointer to delay line + double samplerate; // samplerate, used to convert times into sample counts + double tau; // transition time, signal<->zero + double hangtime; // time to stay at zero after noise is no longer detected + double advtime; // deadtime (zero output) in advance of detected noise + double backtau; // time constant used in averaging the magnitude of the input signal + double threshold; // triggers if (noise > threshold * average_signal_magnitude) + float *wave; // pointer to array holding transition waveform + int state; // state of the state machine + double avg; // average value of the signal magnitude + int dtime; // count when decreasing the signal magnitude + int htime; // count when hanging + int itime; // count when increasing the signal magnitude + int atime; // count at zero before the noise burst (advance count) + double coef; // parameter in calculating transition waveform + int trans_count; // number of samples to equal 'tau' time + int hang_count; // number of samples to equal 'hangtime' time + int adv_count; // number of samples to equal 'advtime' time + int in_idx; // ring buffer position into which new samples are inserted + int out_idx; // ring buffer position from which delayed samples are pulled + double power; // level at which signal was increasing when a new decrease is started + int count; // set each time a noise sample is detected, counts down + double backmult; // multiplier for waveform averaging + double ombackmult; // multiplier for waveform averaging + QRecursiveMutex cs_update; + float *legacy; + + static ANB* create_anb ( + int run, + int buffsize, + float* in, + float* out, + double samplerate, + double tau, + double hangtime, + double advtime, + double backtau, + double threshold + ); + + static void destroy_anb (ANB *a); + static void flush_anb (ANB *a); + static void xanb (ANB *a); + static void setBuffers_anb (ANB *a, float* in, float* out); + static void setSamplerate_anb (ANB *a, int rate); + static void setSize_anb (ANB *a, int size); + // RXA + static void SetRXAANBRun (RXA& rxa, int run); + static void SetRXAANBBuffsize (RXA& rxa, int size); + static void SetRXAANBSamplerate (RXA& rxa, int rate); + static void SetRXAANBTau (RXA& rxa, double tau); + static void SetRXAANBHangtime (RXA& rxa, double time); + static void SetRXAANBAdvtime (RXA& rxa, double time); + static void SetRXAANBBacktau (RXA& rxa, double tau); + static void SetRXAANBThreshold (RXA& rxa, double thresh); + +private: + static void initBlanker(ANB *a); //////////// legacy interface - remove +}; + + + +} // namespace + +#endif diff --git a/wdsp/nob.cpp b/wdsp/nob.cpp new file mode 100644 index 000000000..a00bbf164 --- /dev/null +++ b/wdsp/nob.cpp @@ -0,0 +1,600 @@ +/* nobII.c + +This file is part of a program that implements a Software-Defined Radio. + +Copyright (C) 2014 Warren Pratt, NR0V +Copyright (C) 2024 Edouard Griffiths, F4EXB Adapted to SDRangel + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +The author can be reached by email at + +warren@wpratt.com + +*/ + +#include "comm.hpp" + +#define MAX_ADV_SLEW_TIME (0.002) +#define MAX_ADV_TIME (0.002) +#define MAX_HANG_SLEW_TIME (0.002) +#define MAX_HANG_TIME (0.002) +#define MAX_SEQ_TIME (0.025) +#define MAX_SAMPLERATE (1536000.0) + +#include "nob.hpp" +#include "RXA.hpp" + +namespace WDSP { + +void NOB::init_nob (NOB *a) +{ + int i; + double coef; + a->adv_slew_count = (int)(a->advslewtime * a->samplerate); + a->adv_count = (int)(a->advtime * a->samplerate); + a->hang_count = (int)(a->hangtime * a->samplerate); + a->hang_slew_count = (int)(a->hangslewtime * a->samplerate); + a->max_imp_seq = (int)(a->max_imp_seq_time * a->samplerate); + a->backmult = exp (-1.0 / (a->samplerate * a->backtau)); + a->ombackmult = 1.0 - a->backmult; + if (a->adv_slew_count > 0) + { + coef = PI / (a->adv_slew_count + 1); + for (i = 0; i < a->adv_slew_count; i++) + a->awave[i] = 0.5 * cos ((i + 1) * coef); + } + if (a->hang_slew_count > 0) + { + coef = PI / a->hang_slew_count; + for (i = 0; i < a->hang_slew_count; i++) + a->hwave[i] = 0.5 * cos (i * coef); + } + + flush_nob (a); +} + +NOB* NOB::create_nob ( + int run, + int buffsize, + float* in, + float* out, + double samplerate, + int mode, + double advslewtime, + double advtime, + double hangslewtime, + double hangtime, + double max_imp_seq_time, + double backtau, + double threshold + ) +{ + NOB *a = new NOB; + a->run = run; + a->buffsize = buffsize; + a->in = in; + a->out = out; + a->samplerate = samplerate; + a->mode = mode; + a->advslewtime = advslewtime; + a->advtime = advtime; + a->hangslewtime = hangslewtime; + a->hangtime = hangtime; + a->max_imp_seq_time = max_imp_seq_time; + a->backtau = backtau; + a->threshold = threshold; + a->dline_size = (int)(MAX_SAMPLERATE * (MAX_ADV_SLEW_TIME + + MAX_ADV_TIME + + MAX_HANG_SLEW_TIME + + MAX_HANG_TIME + + MAX_SEQ_TIME ) + 2); + a->dline = new double[a->dline_size * 2]; + a->imp = new int[a->dline_size]; + a->awave = new double[(int)(MAX_ADV_SLEW_TIME * MAX_SAMPLERATE + 1)]; + a->hwave = new double[(int)(MAX_HANG_SLEW_TIME * MAX_SAMPLERATE + 1)]; + + a->filterlen = 10; + a->bfbuff = new double[a->filterlen * 2]; + a->ffbuff = new double[a->filterlen * 2]; + a->fcoefs = new double[a->filterlen]; + a->fcoefs[0] = 0.308720593; + a->fcoefs[1] = 0.216104415; + a->fcoefs[2] = 0.151273090; + a->fcoefs[3] = 0.105891163; + a->fcoefs[4] = 0.074123814; + a->fcoefs[5] = 0.051886670; + a->fcoefs[6] = 0.036320669; + a->fcoefs[7] = 0.025424468; + a->fcoefs[8] = 0.017797128; + a->fcoefs[9] = 0.012457989; + + init_nob (a); + + a->legacy = new double[2048 * 2]; /////////////// legacy interface - remove + return a; +} + +void NOB::destroy_nob (NOB *a) +{ + delete[] (a->legacy); /////////////// remove + delete[] (a->fcoefs); + delete[] (a->ffbuff); + delete[] (a->bfbuff); + delete[] (a->hwave); + delete[] (a->awave); + delete[] (a->imp); + delete[] (a->dline); + delete (a); +} + +void NOB::flush_nob (NOB *a) +{ + a->out_idx = 0; + a->scan_idx = a->out_idx + a->adv_slew_count + a->adv_count + 1; + a->in_idx = a->scan_idx + a->max_imp_seq + a->hang_count + a->hang_slew_count + a->filterlen; + a->state = 0; + a->overflow = 0; + a->avg = 1.0; + a->bfb_in_idx = a->filterlen - 1; + a->ffb_in_idx = a->filterlen - 1; + memset (a->dline, 0, a->dline_size * sizeof (wcomplex)); + memset (a->imp, 0, a->dline_size * sizeof (int)); + memset (a->bfbuff, 0, a->filterlen * sizeof (wcomplex)); + memset (a->ffbuff, 0, a->filterlen * sizeof (wcomplex)); +} + +void NOB::xnob (NOB *a) +{ + double scale; + double mag; + int bf_idx; + int ff_idx; + int lidx, tidx; + int i, j, k; + int bfboutidx; + int ffboutidx; + int hcount; + int len; + int ffcount; + int staydown; + a->cs_update.lock(); + if (a->run) + { + for (i = 0; i < a->buffsize; i++) + { + a->dline[2 * a->in_idx + 0] = a->in[2 * i + 0]; + a->dline[2 * a->in_idx + 1] = a->in[2 * i + 1]; + mag = sqrt(a->dline[2 * a->in_idx + 0] * a->dline[2 * a->in_idx + 0] + a->dline[2 * a->in_idx + 1] * a->dline[2 * a->in_idx + 1]); + a->avg = a->backmult * a->avg + a->ombackmult * mag; + if (mag > (a->avg * a->threshold)) + a->imp[a->in_idx] = 1; + else + a->imp[a->in_idx] = 0; + if ((bf_idx = a->out_idx + a->adv_slew_count) >= a->dline_size) bf_idx -= a->dline_size; + if (a->imp[bf_idx] == 0) + { + if (++a->bfb_in_idx == a->filterlen) a->bfb_in_idx -= a->filterlen; + a->bfbuff[2 * a->bfb_in_idx + 0] = a->dline[2 * bf_idx + 0]; + a->bfbuff[2 * a->bfb_in_idx + 1] = a->dline[2 * bf_idx + 1]; + } + + switch (a->state) + { + case 0: // normal output & impulse setup + { + a->out[2 * i + 0] = a->dline[2 * a->out_idx + 0]; + a->out[2 * i + 1] = a->dline[2 * a->out_idx + 1]; + a->Ilast = a->dline[2 * a->out_idx + 0]; + a->Qlast = a->dline[2 * a->out_idx + 1]; + if (a->imp[a->scan_idx] > 0) + { + a->time = 0; + if (a->adv_slew_count > 0) + a->state = 1; + else if (a->adv_count > 0) + a->state = 2; + else + a->state = 3; + tidx = a->scan_idx; + a->blank_count = 0; + do + { + len = 0; + hcount = 0; + while ((a->imp[tidx] > 0 || hcount > 0) && a->blank_count < a->max_imp_seq) + { + a->blank_count++; + if (hcount > 0) hcount--; + if (a->imp[tidx] > 0) hcount = a->hang_count + a->hang_slew_count; + if (++tidx >= a->dline_size) tidx -= a->dline_size; + } + j = 1; + len = 0; + lidx = tidx; + while (j <= a->adv_slew_count + a->adv_count && len == 0) + { + if (a->imp[lidx] == 1) + { + len = j; + tidx = lidx; + } + if (++lidx >= a->dline_size) lidx -= a->dline_size; + j++; + } + if((a->blank_count += len) > a->max_imp_seq) + { + a->blank_count = a->max_imp_seq; + a->overflow = 1; + break; + } + } while (len != 0); + if (a->overflow == 0) + { + a->blank_count -= a->hang_slew_count; + a->Inext = a->dline[2 * tidx + 0]; + a->Qnext = a->dline[2 * tidx + 1]; + + if (a->mode == 1 || a->mode == 2 || a->mode == 4) + { + bfboutidx = a->bfb_in_idx; + a->I1 = 0.0; + a->Q1 = 0.0; + for (k = 0; k < a->filterlen; k++) + { + a->I1 += a->fcoefs[k] * a->bfbuff[2 * bfboutidx + 0]; + a->Q1 += a->fcoefs[k] * a->bfbuff[2 * bfboutidx + 1]; + if (--bfboutidx < 0) bfboutidx += a->filterlen; + } + } + + if (a->mode == 2 || a->mode == 3 || a->mode == 4) + { + if ((ff_idx = a->scan_idx + a->blank_count) >= a->dline_size) ff_idx -= a->dline_size; + ffcount = 0; + while (ffcount < a->filterlen) + { + if (a->imp[ff_idx] == 0) + { + if (++a->ffb_in_idx == a->filterlen) a->ffb_in_idx -= a->filterlen; + a->ffbuff[2 * a->ffb_in_idx + 0] = a->dline[2 * ff_idx + 0]; + a->ffbuff[2 * a->ffb_in_idx + 1] = a->dline[2 * ff_idx + 1]; + ++ffcount; + } + if (++ff_idx >= a->dline_size) ff_idx -= a->dline_size; + } + if ((ffboutidx = a->ffb_in_idx + 1) >= a->filterlen) ffboutidx -= a->filterlen; + a->I2 = 0.0; + a->Q2 = 0.0; + for (k = 0; k < a->filterlen; k++) + { + a->I2 += a->fcoefs[k] * a->ffbuff[2 * ffboutidx + 0]; + a->Q2 += a->fcoefs[k] * a->ffbuff[2 * ffboutidx + 1]; + if (++ffboutidx >= a->filterlen) ffboutidx -= a->filterlen; + } + } + + switch (a->mode) + { + case 0: // zero + a->deltaI = 0.0; + a->deltaQ = 0.0; + a->I = 0.0; + a->Q = 0.0; + break; + case 1: // sample-hold + a->deltaI = 0.0; + a->deltaQ = 0.0; + a->I = a->I1; + a->Q = a->Q1; + break; + case 2: // mean-hold + a->deltaI = 0.0; + a->deltaQ = 0.0; + a->I = 0.5 * (a->I1 + a->I2); + a->Q = 0.5 * (a->Q1 + a->Q2); + break; + case 3: // hold-sample + a->deltaI = 0.0; + a->deltaQ = 0.0; + a->I = a->I2; + a->Q = a->Q2; + break; + case 4: // linear interpolation + a->deltaI = (a->I2 - a->I1) / (a->adv_count + a->blank_count); + a->deltaQ = (a->Q2 - a->Q1) / (a->adv_count + a->blank_count); + a->I = a->I1; + a->Q = a->Q1; + break; + } + } + else + { + if (a->adv_slew_count > 0) + a->state = 5; + else + { + a->state = 6; + a->time = 0; + a->blank_count += a->adv_count + a->filterlen; + } + } + } + break; + } + case 1: // slew output in advance of blanking period + { + scale = 0.5 + a->awave[a->time]; + a->out[2 * i + 0] = a->Ilast * scale + (1.0 - scale) * a->I; + a->out[2 * i + 1] = a->Qlast * scale + (1.0 - scale) * a->Q; + if (++a->time == a->adv_slew_count) + { + a->time = 0; + if (a->adv_count > 0) + a->state = 2; + else + a->state = 3; + } + break; + } + case 2: // initial advance period + { + a->out[2 * i + 0] = a->I; + a->out[2 * i + 1] = a->Q; + a->I += a->deltaI; + a->Q += a->deltaQ; + + if (++a->time == a->adv_count) + { + a->state = 3; + a->time = 0; + } + break; + } + case 3: // impulse & hang period + { + a->out[2 * i + 0] = a->I; + a->out[2 * i + 1] = a->Q; + a->I += a->deltaI; + a->Q += a->deltaQ; + + if (++a->time == a->blank_count) + { + if (a->hang_slew_count > 0) + { + a->state = 4; + a->time = 0; + } + else + a->state = 0; + } + break; + } + case 4: // slew output after blanking period + { + scale = 0.5 - a->hwave[a->time]; + a->out[2 * i + 0] = a->Inext * scale + (1.0 - scale) * a->I; + a->out[2 * i + 1] = a->Qnext * scale + (1.0 - scale) * a->Q; + if (++a->time == a->hang_slew_count) + a->state = 0; + break; + } + case 5: + { + scale = 0.5 + a->awave[a->time]; + a->out[2 * i + 0] = a->Ilast * scale; + a->out[2 * i + 1] = a->Qlast * scale; + if (++a->time == a->adv_slew_count) + { + a->state = 6; + a->time = 0; + a->blank_count += a->adv_count + a->filterlen; + } + break; + } + case 6: + { + a->out[2 * i + 0] = 0.0; + a->out[2 * i + 1] = 0.0; + if (++a->time == a->blank_count) + a->state = 7; + break; + } + case 7: + { + a->out[2 * i + 0] = 0.0; + a->out[2 * i + 1] = 0.0; + staydown = 0; + a->time = 0; + if ((tidx = a->scan_idx + a->hang_slew_count + a->hang_count) >= a->dline_size) tidx -= a->dline_size; + while (a->time++ <= a->adv_count + a->adv_slew_count + a->hang_slew_count + a->hang_count) // CHECK EXACT COUNTS!!!!!!!!!!!!!!!!!!!!!!! + { + if (a->imp[tidx] == 1) staydown = 1; + if (--tidx < 0) tidx += a->dline_size; + } + if (staydown == 0) + { + if (a->hang_count > 0) + { + a->state = 8; + a->time = 0; + } + else if (a->hang_slew_count > 0) + { + a->state = 9; + a->time = 0; + if ((tidx = a->scan_idx + a->hang_slew_count + a->hang_count - a->adv_count - a->adv_slew_count) >= a->dline_size) tidx -= a->dline_size; + if (tidx < 0) tidx += a->dline_size; + a->Inext = a->dline[2 * tidx + 0]; + a->Qnext = a->dline[2 * tidx + 1]; + } + else + { + a->state = 0; + a->overflow = 0; + } + } + break; + } + case 8: + { + a->out[2 * i + 0] = 0.0; + a->out[2 * i + 1] = 0.0; + if (++a->time == a->hang_count) + { + if (a->hang_slew_count > 0) + { + a->state = 9; + a->time = 0; + if ((tidx = a->scan_idx + a->hang_slew_count - a->adv_count - a->adv_slew_count) >= a->dline_size) tidx -= a->dline_size; + if (tidx < 0) tidx += a->dline_size; + a->Inext = a->dline[2 * tidx + 0]; + a->Qnext = a->dline[2 * tidx + 1]; + } + else + { + a->state = 0; + a->overflow = 0; + } + } + break; + } + case 9: + { + scale = 0.5 - a->hwave[a->time]; + a->out[2 * i + 0] = a->Inext * scale; + a->out[2 * i + 1] = a->Qnext * scale; + + if (++a->time >= a->hang_slew_count) + { + a->state = 0; + a->overflow = 0; + } + break; + } + } + if (++a->in_idx == a->dline_size) a->in_idx = 0; + if (++a->scan_idx == a->dline_size) a->scan_idx = 0; + if (++a->out_idx == a->dline_size) a->out_idx = 0; + } + } + else if (a->in != a->out) + memcpy (a->out, a->in, a->buffsize * sizeof (wcomplex)); + a->cs_update.unlock(); +} + +void NOB::setBuffers_nob (NOB *a, float* in, float* out) +{ + a->in = in; + a->out = out; +} + +void NOB::setSamplerate_nob (NOB *a, int rate) +{ + a->samplerate = rate; + init_nob (a); +} + +void NOB::setSize_nob (NOB *a, int size) +{ + a->buffsize = size; + flush_nob (a); +} + +/******************************************************************************************************** +* * +* RXA PROPERTIES * +* * +********************************************************************************************************/ + +void NOB::SetRXANOBRun (RXA& rxa, int run) +{ + NOB *a = rxa.nob.p; + a->cs_update.lock(); + a->run = run; + a->cs_update.unlock(); +} + +void NOB::SetRXANOBMode (RXA& rxa, int mode) +{ + NOB *a = rxa.nob.p; + a->cs_update.lock(); + a->mode = mode; + a->cs_update.unlock(); +} + +void NOB::SetRXANOBBuffsize (RXA& rxa, int size) +{ + NOB *a = rxa.nob.p; + a->cs_update.lock(); + a->buffsize = size; + a->cs_update.unlock(); +} + +void NOB::SetRXANOBSamplerate (RXA& rxa, int rate) +{ + NOB *a = rxa.nob.p; + a->cs_update.lock(); + a->samplerate = (double) rate; + init_nob (a); + a->cs_update.unlock(); +} + +void NOB::SetRXANOBTau (RXA& rxa, double tau) +{ + NOB *a = rxa.nob.p; + a->cs_update.lock(); + a->advslewtime = tau; + a->hangslewtime = tau; + init_nob (a); + a->cs_update.unlock(); +} + +void NOB::SetRXANOBHangtime (RXA& rxa, double time) +{ + NOB *a = rxa.nob.p; + a->cs_update.lock(); + a->hangtime = time; + init_nob (a); + a->cs_update.unlock(); +} + +void NOB::SetRXANOBAdvtime (RXA& rxa, double time) +{ + NOB *a = rxa.nob.p; + a->cs_update.lock(); + a->advtime = time; + init_nob (a); + a->cs_update.unlock(); +} + +void NOB::SetRXANOBBacktau (RXA& rxa, double tau) +{ + NOB *a = rxa.nob.p; + a->cs_update.lock(); + a->backtau = tau; + init_nob (a); + a->cs_update.unlock(); +} + +void NOB::SetRXANOBThreshold (RXA& rxa, double thresh) +{ + NOB *a = rxa.nob.p; + a->cs_update.lock(); + a->threshold = thresh; + a->cs_update.unlock(); +} + +} // namespace diff --git a/wdsp/nob.hpp b/wdsp/nob.hpp new file mode 100644 index 000000000..bce71aa81 --- /dev/null +++ b/wdsp/nob.hpp @@ -0,0 +1,129 @@ +/* nobII.h + +This file is part of a program that implements a Software-Defined Radio. + +Copyright (C) 2014 Warren Pratt, NR0V +Copyright (C) 2024 Edouard Griffiths, F4EXB Adapted to SDRangel + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +The author can be reached by email at + +warren@wpratt.com + +*/ + +#ifndef wdsp_nob_h +#define wdsp_nob_h + +#include + +namespace WDSP { + +class RXA; + +class NOB +{ +public: + int run; + int buffsize; // size of input/output buffer + float* in; // input buffer + float* out; // output buffer + int mode; + int dline_size; // length of delay line which is 'double dline[length][2]' + double *dline; // pointer to delay line + int *imp; + double samplerate; // samplerate, used to convert times into sample counts + double advslewtime; // transition time, signal<->zero + double advtime; // deadtime (zero output) in advance of detected noise + double hangslewtime; + double hangtime; // time to stay at zero after noise is no longer detected + double max_imp_seq_time; + int filterlen; + double *fcoefs; + double *bfbuff; + int bfb_in_idx; + double *ffbuff; + int ffb_in_idx; + double backtau; // time constant used in averaging the magnitude of the input signal + double threshold; // triggers if (noise > threshold * average_signal_magnitude) + double *awave; // pointer to array holding transition waveform + double *hwave; + int state; // state of the state machine + double avg; // average value of the signal magnitude + int time; // count when decreasing the signal magnitude + int adv_slew_count; + int adv_count; // number of samples to equal 'tau' time + int hang_count; // number of samples to equal 'hangtime' time + int hang_slew_count; // number of samples to equal 'advtime' time + int max_imp_seq; + int blank_count; + int in_idx; // ring buffer position into which new samples are inserted + int scan_idx; + int out_idx; // ring buffer position from which delayed samples are pulled + double backmult; // multiplier for waveform averaging + double ombackmult; // multiplier for waveform averaging + double I1, Q1; + double I2, Q2; + double I, Q; + double Ilast, Qlast; + double deltaI, deltaQ; + double Inext, Qnext; + int overflow; + QRecursiveMutex cs_update; + double *legacy; + + //////////// legacy interface - remove + static NOB* create_nob ( + int run, + int buffsize, + float* in, + float* out, + double samplerate, + int mode, + double advslewtime, + double advtime, + double hangslewtime, + double hangtime, + double max_imp_seq_time, + double backtau, + double threshold + ); + + static void destroy_nob (NOB* a); + static void flush_nob (NOB* a); + static void xnob (NOB* a); + static void setBuffers_nob (NOB *a, float* in, float* out); + static void setSamplerate_nob (NOB *a, int rate); + static void setSize_nob (NOB *a, int size); + // RXA + static void SetRXANOBRun (RXA& rxa, int run); + static void SetRXANOBMode (RXA& rxa, int mode); + static void SetRXANOBBuffsize (RXA& rxa, int size); + static void SetRXANOBSamplerate (RXA& rxa, int size); + static void SetRXANOBTau (RXA& rxa, double tau); + static void SetRXANOBHangtime (RXA& rxa, double time); + static void SetRXANOBAdvtime (RXA& rxa, double time); + static void SetRXANOBBacktau (RXA& rxa, double tau); + static void SetRXANOBThreshold (RXA& rxa, double thresh); + +private: + static void init_nob (NOB *a); +}; + + +} // namespace + +#endif