From efa47660a7917e2edfdc02ea8678b24346aeb7ea Mon Sep 17 00:00:00 2001 From: Vladisslav P Date: Fri, 18 Feb 2022 18:30:20 +0300 Subject: [PATCH] rx_agc: New rx_agc implementation New stereo rx_agc Move rx_agc to the output of demodulator Remove audio_gain# blocks Make audio gain slider contorl rx_agc manual gain Move common code from nbrx/wfmrx to receiver_base Move all agc processing into block class rx_agc_2f Eliminate CAgc class Remove CurrentGainDb and move processing to get_current_gain Rename SetParameters to set_parameters Remove ProcessData and move all processing to work Fix tag propagation setting sample delay Workaroung GNU Radio < 3.8 tag propagation bug Manually calculate new tag offsets Small cleanup Fix initialization Set default current gain to 1 (0 dB) rx_agc: improve compatibility with older GNU Radio versions rx_agc: Restore manual gain correctly Set audio mute by disconnecting audio_snk rx_agc: initialize all class members And add more debug disable agc debug prints rx_agc: switch to using history Prefill magnitude buffer on buffer size change in running state Prefill magnitude buffer on enabled state change in running state And gain up/down keyboard shortcuts --- src/applications/gqrx/mainwindow.cpp | 54 +++- src/applications/gqrx/mainwindow.h | 9 +- src/applications/gqrx/receiver.cpp | 178 +++++++---- src/applications/gqrx/receiver.h | 20 +- src/dsp/CMakeLists.txt | 2 - src/dsp/agc_impl.cpp | 317 ------------------- src/dsp/agc_impl.h | 79 ----- src/dsp/rx_agc_xx.cpp | 435 +++++++++++++++++++++++---- src/dsp/rx_agc_xx.h | 102 ++++--- src/qtgui/agc_options.cpp | 155 +++++----- src/qtgui/agc_options.h | 39 +-- src/qtgui/agc_options.ui | 218 ++++++++------ src/qtgui/dockaudio.cpp | 37 +-- src/qtgui/dockaudio.h | 5 +- src/qtgui/dockaudio.ui | 14 +- src/qtgui/dockrxopt.cpp | 121 +++++--- src/qtgui/dockrxopt.h | 31 +- src/receivers/nbrx.cpp | 116 ++----- src/receivers/nbrx.h | 23 -- src/receivers/receiver_base.cpp | 64 +++- src/receivers/receiver_base.h | 31 +- src/receivers/wfmrx.cpp | 114 ++----- src/receivers/wfmrx.h | 21 +- 23 files changed, 1081 insertions(+), 1104 deletions(-) delete mode 100644 src/dsp/agc_impl.cpp delete mode 100644 src/dsp/agc_impl.h diff --git a/src/applications/gqrx/mainwindow.cpp b/src/applications/gqrx/mainwindow.cpp index bb14e27e1..b6fc95eee 100644 --- a/src/applications/gqrx/mainwindow.cpp +++ b/src/applications/gqrx/mainwindow.cpp @@ -244,16 +244,17 @@ MainWindow::MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent) connect(uiDockRxOpt, SIGNAL(amSyncDcrToggled(bool)), this, SLOT(setAmSyncDcr(bool))); connect(uiDockRxOpt, SIGNAL(amSyncPllBwSelected(float)), this, SLOT(setAmSyncPllBw(float))); connect(uiDockRxOpt, SIGNAL(agcToggled(bool)), this, SLOT(setAgcOn(bool))); - connect(uiDockRxOpt, SIGNAL(agcHangToggled(bool)), this, SLOT(setAgcHang(bool))); - connect(uiDockRxOpt, SIGNAL(agcThresholdChanged(int)), this, SLOT(setAgcThreshold(int))); - connect(uiDockRxOpt, SIGNAL(agcSlopeChanged(int)), this, SLOT(setAgcSlope(int))); - connect(uiDockRxOpt, SIGNAL(agcGainChanged(int)), this, SLOT(setAgcGain(int))); + connect(uiDockRxOpt, SIGNAL(agcTargetLevelChanged(int)), this, SLOT(setAgcTargetLevel(int))); + connect(uiDockRxOpt, SIGNAL(agcMaxGainChanged(int)), this, SLOT(setAgcMaxGain(int))); + connect(uiDockRxOpt, SIGNAL(agcAttackChanged(int)), this, SLOT(setAgcAttack(int))); connect(uiDockRxOpt, SIGNAL(agcDecayChanged(int)), this, SLOT(setAgcDecay(int))); + connect(uiDockRxOpt, SIGNAL(agcHangChanged(int)), this, SLOT(setAgcHang(int))); connect(uiDockRxOpt, SIGNAL(noiseBlankerChanged(int,bool,float)), this, SLOT(setNoiseBlanker(int,bool,float))); connect(uiDockRxOpt, SIGNAL(sqlLevelChanged(double)), this, SLOT(setSqlLevel(double))); connect(uiDockRxOpt, SIGNAL(sqlAutoClicked()), this, SLOT(setSqlLevelAuto())); connect(uiDockAudio, SIGNAL(audioGainChanged(float)), this, SLOT(setAudioGain(float))); connect(uiDockAudio, SIGNAL(audioGainChanged(float)), remote, SLOT(setAudioGain(float))); + connect(uiDockAudio, SIGNAL(audioMuteChanged(bool)), this, SLOT(setAudioMute(bool))); connect(uiDockAudio, SIGNAL(audioStreamingStarted(QString,int,bool)), this, SLOT(startAudioStream(QString,int,bool))); connect(uiDockAudio, SIGNAL(audioStreamingStopped()), this, SLOT(stopAudioStreaming())); connect(uiDockAudio, SIGNAL(audioRecStarted(QString)), this, SLOT(startAudioRec(QString))); @@ -330,7 +331,7 @@ MainWindow::MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent) connect(remote, SIGNAL(newMode(int)), uiDockRxOpt, SLOT(setCurrentDemod(int))); connect(remote, SIGNAL(newSquelchLevel(double)), this, SLOT(setSqlLevel(double))); connect(remote, SIGNAL(newSquelchLevel(double)), uiDockRxOpt, SLOT(setSquelchLevel(double))); - connect(remote, SIGNAL(newAudioGain(float)), uiDockAudio, SLOT(setAudioGainDb(float))); + connect(remote, SIGNAL(newAudioGain(float)), this, SLOT(setAudioGain(float))); connect(uiDockRxOpt, SIGNAL(sqlLevelChanged(double)), remote, SLOT(setSquelchLevel(double))); connect(remote, SIGNAL(startAudioRecorderEvent()), uiDockAudio, SLOT(startAudioRecorder())); connect(remote, SIGNAL(stopAudioRecorderEvent()), uiDockAudio, SLOT(stopAudioRecorder())); @@ -1290,6 +1291,15 @@ void MainWindow::selectDemod(int mode_idx) rx->set_cw_offset(cwofs); rx->set_sql_level(uiDockRxOpt->currentSquelchLevel()); + //Call wrapper to update enable/disabled state + setAgcOn(uiDockRxOpt->getAgcOn()); + rx->set_agc_target_level(uiDockRxOpt->getAgcTargetLevel()); + rx->set_agc_manual_gain(uiDockAudio->audioGain() / 10.0); + rx->set_agc_max_gain(uiDockRxOpt->getAgcMaxGain()); + rx->set_agc_attack(uiDockRxOpt->getAgcAttack()); + rx->set_agc_decay(uiDockRxOpt->getAgcDecay()); + rx->set_agc_hang(uiDockRxOpt->getAgcHang()); + remote->setMode(mode_idx); remote->setPassband(flo, fhi); @@ -1366,37 +1376,47 @@ void MainWindow::setAmSyncPllBw(float pll_bw) */ void MainWindow::setAudioGain(float value) { - rx->set_af_gain(value); + rx->set_agc_manual_gain(value); +} + +/** + * @brief Audio mute changed. + * @param mute New state. + */ +void MainWindow::setAudioMute(bool mute) +{ + rx->set_mute(mute); } /** Set AGC ON/OFF. */ void MainWindow::setAgcOn(bool agc_on) { rx->set_agc_on(agc_on); + uiDockAudio->setGainEnabled(!agc_on); } /** AGC hang ON/OFF. */ -void MainWindow::setAgcHang(bool use_hang) +void MainWindow::setAgcHang(int hang) { - rx->set_agc_hang(use_hang); + rx->set_agc_hang(hang); } /** AGC threshold changed. */ -void MainWindow::setAgcThreshold(int threshold) +void MainWindow::setAgcTargetLevel(int targetLevel) { - rx->set_agc_threshold(threshold); + rx->set_agc_target_level(targetLevel); } /** AGC slope factor changed. */ -void MainWindow::setAgcSlope(int factor) +void MainWindow::setAgcAttack(int attack) { - rx->set_agc_slope(factor); + rx->set_agc_attack(attack); } -/** AGC manual gain changed. */ -void MainWindow::setAgcGain(int gain) +/** AGC maximum gain changed. */ +void MainWindow::setAgcMaxGain(int gain) { - rx->set_agc_manual_gain(gain); + rx->set_agc_max_gain(gain); } /** AGC decay changed. */ @@ -1452,6 +1472,10 @@ void MainWindow::meterTimeout() level = rx->get_signal_pwr(); ui->sMeter->setLevel(level); remote->setSignalLevel(level); + if(uiDockRxOpt->getAgcOn()) + { + uiDockAudio->setAudioGain(rx->get_agc_gain() * 10.f); + } } /** Baseband FFT plot timeout. */ diff --git a/src/applications/gqrx/mainwindow.h b/src/applications/gqrx/mainwindow.h index df287cd87..ca06887ea 100644 --- a/src/applications/gqrx/mainwindow.h +++ b/src/applications/gqrx/mainwindow.h @@ -174,15 +174,16 @@ private slots: void setAmSyncDcr(bool enabled); void setAmSyncPllBw(float pll_bw); void setAgcOn(bool agc_on); - void setAgcHang(bool use_hang); - void setAgcThreshold(int threshold); - void setAgcSlope(int factor); + void setAgcHang(int hang); + void setAgcTargetLevel(int targetLevel); + void setAgcAttack(int attack); void setAgcDecay(int msec); - void setAgcGain(int gain); + void setAgcMaxGain(int gain); void setNoiseBlanker(int nbid, bool on, float threshold); void setSqlLevel(double level_db); double setSqlLevelAuto(); void setAudioGain(float gain); + void setAudioMute(bool mute); void setPassband(int bandwidth); /* audio recording and playback */ diff --git a/src/applications/gqrx/receiver.cpp b/src/applications/gqrx/receiver.cpp index 3616d92c3..b4f7fcb51 100644 --- a/src/applications/gqrx/receiver.cpp +++ b/src/applications/gqrx/receiver.cpp @@ -46,8 +46,6 @@ #include #endif -#define DEFAULT_AUDIO_GAIN -6.0 -#define WAV_FILE_GAIN 0.5 #define TARGET_QUAD_RATE 1e6 /** @@ -72,6 +70,7 @@ receiver::receiver(const std::string input_device, d_iq_rev(false), d_dc_cancel(false), d_iq_balance(false), + d_mute(false), d_demod(RX_DEMOD_OFF) { @@ -119,9 +118,6 @@ receiver::receiver(const std::string input_device, iq_fft = make_rx_fft_c(DEFAULT_FFT_SIZE, d_decim_rate, gr::fft::window::WIN_HANN); audio_fft = make_rx_fft_f(DEFAULT_FFT_SIZE, d_audio_rate, gr::fft::window::WIN_HANN); - audio_gain0 = gr::blocks::multiply_const_ff::make(0); - audio_gain1 = gr::blocks::multiply_const_ff::make(0); - set_af_gain(DEFAULT_AUDIO_GAIN); audio_udp_sink = make_udp_sink_f(); @@ -271,8 +267,14 @@ void receiver::set_output_device(const std::string device) if (d_demod != RX_DEMOD_OFF) { - tb->disconnect(audio_gain0, 0, audio_snk, 0); - tb->disconnect(audio_gain1, 0, audio_snk, 1); + try + { + tb->disconnect(rx, 0, audio_snk, 0); + tb->disconnect(rx, 1, audio_snk, 1); + } + catch(std::exception &x) + { + } } audio_snk.reset(); @@ -285,10 +287,10 @@ void receiver::set_output_device(const std::string device) audio_snk = gr::audio::sink::make(d_audio_rate, device, true); #endif - if (d_demod != RX_DEMOD_OFF) + if ((d_demod != RX_DEMOD_OFF) && !d_mute) { - tb->connect(audio_gain0, 0, audio_snk, 0); - tb->connect(audio_gain1, 0, audio_snk, 1); + tb->connect(rx, 0, audio_snk, 0); + tb->connect(rx, 1, audio_snk, 1); } tb->unlock(); @@ -817,29 +819,47 @@ receiver::status receiver::set_agc_on(bool agc_on) return STATUS_OK; // FIXME } -/** Enable/disable AGC hang. */ -receiver::status receiver::set_agc_hang(bool use_hang) +/** Set AGC hang. */ +receiver::status receiver::set_agc_hang(int hang_ms) { if (rx->has_agc()) - rx->set_agc_hang(use_hang); + rx->set_agc_hang(hang_ms); return STATUS_OK; // FIXME } -/** Set AGC threshold. */ -receiver::status receiver::set_agc_threshold(int threshold) +/** Set AGC target level. */ +receiver::status receiver::set_agc_target_level(int target_level) { if (rx->has_agc()) - rx->set_agc_threshold(threshold); + rx->set_agc_target_level(target_level); return STATUS_OK; // FIXME } -/** Set AGC slope. */ -receiver::status receiver::set_agc_slope(int slope) +/** Set fixed gain used when AGC is OFF. */ +receiver::status receiver::set_agc_manual_gain(float gain) { if (rx->has_agc()) - rx->set_agc_slope(slope); + rx->set_agc_manual_gain(gain); + + return STATUS_OK; // FIXME +} + +/** Set maximum gain used when AGC is ON. */ +receiver::status receiver::set_agc_max_gain(int gain) +{ + if (rx->has_agc()) + rx->set_agc_max_gain(gain); + + return STATUS_OK; // FIXME +} + +/** Set AGC attack. */ +receiver::status receiver::set_agc_attack(int attack_ms) +{ + if (rx->has_agc()) + rx->set_agc_attack(attack_ms); return STATUS_OK; // FIXME } @@ -853,13 +873,40 @@ receiver::status receiver::set_agc_decay(int decay_ms) return STATUS_OK; // FIXME } -/** Set fixed gain used when AGC is OFF. */ -receiver::status receiver::set_agc_manual_gain(int gain) +/** Get AGC current gain. */ +float receiver::get_agc_gain() { if (rx->has_agc()) - rx->set_agc_manual_gain(gain); + return rx->get_agc_gain(); + else + return 0; +} - return STATUS_OK; // FIXME +/** Set audio mute. */ +receiver::status receiver::set_mute(bool mute) +{ + if (d_mute == mute) + return STATUS_OK; + tb->lock(); + if (mute) + { + tb->disconnect(rx, 0, audio_snk, 0); + tb->disconnect(rx, 1, audio_snk, 1); + } + else + { + tb->connect(rx, 0, audio_snk, 0); + tb->connect(rx, 1, audio_snk, 1); + } + tb->unlock(); + d_mute = mute; + return STATUS_OK; +} + +/** Get audio mute. */ +bool receiver::get_mute() +{ + return d_mute; } receiver::status receiver::set_demod(rx_demod demod, bool force) @@ -981,20 +1028,6 @@ receiver::status receiver::set_amsync_pll_bw(float pll_bw) return STATUS_OK; } -receiver::status receiver::set_af_gain(float gain_db) -{ - float k; - - /* convert dB to factor */ - k = powf(10.0f, gain_db / 20.0f); - //std::cout << "G:" << gain_db << "dB / K:" << k << std::endl; - audio_gain0->set_k(k); - audio_gain1->set_k(k); - - return STATUS_OK; -} - - /** * @brief Start WAV file recorder. * @param filename The filename where to record. @@ -1021,9 +1054,6 @@ receiver::status receiver::start_audio_recording(const std::string filename) return STATUS_ERROR; } - wav_gain0 = gr::blocks::multiply_const_ff::make(WAV_FILE_GAIN); - wav_gain1 = gr::blocks::multiply_const_ff::make(WAV_FILE_GAIN); - // if this fails, we don't want to go and crash now, do we try { #if GNURADIO_VERSION < 0x030900 @@ -1042,10 +1072,8 @@ receiver::status receiver::start_audio_recording(const std::string filename) } tb->lock(); - tb->connect(rx, 0, wav_gain0, 0); - tb->connect(rx, 1, wav_gain1, 0); - tb->connect(wav_gain0, 0, wav_sink, 0); - tb->connect(wav_gain1, 0, wav_sink, 1); + tb->connect(rx, 0, wav_sink, 0); + tb->connect(rx, 1, wav_sink, 1); tb->unlock(); d_recording_wav = true; @@ -1074,10 +1102,8 @@ receiver::status receiver::stop_audio_recording() // not strictly necessary to lock but I think it is safer tb->lock(); wav_sink->close(); - tb->disconnect(rx, 0, wav_gain0, 0); - tb->disconnect(rx, 1, wav_gain1, 0); - tb->disconnect(wav_gain0, 0, wav_sink, 0); - tb->disconnect(wav_gain1, 0, wav_sink, 1); + tb->disconnect(rx, 0, wav_sink, 0); + tb->disconnect(rx, 1, wav_sink, 1); // Temporary workaround for https://github.com/gnuradio/gnuradio/issues/5436 tb->disconnect(ddc, 0, rx, 0); @@ -1085,8 +1111,6 @@ receiver::status receiver::stop_audio_recording() // End temporary workaronud tb->unlock(); - wav_gain0.reset(); - wav_gain1.reset(); wav_sink.reset(); d_recording_wav = false; @@ -1136,15 +1160,21 @@ receiver::status receiver::start_audio_playback(const std::string filename) stop(); /* route demodulator output to null sink */ - tb->disconnect(rx, 0, audio_gain0, 0); - tb->disconnect(rx, 1, audio_gain1, 0); + if (!d_mute) + { + tb->disconnect(rx, 0, audio_snk, 0); + tb->disconnect(rx, 1, audio_snk, 1); + } tb->disconnect(rx, 0, audio_fft, 0); tb->disconnect(rx, 0, audio_udp_sink, 0); tb->disconnect(rx, 1, audio_udp_sink, 1); tb->connect(rx, 0, audio_null_sink0, 0); /** FIXME: other channel? */ tb->connect(rx, 1, audio_null_sink1, 0); /** FIXME: other channel? */ - tb->connect(wav_src, 0, audio_gain0, 0); - tb->connect(wav_src, 1, audio_gain1, 0); + if (!d_mute) + { + tb->connect(wav_src, 0, audio_snk, 0); + tb->connect(wav_src, 1, audio_snk, 1); + } tb->connect(wav_src, 0, audio_fft, 0); tb->connect(wav_src, 0, audio_udp_sink, 0); tb->connect(wav_src, 1, audio_udp_sink, 1); @@ -1160,15 +1190,21 @@ receiver::status receiver::stop_audio_playback() { /* disconnect wav source and reconnect receiver */ stop(); - tb->disconnect(wav_src, 0, audio_gain0, 0); - tb->disconnect(wav_src, 1, audio_gain1, 0); + if (!d_mute) + { + tb->disconnect(wav_src, 0, audio_snk, 0); + tb->disconnect(wav_src, 1, audio_snk, 1); + } tb->disconnect(wav_src, 0, audio_fft, 0); tb->disconnect(wav_src, 0, audio_udp_sink, 0); tb->disconnect(wav_src, 1, audio_udp_sink, 1); tb->disconnect(rx, 0, audio_null_sink0, 0); tb->disconnect(rx, 1, audio_null_sink1, 0); - tb->connect(rx, 0, audio_gain0, 0); - tb->connect(rx, 1, audio_gain1, 0); + if (!d_mute) + { + tb->connect(rx, 0, audio_snk, 0); + tb->connect(rx, 1, audio_snk, 1); + } tb->connect(rx, 0, audio_fft, 0); /** FIXME: other channel? */ tb->connect(rx, 0, audio_udp_sink, 0); tb->connect(rx, 1, audio_udp_sink, 1); @@ -1396,19 +1432,29 @@ void receiver::connect_all(rx_chain type) tb->connect(rx, 0, audio_fft, 0); tb->connect(rx, 0, audio_udp_sink, 0); tb->connect(rx, 1, audio_udp_sink, 1); - tb->connect(rx, 0, audio_gain0, 0); - tb->connect(rx, 1, audio_gain1, 0); - tb->connect(audio_gain0, 0, audio_snk, 0); - tb->connect(audio_gain1, 0, audio_snk, 1); + if (!d_mute) + { + tb->connect(rx, 0, audio_snk, 0); + tb->connect(rx, 1, audio_snk, 1); + } + // Recorders and sniffers + if (d_recording_wav) + { + tb->connect(rx, 0, wav_sink, 0); + tb->connect(rx, 1, wav_sink, 1); + } + if (d_sniffer_active) + { + tb->connect(rx, 0, sniffer_rr, 0); + tb->connect(sniffer_rr, 0, sniffer, 0); + } } // Recorders and sniffers if (d_recording_wav) { - tb->connect(rx, 0, wav_gain0, 0); - tb->connect(rx, 1, wav_gain1, 0); - tb->connect(wav_gain0, 0, wav_sink, 0); - tb->connect(wav_gain1, 0, wav_sink, 1); + tb->connect(rx, 0, wav_sink, 0); + tb->connect(rx, 1, wav_sink, 1); } if (d_sniffer_active) diff --git a/src/applications/gqrx/receiver.h b/src/applications/gqrx/receiver.h index 4d00a8d92..8d0883891 100644 --- a/src/applications/gqrx/receiver.h +++ b/src/applications/gqrx/receiver.h @@ -177,11 +177,16 @@ class receiver /* AGC */ status set_agc_on(bool agc_on); - status set_agc_hang(bool use_hang); - status set_agc_threshold(int threshold); - status set_agc_slope(int slope); + status set_agc_target_level(int target_level); + status set_agc_manual_gain(float gain); + status set_agc_max_gain(int gain); + status set_agc_attack(int attack_ms); status set_agc_decay(int decay_ms); - status set_agc_manual_gain(int gain); + status set_agc_hang(int hang_ms); + float get_agc_gain(); + + status set_mute(bool mute); + bool get_mute(); status set_demod(rx_demod demod, bool force=false); @@ -197,7 +202,6 @@ class receiver status set_amsync_pll_bw(float pll_bw); /* Audio parameters */ - status set_af_gain(float gain_db); status start_audio_recording(const std::string filename); status stop_audio_recording(); status start_audio_playback(const std::string filename); @@ -249,6 +253,7 @@ class receiver bool d_iq_rev; /*!< Whether I/Q is reversed or not. */ bool d_dc_cancel; /*!< Enable automatic DC removal. */ bool d_iq_balance; /*!< Enable automatic IQ balance. */ + bool d_mute; /*!< Enable audio mute. */ std::string input_devstr; /*!< Current input device string. */ std::string output_devstr; /*!< Current output device string. */ @@ -269,11 +274,6 @@ class receiver downconverter_cc_sptr ddc; /*!< Digital down-converter for demod chain. */ - gr::blocks::multiply_const_ff::sptr audio_gain0; /*!< Audio gain block. */ - gr::blocks::multiply_const_ff::sptr audio_gain1; /*!< Audio gain block. */ - gr::blocks::multiply_const_ff::sptr wav_gain0; /*!< WAV file gain block. */ - gr::blocks::multiply_const_ff::sptr wav_gain1; /*!< WAV file gain block. */ - gr::blocks::file_sink::sptr iq_sink; /*!< I/Q file sink. */ gr::blocks::wavfile_sink::sptr wav_sink; /*!< WAV file sink for recording. */ diff --git a/src/dsp/CMakeLists.txt b/src/dsp/CMakeLists.txt index 3f8e38e80..8555509be 100644 --- a/src/dsp/CMakeLists.txt +++ b/src/dsp/CMakeLists.txt @@ -17,8 +17,6 @@ add_source_files(SRCS_LIST rds/parser_impl.h rds/parser.h rds/tmc_events.h - agc_impl.cpp - agc_impl.h correct_iq_cc.cpp correct_iq_cc.h downconverter.cpp diff --git a/src/dsp/agc_impl.cpp b/src/dsp/agc_impl.cpp deleted file mode 100644 index 86a094b2e..000000000 --- a/src/dsp/agc_impl.cpp +++ /dev/null @@ -1,317 +0,0 @@ -////////////////////////////////////////////////////////////////////// -// agc_impl.cpp: implementation of the CAgc class. -// -// This class implements an automatic gain function. -// -// History: -// 2010-09-15 Initial creation MSW -// 2011-03-27 Initial release -// 2011-09-24 Adapted for gqrx -////////////////////////////////////////////////////////////////////// -//========================================================================================== -// + + + This Software is released under the "Simplified BSD License" + + + -//Copyright 2010 Moe Wheatley. All rights reserved. -// -//Redistribution and use in source and binary forms, with or without modification, are -//permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list -// of conditions and the following disclaimer in the documentation and/or other materials -// provided with the distribution. -// -//THIS SOFTWARE IS PROVIDED BY Moe Wheatley ``AS IS'' AND ANY EXPRESS OR IMPLIED -//WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -//FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Moe Wheatley OR -//CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -//CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -//SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -//NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -//ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -//The views and conclusions contained in the software and documentation are those of the -//authors and should not be interpreted as representing official policies, either expressed -//or implied, of Moe Wheatley. -//========================================================================================== - -#include -#include - -////////////////////////////////////////////////////////////////////// -// Local Defines -////////////////////////////////////////////////////////////////////// - -//signal delay line time delay in seconds. -//adjust to cover the impulse response time of filter -#define DELAY_TIMECONST .015f - -//Peak Detector window time delay in seconds. -#define WINDOW_TIMECONST .018f - -//attack time constant in seconds -//just small enough to let attackave charge up within the DELAY_TIMECONST time -#define ATTACK_RISE_TIMECONST .002f -#define ATTACK_FALL_TIMECONST .005f - -#define DECAY_RISEFALL_RATIO .3f //ratio between rise and fall times of Decay time constants -//adjust for best action with SSB - -// hang timer release decay time constant in seconds -#define RELEASE_TIMECONST .05f - -//limit output to about 3db of max -#define AGC_OUTSCALE 0.7f - -// keep max in and out the same -#define MAX_AMPLITUDE 1.0f //32767.0 -#define MAX_MANUAL_AMPLITUDE 1.0f //32767.0 - -#define LOG_MAX_AMPL log10f(MAX_AMPLITUDE) - -#define MIN_CONSTANT 1e-8f // const for calc log() so that a value of 0 magnitude == -8 - // corresponding to -160dB. - // K = 10^(-8 + log(MAX_AMP)) - -////////////////////////////////////////////////////////////////////// -// Construction/Destruction -////////////////////////////////////////////////////////////////////// - -CAgc::CAgc() -{ - m_AgcOn = true; - m_UseHang = false; - m_Threshold = 0; - m_ManualGain = 0; - m_SlopeFactor = 0; - m_Decay = 0; - m_SampleRate = 100.0; - m_SigDelayBuf_r = (float*)(&m_SigDelayBuf); - m_ManualAgcGain = 0.f; - m_DecayAve = 0.f; - m_AttackAve = 0.f; - m_AttackRiseAlpha = 0.f; - m_AttackFallAlpha = 0.f; - m_DecayRiseAlpha = 0.f; - m_DecayFallAlpha = 0.f; - m_FixedGain = 0.f; - m_Knee = 0.f; - m_GainSlope = 0.f; - m_Peak = 0.f; - m_SigDelayPtr = 0; - m_MagBufPos = 0; - m_DelaySamples = 0; - m_WindowSamples = 0; - m_HangTime = 0; - m_HangTimer = 0; -} - -CAgc::~CAgc() -{ -} - -//////////////////////////////////////////////////////////////////////////////// -// Sets and calculates various AGC parameters -// "On" switches between AGC on and off. -// "Threshold" specifies AGC Knee in dB if AGC is active.( nominal range -160 to 0dB) -// "ManualGain" specifies AGC manual gain in dB if AGC is not active.(nominal range 0 to 100dB) -// "SlopeFactor" specifies dB reduction in output at knee from maximum output level(nominal range 0 to 10dB) -// "Decay" is AGC decay value in milliseconds ( nominal range 20 to 5000 milliSeconds) -// "SampleRate" is current sample rate of AGC data -//////////////////////////////////////////////////////////////////////////////// -void CAgc::SetParameters(bool AgcOn, bool UseHang, int Threshold, int ManualGain, - int SlopeFactor, int Decay, float SampleRate) -{ - if((AgcOn == m_AgcOn) && (UseHang == m_UseHang) && - (Threshold == m_Threshold) && (ManualGain == m_ManualGain) && - (SlopeFactor == m_SlopeFactor) && (Decay == m_Decay) && - (SampleRate == m_SampleRate)) - { - return; //just return if no parameter changed - } - - m_AgcOn = AgcOn; - m_UseHang = UseHang; - m_Threshold = Threshold; - m_ManualGain = ManualGain; - m_SlopeFactor = SlopeFactor; - m_Decay = Decay; - - if (m_SampleRate != SampleRate) - { - //clear out delay buffer and init some things if sample rate changes - m_SampleRate = SampleRate; - m_DelaySamples = (int)(m_SampleRate * DELAY_TIMECONST); - m_WindowSamples = (int)(m_SampleRate * WINDOW_TIMECONST); - for (int i = 0; i < MAX_DELAY_BUF; i++) - { - m_SigDelayBuf[i] = 0.0; - m_MagBuf[i] = -16.0; - } - m_SigDelayPtr = 0; - m_HangTimer = 0; - m_Peak = -16.0; - m_DecayAve = -5.0; - m_AttackAve = -5.0; - m_MagBufPos = 0; - - m_MagDeque.clear(); - m_MagDeque.push_back(m_WindowSamples - 1); - } - - // convert m_ThreshGain to linear manual gain value - m_ManualAgcGain = MAX_MANUAL_AMPLITUDE * powf(10.0f, (float)m_ManualGain / 20.0f); - - // calculate parameters for AGC gain as a function of input magnitude - m_Knee = (float)m_Threshold / 20.0f; - m_GainSlope = m_SlopeFactor / 100.0f; - - // fixed gain value used below knee threshold - m_FixedGain = AGC_OUTSCALE * powf(10.0f, m_Knee * (m_GainSlope - 1.0f) ); - - // calculate fast and slow filter values. - m_AttackRiseAlpha = (1.0f - expf(-1.0f / (m_SampleRate * ATTACK_RISE_TIMECONST))); - m_AttackFallAlpha = (1.0f - expf(-1.0f / (m_SampleRate * ATTACK_FALL_TIMECONST))); - - // make rise time DECAY_RISEFALL_RATIO of fall - m_DecayRiseAlpha = (1.0f - expf(-1.0f / (m_SampleRate * (float)m_Decay * 0.001f * DECAY_RISEFALL_RATIO))); - m_HangTime = (int)(m_SampleRate * (float)m_Decay * .001f); - - if (m_UseHang) - m_DecayFallAlpha = (1.0f - expf(-1.0f / (m_SampleRate * RELEASE_TIMECONST))); - else - m_DecayFallAlpha = (1.0f - expf(-1.0f / (m_SampleRate * (float)m_Decay * 0.001f))); - - // clamp Delay samples within buffer limit - if (m_DelaySamples >= MAX_DELAY_BUF - 1) - m_DelaySamples = MAX_DELAY_BUF - 1; -} - - - -////////////////////////////////////////////////////////////////////// -// Automatic Gain Control calculator for COMPLEX data -////////////////////////////////////////////////////////////////////// -void CAgc::ProcessData(int Length, const TYPECPX * pInData, TYPECPX * pOutData) -{ - float gain; - float mag; - TYPECPX delayedin; - - if (m_AgcOn) - { - for (int i = 0; i < Length; i++) - { - // get latest input sample - TYPECPX in = pInData[i]; - - // Get delayed sample of input signal - delayedin = m_SigDelayBuf[m_SigDelayPtr]; - - // put new input sample into signal delay buffer - m_SigDelayBuf[m_SigDelayPtr++] = in; - - // deal with delay buffer wrap around - if (m_SigDelayPtr >= m_DelaySamples) - m_SigDelayPtr = 0; - - mag = fabsf(in.real()); - float mim = fabsf(in.imag()); - if (mim > mag) - mag = mim; - mag = log10f(mag + MIN_CONSTANT) - LOG_MAX_AMPL; - - // create a sliding window of 'm_WindowSamples' magnitudes and output the peak value within the sliding window - if (m_MagDeque.front() == m_MagBufPos) - m_MagDeque.pop_front(); - - while ((!m_MagDeque.empty()) && mag >= m_MagBuf[m_MagDeque.back()]) - m_MagDeque.pop_back(); - - m_MagDeque.push_back(m_MagBufPos); - - m_Peak = m_MagBuf[m_MagDeque.front()]; - - m_MagBuf[m_MagBufPos++] = mag; // put latest mag sample in buffer; - if (m_MagBufPos >= m_WindowSamples) // deal with magnitude buffer wrap around - m_MagBufPos = 0; - - if (m_UseHang) - { - // using hang timer mode - if (m_Peak > m_AttackAve) - // if power is rising (use m_AttackRiseAlpha time constant) - m_AttackAve = (1.0f - m_AttackRiseAlpha) * m_AttackAve + - m_AttackRiseAlpha * m_Peak; - else - // else magnitude is falling (use m_AttackFallAlpha time constant) - m_AttackAve = (1.0f - m_AttackFallAlpha) * m_AttackAve + - m_AttackFallAlpha * m_Peak; - - if (m_Peak > m_DecayAve) - { - // if magnitude is rising (use m_DecayRiseAlpha time constant) - m_DecayAve = (1.0f - m_DecayRiseAlpha) * m_DecayAve + - m_DecayRiseAlpha * m_Peak; - // reset hang timer - m_HangTimer = 0; - } - else - { // here if decreasing signal - if (m_HangTimer < m_HangTime) - m_HangTimer++; // just inc and hold current m_DecayAve - else // else decay with m_DecayFallAlpha which is RELEASE_TIMECONST - m_DecayAve = (1.0f - m_DecayFallAlpha) * m_DecayAve + - m_DecayFallAlpha * m_Peak; - } - } - else - { - // using exponential decay mode - // perform average of magnitude using 2 averagers each with separate rise and fall time constants - if (m_Peak > m_AttackAve) //if magnitude is rising (use m_AttackRiseAlpha time constant) - m_AttackAve = (1.0f - m_AttackRiseAlpha) * m_AttackAve + - m_AttackRiseAlpha * m_Peak; - else - // else magnitude is falling (use m_AttackFallAlpha time constant) - m_AttackAve = (1.0f - m_AttackFallAlpha) * m_AttackAve + - m_AttackFallAlpha * m_Peak; - - if (m_Peak > m_DecayAve) - // if magnitude is rising (use m_DecayRiseAlpha time constant) - m_DecayAve = (1.0f - m_DecayRiseAlpha) * m_DecayAve + - m_DecayRiseAlpha * m_Peak; - else - // else magnitude is falling (use m_DecayFallAlpha time constant) - m_DecayAve = (1.0f - m_DecayFallAlpha) * m_DecayAve + - m_DecayFallAlpha * m_Peak; - } - - // use greater magnitude of attack or Decay Averager - if (m_AttackAve > m_DecayAve) - mag = m_AttackAve; - else - mag = m_DecayAve; - - // calc gain depending on which side of knee the magnitude is on - if (mag <= m_Knee) - // use fixed gain if below knee - gain = m_FixedGain; - else - // use variable gain if above knee - gain = AGC_OUTSCALE * powf(10.0f, mag * (m_GainSlope - 1.0f)); - - pOutData[i] = delayedin * gain; - } - } - else - { - // manual gain just multiply by m_ManualGain - for (int i = 0; i < Length; i++) - { - pOutData[i] = m_ManualAgcGain * pInData[i]; - } - } -} diff --git a/src/dsp/agc_impl.h b/src/dsp/agc_impl.h deleted file mode 100644 index 6ec6391c5..000000000 --- a/src/dsp/agc_impl.h +++ /dev/null @@ -1,79 +0,0 @@ -////////////////////////////////////////////////////////////////////// -// agc_impl.h: interface for the CAgc class. -// -// This class implements an automatic gain function. -// -// History: -// 2010-09-15 Initial creation MSW -// 2011-03-27 Initial release -// 2011-09-24 Adapted for gqrx -////////////////////////////////////////////////////////////////////// -#ifndef AGC_IMPL_H -#define AGC_IMPL_H - -#include -#include - -#define MAX_DELAY_BUF 2048 - -/* -typedef struct _dCplx -{ - double re; - double im; -} tDComplex; - -#define TYPECPX tDComplex -*/ - -#define TYPECPX std::complex - - -class CAgc -{ -public: - CAgc(); - virtual ~CAgc(); - void SetParameters(bool AgcOn, bool UseHang, int Threshold, int ManualGain, int Slope, int Decay, float SampleRate); - void ProcessData(int Length, const TYPECPX * pInData, TYPECPX * pOutData); - -private: - bool m_AgcOn; - bool m_UseHang; - int m_Threshold; - int m_ManualGain; - int m_Decay; - - float m_SampleRate; - - float m_SlopeFactor; - float m_ManualAgcGain; - - float m_DecayAve; - float m_AttackAve; - - float m_AttackRiseAlpha; - float m_AttackFallAlpha; - float m_DecayRiseAlpha; - float m_DecayFallAlpha; - - float m_FixedGain; - float m_Knee; - float m_GainSlope; - float m_Peak; - - int m_SigDelayPtr; - int m_MagBufPos; - int m_DelaySamples; - int m_WindowSamples; - int m_HangTime; - int m_HangTimer; - - TYPECPX m_SigDelayBuf[MAX_DELAY_BUF]; - float* m_SigDelayBuf_r; - - float m_MagBuf[MAX_DELAY_BUF]; - std::deque m_MagDeque; -}; - -#endif // AGC_IMPL_H diff --git a/src/dsp/rx_agc_xx.cpp b/src/dsp/rx_agc_xx.cpp index ad5f9744f..122f9dfd0 100644 --- a/src/dsp/rx_agc_xx.cpp +++ b/src/dsp/rx_agc_xx.cpp @@ -24,41 +24,78 @@ #include #include #include +#include -rx_agc_cc_sptr make_rx_agc_cc(double sample_rate, bool agc_on, int threshold, - int manual_gain, int slope, int decay, bool use_hang) +#define NO_AGC_DEBUG + +#define exp10f(K) powf(10.0,(K)) +#define exp10(K) pow(10.0,(K)) + +#define MIN_GAIN_DB (-20.0f) +#define MIN_GAIN exp10f(MIN_GAIN_DB) +#define MAX_SAMPLE_RATE 96000 + +rx_agc_2f_sptr make_rx_agc_2f(double sample_rate, bool agc_on, int target_level, + int manual_gain, int max_gain, int attack, int decay, int hang) { - return gnuradio::get_initial_sptr(new rx_agc_cc(sample_rate, agc_on, threshold, - manual_gain, slope, decay, - use_hang)); + return gnuradio::get_initial_sptr(new rx_agc_2f(sample_rate, agc_on, target_level, + manual_gain, max_gain, attack, decay, + hang)); } /** * \brief Create receiver AGC object. * - * Use make_rx_agc_cc() instead. + * Use make_rx_agc_2f() instead. */ -rx_agc_cc::rx_agc_cc(double sample_rate, bool agc_on, int threshold, - int manual_gain, int slope, int decay, bool use_hang) - : gr::sync_block ("rx_agc_cc", - gr::io_signature::make(1, 1, sizeof(gr_complex)), - gr::io_signature::make(1, 1, sizeof(gr_complex))), +rx_agc_2f::rx_agc_2f(double sample_rate, bool agc_on, int target_level, + int manual_gain, int max_gain, int attack, int decay, int hang) + : gr::sync_block ("rx_agc_2f", + gr::io_signature::make(2, 2, sizeof(float)), + gr::io_signature::make(2, 2, sizeof(float))), d_agc_on(agc_on), d_sample_rate(sample_rate), - d_threshold(threshold), + d_target_level(target_level), d_manual_gain(manual_gain), - d_slope(slope), + d_max_gain(max_gain), + d_attack(attack), d_decay(decay), - d_use_hang(use_hang) + d_hang(hang), + d_target_mag(1), + d_hang_samp(0), + d_buf_samples(0), + d_buf_size(0), + d_max_idx(0), + d_buf_p(0), + d_hang_counter(0), + d_max_gain_mag(1.0), + d_current_gain(1.0), + d_target_gain(1.0), + d_decay_step(1.01), + d_attack_step(0.99), + d_floor(0.0001), + d_refill(false), + d_running(false) + +{ + set_parameters(d_sample_rate, d_agc_on, d_target_level, d_manual_gain, d_max_gain, d_attack, d_decay, d_hang, true); + set_history(MAX_SAMPLE_RATE + 1); +} + +rx_agc_2f::~rx_agc_2f() +{ +} + +bool rx_agc_2f::start() { - d_agc = new CAgc(); - d_agc->SetParameters(d_agc_on, d_use_hang, d_threshold, d_manual_gain, - d_slope, d_decay, d_sample_rate); + d_running = true; + return gr::sync_block::start(); } -rx_agc_cc::~rx_agc_cc() +bool rx_agc_2f::stop() { - delete d_agc; + d_running = false; + return gr::sync_block::stop(); } /** @@ -67,15 +104,116 @@ rx_agc_cc::~rx_agc_cc() * \param input_items * \param output_items */ -int rx_agc_cc::work(int noutput_items, +int rx_agc_2f::work(int noutput_items, gr_vector_const_void_star &input_items, gr_vector_void_star &output_items) { - const gr_complex *in = (const gr_complex *) input_items[0]; - gr_complex *out = (gr_complex *) output_items[0]; + const float *in0 = (const float *) input_items[0]; + const float *in1 = (const float *) input_items[1]; + float *out0 = (float *) output_items[0]; + float *out1 = (float *) output_items[1]; std::lock_guard lock(d_mutex); - d_agc->ProcessData(noutput_items, in, out); + + int k; + TYPEFLOAT max_out = 0; + TYPEFLOAT mag_in = 0; + if (d_agc_on) + { +#if GNURADIO_VERSION < 0x030800 + std::vector work_tags; + get_tags_in_window(work_tags, 0, 0, noutput_items); + for (const auto& tag : work_tags) + add_item_tag(0, tag.offset + d_buf_samples, tag.key, tag.value); + get_tags_in_window(work_tags, 1, 0, noutput_items); + for (const auto& tag : work_tags) + add_item_tag(1, tag.offset + d_buf_samples, tag.key, tag.value); +#endif + if (d_refill) + { + d_refill = false; + int p = history() - 1 - d_buf_size; + for (k = 0; k < d_buf_size; k++, p++) + { + float sample_in0 = in0[p]; + float sample_in1 = in1[p]; + mag_in = std::max(fabs(sample_in0),fabs(sample_in1)); + d_mag_buf[k] = mag_in; + update_buffer(k); + } + } + for (k = 0; k < noutput_items; k++) + { + int k_hist = k + history() - 1; + float sample_in0 = in0[k_hist]; + float sample_in1 = in1[k_hist]; + mag_in = std::max(fabs(sample_in0),fabs(sample_in1)); + float sample_out0 = in0[k_hist - d_buf_samples]; + float sample_out1 = in1[k_hist - d_buf_samples]; + + d_mag_buf[d_buf_p] = mag_in; + update_buffer(d_buf_p); + max_out = get_peak(); + + int buf_p_next = d_buf_p + 1; + if (buf_p_next >= d_buf_samples) + buf_p_next = 0; + + if (max_out > d_floor) + { + float new_target = d_target_mag / max_out; + if (new_target < d_target_gain) + { + if (d_current_gain > d_target_gain) + d_hang_counter = d_buf_samples + d_hang_samp; + d_target_gain = new_target; + } + else + if (!d_hang_counter) + d_target_gain = new_target; + } + else + { + d_target_gain = d_max_gain_mag; + d_hang_counter = 0; + } + if (d_current_gain > d_target_gain) + { + //attack, decrease gain one step per sample + d_current_gain *= d_attack_step; + } + else + { + if (d_hang_counter <= 0) + { + //decay, increase gain one step per sample until we reach d_max_gain + if (d_current_gain < d_target_gain) + d_current_gain *= d_decay_step; + if (d_current_gain > d_target_gain) + d_current_gain = d_target_gain; + } + } + if (d_hang_counter > 0) + d_hang_counter--; + if (d_current_gain < MIN_GAIN) + d_current_gain = MIN_GAIN; + out0[k] = sample_out0 * d_current_gain; + out1[k] = sample_out1 * d_current_gain; + d_buf_p = buf_p_next; + } + } + else{ + volk_32f_s32f_multiply_32f((float *)out0, (float *)&in0[history() - 1], d_current_gain, noutput_items); + volk_32f_s32f_multiply_32f((float *)out1, (float *)&in1[history() - 1], d_current_gain, noutput_items); + } + #ifdef AGC_DEBUG2 + static TYPEFLOAT d_prev_dbg = 0.0; + if(d_prev_dbg != d_target_gain) + { + std::cerr<<"------ d_target_gain="<= -160) && (threshold <= 0)) { + if ((target_level != d_target_level) && (target_level >= -160) && (target_level <= 0)) { std::lock_guard lock(d_mutex); - d_threshold = threshold; - d_agc->SetParameters(d_agc_on, d_use_hang, d_threshold, d_manual_gain, - d_slope, d_decay, d_sample_rate); + set_parameters(d_sample_rate, d_agc_on, target_level, d_manual_gain, d_max_gain, d_attack, d_decay, d_hang); } } /** * \brief Set new manual gain. - * \param gain The new manual gain between 0 and 100dB. + * \param gain The new manual gain between -160 and 160dB. * * The manual gain is used when AGC is switched off. * * \sa set_agc_on() */ -void rx_agc_cc::set_manual_gain(int gain) +void rx_agc_2f::set_manual_gain(float gain) { - if ((gain != d_manual_gain) && (gain >= 0) && (gain <= 100)) { - std::lock_guard lock(d_mutex); + if(d_agc_on) + { d_manual_gain = gain; - d_agc->SetParameters(d_agc_on, d_use_hang, d_threshold, d_manual_gain, - d_slope, d_decay, d_sample_rate); + return; + } + if ((gain != d_manual_gain) && (gain >= -160.f) && (gain <= 160.f)) { + std::lock_guard lock(d_mutex); + set_parameters(d_sample_rate, d_agc_on, d_target_level, gain, d_max_gain, d_attack, d_decay, d_hang); + } +} + +/** + * \brief Set new max gain. + * \param gain The new max gain between 0 and 100dB. + * + * Limits maximum AGC gain to reduce noise. + * + * \sa set_agc_on() + */ +void rx_agc_2f::set_max_gain(int gain) +{ + if ((gain != d_max_gain) && (gain >= 0) && (gain <= 160)) { + std::lock_guard lock(d_mutex); + set_parameters(d_sample_rate, d_agc_on, d_target_level, d_manual_gain, gain, d_attack, d_decay, d_hang); } } /** - * \brief Set AGC slope factor. - * \param slope The new slope factor between 0 and 10dB. + * \brief Set AGC attack time. + * \param decay The new AGC attack time between 20 to 5000 ms. + * + * Sets length of the delay buffer * - * The slope factor specifies dB reduction in output at knee from maximum output level */ -void rx_agc_cc::set_slope(int slope) +void rx_agc_2f::set_attack(int attack) { - if ((slope != d_slope) && (slope >= 0) && (slope <= 10)) { + if ((attack != d_attack) && (attack >= 20) && (attack <= 5000)) { std::lock_guard lock(d_mutex); - d_slope = slope; - d_agc->SetParameters(d_agc_on, d_use_hang, d_threshold, d_manual_gain, - d_slope, d_decay, d_sample_rate); + set_parameters(d_sample_rate, d_agc_on, d_target_level, d_manual_gain, d_max_gain, attack, d_decay, d_hang); +#if GNURADIO_VERSION >= 0x030800 + if(d_agc_on) + declare_sample_delay(d_sample_rate * d_attack / 1000); +#endif } } @@ -169,26 +333,169 @@ void rx_agc_cc::set_slope(int slope) * \brief Set AGC decay time. * \param decay The new AGC decay time between 20 to 5000 ms. */ -void rx_agc_cc::set_decay(int decay) +void rx_agc_2f::set_decay(int decay) { if ((decay != d_decay) && (decay >= 20) && (decay <= 5000)) { std::lock_guard lock(d_mutex); - d_decay = decay; - d_agc->SetParameters(d_agc_on, d_use_hang, d_threshold, d_manual_gain, - d_slope, d_decay, d_sample_rate); + set_parameters(d_sample_rate, d_agc_on, d_target_level, d_manual_gain, d_max_gain, d_attack, decay, d_hang); } } /** - * \brief Enable/disable AGC hang. - * \param use_hang Whether to use hang or not. + * \brief Set AGC hang time between 0 to 5000 ms. + * \param hang Time to keep AGC gain at constant level after the peak. */ -void rx_agc_cc::set_use_hang(bool use_hang) +void rx_agc_2f::set_hang(int hang) { - if (use_hang != d_use_hang) { + if ((hang != d_hang) && (hang >= 0) && (hang <= 5000)) { std::lock_guard lock(d_mutex); - d_use_hang = use_hang; - d_agc->SetParameters(d_agc_on, d_use_hang, d_threshold, d_manual_gain, - d_slope, d_decay, d_sample_rate); + set_parameters(d_sample_rate, d_agc_on, d_target_level, d_manual_gain, d_max_gain, d_attack, d_decay, hang); + } +} + +float rx_agc_2f::get_current_gain() +{ + std::lock_guard lock(d_mutex); + return 20.f * log10f(d_current_gain); +} + +void rx_agc_2f::set_parameters(double sample_rate, bool agc_on, int target_level, + float manual_gain, int max_gain, int attack, + int decay, int hang, bool force) +{ + bool samp_rate_changed = false; + bool agc_on_changed = false; + bool target_level_changed = false; + bool manual_gain_changed = false; + bool max_gain_changed = false; + bool attack_changed = false; + bool decay_changed = false; + bool hang_changed = false; + if (d_sample_rate != sample_rate || force) + { + d_sample_rate = sample_rate; + samp_rate_changed = true; + } + if (d_agc_on != agc_on || force) + { + d_agc_on = agc_on; + agc_on_changed = true; + if(d_agc_on && d_running) + d_refill = true; +#if GNURADIO_VERSION < 0x030800 + if(d_agc_on) + set_tag_propagation_policy(TPP_DONT); + else + set_tag_propagation_policy(TPP_ONE_TO_ONE); +#endif + } + if (d_target_level != target_level || force) + { + d_target_level = target_level; + d_target_mag = exp10f(TYPEFLOAT(d_target_level) / 20.f) * 32767.f / 32768.f; + target_level_changed = true; + } + if (d_manual_gain != manual_gain || force) + { + d_manual_gain = manual_gain; + manual_gain_changed = true; } + if (d_max_gain != max_gain || force) + { + d_max_gain = max_gain; + if(d_max_gain < 1) + d_max_gain = 1; + d_max_gain_mag = exp10f(TYPEFLOAT(d_max_gain) / 20.f); + max_gain_changed = true; + } + if (d_attack != attack || force) + { + d_attack = attack; + attack_changed = true; + } + if (d_decay != decay || force) + { + d_decay = decay; + decay_changed = true; + } + if (d_hang != hang || force) + { + d_hang = hang; + hang_changed = true; + } + if (samp_rate_changed || attack_changed) + { + d_buf_samples = sample_rate * d_attack / 1000.0; + int buf_size = 1; + for(unsigned int k = 0; k < sizeof(int) * 8; k++) + { + buf_size *= 2; + if(buf_size >= d_buf_samples) + break; + } + if (d_buf_p >= d_buf_samples) + d_buf_p %= d_buf_samples; + if(d_buf_size != buf_size) + { + d_buf_size = buf_size; + d_mag_buf.clear(); + d_mag_buf.resize(d_buf_size * 2, 0); + d_buf_p = 0; + d_max_idx = d_buf_size * 2 - 2; + if(d_agc_on && d_running) + d_refill = true; + } + } + if ((manual_gain_changed || agc_on_changed) && !agc_on) + d_current_gain = exp10f(TYPEFLOAT(d_manual_gain) / 20.f); + if (max_gain_changed || attack_changed || samp_rate_changed) + d_attack_step = 1.f / exp10f(std::max(TYPEFLOAT(d_max_gain), - MIN_GAIN_DB) / TYPEFLOAT(d_buf_samples) / 20.f); + if (max_gain_changed || decay_changed || samp_rate_changed) + d_decay_step = exp10f(TYPEFLOAT(d_max_gain) / TYPEFLOAT(sample_rate * d_decay / 1000.0) / 20.f); + if (hang_changed || samp_rate_changed) + d_hang_samp = sample_rate * d_hang / 1000.0; + + if (target_level_changed || max_gain_changed) + d_floor = exp10f(TYPEFLOAT(d_target_level - d_max_gain) / 20.f); + #ifdef AGC_DEBUG + std::cerr< 1) + { + float max_p = std::max(d_mag_buf[ofs + p], d_mag_buf[ofs + (p ^ 1)]); + p = p >> 1; + ofs += base; + if(d_mag_buf[ofs + p] != max_p) + d_mag_buf[ofs + p] = max_p; + else + break; + base = base >> 1; + } + } diff --git a/src/dsp/rx_agc_xx.h b/src/dsp/rx_agc_xx.h index b8ad21f4e..c3378be42 100644 --- a/src/dsp/rx_agc_xx.h +++ b/src/dsp/rx_agc_xx.h @@ -26,36 +26,41 @@ #include #include #include -#include -class rx_agc_cc; +#define TYPECPX std::complex +#define TYPEFLOAT float + +class rx_agc_2f; #if GNURADIO_VERSION < 0x030900 -typedef boost::shared_ptr rx_agc_cc_sptr; +typedef boost::shared_ptr rx_agc_2f_sptr; #else -typedef std::shared_ptr rx_agc_cc_sptr; +typedef std::shared_ptr rx_agc_2f_sptr; #endif /** * \brief Return a shared_ptr to a new instance of rx_agc_cc. - * \param sample_rate The sample rate (default = 96000). - * \param agc_on Whether AGC should be ON (default = true). - * \param threshold AGC Knee in dB if AGC is active. Range -160 to 0 dB. - * \param manual_gain Manual gain when AGC is OFF. Range 0 to 100 dB. - * \param slope AGC slope factor. Specifies dB reduction in output at - * knee from maximum output level. Range 0 to 10 dB - * \param decay AGC decay time in milliseconds. Range 20 to 5000. This - * parameter determines whether AGC is fast, slow or medium. - * \param use_hang Whether AGC should "hang" before starting to decay. + * \param sample_rate The sample rate (default = 96000). + * \param agc_on Whether AGC should be ON (default = true). + * \param target_level Target output level in dB if AGC is active. Range -160 to 0 dB. + * \param manual_gain Manual gain when AGC is OFF. Range -160 to 160 dB. + * \param max_gain Maximum gain when AGC is ON. Range 0 to 100 dB. + * \param attack AGC maximum attack time in milliseconds. Range 20 to 5000. This + * parameter determines whether AGC is fast, slow or medium. + * It is recommenfded to set it below 1000 ms to reduce audio lag. + * \param decay AGC decay time in milliseconds. Range 20 to 5000. This + * parameter determines whether AGC is fast, slow or medium. + * \param hang The time AGC should "hang" before starting to decay in + * milliseconds. Range 0 to 5000. * * This is effectively the public constructor for a new AGC block. * To avoid accidental use of raw pointers, the rx_agc_cc constructor is private. * make_rx_agc_cc is the public interface for creating new instances. */ -rx_agc_cc_sptr make_rx_agc_cc(double sample_rate, bool agc_on, int threshold, - int manual_gain, int slope, int decay, - bool use_hang); +rx_agc_2f_sptr make_rx_agc_2f(double sample_rate, bool agc_on, int target_level, + int manual_gain, int max_gain, int attack, + int decay, int hang); /** * \brief Experimental AGC block for analog voice modes (AM, SSB, CW). @@ -64,42 +69,71 @@ rx_agc_cc_sptr make_rx_agc_cc(double sample_rate, bool agc_on, int threshold, * This block performs automatic gain control. * To be written... */ -class rx_agc_cc : public gr::sync_block +class rx_agc_2f : public gr::sync_block { - friend rx_agc_cc_sptr make_rx_agc_cc(double sample_rate, bool agc_on, - int threshold, int manual_gain, - int slope, int decay, bool use_hang); + friend rx_agc_2f_sptr make_rx_agc_2f(double sample_rate, bool agc_on, + int target_level, int manual_gain, + int max_gain, int attack, int decay, + int hang); protected: - rx_agc_cc(double sample_rate, bool agc_on, int threshold, int manual_gain, - int slope, int decay, bool use_hang); + rx_agc_2f(double sample_rate, bool agc_on, int target_level, + int manual_gain, int max_gain, int attack, int decay, int hang); public: - ~rx_agc_cc(); + ~rx_agc_2f(); + bool start() override; + bool stop() override; int work(int noutput_items, gr_vector_const_void_star &input_items, - gr_vector_void_star &output_items); + gr_vector_void_star &output_items) override; void set_agc_on(bool agc_on); void set_sample_rate(double sample_rate); - void set_threshold(int threshold); - void set_manual_gain(int gain); - void set_slope(int slope); + void set_target_level(int target_level); + void set_manual_gain(float gain); + void set_max_gain(int gain); + void set_attack(int attack); void set_decay(int decay); - void set_use_hang(bool use_hang); - + void set_hang(int hang); + float get_current_gain(); private: - CAgc *d_agc; + void set_parameters(double sample_rate, bool agc_on, int target_level, + float manual_gain, int max_gain, int attack, int decay, + int hang, bool force = false); + std::mutex d_mutex; /*! Used to lock internal data while processing or setting parameters. */ bool d_agc_on; /*! Current AGC status (true/false). */ double d_sample_rate; /*! Current sample rate. */ - int d_threshold; /*! Current AGC threshold (-160...0 dB). */ - int d_manual_gain; /*! Current gain when AGC is OFF. */ - int d_slope; /*! Current AGC slope (0...10 dB). */ + int d_target_level; /*! SGC target level (-160...0 dB). */ + float d_manual_gain; /*! Current gain when AGC is OFF. */ + int d_max_gain; /*! Maximum gain when AGC is ON. */ + int d_attack; /*! Current AGC attack (20...5000 ms). */ int d_decay; /*! Current AGC decay (20...5000 ms). */ - bool d_use_hang; /*! Current AGC hang status (true/false). */ + int d_hang; /*! Current AGC hang (0...5000 ms). */ +private: + float get_peak(); + void update_buffer(int p); + + TYPEFLOAT d_target_mag; + int d_hang_samp; + int d_buf_samples; + int d_buf_size; + int d_max_idx; + int d_buf_p; + int d_hang_counter; + TYPEFLOAT d_max_gain_mag; + TYPEFLOAT d_current_gain; + TYPEFLOAT d_target_gain; + TYPEFLOAT d_decay_step; + TYPEFLOAT d_attack_step; + TYPEFLOAT d_floor; + + std::vector d_mag_buf; + bool d_refill; + bool d_running; }; #endif /* RX_AGC_XX_H */ diff --git a/src/qtgui/agc_options.cpp b/src/qtgui/agc_options.cpp index 7d599c774..05e4de301 100644 --- a/src/qtgui/agc_options.cpp +++ b/src/qtgui/agc_options.cpp @@ -49,10 +49,10 @@ void CAgcOptions::closeEvent(QCloseEvent *event) event->ignore(); } -/*! \brief Get current gain slider value. */ -int CAgcOptions::gain() +/*! \brief Get current max gain slider value. */ +int CAgcOptions::maxGain() { - return ui->gainSlider->value(); + return ui->maxGainSlider->value(); } /*! \brief Set AGC preset. */ @@ -61,37 +61,39 @@ void CAgcOptions::setPreset(agc_preset_e preset) switch (preset) { case AGC_FAST: + setAttack(20); setDecay(100); + setHang(0); + enableAttack(false); enableDecay(false); - setSlope(0); - enableSlope(false); - enableGain(false); + enableHang(false); break; case AGC_MEDIUM: + setAttack(50); setDecay(500); + setHang(0); + enableAttack(false); enableDecay(false); - setSlope(0); - enableSlope(false); - enableGain(false); + enableHang(false); break; case AGC_SLOW: + setAttack(100); setDecay(2000); + setHang(0); + enableAttack(false); enableDecay(false); - setSlope(0); - enableSlope(false); - enableGain(false); + enableHang(false); break; case AGC_USER: + enableAttack(true); enableDecay(true); - enableSlope(true); - enableGain(false); + enableHang(true); break; case AGC_OFF: - enableGain(true); break; default: @@ -101,62 +103,50 @@ void CAgcOptions::setPreset(agc_preset_e preset) } } -/*! \brief Set new gain slider value. */ -void CAgcOptions::setGain(int value) +/*! \brief Set new max gain slider value. */ +void CAgcOptions::setMaxGain(int value) { - ui->gainSlider->setValue(value); - ui->gainLabel->setText(QString("%1 dB").arg(ui->gainSlider->value())); + ui->maxGainSlider->setValue(value); + ui->maxGainLabel->setText(QString("%1 dB").arg(ui->maxGainSlider->value())); } -/*! \brief Enable or disable gain slider. - * \param enabled Whether the slider should be enabled or not. - * - * The gain slider is enabled when AGC is OFF to provide manual gain - * control. It is disabled when AGC is ON. - */ -void CAgcOptions::enableGain(bool enabled) +/*! \brief Get current AGC target level. */ +int CAgcOptions::targetLevel() { - ui->gainLabel->setEnabled(enabled); - ui->gainSlider->setEnabled(enabled); - ui->label1->setEnabled(enabled); + return ui->targetLevelSlider->value(); } -/*! \brief Get current AGC threshold. */ -int CAgcOptions::threshold() +/*! \brief Set new AGC target level. */ +void CAgcOptions::setTargetLevel(int value) { - return ui->thresholdSlider->value(); + ui->targetLevelSlider->setValue(value); + ui->targetLevelLabel->setText(QString("%1 dB").arg(ui->targetLevelSlider->value())); } -/*! \brief Set new AGC threshold. */ -void CAgcOptions::setThreshold(int value) -{ - ui->thresholdSlider->setValue(value); - ui->thresholdLabel->setText(QString("%1 dB").arg(ui->thresholdSlider->value())); -} -/*! \brief Get current AGC slope. */ -int CAgcOptions::slope() +/*! \brief Get current attack value. */ +int CAgcOptions::attack() { - return ui->slopeSlider->value(); + return ui->attackSlider->value(); } -/*! \brief Set new AGC slope. */ -void CAgcOptions::setSlope(int value) +/*! \brief Set new attack value. */ +void CAgcOptions::setAttack(int value) { - ui->slopeSlider->setValue(value); - ui->slopeLabel->setText(QString("%1 dB").arg(ui->slopeSlider->value())); + ui->attackSlider->setValue(value); + ui->attackLabel->setText(QString("%1 ms").arg(ui->attackSlider->value())); } -/*! \brief Enable or disable AGC slope slider. +/*! \brief Enable or disable AGC attack slider. * \param enabled Whether the slider should be enabled or not. * - * The slope slider is enabled when AGC is in user mode. + * The attack slider is enabled when AGC is in user mode. */ -void CAgcOptions::enableSlope(bool enabled) +void CAgcOptions::enableAttack(bool enabled) { - ui->slopeSlider->setEnabled(enabled); - ui->slopeLabel->setEnabled(enabled); - ui->label3->setEnabled(enabled); + ui->attackSlider->setEnabled(enabled); + ui->attackLabel->setEnabled(enabled); + ui->attackTitle->setEnabled(enabled); } /*! \brief Get current decay value. */ @@ -181,54 +171,67 @@ void CAgcOptions::enableDecay(bool enabled) { ui->decaySlider->setEnabled(enabled); ui->decayLabel->setEnabled(enabled); - ui->label4->setEnabled(enabled); + ui->decayTitle->setEnabled(enabled); } -/*! \brief Get current state of AGC hang button. */ -bool CAgcOptions::hang() +/*! \brief Get current hang value. */ +int CAgcOptions::hang() { - return ui->hangButton->isChecked(); + return ui->hangSlider->value(); } -/*! \brief Set state og AGC hang button. */ -void CAgcOptions::setHang(bool checked) +/*! \brief Set new hang value. */ +void CAgcOptions::setHang(int value) { - ui->hangButton->setChecked(checked); + ui->hangSlider->setValue(value); + ui->hangLabel->setText(QString("%1 ms").arg(ui->hangSlider->value())); } +/*! \brief Enable or disable AGC hang slider. + * \param enabled Whether the slider should be enabled or not. + * + * The hang slider is enabled when AGC is in user mode. + */ +void CAgcOptions::enableHang(bool enabled) +{ + ui->hangSlider->setEnabled(enabled); + ui->hangLabel->setEnabled(enabled); + ui->hangTitle->setEnabled(enabled); +} -/*! \brief AGC gain slider value has changed. */ -void CAgcOptions::on_gainSlider_valueChanged(int gain) +/*! \brief AGC max gain slider value has changed. */ +void CAgcOptions::on_maxGainSlider_valueChanged(int value) { - ui->gainLabel->setText(QString("%1 dB").arg(ui->gainSlider->value())); - emit gainChanged(gain); + ui->maxGainLabel->setText(QString("%1 dB").arg(ui->maxGainSlider->value())); + emit maxGainChanged(value); } -/*! \brief AGC threshold slider value has changed. */ -void CAgcOptions::on_thresholdSlider_valueChanged(int threshold) +/*! \brief AGC target level slider value has changed. */ +void CAgcOptions::on_targetLevelSlider_valueChanged(int value) { - ui->thresholdLabel->setText(QString("%1 dB").arg(ui->thresholdSlider->value())); - emit thresholdChanged(threshold); + ui->targetLevelLabel->setText(QString("%1 dB").arg(ui->targetLevelSlider->value())); + emit targetLevelChanged(value); } -/*! \brief AGC slope slider value has changed. */ -void CAgcOptions::on_slopeSlider_valueChanged(int slope) +/*! \brief AGC attack slider value has changed. */ +void CAgcOptions::on_attackSlider_valueChanged(int value) { - ui->slopeLabel->setText(QString("%1 dB").arg(ui->slopeSlider->value())); - emit slopeChanged(slope); + ui->attackLabel->setText(QString("%1 ms").arg(ui->attackSlider->value())); + emit attackChanged(value); } /*! \brief AGC decay slider value has changed. */ -void CAgcOptions::on_decaySlider_valueChanged(int decay) +void CAgcOptions::on_decaySlider_valueChanged(int value) { ui->decayLabel->setText(QString("%1 ms").arg(ui->decaySlider->value())); - emit decayChanged(decay); + emit decayChanged(value); } -/*! \brief AGC hang button has been toggled. */ -void CAgcOptions::on_hangButton_toggled(bool checked) +/*! \brief AGC hang slider value has changed. */ +void CAgcOptions::on_hangSlider_valueChanged(int value) { - ui->hangButton->setText(checked ? tr("Enabled") : tr("Disabled")); - emit hangChanged(checked); + ui->hangLabel->setText(QString("%1 ms").arg(ui->hangSlider->value())); + emit hangChanged(value); } + diff --git a/src/qtgui/agc_options.h b/src/qtgui/agc_options.h index 6714a695c..d4a36b47e 100644 --- a/src/qtgui/agc_options.h +++ b/src/qtgui/agc_options.h @@ -52,23 +52,24 @@ class CAgcOptions : public QDialog void closeEvent(QCloseEvent *event); - int gain(); - void setGain(int value); - void enableGain(bool enabled); + int maxGain(); + void setMaxGain(int value); - int threshold(); - void setThreshold(int value); + int targetLevel(); + void setTargetLevel(int value); - int slope(); - void setSlope(int value); - void enableSlope(bool enabled); + int attack(); + void setAttack(int value); + void enableAttack(bool enabled); int decay(); void setDecay(int value); void enableDecay(bool enabled); - bool hang(); - void setHang(bool checked); + int hang(); + void setHang(int value); + void enableHang(bool enabled); + enum agc_preset_e { @@ -82,18 +83,18 @@ class CAgcOptions : public QDialog void setPreset(agc_preset_e preset); signals: - void gainChanged(int gain); - void thresholdChanged(int threshold); - void slopeChanged(int slope); + void maxGainChanged(int gain); + void targetLevelChanged(int level); + void attackChanged(int decay); void decayChanged(int decay); - void hangChanged(bool on); + void hangChanged(int hang); private slots: - void on_gainSlider_valueChanged(int gain); - void on_thresholdSlider_valueChanged(int threshold); - void on_slopeSlider_valueChanged(int slope); - void on_decaySlider_valueChanged(int decay); - void on_hangButton_toggled(bool checked); + void on_maxGainSlider_valueChanged(int value); + void on_targetLevelSlider_valueChanged(int value); + void on_attackSlider_valueChanged(int value); + void on_decaySlider_valueChanged(int value); + void on_hangSlider_valueChanged(int value); private: Ui::CAgcOptions *ui; diff --git a/src/qtgui/agc_options.ui b/src/qtgui/agc_options.ui index 737eb6524..75a72c743 100644 --- a/src/qtgui/agc_options.ui +++ b/src/qtgui/agc_options.ui @@ -6,8 +6,8 @@ 0 0 - 263 - 197 + 293 + 253 @@ -33,23 +33,33 @@ 5 - - - - false + + + + Target + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + - Slope + 0 dB Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + + + false + - Threshold + Decay Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -57,7 +67,7 @@ - + false @@ -65,26 +75,38 @@ Qt::StrongFocus - AGC slope + AGC decay time. Time to change from min gain to max gain. Smaller gain changes are faster. + + + 50 + 5000 + + 10 - 1 + 50 - 0 + 500 + + + 500 Qt::Horizontal - - + + + + false + - -100 dB + 500 ms Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -92,102 +114,93 @@ - - - false - + - Decay + Hang Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + + + Qt::StrongFocus + - Enable / disable AGC hang + Target output level. - - Disabled + + -100 - - true + + 0 - - false + + 0 - - false + + Qt::Horizontal - - + + - false - - - Manual gain. Used when AGC is switched off + true - 0 dB + Max Gain Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + - false + true Qt::StrongFocus - AGC decay time + Maximum automatic gain. - 50 + 0 - 5000 - - - 10 - - - 50 + 150 - 500 - - - 500 + 100 Qt::Horizontal - - + + - false + true + + + Manual gain. Used when AGC is switched off - 500 ms + 100 dB Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + false @@ -195,85 +208,112 @@ Qt::StrongFocus - Manual gain. Used when AGC is switched off + AGC attack time. Time to change from max gain to min gain. Smaller gain changes are faster. + + + 20 - 100 + 1000 + + + 10 + + + 50 + + + 500 + + + 500 Qt::Horizontal - - + + false - Gain + Attack Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + false - 0 dB + 500 ms Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - Hang - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + false - - - - Qt::StrongFocus - AGC threshold (aka. knee) + AGC hang time. Time to wait before trying to increase the gain after the peak. - -160 + 0 - 0 + 5000 + + + 10 + + + 50 - -100 + 500 + + + 500 Qt::Horizontal + + + + false + + + 500 ms + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + - gainSlider - thresholdSlider - slopeSlider + targetLevelSlider decaySlider - hangButton diff --git a/src/qtgui/dockaudio.cpp b/src/qtgui/dockaudio.cpp index 9db6a7022..097469835 100644 --- a/src/qtgui/dockaudio.cpp +++ b/src/qtgui/dockaudio.cpp @@ -113,15 +113,6 @@ void DockAudio::setAudioGain(int gain) ui->audioGainSlider->setValue(gain); } -/*! \brief Set new audio gain. - * \param gain the new audio gain in dB - */ -void DockAudio::setAudioGainDb(float gain) -{ - ui->audioGainSlider->setValue(int(std::round(gain*10.0f))); -} - - /*! \brief Get current audio gain. * \returns The current audio gain in tens of dB (0 dB = 10). */ @@ -130,6 +121,14 @@ int DockAudio::audioGain() return ui->audioGainSlider->value(); } +/*! \brief Set audio gain slider state. + * \param state new slider state. + */ +void DockAudio::setGainEnabled(bool state) +{ + ui->audioGainSlider->setEnabled(state); +} + /*! Set FFT plot color. */ void DockAudio::setFftColor(QColor color) { @@ -190,8 +189,7 @@ void DockAudio::on_audioGainSlider_valueChanged(int value) // update dB label ui->audioGainDbLabel->setText(QString("%1 dB").arg((double)gain, 5, 'f', 1)); - if (!ui->audioMuteButton->isChecked()) - emit audioGainChanged(gain); + emit audioGainChanged(gain); } /*! \brief Streaming button clicked. @@ -278,16 +276,7 @@ void DockAudio::on_audioConfButton_clicked() /*! \brief Mute audio. */ void DockAudio::on_audioMuteButton_clicked(bool checked) { - if (checked) - { - emit audioGainChanged(-INFINITY); - } - else - { - int value = ui->audioGainSlider->value(); - float gain = float(value) / 10.0f; - emit audioGainChanged(gain); - } + emit audioMuteChanged(checked); } /*! \brief Set status of audio record button. */ @@ -492,9 +481,11 @@ void DockAudio::muteToggleShortcut() { } void DockAudio::increaseAudioGainShortcut() { - ui->audioGainSlider->triggerAction(QSlider::SliderPageStepAdd); + if(ui->audioGainSlider->isEnabled()) + ui->audioGainSlider->triggerAction(QSlider::SliderPageStepAdd); } void DockAudio::decreaseAudioGainShortcut() { - ui->audioGainSlider->triggerAction(QSlider::SliderPageStepSub); + if(ui->audioGainSlider->isEnabled()) + ui->audioGainSlider->triggerAction(QSlider::SliderPageStepSub); } diff --git a/src/qtgui/dockaudio.h b/src/qtgui/dockaudio.h index 57ccb240a..efdbd4318 100644 --- a/src/qtgui/dockaudio.h +++ b/src/qtgui/dockaudio.h @@ -57,6 +57,7 @@ class DockAudio : public QDockWidget void setAudioGain(int gain); int audioGain(); + void setGainEnabled(bool state); void setAudioRecButtonState(bool checked); void setAudioPlayButtonState(bool checked); @@ -72,7 +73,6 @@ public slots: void stopAudioRecorder(void); void setRxFrequency(qint64 freq); void setWfColormap(const QString &cmap); - void setAudioGainDb(float gain); signals: /*! \brief Signal emitted when audio gain has changed. Gain is in dB. */ @@ -99,6 +99,9 @@ public slots: /*! \brief FFT rate changed. */ void fftRateChanged(int fps); + /*! \brief Signal emitted when audio mute has changed. */ + void audioMuteChanged(bool mute); + private slots: void on_audioGainSlider_valueChanged(int value); void on_audioStreamButton_clicked(bool checked); diff --git a/src/qtgui/dockaudio.ui b/src/qtgui/dockaudio.ui index 814fb5dc6..4f2e23b9d 100644 --- a/src/qtgui/dockaudio.ui +++ b/src/qtgui/dockaudio.ui @@ -113,10 +113,10 @@ Audio gain - -800 + -1000 - 500 + 1000 -60 @@ -129,16 +129,22 @@ - + 0 0 + + + 60 + 0 + + -6.0 dB - Qt::AlignCenter + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 diff --git a/src/qtgui/dockrxopt.cpp b/src/qtgui/dockrxopt.cpp index 02f7e2709..78bd45114 100644 --- a/src/qtgui/dockrxopt.cpp +++ b/src/qtgui/dockrxopt.cpp @@ -108,11 +108,11 @@ DockRxOpt::DockRxOpt(qint64 filterOffsetRange, QWidget *parent) : // AGC options dialog agcOpt = new CAgcOptions(this); - connect(agcOpt, SIGNAL(gainChanged(int)), this, SLOT(agcOpt_gainChanged(int))); - connect(agcOpt, SIGNAL(thresholdChanged(int)), this, SLOT(agcOpt_thresholdChanged(int))); + connect(agcOpt, SIGNAL(maxGainChanged(int)), this, SLOT(agcOpt_maxGainChanged(int))); + connect(agcOpt, SIGNAL(targetLevelChanged(int)), this, SLOT(agcOpt_targetLevelChanged(int))); + connect(agcOpt, SIGNAL(attackChanged(int)), this, SLOT(agcOpt_attackChanged(int))); connect(agcOpt, SIGNAL(decayChanged(int)), this, SLOT(agcOpt_decayChanged(int))); - connect(agcOpt, SIGNAL(slopeChanged(int)), this, SLOT(agcOpt_slopeChanged(int))); - connect(agcOpt, SIGNAL(hangChanged(bool)), this, SLOT(agcOpt_hangToggled(bool))); + connect(agcOpt, SIGNAL(hangChanged(int)), this, SLOT(agcOpt_hangChanged(int))); // Noise blanker options nbOpt = new CNbOptions(this); @@ -400,6 +400,37 @@ int DockRxOpt::getCwOffset() const return demodOpt->getCwOffset(); } +/** Get agc settings */ +bool DockRxOpt::getAgcOn() +{ + return agc_is_on; +} + +int DockRxOpt::getAgcTargetLevel() +{ + return agcOpt->targetLevel(); +} + +int DockRxOpt::getAgcMaxGain() +{ + return agcOpt->maxGain(); +} + +int DockRxOpt::getAgcAttack() +{ + return agcOpt->attack(); +} + +int DockRxOpt::getAgcDecay() +{ + return agcOpt->decay(); +} + +int DockRxOpt::getAgcHang() +{ + return agcOpt->hang(); +} + /** Read receiver configuration from settings data. */ void DockRxOpt::readSettings(QSettings *settings) { @@ -431,10 +462,17 @@ void DockRxOpt::readSettings(QSettings *settings) ui->sqlSpinBox->setValue(dbl_val); // AGC settings + //TODO: cleanup config + #if 0 int_val = settings->value("receiver/agc_threshold", -100).toInt(&conv_ok); if (conv_ok) agcOpt->setThreshold(int_val); + #endif + int_val = settings->value("receiver/agc_target_level", 0).toInt(&conv_ok); + if (conv_ok) + agcOpt->setTargetLevel(int_val); + //TODO: store/restore the preset correctly int_val = settings->value("receiver/agc_decay", 500).toInt(&conv_ok); if (conv_ok) { @@ -449,15 +487,17 @@ void DockRxOpt::readSettings(QSettings *settings) ui->agcPresetCombo->setCurrentIndex(3); } - int_val = settings->value("receiver/agc_slope", 0).toInt(&conv_ok); + int_val = settings->value("receiver/agc_attack", 20).toInt(&conv_ok); if (conv_ok) - agcOpt->setSlope(int_val); + agcOpt->setAttack(int_val); - int_val = settings->value("receiver/agc_gain", 0).toInt(&conv_ok); + int_val = settings->value("receiver/agc_hang", 0).toInt(&conv_ok); if (conv_ok) - agcOpt->setGain(int_val); + agcOpt->setHang(int_val); - agcOpt->setHang(settings->value("receiver/agc_usehang", false).toBool()); + int_val = settings->value("receiver/agc_maxgain", 100).toInt(&conv_ok); + if (conv_ok) + agcOpt->setMaxGain(int_val); if (settings->value("receiver/agc_off", false).toBool()) ui->agcPresetCombo->setCurrentIndex(4); @@ -526,11 +566,17 @@ void DockRxOpt::saveSettings(QSettings *settings) settings->remove("receiver/sql_level"); // AGC settings - int_val = agcOpt->threshold(); - if (int_val != -100) - settings->setValue("receiver/agc_threshold", int_val); + int_val = agcOpt->targetLevel(); + if (int_val != 0) + settings->setValue("receiver/agc_target_level", int_val); + else + settings->remove("receiver/agc_target_level"); + + int_val = agcOpt->attack(); + if (int_val != 20) + settings->setValue("receiver/agc_attack", int_val); else - settings->remove("receiver/agc_threshold"); + settings->remove("receiver/agc_decay"); int_val = agcOpt->decay(); if (int_val != 500) @@ -538,22 +584,17 @@ void DockRxOpt::saveSettings(QSettings *settings) else settings->remove("receiver/agc_decay"); - int_val = agcOpt->slope(); + int_val = agcOpt->hang(); if (int_val != 0) - settings->setValue("receiver/agc_slope", int_val); + settings->setValue("receiver/agc_hang", int_val); else - settings->remove("receiver/agc_slope"); + settings->remove("receiver/agc_hang"); - int_val = agcOpt->gain(); - if (int_val != 0) - settings->setValue("receiver/agc_gain", int_val); + int_val = agcOpt->maxGain(); + if (int_val != 100) + settings->setValue("receiver/agc_maxgain", int_val); else - settings->remove("receiver/agc_gain"); - - if (agcOpt->hang()) - settings->setValue("receiver/agc_usehang", true); - else - settings->remove("receiver/agc_usehang"); + settings->remove("receiver/agc_maxgain"); // AGC Off if (ui->agcPresetCombo->currentIndex() == 4) @@ -730,27 +771,31 @@ void DockRxOpt::on_agcPresetCombo_currentIndexChanged(int index) } } -void DockRxOpt::agcOpt_hangToggled(bool checked) +/** + * @brief AGC hang time changed. + * @param value The new AGC hang time in ms. + */ +void DockRxOpt::agcOpt_hangChanged(int value) { - emit agcHangToggled(checked); + emit agcHangChanged(value); } /** - * @brief AGC threshold ("knee") changed. - * @param value The new AGC threshold in dB. + * @brief AGC target level changed. + * @param value The new AGC target level in dB. */ -void DockRxOpt::agcOpt_thresholdChanged(int value) +void DockRxOpt::agcOpt_targetLevelChanged(int value) { - emit agcThresholdChanged(value); + emit agcTargetLevelChanged(value); } /** - * @brief AGC slope factor changed. - * @param value The new slope factor in dB. + * @brief AGC attack changed. + * @param value The new attack rate in ms (tbc). */ -void DockRxOpt::agcOpt_slopeChanged(int value) +void DockRxOpt::agcOpt_attackChanged(int value) { - emit agcSlopeChanged(value); + emit agcAttackChanged(value); } /** @@ -763,12 +808,12 @@ void DockRxOpt::agcOpt_decayChanged(int value) } /** - * @brief AGC manual gain changed. + * @brief AGC maimum gain changed. * @param gain The new gain in dB. */ -void DockRxOpt::agcOpt_gainChanged(int gain) +void DockRxOpt::agcOpt_maxGainChanged(int gain) { - emit agcGainChanged(gain); + emit agcMaxGainChanged(gain); } /** diff --git a/src/qtgui/dockrxopt.h b/src/qtgui/dockrxopt.h index afbda61df..e86de775f 100644 --- a/src/qtgui/dockrxopt.h +++ b/src/qtgui/dockrxopt.h @@ -116,6 +116,13 @@ class DockRxOpt : public QDockWidget double getSqlLevel(void) const; + bool getAgcOn(); + int getAgcTargetLevel(); + int getAgcMaxGain(); + int getAgcAttack(); + int getAgcDecay(); + int getAgcHang(); + static QStringList ModulationStrings; static QString GetStringForModulationIndex(int iModulationIndex); static int GetEnumForModulationString(QString param); @@ -189,20 +196,20 @@ public slots: /** Signal emitted when AGC is togglen ON/OFF. */ void agcToggled(bool agc_on); - /** Signal emitted when AGC hang is toggled. */ - void agcHangToggled(bool use_hang); + /** Signal emitted when AGC target level has changed. Level in dB. */ + void agcTargetLevelChanged(int value); - /** Signal emitted when AGC threshold has changed. Threshold in dB. */ - void agcThresholdChanged(int value); + /** Signal emitted when AGC maximum gain has changed. Gain is in dB.*/ + void agcMaxGainChanged(int gain); - /** Signal emitted when AGC slope has changed. Slope is in dB.*/ - void agcSlopeChanged(int slope); + /** Signal emitted when AGC attack has changed. Decay is in millisec.*/ + void agcAttackChanged(int attack); /** Signal emitted when AGC decay has changed. Decay is in millisec.*/ void agcDecayChanged(int decay); - /** Signal emitted when AGC manual gain has changed. Gain is in dB.*/ - void agcGainChanged(int gain); + /** Signal emitted when AGC hang is changed. Hang is in millisec.*/ + void agcHangChanged(int hang); /** Signal emitted when noise blanker status has changed. */ void noiseBlankerChanged(int nbid, bool on, float threshold); @@ -237,11 +244,11 @@ private slots: void demodOpt_amSyncPllBwSelected(float pll_bw); // Signals coming from AGC options popup - void agcOpt_hangToggled(bool checked); - void agcOpt_gainChanged(int value); - void agcOpt_thresholdChanged(int value); - void agcOpt_slopeChanged(int value); + void agcOpt_maxGainChanged(int value); + void agcOpt_targetLevelChanged(int value); + void agcOpt_attackChanged(int value); void agcOpt_decayChanged(int value); + void agcOpt_hangChanged(int value); private: Ui::DockRxOpt *ui; /** The Qt designer UI file. */ diff --git a/src/receivers/nbrx.cpp b/src/receivers/nbrx.cpp index b95c6b761..f7e960789 100644 --- a/src/receivers/nbrx.cpp +++ b/src/receivers/nbrx.cpp @@ -34,19 +34,13 @@ nbrx_sptr make_nbrx(float quad_rate, float audio_rate) } nbrx::nbrx(float quad_rate, float audio_rate) - : receiver_base_cf("NBRX"), + : receiver_base_cf("NBRX", PREF_QUAD_RATE, quad_rate, audio_rate), d_running(false), - d_quad_rate(quad_rate), - d_audio_rate(audio_rate), d_demod(NBRX_DEMOD_FM) { - iq_resamp = make_resampler_cc(PREF_QUAD_RATE/d_quad_rate); nb = make_rx_nb_cc((double)PREF_QUAD_RATE, 3.3, 2.5); filter = make_rx_filter((double)PREF_QUAD_RATE, -5000.0, 5000.0, 1000.0); - agc = make_rx_agc_cc((double)PREF_QUAD_RATE, true, -100, 0, 0, 500, false); - sql = gr::analog::simple_squelch_cc::make(-150.0, 0.001); - meter = make_rx_meter_c((double)PREF_QUAD_RATE); demod_raw = gr::blocks::complex_to_float::make(1); demod_ssb = gr::blocks::complex_to_real::make(1); demod_fm = make_rx_demod_fm(PREF_QUAD_RATE, 5000.0, 75.0e-6); @@ -75,21 +69,24 @@ nbrx::nbrx(float quad_rate, float audio_rate) connect(nb, 0, filter, 0); connect(filter, 0, meter, 0); connect(filter, 0, sql, 0); - connect(sql, 0, agc, 0); - connect(agc, 0, demod, 0); + connect(sql, 0, demod, 0); +// connect(sql, 0, agc, 0); +// connect(agc, 0, demod, 0); if (audio_rr0) { connect(demod, 0, audio_rr0, 0); - connect(audio_rr0, 0, self(), 0); // left channel - connect(audio_rr0, 0, self(), 1); // right channel + connect(audio_rr0, 0, agc, 0); // left channel + connect(audio_rr0, 0, agc, 1); // right channel } else { - connect(demod, 0, self(), 0); - connect(demod, 0, self(), 1); + connect(demod, 0, agc, 0); + connect(demod, 0, agc, 1); } + connect(agc, 0, self(), 0); + connect(agc, 1, self(), 1); } bool nbrx::start() @@ -106,18 +103,6 @@ bool nbrx::stop() return true; } -void nbrx::set_quad_rate(float quad_rate) -{ - if (std::abs(d_quad_rate-quad_rate) > 0.5f) - { - qDebug() << "Changing NB_RX quad rate:" << d_quad_rate << "->" << quad_rate; - d_quad_rate = quad_rate; - lock(); - iq_resamp->set_rate(PREF_QUAD_RATE/d_quad_rate); - unlock(); - } -} - void nbrx::set_filter(double low, double high, double tw) { filter->set_param(low, high, tw); @@ -128,11 +113,6 @@ void nbrx::set_cw_offset(double offset) filter->set_cw_offset(offset); } -float nbrx::get_signal_level() -{ - return meter->get_level_db(); -} - void nbrx::set_nb_on(int nbid, bool on) { if (nbid == 1) @@ -149,46 +129,6 @@ void nbrx::set_nb_threshold(int nbid, float threshold) nb->set_threshold2(threshold); } -void nbrx::set_sql_level(double level_db) -{ - sql->set_threshold(level_db); -} - -void nbrx::set_sql_alpha(double alpha) -{ - sql->set_alpha(alpha); -} - -void nbrx::set_agc_on(bool agc_on) -{ - agc->set_agc_on(agc_on); -} - -void nbrx::set_agc_hang(bool use_hang) -{ - agc->set_use_hang(use_hang); -} - -void nbrx::set_agc_threshold(int threshold) -{ - agc->set_threshold(threshold); -} - -void nbrx::set_agc_slope(int slope) -{ - agc->set_slope(slope); -} - -void nbrx::set_agc_decay(int decay_ms) -{ - agc->set_decay(decay_ms); -} - -void nbrx::set_agc_manual_gain(int gain) -{ - agc->set_manual_gain(gain); -} - void nbrx::set_demod(int rx_demod) { nbrx_demod current_demod = d_demod; @@ -202,7 +142,7 @@ void nbrx::set_demod(int rx_demod) return; } - disconnect(agc, 0, demod, 0); + disconnect(sql, 0, demod, 0); if (audio_rr0) { if (current_demod == NBRX_DEMOD_NONE) @@ -210,28 +150,28 @@ void nbrx::set_demod(int rx_demod) disconnect(demod, 0, audio_rr0, 0); disconnect(demod, 1, audio_rr1, 0); - disconnect(audio_rr0, 0, self(), 0); - disconnect(audio_rr1, 0, self(), 1); + disconnect(audio_rr0, 0, agc, 0); + disconnect(audio_rr1, 0, agc, 1); } else { disconnect(demod, 0, audio_rr0, 0); - disconnect(audio_rr0, 0, self(), 0); - disconnect(audio_rr0, 0, self(), 1); + disconnect(audio_rr0, 0, agc, 0); + disconnect(audio_rr0, 0, agc, 1); } } else { if (current_demod == NBRX_DEMOD_NONE) { - disconnect(demod, 0, self(), 0); - disconnect(demod, 1, self(), 1); + disconnect(demod, 0, agc, 0); + disconnect(demod, 1, agc, 1); } else { - disconnect(demod, 0, self(), 0); - disconnect(demod, 0, self(), 1); + disconnect(demod, 0, agc, 0); + disconnect(demod, 0, agc, 1); } } @@ -264,7 +204,7 @@ void nbrx::set_demod(int rx_demod) break; } - connect(agc, 0, demod, 0); + connect(sql, 0, demod, 0); if (audio_rr0) { if (d_demod == NBRX_DEMOD_NONE) @@ -272,28 +212,28 @@ void nbrx::set_demod(int rx_demod) connect(demod, 0, audio_rr0, 0); connect(demod, 1, audio_rr1, 0); - connect(audio_rr0, 0, self(), 0); - connect(audio_rr1, 0, self(), 1); + connect(audio_rr0, 0, agc, 0); + connect(audio_rr1, 0, agc, 1); } else { connect(demod, 0, audio_rr0, 0); - connect(audio_rr0, 0, self(), 0); - connect(audio_rr0, 0, self(), 1); + connect(audio_rr0, 0, agc, 0); + connect(audio_rr0, 0, agc, 1); } } else { if (d_demod == NBRX_DEMOD_NONE) { - connect(demod, 0, self(), 0); - connect(demod, 1, self(), 1); + connect(demod, 0, agc, 0); + connect(demod, 1, agc, 1); } else { - connect(demod, 0, self(), 0); - connect(demod, 0, self(), 1); + connect(demod, 0, agc, 0); + connect(demod, 0, agc, 1); } } } diff --git a/src/receivers/nbrx.h b/src/receivers/nbrx.h index 20fcd7d3f..55e0dd97b 100644 --- a/src/receivers/nbrx.h +++ b/src/receivers/nbrx.h @@ -23,19 +23,14 @@ #ifndef NBRX_H #define NBRX_H -#include #include #include #include #include "receivers/receiver_base.h" #include "dsp/rx_noise_blanker_cc.h" #include "dsp/rx_filter.h" -#include "dsp/rx_meter.h" -#include "dsp/rx_agc_xx.h" #include "dsp/rx_demod_fm.h" #include "dsp/rx_demod_am.h" -//#include "dsp/resampler_ff.h" -#include "dsp/resampler_xx.h" class nbrx; @@ -73,13 +68,9 @@ class nbrx : public receiver_base_cf bool start(); bool stop(); - void set_quad_rate(float quad_rate); - void set_filter(double low, double high, double tw); void set_cw_offset(double offset); - float get_signal_level(); - /* Noise blanker */ bool has_nb() { return true; } void set_nb_on(int nbid, bool on); @@ -87,17 +78,9 @@ class nbrx : public receiver_base_cf /* Squelch parameter */ bool has_sql() { return true; } - void set_sql_level(double level_db); - void set_sql_alpha(double alpha); /* AGC */ bool has_agc() { return true; } - void set_agc_on(bool agc_on); - void set_agc_hang(bool use_hang); - void set_agc_threshold(int threshold); - void set_agc_slope(int slope); - void set_agc_decay(int decay_ms); - void set_agc_manual_gain(int gain); void set_demod(int demod); @@ -117,18 +100,12 @@ class nbrx : public receiver_base_cf private: bool d_running; /*!< Whether receiver is running or not. */ - float d_quad_rate; /*!< Input sample rate. */ - int d_audio_rate; /*!< Audio output rate. */ nbrx_demod d_demod; /*!< Current demodulator. */ - resampler_cc_sptr iq_resamp; /*!< Baseband resampler. */ rx_filter_sptr filter; /*!< Non-translating bandpass filter.*/ rx_nb_cc_sptr nb; /*!< Noise blanker. */ - rx_meter_c_sptr meter; /*!< Signal strength. */ - rx_agc_cc_sptr agc; /*!< Receiver AGC. */ - gr::analog::simple_squelch_cc::sptr sql; /*!< Squelch. */ gr::blocks::complex_to_float::sptr demod_raw; /*!< Raw I/Q passthrough. */ gr::blocks::complex_to_real::sptr demod_ssb; /*!< SSB demodulator. */ rx_demod_fm_sptr demod_fm; /*!< FM demodulator. */ diff --git a/src/receivers/receiver_base.cpp b/src/receivers/receiver_base.cpp index 0834e6d21..8ee6779ba 100644 --- a/src/receivers/receiver_base.cpp +++ b/src/receivers/receiver_base.cpp @@ -22,6 +22,7 @@ */ #include #include "receivers/receiver_base.h" +#include static const int MIN_IN = 1; /* Minimum number of input streams. */ @@ -29,12 +30,18 @@ static const int MAX_IN = 1; /* Maximum number of input streams. */ static const int MIN_OUT = 2; /* Minimum number of output streams. */ static const int MAX_OUT = 2; /* Maximum number of output streams. */ -receiver_base_cf::receiver_base_cf(std::string src_name) +receiver_base_cf::receiver_base_cf(std::string src_name, float pref_quad_rate, float quad_rate, int audio_rate) : gr::hier_block2 (src_name, gr::io_signature::make (MIN_IN, MAX_IN, sizeof(gr_complex)), - gr::io_signature::make (MIN_OUT, MAX_OUT, sizeof(float))) + gr::io_signature::make (MIN_OUT, MAX_OUT, sizeof(float))), + d_quad_rate(quad_rate), + d_audio_rate(audio_rate), + d_pref_quad_rate(pref_quad_rate) { - + iq_resamp = make_resampler_cc(d_pref_quad_rate/d_quad_rate); + agc = make_rx_agc_2f(d_audio_rate, false, 0, 0, 100, 500, 500, 0); + sql = gr::analog::simple_squelch_cc::make(-150.0, 0.001); + meter = make_rx_meter_c(d_pref_quad_rate); } receiver_base_cf::~receiver_base_cf() @@ -42,6 +49,23 @@ receiver_base_cf::~receiver_base_cf() } +void receiver_base_cf::set_quad_rate(float quad_rate) +{ + if (std::abs(d_quad_rate-quad_rate) > 0.5f) + { + qDebug() << "Changing RX quad rate:" << d_quad_rate << "->" << quad_rate; + d_quad_rate = quad_rate; + lock(); + iq_resamp->set_rate(d_pref_quad_rate/d_quad_rate); + unlock(); + } +} + +float receiver_base_cf::get_signal_level() +{ + return meter->get_level_db(); +} + bool receiver_base_cf::has_nb() { return false; @@ -66,12 +90,12 @@ bool receiver_base_cf::has_sql() void receiver_base_cf::set_sql_level(double level_db) { - (void) level_db; + sql->set_threshold(level_db); } void receiver_base_cf::set_sql_alpha(double alpha) { - (void) alpha; + sql->set_alpha(alpha); } bool receiver_base_cf::has_agc() @@ -81,32 +105,42 @@ bool receiver_base_cf::has_agc() void receiver_base_cf::set_agc_on(bool agc_on) { - (void) agc_on; + agc->set_agc_on(agc_on); } -void receiver_base_cf::set_agc_hang(bool use_hang) +void receiver_base_cf::set_agc_target_level(int target_level) { - (void) use_hang; + agc->set_target_level(target_level); } -void receiver_base_cf::set_agc_threshold(int threshold) +void receiver_base_cf::set_agc_manual_gain(float gain) { - (void) threshold; + agc->set_manual_gain(gain); +} + +void receiver_base_cf::set_agc_max_gain(int gain) +{ + agc->set_max_gain(gain); } -void receiver_base_cf::set_agc_slope(int slope) +void receiver_base_cf::set_agc_attack(int attack_ms) { - (void) slope; + agc->set_attack(attack_ms); } void receiver_base_cf::set_agc_decay(int decay_ms) { - (void) decay_ms; + agc->set_decay(decay_ms); +} + +void receiver_base_cf::set_agc_hang(int hang_ms) +{ + agc->set_hang(hang_ms); } -void receiver_base_cf::set_agc_manual_gain(int gain) +float receiver_base_cf::get_agc_gain() { - (void) gain; + return agc->get_current_gain(); } bool receiver_base_cf::has_fm() diff --git a/src/receivers/receiver_base.h b/src/receivers/receiver_base.h index 8a5d7cbaa..53785a14b 100644 --- a/src/receivers/receiver_base.h +++ b/src/receivers/receiver_base.h @@ -24,6 +24,10 @@ #define RECEIVER_BASE_H #include +#include +#include "dsp/resampler_xx.h" +#include "dsp/rx_meter.h" +#include "dsp/rx_agc_xx.h" class receiver_base_cf; @@ -49,18 +53,18 @@ class receiver_base_cf : public gr::hier_block2 /*! \brief Public constructor. * \param src_name Descriptive name used in the constructor of gr::hier_block2 */ - receiver_base_cf(std::string src_name); + receiver_base_cf(std::string src_name, float pref_quad_rate, float quad_rate, int audio_rate); virtual ~receiver_base_cf(); virtual bool start() = 0; virtual bool stop() = 0; - virtual void set_quad_rate(float quad_rate) = 0; + virtual void set_quad_rate(float quad_rate); virtual void set_filter(double low, double high, double tw) = 0; virtual void set_cw_offset(double offset) = 0; - virtual float get_signal_level() = 0; + virtual float get_signal_level(); virtual void set_demod(int demod) = 0; @@ -79,11 +83,13 @@ class receiver_base_cf : public gr::hier_block2 /* AGC */ virtual bool has_agc(); virtual void set_agc_on(bool agc_on); - virtual void set_agc_hang(bool use_hang); - virtual void set_agc_threshold(int threshold); - virtual void set_agc_slope(int slope); + virtual void set_agc_target_level(int target_level); + virtual void set_agc_manual_gain(float gain); + virtual void set_agc_max_gain(int gain); + virtual void set_agc_attack(int attack_ms); virtual void set_agc_decay(int decay_ms); - virtual void set_agc_manual_gain(int gain); + virtual void set_agc_hang(int hang_ms); + virtual float get_agc_gain(); /* FM parameters */ virtual bool has_fm(); @@ -104,7 +110,16 @@ class receiver_base_cf : public gr::hier_block2 virtual void stop_rds_decoder(); virtual void reset_rds_parser(); virtual bool is_rds_decoder_active(); - +protected: + float d_quad_rate; /*!< Input sample rate. */ + int d_audio_rate; /*!< Audio output rate. */ + + resampler_cc_sptr iq_resamp; /*!< Baseband resampler. */ + rx_meter_c_sptr meter; /*!< Signal strength. */ + rx_agc_2f_sptr agc; /*!< Receiver AGC. */ + gr::analog::simple_squelch_cc::sptr sql; /*!< Squelch. */ +private: + float d_pref_quad_rate; }; #endif // RECEIVER_BASE_H diff --git a/src/receivers/wfmrx.cpp b/src/receivers/wfmrx.cpp index 3e5838646..a6304009c 100644 --- a/src/receivers/wfmrx.cpp +++ b/src/receivers/wfmrx.cpp @@ -34,17 +34,12 @@ wfmrx_sptr make_wfmrx(float quad_rate, float audio_rate) } wfmrx::wfmrx(float quad_rate, float audio_rate) - : receiver_base_cf("WFMRX"), + : receiver_base_cf("WFMRX", PREF_QUAD_RATE, quad_rate, audio_rate), d_running(false), - d_quad_rate(quad_rate), - d_audio_rate(audio_rate), d_demod(WFMRX_DEMOD_MONO) { - iq_resamp = make_resampler_cc(PREF_QUAD_RATE/d_quad_rate); filter = make_rx_filter((double)PREF_QUAD_RATE, -80000.0, 80000.0, 20000.0); - sql = gr::analog::simple_squelch_cc::make(-150.0, 0.001); - meter = make_rx_meter_c((double)PREF_QUAD_RATE); demod_fm = make_rx_demod_fm(PREF_QUAD_RATE, 75000.0, 0.0); stereo = make_stereo_demod(PREF_QUAD_RATE, d_audio_rate, true); stereo_oirt = make_stereo_demod(PREF_QUAD_RATE, d_audio_rate, true, true); @@ -63,8 +58,10 @@ wfmrx::wfmrx(float quad_rate, float audio_rate) connect(filter, 0, sql, 0); connect(sql, 0, demod_fm, 0); connect(demod_fm, 0, mono, 0); - connect(mono, 0, self(), 0); // left channel - connect(mono, 1, self(), 1); // right channel + connect(mono, 0, agc, 0); // left channel + connect(mono, 1, agc, 1); // right channel + connect(agc, 0, self(), 0); + connect(agc, 1, self(), 1); } wfmrx::~wfmrx() @@ -86,88 +83,11 @@ bool wfmrx::stop() return true; } -void wfmrx::set_quad_rate(float quad_rate) -{ - if (std::abs(d_quad_rate-quad_rate) > 0.5f) - { - qDebug() << "Changing WFM RX quad rate:" << d_quad_rate << "->" << quad_rate; - d_quad_rate = quad_rate; - lock(); - iq_resamp->set_rate(PREF_QUAD_RATE/d_quad_rate); - unlock(); - } -} - void wfmrx::set_filter(double low, double high, double tw) { filter->set_param(low, high, tw); } -float wfmrx::get_signal_level() -{ - return meter->get_level_db(); -} - -/* -void nbrx::set_nb_on(int nbid, bool on) -{ - if (nbid == 1) - nb->set_nb1_on(on); - else if (nbid == 2) - nb->set_nb2_on(on); -} - -void nbrx::set_nb_threshold(int nbid, float threshold) -{ - if (nbid == 1) - nb->set_threshold1(threshold); - else if (nbid == 2) - nb->set_threshold2(threshold); -} -*/ - -void wfmrx::set_sql_level(double level_db) -{ - sql->set_threshold(level_db); -} - -void wfmrx::set_sql_alpha(double alpha) -{ - sql->set_alpha(alpha); -} - -/* -void nbrx::set_agc_on(bool agc_on) -{ - agc->set_agc_on(agc_on); -} - -void nbrx::set_agc_hang(bool use_hang) -{ - agc->set_use_hang(use_hang); -} - -void nbrx::set_agc_threshold(int threshold) -{ - agc->set_threshold(threshold); -} - -void nbrx::set_agc_slope(int slope) -{ - agc->set_slope(slope); -} - -void nbrx::set_agc_decay(int decay_ms) -{ - agc->set_decay(decay_ms); -} - -void nbrx::set_agc_manual_gain(int gain) -{ - agc->set_manual_gain(gain); -} -*/ - void wfmrx::set_demod(int demod) { /* check if new demodulator selection is valid */ @@ -188,20 +108,20 @@ void wfmrx::set_demod(int demod) case WFMRX_DEMOD_MONO: default: disconnect(demod_fm, 0, mono, 0); - disconnect(mono, 0, self(), 0); // left channel - disconnect(mono, 1, self(), 1); // right channel + disconnect(mono, 0, agc, 0); // left channel + disconnect(mono, 1, agc, 1); // right channel break; case WFMRX_DEMOD_STEREO: disconnect(demod_fm, 0, stereo, 0); - disconnect(stereo, 0, self(), 0); // left channel - disconnect(stereo, 1, self(), 1); // right channel + disconnect(stereo, 0, agc, 0); // left channel + disconnect(stereo, 1, agc, 1); // right channel break; case WFMRX_DEMOD_STEREO_UKW: disconnect(demod_fm, 0, stereo_oirt, 0); - disconnect(stereo_oirt, 0, self(), 0); // left channel - disconnect(stereo_oirt, 1, self(), 1); // right channel + disconnect(stereo_oirt, 0, agc, 0); // left channel + disconnect(stereo_oirt, 1, agc, 1); // right channel break; } @@ -210,20 +130,20 @@ void wfmrx::set_demod(int demod) case WFMRX_DEMOD_MONO: default: connect(demod_fm, 0, mono, 0); - connect(mono, 0, self(), 0); // left channel - connect(mono, 1, self(), 1); // right channel + connect(mono, 0, agc, 0); // left channel + connect(mono, 1, agc, 1); // right channel break; case WFMRX_DEMOD_STEREO: connect(demod_fm, 0, stereo, 0); - connect(stereo, 0, self(), 0); // left channel - connect(stereo, 1, self(), 1); // right channel + connect(stereo, 0, agc, 0); // left channel + connect(stereo, 1, agc, 1); // right channel break; case WFMRX_DEMOD_STEREO_UKW: connect(demod_fm, 0, stereo_oirt, 0); - connect(stereo_oirt, 0, self(), 0); // left channel - connect(stereo_oirt, 1, self(), 1); // right channel + connect(stereo_oirt, 0, agc, 0); // left channel + connect(stereo_oirt, 1, agc, 1); // right channel break; } d_demod = (wfmrx_demod) demod; diff --git a/src/receivers/wfmrx.h b/src/receivers/wfmrx.h index cc0e03614..2c05a12fc 100644 --- a/src/receivers/wfmrx.h +++ b/src/receivers/wfmrx.h @@ -24,14 +24,11 @@ #ifndef WFMRX_H #define WFMRX_H -#include #include "receivers/receiver_base.h" #include "dsp/rx_noise_blanker_cc.h" #include "dsp/rx_filter.h" -#include "dsp/rx_meter.h" #include "dsp/rx_demod_fm.h" #include "dsp/stereo_demod.h" -#include "dsp/resampler_xx.h" #include "dsp/rx_rds.h" #include "dsp/rds/decoder.h" #include "dsp/rds/parser.h" @@ -69,13 +66,10 @@ class wfmrx : public receiver_base_cf bool start(); bool stop(); - void set_quad_rate(float quad_rate); void set_filter(double low, double high, double tw); void set_cw_offset(double offset) { (void)offset; } - float get_signal_level(); - /* Noise blanker */ bool has_nb() { return false; } //void set_nb_on(int nbid, bool on); @@ -83,17 +77,9 @@ class wfmrx : public receiver_base_cf /* Squelch parameter */ bool has_sql() { return true; } - void set_sql_level(double level_db); - void set_sql_alpha(double alpha); /* AGC */ - bool has_agc() { return false; } - /*void set_agc_on(bool agc_on); - void set_agc_hang(bool use_hang); - void set_agc_threshold(int threshold); - void set_agc_slope(int slope); - void set_agc_decay(int decay_ms); - void set_agc_manual_gain(int gain);*/ + bool has_agc() { return true; } void set_demod(int demod); @@ -110,16 +96,11 @@ class wfmrx : public receiver_base_cf private: bool d_running; /*!< Whether receiver is running or not. */ - float d_quad_rate; /*!< Input sample rate. */ - int d_audio_rate; /*!< Audio output rate. */ wfmrx_demod d_demod; /*!< Current demodulator. */ - resampler_cc_sptr iq_resamp; /*!< Baseband resampler. */ rx_filter_sptr filter; /*!< Non-translating bandpass filter.*/ - rx_meter_c_sptr meter; /*!< Signal strength. */ - gr::analog::simple_squelch_cc::sptr sql; /*!< Squelch. */ rx_demod_fm_sptr demod_fm; /*!< FM demodulator. */ stereo_demod_sptr stereo; /*!< FM stereo demodulator. */ stereo_demod_sptr stereo_oirt; /*!< FM stereo oirt demodulator. */