From 85d4d8029ca70f0c6ccc0bb0ec79b707c4bf5ac4 Mon Sep 17 00:00:00 2001 From: f4exb Date: Mon, 14 May 2018 00:34:37 +0200 Subject: [PATCH] AM demod: use MagAGC for synchronous AM --- plugins/channelrx/demodam/amdemod.cpp | 7 +- plugins/channelrx/demodam/amdemod.h | 633 +++++++++++++------------- 2 files changed, 330 insertions(+), 310 deletions(-) diff --git a/plugins/channelrx/demodam/amdemod.cpp b/plugins/channelrx/demodam/amdemod.cpp index d7a94dd99..a48561fc0 100644 --- a/plugins/channelrx/demodam/amdemod.cpp +++ b/plugins/channelrx/demodam/amdemod.cpp @@ -54,6 +54,7 @@ AMDemod::AMDemod(DeviceSourceAPI *deviceAPI) : m_magsqPeak(0.0f), m_magsqCount(0), m_volumeAGC(0.003), + m_syncAMAGC(12000, 0.1, 1e-2), m_audioFifo(48000), m_settingsMutex(QMutex::Recursive) { @@ -68,6 +69,8 @@ AMDemod::AMDemod(DeviceSourceAPI *deviceAPI) : m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate(); DSBFilter = new fftfilt((2.0f * m_settings.m_rfBandwidth) / m_audioSampleRate, 2 * 1024); SSBFilter = new fftfilt(0.0f, m_settings.m_rfBandwidth / m_audioSampleRate, 1024); + m_syncAMAGC.setThresholdEnable(false); + m_syncAMAGC.resize(12000, 6000, 0.1); applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); applySettings(m_settings, true); @@ -251,6 +254,8 @@ void AMDemod::applyAudioSampleRate(int sampleRate) m_volumeAGC.resizeNew(sampleRate/10, 0.003); } + m_syncAMAGC.resize(sampleRate/4, sampleRate/8, 0.1); + m_settingsMutex.unlock(); m_audioSampleRate = sampleRate; } @@ -308,7 +313,7 @@ void AMDemod::applySettings(const AMDemodSettings& settings, bool force) if ((m_settings.m_squelch != settings.m_squelch) || force) { - m_squelchLevel = pow(10.0, settings.m_squelch / 10.0); + m_squelchLevel = CalcDb::powerFromdB(settings.m_squelch); } if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) diff --git a/plugins/channelrx/demodam/amdemod.h b/plugins/channelrx/demodam/amdemod.h index ff53b2ed3..a70f4070e 100644 --- a/plugins/channelrx/demodam/amdemod.h +++ b/plugins/channelrx/demodam/amdemod.h @@ -1,309 +1,324 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2015 Edouard Griffiths, F4EXB. // -// // -// This program is free software; you can redistribute it and/or modify // -// it under the terms of the GNU General Public License as published by // -// the Free Software Foundation as version 3 of the License, or // -// // -// This program is distributed in the hope that it will be useful, // -// but WITHOUT ANY WARRANTY; without even the implied warranty of // -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // -// GNU General Public License V3 for more details. // -// // -// You should have received a copy of the GNU General Public License // -// along with this program. If not, see . // -/////////////////////////////////////////////////////////////////////////////////// - -#ifndef INCLUDE_AMDEMOD_H -#define INCLUDE_AMDEMOD_H - -#include -#include - -#include "dsp/basebandsamplesink.h" -#include "channel/channelsinkapi.h" -#include "dsp/nco.h" -#include "dsp/interpolator.h" -#include "util/movingaverage.h" -#include "dsp/agc.h" -#include "dsp/bandpass.h" -#include "dsp/lowpass.h" -#include "dsp/phaselockcomplex.h" -#include "dsp/fftfilt.h" -#include "audio/audiofifo.h" -#include "util/message.h" -#include "util/doublebufferfifo.h" -#include "amdemodsettings.h" - -class DeviceSourceAPI; -class DownChannelizer; -class ThreadedBasebandSampleSink; - -class AMDemod : public BasebandSampleSink, public ChannelSinkAPI { - Q_OBJECT -public: - class MsgConfigureAMDemod : public Message { - MESSAGE_CLASS_DECLARATION - - public: - const AMDemodSettings& getSettings() const { return m_settings; } - bool getForce() const { return m_force; } - - static MsgConfigureAMDemod* create(const AMDemodSettings& settings, bool force) - { - return new MsgConfigureAMDemod(settings, force); - } - - private: - AMDemodSettings m_settings; - bool m_force; - - MsgConfigureAMDemod(const AMDemodSettings& settings, bool force) : - Message(), - m_settings(settings), - m_force(force) - { } - }; - - class MsgConfigureChannelizer : public Message { - MESSAGE_CLASS_DECLARATION - - public: - int getSampleRate() const { return m_sampleRate; } - int getCenterFrequency() const { return m_centerFrequency; } - - static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency) - { - return new MsgConfigureChannelizer(sampleRate, centerFrequency); - } - - private: - int m_sampleRate; - int m_centerFrequency; - - MsgConfigureChannelizer(int sampleRate, int centerFrequency) : - Message(), - m_sampleRate(sampleRate), - m_centerFrequency(centerFrequency) - { } - }; - - AMDemod(DeviceSourceAPI *deviceAPI); - ~AMDemod(); - virtual void destroy() { delete this; } - - virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool po); - virtual void start(); - virtual void stop(); - virtual bool handleMessage(const Message& cmd); - - virtual void getIdentifier(QString& id) { id = objectName(); } - virtual void getTitle(QString& title) { title = m_settings.m_title; } - virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; } - - virtual QByteArray serialize() const; - virtual bool deserialize(const QByteArray& data); - - virtual int webapiSettingsGet( - SWGSDRangel::SWGChannelSettings& response, - QString& errorMessage); - - virtual int webapiSettingsPutPatch( - bool force, - const QStringList& channelSettingsKeys, - SWGSDRangel::SWGChannelSettings& response, - QString& errorMessage); - - virtual int webapiReportGet( - SWGSDRangel::SWGChannelReport& response, - QString& errorMessage); - - uint32_t getAudioSampleRate() const { return m_audioSampleRate; } - double getMagSq() const { return m_magsq; } - bool getSquelchOpen() const { return m_squelchOpen; } - bool getPllLocked() const { return m_settings.m_pll && m_pll.locked(); } - - void getMagSqLevels(double& avg, double& peak, int& nbSamples) - { - avg = m_magsqCount == 0 ? 1e-10 : m_magsqSum / m_magsqCount; - peak = m_magsqPeak == 0.0 ? 1e-10 : m_magsqPeak; - nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; - m_magsqSum = 0.0f; - m_magsqPeak = 0.0f; - m_magsqCount = 0; - } - - static const QString m_channelIdURI; - static const QString m_channelId; - -private: - enum RateState { - RSInitialFill, - RSRunning - }; - - DeviceSourceAPI *m_deviceAPI; - ThreadedBasebandSampleSink* m_threadedChannelizer; - DownChannelizer* m_channelizer; - - int m_inputSampleRate; - int m_inputFrequencyOffset; - AMDemodSettings m_settings; - uint32_t m_audioSampleRate; - bool m_running; - - NCO m_nco; - Interpolator m_interpolator; - Real m_interpolatorDistance; - Real m_interpolatorDistanceRemain; - - Real m_squelchLevel; - uint32_t m_squelchCount; - bool m_squelchOpen; - DoubleBufferFIFO m_squelchDelayLine; - double m_magsq; - double m_magsqSum; - double m_magsqPeak; - int m_magsqCount; - - MovingAverageUtil m_movingAverage; - SimpleAGC<4800> m_volumeAGC; - Bandpass m_bandpass; - Lowpass > m_pllFilt; - PhaseLockComplex m_pll; - fftfilt* DSBFilter; - fftfilt* SSBFilter; - Real m_syncAMBuff[2*1024]; - uint32_t m_syncAMBuffIndex; - - AudioVector m_audioBuffer; - uint32_t m_audioBufferFill; - AudioFifo m_audioFifo; - - static const int m_udpBlockSize; - - QMutex m_settingsMutex; - - void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); - void applySettings(const AMDemodSettings& settings, bool force = false); - void applyAudioSampleRate(int sampleRate); - void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const AMDemodSettings& settings); - void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); - - void processOneSample(Complex &ci) - { - Real re = ci.real() / SDR_RX_SCALEF; - Real im = ci.imag() / SDR_RX_SCALEF; - Real magsq = re*re + im*im; - m_movingAverage(magsq); - m_magsq = m_movingAverage.asDouble(); - m_magsqSum += magsq; - - if (magsq > m_magsqPeak) - { - m_magsqPeak = magsq; - } - - m_magsqCount++; - - m_squelchDelayLine.write(magsq); - - if (m_magsq < m_squelchLevel) - { - if (m_squelchCount > 0) { - m_squelchCount--; - } - } - else - { - if (m_squelchCount < m_audioSampleRate / 10) { - m_squelchCount++; - } - } - - qint16 sample; - - m_squelchOpen = (m_squelchCount >= m_audioSampleRate / 20); - - if (m_squelchOpen && !m_settings.m_audioMute) - { - Real demod; - - if (m_settings.m_pll) - { - std::complex s(re, im); - s = m_pllFilt.filter(s); - m_pll.feed(s.real(), s.imag()); - float yr = re * m_pll.getImag() - im * m_pll.getReal(); - float yi = re * m_pll.getReal() + im * m_pll.getImag(); - - fftfilt::cmplx *sideband; - std::complex cs(yr, yi); - int n_out; - - if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMDSB) { - n_out = DSBFilter->runDSB(cs, &sideband, false); - } else { - n_out = SSBFilter->runSSB(cs, &sideband, m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB, false); - } - - for (int i = 0; i < n_out; i++) - { - if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMDSB) { - m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag())/2.0f; - } else if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB) { - m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag()); - } else { - m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag()); - } - - m_syncAMBuffIndex = 0; - } - - m_syncAMBuffIndex = m_syncAMBuffIndex < 2*1024 ? m_syncAMBuffIndex : 0; - demod = m_syncAMBuff[m_syncAMBuffIndex++]*(SDR_RX_SCALEF/602.0f); - m_volumeAGC.feed(demod); - demod /= (10.0*m_volumeAGC.getValue()); - } - else - { - demod = sqrt(m_squelchDelayLine.readBack(m_audioSampleRate/20)); - m_volumeAGC.feed(demod); - demod = (demod - m_volumeAGC.getValue()) / m_volumeAGC.getValue(); - } - - if (m_settings.m_bandpassEnable) - { - demod = m_bandpass.filter(demod); - demod /= 301.0f; - } - - Real attack = (m_squelchCount - 0.05f * m_audioSampleRate) / (0.05f * m_audioSampleRate); - sample = demod * attack * (m_audioSampleRate/24) * m_settings.m_volume; - } - else - { - sample = 0; - } - - m_audioBuffer[m_audioBufferFill].l = sample; - m_audioBuffer[m_audioBufferFill].r = sample; - ++m_audioBufferFill; - - if (m_audioBufferFill >= m_audioBuffer.size()) - { - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 10); - - if (res != m_audioBufferFill) - { - qDebug("AMDemod::processOneSample: %u/%u audio samples written", res, m_audioBufferFill); - m_audioFifo.clear(); - } - - m_audioBufferFill = 0; - } - } - -}; - -#endif // INCLUDE_AMDEMOD_H +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 Edouard Griffiths, F4EXB. // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_AMDEMOD_H +#define INCLUDE_AMDEMOD_H + +#include +#include + +#include "dsp/basebandsamplesink.h" +#include "channel/channelsinkapi.h" +#include "dsp/nco.h" +#include "dsp/interpolator.h" +#include "util/movingaverage.h" +#include "dsp/agc.h" +#include "dsp/bandpass.h" +#include "dsp/lowpass.h" +#include "dsp/phaselockcomplex.h" +#include "dsp/fftfilt.h" +#include "audio/audiofifo.h" +#include "util/message.h" +#include "util/doublebufferfifo.h" +#include "util/stepfunctions.h" + +#include "amdemodsettings.h" + +class DeviceSourceAPI; +class DownChannelizer; +class ThreadedBasebandSampleSink; + +class AMDemod : public BasebandSampleSink, public ChannelSinkAPI { + Q_OBJECT +public: + class MsgConfigureAMDemod : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const AMDemodSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureAMDemod* create(const AMDemodSettings& settings, bool force) + { + return new MsgConfigureAMDemod(settings, force); + } + + private: + AMDemodSettings m_settings; + bool m_force; + + MsgConfigureAMDemod(const AMDemodSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgConfigureChannelizer : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getSampleRate() const { return m_sampleRate; } + int getCenterFrequency() const { return m_centerFrequency; } + + static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency) + { + return new MsgConfigureChannelizer(sampleRate, centerFrequency); + } + + private: + int m_sampleRate; + int m_centerFrequency; + + MsgConfigureChannelizer(int sampleRate, int centerFrequency) : + Message(), + m_sampleRate(sampleRate), + m_centerFrequency(centerFrequency) + { } + }; + + AMDemod(DeviceSourceAPI *deviceAPI); + ~AMDemod(); + virtual void destroy() { delete this; } + + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool po); + virtual void start(); + virtual void stop(); + virtual bool handleMessage(const Message& cmd); + + virtual void getIdentifier(QString& id) { id = objectName(); } + virtual void getTitle(QString& title) { title = m_settings.m_title; } + virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; } + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + virtual int webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + + uint32_t getAudioSampleRate() const { return m_audioSampleRate; } + double getMagSq() const { return m_magsq; } + bool getSquelchOpen() const { return m_squelchOpen; } + bool getPllLocked() const { return m_settings.m_pll && m_pll.locked(); } + + void getMagSqLevels(double& avg, double& peak, int& nbSamples) + { + avg = m_magsqCount == 0 ? 1e-10 : m_magsqSum / m_magsqCount; + peak = m_magsqPeak == 0.0 ? 1e-10 : m_magsqPeak; + nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; + m_magsqSum = 0.0f; + m_magsqPeak = 0.0f; + m_magsqCount = 0; + } + + static const QString m_channelIdURI; + static const QString m_channelId; + +private: + enum RateState { + RSInitialFill, + RSRunning + }; + + DeviceSourceAPI *m_deviceAPI; + ThreadedBasebandSampleSink* m_threadedChannelizer; + DownChannelizer* m_channelizer; + + int m_inputSampleRate; + int m_inputFrequencyOffset; + AMDemodSettings m_settings; + uint32_t m_audioSampleRate; + bool m_running; + + NCO m_nco; + Interpolator m_interpolator; + Real m_interpolatorDistance; + Real m_interpolatorDistanceRemain; + + Real m_squelchLevel; + uint32_t m_squelchCount; + bool m_squelchOpen; + DoubleBufferFIFO m_squelchDelayLine; + double m_magsq; + double m_magsqSum; + double m_magsqPeak; + int m_magsqCount; + + MovingAverageUtil m_movingAverage; + SimpleAGC<4800> m_volumeAGC; + Bandpass m_bandpass; + Lowpass > m_pllFilt; + PhaseLockComplex m_pll; + fftfilt* DSBFilter; + fftfilt* SSBFilter; + Real m_syncAMBuff[2*1024]; + uint32_t m_syncAMBuffIndex; + MagAGC m_syncAMAGC; + + AudioVector m_audioBuffer; + uint32_t m_audioBufferFill; + AudioFifo m_audioFifo; + + static const int m_udpBlockSize; + + QMutex m_settingsMutex; + + void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); + void applySettings(const AMDemodSettings& settings, bool force = false); + void applyAudioSampleRate(int sampleRate); + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const AMDemodSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); + + void processOneSample(Complex &ci) + { + Real re = ci.real() / SDR_RX_SCALEF; + Real im = ci.imag() / SDR_RX_SCALEF; + Real magsq = re*re + im*im; + m_movingAverage(magsq); + m_magsq = m_movingAverage.asDouble(); + m_magsqSum += magsq; + + if (magsq > m_magsqPeak) + { + m_magsqPeak = magsq; + } + + m_magsqCount++; + + m_squelchDelayLine.write(magsq); + + if (m_magsq < m_squelchLevel) + { + if (m_squelchCount > 0) { + m_squelchCount--; + } + } + else + { + if (m_squelchCount < m_audioSampleRate / 10) { + m_squelchCount++; + } + } + + qint16 sample; + + m_squelchOpen = (m_squelchCount >= m_audioSampleRate / 20); + + if (m_squelchOpen && !m_settings.m_audioMute) + { + Real demod; + + if (m_settings.m_pll) + { + std::complex s(re, im); + s = m_pllFilt.filter(s); + m_pll.feed(s.real(), s.imag()); + float yr = re * m_pll.getImag() - im * m_pll.getReal(); + float yi = re * m_pll.getReal() + im * m_pll.getImag(); + + fftfilt::cmplx *sideband; + std::complex cs(yr, yi); + int n_out; + + if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMDSB) { + n_out = DSBFilter->runDSB(cs, &sideband, false); + } else { + n_out = SSBFilter->runSSB(cs, &sideband, m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB, false); + } + + for (int i = 0; i < n_out; i++) + { + float agcVal = m_syncAMAGC.feedAndGetValue(sideband[i]); + fftfilt::cmplx z = sideband[i] * agcVal; // * m_syncAMAGC.getStepValue(); + + if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMDSB) { + m_syncAMBuff[i] = (z.real() + z.imag())/2.0f; + } else if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB) { + m_syncAMBuff[i] = (z.real() + z.imag()); + } else { + m_syncAMBuff[i] = (z.real() + z.imag()); + } + +// if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMDSB) { +// m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag())/2.0f; +// } else if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB) { +// m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag()); +// } else { +// m_syncAMBuff[i] = (sideband[i].real() + sideband[i].imag()); +// } + + m_syncAMBuffIndex = 0; + } + + m_syncAMBuffIndex = m_syncAMBuffIndex < 2*1024 ? m_syncAMBuffIndex : 0; + demod = m_syncAMBuff[m_syncAMBuffIndex++]*4.0f; // mos pifometrico +// demod = m_syncAMBuff[m_syncAMBuffIndex++]*(SDR_RX_SCALEF/602.0f); +// m_volumeAGC.feed(demod); +// demod /= (10.0*m_volumeAGC.getValue()); + } + else + { + demod = sqrt(m_squelchDelayLine.readBack(m_audioSampleRate/20)); + m_volumeAGC.feed(demod); + demod = (demod - m_volumeAGC.getValue()) / m_volumeAGC.getValue(); + } + + if (m_settings.m_bandpassEnable) + { + demod = m_bandpass.filter(demod); + demod /= 301.0f; + } + + Real attack = (m_squelchCount - 0.05f * m_audioSampleRate) / (0.05f * m_audioSampleRate); + sample = demod * StepFunctions::smootherstep(attack) * (m_audioSampleRate/24) * m_settings.m_volume; + } + else + { + sample = 0; + } + + m_audioBuffer[m_audioBufferFill].l = sample; + m_audioBuffer[m_audioBufferFill].r = sample; + ++m_audioBufferFill; + + if (m_audioBufferFill >= m_audioBuffer.size()) + { + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 10); + + if (res != m_audioBufferFill) + { + qDebug("AMDemod::processOneSample: %u/%u audio samples written", res, m_audioBufferFill); + m_audioFifo.clear(); + } + + m_audioBufferFill = 0; + } + } + +}; + +#endif // INCLUDE_AMDEMOD_H