diff --git a/plugins/channelrx/CMakeLists.txt b/plugins/channelrx/CMakeLists.txt index 60f72906d..32d27cc91 100644 --- a/plugins/channelrx/CMakeLists.txt +++ b/plugins/channelrx/CMakeLists.txt @@ -3,6 +3,7 @@ project(demod) add_subdirectory(demodam) add_subdirectory(demodbfm) add_subdirectory(demodnfm) +add_subdirectory(demodnfmtest) add_subdirectory(demodssb) add_subdirectory(udpsink) add_subdirectory(demodwfm) diff --git a/plugins/channelrx/demodam/CMakeLists.txt b/plugins/channelrx/demodam/CMakeLists.txt index 895430ecb..ef6a53c33 100644 --- a/plugins/channelrx/demodam/CMakeLists.txt +++ b/plugins/channelrx/demodam/CMakeLists.txt @@ -2,14 +2,18 @@ project(am) set(am_SOURCES amdemod.cpp - amdemodsettings.cpp + amdemodsettings.cpp + amdemodbaseband.cpp + amdemodsink.cpp amdemodplugin.cpp amdemodwebapiadapter.cpp ) set(am_HEADERS amdemod.h - amdemodsettings.h + amdemodsettings.h + amdemodbaseband.h + amdemodsink.h amdemodplugin.h amdemodwebapiadapter.h ) @@ -24,13 +28,12 @@ if(NOT SERVER_MODE) ${am_SOURCES} amdemodgui.cpp amdemodssbdialog.cpp - amdemodgui.ui amdemodssb.ui ) set(am_HEADERS ${am_HEADERS} - amdemodgui.h + amdemodgui.h amdemodssbdialog.h ) @@ -50,8 +53,8 @@ add_library(${TARGET_NAME} SHARED ) target_link_libraries(${TARGET_NAME} - Qt5::Core - ${TARGET_LIB} + Qt5::Core + ${TARGET_LIB} sdrbase ${TARGET_LIB_GUI} ) diff --git a/plugins/channelrx/demodam/amdemod.cpp b/plugins/channelrx/demodam/amdemod.cpp index aba67ccb7..bc70f0ff9 100644 --- a/plugins/channelrx/demodam/amdemod.cpp +++ b/plugins/channelrx/demodam/amdemod.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -31,19 +32,12 @@ #include "SWGChannelReport.h" #include "SWGAMDemodReport.h" -#include "dsp/downchannelizer.h" -#include "audio/audiooutput.h" #include "dsp/dspengine.h" -#include "dsp/threadedbasebandsamplesink.h" #include "dsp/dspcommands.h" -#include "dsp/devicesamplemimo.h" -#include "dsp/fftfilt.h" #include "device/deviceapi.h" #include "util/db.h" -#include "util/stepfunctions.h" MESSAGE_CLASS_DEFINITION(AMDemod::MsgConfigureAMDemod, Message) -MESSAGE_CLASS_DEFINITION(AMDemod::MsgConfigureChannelizer, Message) const QString AMDemod::m_channelIdURI = "sdrangel.channel.amdemod"; const QString AMDemod::m_channelId = "AMDemod"; @@ -52,45 +46,19 @@ const int AMDemod::m_udpBlockSize = 512; AMDemod::AMDemod(DeviceAPI *deviceAPI) : ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink), m_deviceAPI(deviceAPI), - m_inputSampleRate(48000), - m_inputFrequencyOffset(0), - m_running(false), - m_squelchOpen(false), - m_squelchDelayLine(9600), - m_magsqSum(0.0f), - 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) + m_basebandSampleRate(0) { setObjectName(m_channelId); - m_audioBuffer.resize(1<<14); - m_audioBufferFill = 0; + m_thread = new QThread(this); + m_basebandSink = new AMDemodBaseband(); + m_basebandSink->moveToThread(m_thread); - m_magsq = 0.0; + applySettings(m_settings, true); - DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo, getInputMessageQueue()); - 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); - - m_channelizer = new DownChannelizer(this); - m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); - m_deviceAPI->addChannelSink(m_threadedChannelizer); + m_deviceAPI->addChannelSink(this); m_deviceAPI->addChannelSinkAPI(this); - m_pllFilt.create(101, m_audioSampleRate, 200.0); - m_pll.computeCoefficients(0.05, 0.707, 1000); - m_syncAMBuffIndex = 0; - m_networkManager = new QNetworkAccessManager(); connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); } @@ -99,13 +67,10 @@ AMDemod::~AMDemod() { disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); delete m_networkManager; - DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo); - m_deviceAPI->removeChannelSinkAPI(this); - m_deviceAPI->removeChannelSink(m_threadedChannelizer); - delete m_threadedChannelizer; - delete m_channelizer; - delete DSBFilter; - delete SSBFilter; + m_deviceAPI->removeChannelSinkAPI(this); + m_deviceAPI->removeChannelSink(this); + delete m_basebandSink; + delete m_thread; } uint32_t AMDemod::getNumberOfDeviceStreams() const @@ -116,226 +81,31 @@ uint32_t AMDemod::getNumberOfDeviceStreams() const void AMDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst) { (void) firstOfBurst; - Complex ci; - - if (!m_running) { - return; - } - - m_settingsMutex.lock(); - - for (SampleVector::const_iterator it = begin; it != end; ++it) - { - Complex c(it->real(), it->imag()); - c *= m_nco.nextIQ(); - - if (m_interpolatorDistance < 1.0f) // interpolate - { - while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci)) - { - processOneSample(ci); - m_interpolatorDistanceRemain += m_interpolatorDistance; - } - } - else // decimate - { - if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci)) - { - processOneSample(ci); - m_interpolatorDistanceRemain += m_interpolatorDistance; - } - } - } - - if (m_audioBufferFill > 0) - { - uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); - - if (res != m_audioBufferFill) - { - qDebug("AMDemod::feed: %u/%u tail samples written", res, m_audioBufferFill); - } - - m_audioBufferFill = 0; - } - - m_settingsMutex.unlock(); -} - -void AMDemod::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()); - } 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; - } - else - { - demod = m_lowpass.filter(demod); - } - - 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); - - if (res != m_audioBufferFill) - { - qDebug("AMDemod::processOneSample: %u/%u audio samples written", res, m_audioBufferFill); - m_audioFifo.clear(); - } - - m_audioBufferFill = 0; - } + m_basebandSink->feed(begin, end); } void AMDemod::start() { qDebug("AMDemod::start"); - m_squelchCount = 0; - m_audioFifo.clear(); - applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); - m_running = true; + + if (m_basebandSampleRate != 0) { + m_basebandSink->setBasebandSampleRate(m_basebandSampleRate); + } + + m_basebandSink->reset(); + m_thread->start(); } void AMDemod::stop() { qDebug("AMDemod::stop"); - m_running = false; + m_thread->exit(); + m_thread->wait(); } bool AMDemod::handleMessage(const Message& cmd) { - if (DownChannelizer::MsgChannelizerNotification::match(cmd)) - { - DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd; - - qDebug() << "AMDemod::handleMessage: MsgChannelizerNotification:" - << " inputSampleRate: " << notif.getSampleRate() - << " inputFrequencyOffset: " << notif.getFrequencyOffset(); - - applyChannelSettings(notif.getSampleRate(), notif.getFrequencyOffset()); - - return true; - } - else if (MsgConfigureChannelizer::match(cmd)) - { - MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; - - qDebug() << "AMDemod::handleMessage: MsgConfigureChannelizer:" - << " sampleRate: " << cfg.getSampleRate() - << " inputFrequencyOffset: " << cfg.getCenterFrequency(); - - m_channelizer->configure(m_channelizer->getInputMessageQueue(), - cfg.getSampleRate(), - cfg.getCenterFrequency()); - - return true; - } - else if (MsgConfigureAMDemod::match(cmd)) + if (MsgConfigureAMDemod::match(cmd)) { MsgConfigureAMDemod& cfg = (MsgConfigureAMDemod&) cmd; qDebug() << "AMDemod::handleMessage: MsgConfigureAMDemod"; @@ -343,30 +113,16 @@ bool AMDemod::handleMessage(const Message& cmd) return true; } - else if (BasebandSampleSink::MsgThreadedSink::match(cmd)) - { - BasebandSampleSink::MsgThreadedSink& cfg = (BasebandSampleSink::MsgThreadedSink&) cmd; - const QThread *thread = cfg.getThread(); - qDebug("AMDemod::handleMessage: BasebandSampleSink::MsgThreadedSink: %p", thread); - return true; - } else if (DSPSignalNotification::match(cmd)) { - return true; - } - else if (DSPConfigureAudio::match(cmd)) - { - DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd; - uint32_t sampleRate = cfg.getSampleRate(); + DSPSignalNotification& notif = (DSPSignalNotification&) cmd; + m_basebandSampleRate = notif.getSampleRate(); + // Forward to the sink + DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy + qDebug() << "AMDemod::handleMessage: DSPSignalNotification"; + m_basebandSink->getInputMessageQueue()->push(rep); - qDebug() << "AMDemod::handleMessage: DSPConfigureAudio:" - << " sampleRate: " << sampleRate; - - if (sampleRate != m_audioSampleRate) { - applyAudioSampleRate(sampleRate); - } - - return true; + return true; } else { @@ -374,65 +130,6 @@ bool AMDemod::handleMessage(const Message& cmd) } } -void AMDemod::applyAudioSampleRate(int sampleRate) -{ - qDebug("AMDemod::applyAudioSampleRate: sampleRate: %d m_inputSampleRate: %d", sampleRate, m_inputSampleRate); - - MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( - sampleRate, m_settings.m_inputFrequencyOffset); - m_inputMessageQueue.push(channelConfigMsg); - - m_settingsMutex.lock(); - - m_interpolator.create(16, m_inputSampleRate, m_settings.m_rfBandwidth / 2.2f); - m_interpolatorDistanceRemain = 0; - m_interpolatorDistance = (Real) m_inputSampleRate / (Real) sampleRate; - m_bandpass.create(301, sampleRate, 300.0, m_settings.m_rfBandwidth / 2.0f); - m_lowpass.create(301, sampleRate, m_settings.m_rfBandwidth / 2.0f); - m_audioFifo.setSize(sampleRate); - m_squelchDelayLine.resize(sampleRate/5); - DSBFilter->create_dsb_filter((2.0f * m_settings.m_rfBandwidth) / (float) sampleRate); - m_pllFilt.create(101, sampleRate, 200.0); - - if (m_settings.m_pll) { - m_volumeAGC.resizeNew(sampleRate, 0.003); - } else { - m_volumeAGC.resizeNew(sampleRate/10, 0.003); - } - - m_syncAMAGC.resize(sampleRate/4, sampleRate/8, 0.1); - m_pll.setSampleRate(sampleRate); - - m_settingsMutex.unlock(); - m_audioSampleRate = sampleRate; -} - -void AMDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force) -{ - qDebug() << "AMDemod::applyChannelSettings:" - << " inputSampleRate: " << inputSampleRate - << " inputFrequencyOffset: " << inputFrequencyOffset - << " m_audioSampleRate: " << m_audioSampleRate; - - if ((m_inputFrequencyOffset != inputFrequencyOffset) || - (m_inputSampleRate != inputSampleRate) || force) - { - m_nco.setFreq(-inputFrequencyOffset, inputSampleRate); - } - - if ((m_inputSampleRate != inputSampleRate) || force) - { - m_settingsMutex.lock(); - m_interpolator.create(16, inputSampleRate, m_settings.m_rfBandwidth / 2.2f); - m_interpolatorDistanceRemain = 0; - m_interpolatorDistance = (Real) inputSampleRate / (Real) m_audioSampleRate; - m_settingsMutex.unlock(); - } - - m_inputSampleRate = inputSampleRate; - m_inputFrequencyOffset = inputFrequencyOffset; -} - void AMDemod::applySettings(const AMDemodSettings& settings, bool force) { qDebug() << "AMDemod::applySettings:" @@ -455,66 +152,27 @@ void AMDemod::applySettings(const AMDemodSettings& settings, bool force) QList reverseAPIKeys; - if((m_settings.m_rfBandwidth != settings.m_rfBandwidth) || - (m_settings.m_bandpassEnable != settings.m_bandpassEnable) || force) - { - m_settingsMutex.lock(); - m_interpolator.create(16, m_inputSampleRate, settings.m_rfBandwidth / 2.2f); - m_interpolatorDistanceRemain = 0; - m_interpolatorDistance = (Real) m_inputSampleRate / (Real) m_audioSampleRate; - m_bandpass.create(301, m_audioSampleRate, 300.0, settings.m_rfBandwidth / 2.0f); - m_lowpass.create(301, m_audioSampleRate, settings.m_rfBandwidth / 2.0f); - DSBFilter->create_dsb_filter((2.0f * settings.m_rfBandwidth) / (float) m_audioSampleRate); - m_settingsMutex.unlock(); - - if ((m_settings.m_rfBandwidth != settings.m_rfBandwidth) || force) { - reverseAPIKeys.append("rfBandwidth"); - } - if ((m_settings.m_bandpassEnable != settings.m_bandpassEnable) || force) { - reverseAPIKeys.append("bandpassEnable"); - } + if ((m_settings.m_rfBandwidth != settings.m_rfBandwidth) || force) { + reverseAPIKeys.append("rfBandwidth"); } - - if ((m_settings.m_squelch != settings.m_squelch) || force) - { - m_squelchLevel = CalcDb::powerFromdB(settings.m_squelch); + if ((m_settings.m_bandpassEnable != settings.m_bandpassEnable) || force) { + reverseAPIKeys.append("bandpassEnable"); + } + if ((m_settings.m_squelch != settings.m_squelch) || force) { reverseAPIKeys.append("squelch"); } - - if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) - { - AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); - int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName); - //qDebug("AMDemod::applySettings: audioDeviceName: %s audioDeviceIndex: %d", qPrintable(settings.m_audioDeviceName), audioDeviceIndex); - audioDeviceManager->addAudioSink(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex); - uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex); - - if (m_audioSampleRate != audioSampleRate) { - applyAudioSampleRate(audioSampleRate); - } - + if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) { reverseAPIKeys.append("audioDeviceName"); } if ((m_settings.m_pll != settings.m_pll) || force) { - if (settings.m_pll) - { - m_volumeAGC.resizeNew(m_audioSampleRate/4, 0.003); - m_syncAMBuffIndex = 0; - } - else - { - m_volumeAGC.resizeNew(m_audioSampleRate/10, 0.003); - } - reverseAPIKeys.append("pll"); reverseAPIKeys.append("syncAMOperation"); } if ((m_settings.m_syncAMOperation != settings.m_syncAMOperation) || force) { - m_syncAMBuffIndex = 0; reverseAPIKeys.append("pll"); reverseAPIKeys.append("syncAMOperation"); } @@ -536,16 +194,17 @@ void AMDemod::applySettings(const AMDemodSettings& settings, bool force) if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only { m_deviceAPI->removeChannelSinkAPI(this, m_settings.m_streamIndex); - m_deviceAPI->removeChannelSink(m_threadedChannelizer, m_settings.m_streamIndex); - m_deviceAPI->addChannelSink(m_threadedChannelizer, settings.m_streamIndex); + m_deviceAPI->removeChannelSink(this, m_settings.m_streamIndex); + m_deviceAPI->addChannelSink(this, settings.m_streamIndex); m_deviceAPI->addChannelSinkAPI(this, settings.m_streamIndex); - // apply stream sample rate to itself - applyChannelSettings(m_deviceAPI->getSampleMIMO()->getSourceSampleRate(settings.m_streamIndex), m_inputFrequencyOffset); } reverseAPIKeys.append("streamIndex"); } + AMDemodBaseband::MsgConfigureAMDemodBaseband *msg = AMDemodBaseband::MsgConfigureAMDemodBaseband::create(settings, force); + m_basebandSink->getInputMessageQueue()->push(msg); + if (settings.m_useReverseAPI) { bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || @@ -602,13 +261,6 @@ int AMDemod::webapiSettingsPutPatch( AMDemodSettings settings = m_settings; webapiUpdateChannelSettings(settings, channelSettingsKeys, response); - if (settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) - { - MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( - m_audioSampleRate, settings.m_inputFrequencyOffset); - m_inputMessageQueue.push(channelConfigMsg); - } - MsgConfigureAMDemod *msg = MsgConfigureAMDemod::create(settings, force); m_inputMessageQueue.push(msg); @@ -744,9 +396,9 @@ void AMDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples); response.getAmDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg)); - response.getAmDemodReport()->setSquelch(m_squelchOpen ? 1 : 0); - response.getAmDemodReport()->setAudioSampleRate(m_audioSampleRate); - response.getAmDemodReport()->setChannelSampleRate(m_inputSampleRate); + response.getAmDemodReport()->setSquelch(m_basebandSink->getSquelchOpen() ? 1 : 0); + response.getAmDemodReport()->setAudioSampleRate(m_basebandSink->getAudioSampleRate()); + response.getAmDemodReport()->setChannelSampleRate(m_basebandSink->getChannelSampleRate()); } void AMDemod::webapiReverseSendSettings(QList& channelSettingsKeys, const AMDemodSettings& settings, bool force) diff --git a/plugins/channelrx/demodam/amdemod.h b/plugins/channelrx/demodam/amdemod.h index ae0c748f5..dbf029bc5 100644 --- a/plugins/channelrx/demodam/amdemod.h +++ b/plugins/channelrx/demodam/amdemod.h @@ -21,29 +21,18 @@ #include #include -#include #include "dsp/basebandsamplesink.h" #include "channel/channelapi.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 "audio/audiofifo.h" #include "util/message.h" -#include "util/doublebufferfifo.h" +#include "amdemodbaseband.h" #include "amdemodsettings.h" class QNetworkAccessManager; class QNetworkReply; +class QThread; class DeviceAPI; -class DownChannelizer; -class ThreadedBasebandSampleSink; -class fftfilt; class AMDemod : public BasebandSampleSink, public ChannelAPI { Q_OBJECT @@ -71,29 +60,6 @@ public: { } }; - 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(DeviceAPI *deviceAPI); ~AMDemod(); virtual void destroy() { delete this; } @@ -143,28 +109,14 @@ public: const QStringList& channelSettingsKeys, SWGSDRangel::SWGChannelSettings& response); - 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(); } - Real getPllFrequency() const { return m_pll.getFreq(); } + uint32_t getAudioSampleRate() const { return m_basebandSink->getAudioSampleRate(); } + double getMagSq() const { return m_basebandSink->getMagSq(); } + bool getSquelchOpen() const { return m_basebandSink->getSquelchOpen(); } + bool getPllLocked() const { return m_settings.m_pll && m_basebandSink->getPllLocked(); } + Real getPllFrequency() const { return m_basebandSink->getPllFrequency(); } - void getMagSqLevels(double& avg, double& peak, int& nbSamples) - { - if (m_magsqCount > 0) - { - m_magsq = m_magsqSum / m_magsqCount; - m_magSqLevelStore.m_magsq = m_magsq; - m_magSqLevelStore.m_magsqPeak = m_magsqPeak; - } - - avg = m_magSqLevelStore.m_magsq; - peak = m_magSqLevelStore.m_magsqPeak; - nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; - - m_magsqSum = 0.0f; - m_magsqPeak = 0.0f; - m_magsqCount = 0; + void getMagSqLevels(double& avg, double& peak, int& nbSamples) { + m_basebandSink->getMagSqLevels(avg, peak, nbSamples); } uint32_t getNumberOfDeviceStreams() const; @@ -173,77 +125,21 @@ public: static const QString m_channelId; private: - struct MagSqLevelsStore - { - MagSqLevelsStore() : - m_magsq(1e-12), - m_magsqPeak(1e-12) - {} - double m_magsq; - double m_magsqPeak; - }; - - enum RateState { - RSInitialFill, - RSRunning - }; - DeviceAPI *m_deviceAPI; - ThreadedBasebandSampleSink* m_threadedChannelizer; - DownChannelizer* m_channelizer; - - int m_inputSampleRate; - int m_inputFrequencyOffset; + QThread *m_thread; + AMDemodBaseband* m_basebandSink; 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; - MagSqLevelsStore m_magSqLevelStore; - - MovingAverageUtil m_movingAverage; - SimpleAGC<4800> m_volumeAGC; - Bandpass m_bandpass; - Lowpass m_lowpass; - 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; + int m_basebandSampleRate; //!< stored from device message used when starting baseband sink static const int m_udpBlockSize; QNetworkAccessManager *m_networkManager; QNetworkRequest m_networkRequest; - 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 webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); void webapiReverseSendSettings(QList& channelSettingsKeys, const AMDemodSettings& settings, bool force); - void processOneSample(Complex &ci); - private slots: void networkManagerFinished(QNetworkReply *reply); diff --git a/plugins/channelrx/demodam/amdemodbaseband.cpp b/plugins/channelrx/demodam/amdemodbaseband.cpp new file mode 100644 index 000000000..e02a30b22 --- /dev/null +++ b/plugins/channelrx/demodam/amdemodbaseband.cpp @@ -0,0 +1,172 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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 // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "dsp/downsamplechannelizer.h" + +#include "amdemodbaseband.h" + +MESSAGE_CLASS_DEFINITION(AMDemodBaseband::MsgConfigureAMDemodBaseband, Message) + +AMDemodBaseband::AMDemodBaseband() : + m_mutex(QMutex::Recursive) +{ + qDebug("AMDemodBaseband::AMDemodBaseband"); + + m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000)); + m_channelizer = new DownSampleChannelizer(&m_sink); + + QObject::connect( + &m_sampleFifo, + &SampleSinkFifo::dataReady, + this, + &AMDemodBaseband::handleData, + Qt::QueuedConnection + ); + + DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(m_sink.getAudioFifo(), getInputMessageQueue()); + m_sink.applyAudioSampleRate(DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate()); + + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); +} + +AMDemodBaseband::~AMDemodBaseband() +{ + DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(m_sink.getAudioFifo()); + delete m_channelizer; +} + +void AMDemodBaseband::reset() +{ + QMutexLocker mutexLocker(&m_mutex); + m_sampleFifo.reset(); +} + +void AMDemodBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) +{ + QMutexLocker mutexLocker(&m_mutex); + m_sampleFifo.write(begin, end); +} + +void AMDemodBaseband::handleData() +{ + while ((m_sampleFifo.fill() > 0) && (m_inputMessageQueue.size() == 0)) + { + SampleVector::iterator part1begin; + SampleVector::iterator part1end; + SampleVector::iterator part2begin; + SampleVector::iterator part2end; + + std::size_t count = m_sampleFifo.readBegin(m_sampleFifo.fill(), &part1begin, &part1end, &part2begin, &part2end); + + // first part of FIFO data + if (part1begin != part1end) { + m_channelizer->feed(part1begin, part1end); + } + + // second part of FIFO data (used when block wraps around) + if(part2begin != part2end) { + m_channelizer->feed(part2begin, part2end); + } + + m_sampleFifo.readCommit((unsigned int) count); + } +} + +void AMDemodBaseband::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != nullptr) + { + if (handleMessage(*message)) { + delete message; + } + } +} + +bool AMDemodBaseband::handleMessage(const Message& cmd) +{ + if (MsgConfigureAMDemodBaseband::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + MsgConfigureAMDemodBaseband& cfg = (MsgConfigureAMDemodBaseband&) cmd; + qDebug() << "AMDemodBaseband::handleMessage: MsgConfigureAMDemodBaseband"; + + applySettings(cfg.getSettings(), cfg.getForce()); + + return true; + } + else if (DSPSignalNotification::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + DSPSignalNotification& notif = (DSPSignalNotification&) cmd; + qDebug() << "AMDemodBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate(); + m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate())); + m_channelizer->setBasebandSampleRate(notif.getSampleRate()); + m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); + + return true; + } + else + { + return false; + } +} + +void AMDemodBaseband::applySettings(const AMDemodSettings& settings, bool force) +{ + if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) + { + m_channelizer->setChannelization(m_sink.getAudioSampleRate(), settings.m_inputFrequencyOffset); + m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); + } + + if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) + { + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName); + //qDebug("AMDemod::applySettings: audioDeviceName: %s audioDeviceIndex: %d", qPrintable(settings.m_audioDeviceName), audioDeviceIndex); + audioDeviceManager->addAudioSink(m_sink.getAudioFifo(), getInputMessageQueue(), audioDeviceIndex); + uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex); + + if (m_sink.getAudioSampleRate() != audioSampleRate) + { + m_sink.applyAudioSampleRate(audioSampleRate); + m_channelizer->setChannelization(audioSampleRate, settings.m_inputFrequencyOffset); + m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); + } + } + + m_sink.applySettings(settings, force); + + m_settings = settings; +} + +int AMDemodBaseband::getChannelSampleRate() const +{ + return m_channelizer->getChannelSampleRate(); +} + + +void AMDemodBaseband::setBasebandSampleRate(int sampleRate) +{ + m_channelizer->setBasebandSampleRate(sampleRate); +} diff --git a/plugins/channelrx/demodam/amdemodbaseband.h b/plugins/channelrx/demodam/amdemodbaseband.h new file mode 100644 index 000000000..ae8ddc8e9 --- /dev/null +++ b/plugins/channelrx/demodam/amdemodbaseband.h @@ -0,0 +1,89 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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 // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License 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_AMDEMODBASEBAND_H +#define INCLUDE_AMDEMODBASEBAND_H + +#include +#include + +#include "dsp/samplesinkfifo.h" +#include "util/message.h" +#include "util/messagequeue.h" + +#include "amdemodsink.h" + +class DownSampleChannelizer; + +class AMDemodBaseband : public QObject +{ + Q_OBJECT +public: + class MsgConfigureAMDemodBaseband : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const AMDemodSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureAMDemodBaseband* create(const AMDemodSettings& settings, bool force) + { + return new MsgConfigureAMDemodBaseband(settings, force); + } + + private: + AMDemodSettings m_settings; + bool m_force; + + MsgConfigureAMDemodBaseband(const AMDemodSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + AMDemodBaseband(); + ~AMDemodBaseband(); + void reset(); + void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); + MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication + int getChannelSampleRate() const; + void getMagSqLevels(double& avg, double& peak, int& nbSamples) { m_sink.getMagSqLevels(avg, peak, nbSamples); } + bool getSquelchOpen() const { return m_sink.getSquelchOpen(); } + unsigned int getAudioSampleRate() const { return m_sink.getAudioSampleRate(); } + void setBasebandSampleRate(int sampleRate); + double getMagSq() const { return m_sink.getMagSq(); } + bool getPllLocked() const { return m_sink.getPllLocked(); } + Real getPllFrequency() const { return m_sink.getPllFrequency(); } + +private: + SampleSinkFifo m_sampleFifo; + DownSampleChannelizer *m_channelizer; + AMDemodSink m_sink; + MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication + AMDemodSettings m_settings; + QMutex m_mutex; + + bool handleMessage(const Message& cmd); + void applySettings(const AMDemodSettings& settings, bool force = false); + +private slots: + void handleInputMessages(); + void handleData(); //!< Handle data when samples have to be processed +}; + +#endif // INCLUDE_AMDEMODBASEBAND_H \ No newline at end of file diff --git a/plugins/channelrx/demodam/amdemodgui.cpp b/plugins/channelrx/demodam/amdemodgui.cpp index e93317d08..c9154c093 100644 --- a/plugins/channelrx/demodam/amdemodgui.cpp +++ b/plugins/channelrx/demodam/amdemodgui.cpp @@ -325,11 +325,6 @@ void AMDemodGUI::applySettings(bool force) { if (m_doApplySettings) { - AMDemod::MsgConfigureChannelizer* channelConfigMsg = AMDemod::MsgConfigureChannelizer::create( - m_amDemod->getAudioSampleRate(), m_channelMarker.getCenterFrequency()); - m_amDemod->getInputMessageQueue()->push(channelConfigMsg); - - AMDemod::MsgConfigureAMDemod* message = AMDemod::MsgConfigureAMDemod::create( m_settings, force); m_amDemod->getInputMessageQueue()->push(message); } diff --git a/plugins/channelrx/demodam/amdemodplugin.cpp b/plugins/channelrx/demodam/amdemodplugin.cpp index 567c0361f..82ac29c4e 100644 --- a/plugins/channelrx/demodam/amdemodplugin.cpp +++ b/plugins/channelrx/demodam/amdemodplugin.cpp @@ -10,7 +10,7 @@ const PluginDescriptor AMDemodPlugin::m_pluginDescriptor = { QString("AM Demodulator"), - QString("4.11.6"), + QString("4.12.2"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/plugins/channelrx/demodam/amdemodsink.cpp b/plugins/channelrx/demodam/amdemodsink.cpp new file mode 100644 index 000000000..c0ef9d397 --- /dev/null +++ b/plugins/channelrx/demodam/amdemodsink.cpp @@ -0,0 +1,320 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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 // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include + +#include "audio/audiooutput.h" +#include "dsp/fftfilt.h" +#include "util/db.h" +#include "util/stepfunctions.h" + +#include "amdemodsink.h" + +AMDemodSink::AMDemodSink() : + m_channelSampleRate(48000), + m_channelFrequencyOffset(0), + m_squelchOpen(false), + m_squelchDelayLine(9600), + m_magsqSum(0.0f), + m_magsqPeak(0.0f), + m_magsqCount(0), + m_volumeAGC(0.003), + m_syncAMAGC(12000, 0.1, 1e-2), + m_audioFifo(48000) +{ + m_audioBuffer.resize(1<<14); + m_audioBufferFill = 0; + + m_magsq = 0.0; + + 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); + + m_pllFilt.create(101, m_audioSampleRate, 200.0); + m_pll.computeCoefficients(0.05, 0.707, 1000); + m_syncAMBuffIndex = 0; + + applySettings(m_settings, true); + applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true); +} + +AMDemodSink::~AMDemodSink() +{ + delete DSBFilter; + delete SSBFilter; +} + +void AMDemodSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) +{ + Complex ci; + + for (SampleVector::const_iterator it = begin; it != end; ++it) + { + Complex c(it->real(), it->imag()); + c *= m_nco.nextIQ(); + + if (m_interpolatorDistance < 1.0f) // interpolate + { + while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci)) + { + processOneSample(ci); + m_interpolatorDistanceRemain += m_interpolatorDistance; + } + } + else // decimate + { + if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci)) + { + processOneSample(ci); + m_interpolatorDistanceRemain += m_interpolatorDistance; + } + } + } + + if (m_audioBufferFill > 0) + { + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); + + if (res != m_audioBufferFill) { + qDebug("AMDemodSink::feed: %u/%u tail samples written", res, m_audioBufferFill); + } + + m_audioBufferFill = 0; + } +} + +void AMDemodSink::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()); + } else if (m_settings.m_syncAMOperation == AMDemodSettings::SyncAMUSB) { + m_syncAMBuff[i] = (z.real() + z.imag()); + } else { + m_syncAMBuff[i] = (z.real() + z.imag()); + } + + m_syncAMBuffIndex = 0; + } + + m_syncAMBuffIndex = m_syncAMBuffIndex < 2*1024 ? m_syncAMBuffIndex : 0; + demod = m_syncAMBuff[m_syncAMBuffIndex++]*4.0f; // mos pifometrico + } + 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; + } + else + { + demod = m_lowpass.filter(demod); + } + + 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); + + if (res != m_audioBufferFill) + { + qDebug("AMDemodSink::processOneSample: %u/%u audio samples written", res, m_audioBufferFill); + m_audioFifo.clear(); + } + + m_audioBufferFill = 0; + } +} + +void AMDemodSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force) +{ + qDebug() << "AMDemodSink::applyChannelSettings:" + << " channelSampleRate: " << channelSampleRate + << " channelFrequencyOffset: " << channelFrequencyOffset + << " m_audioSampleRate: " << m_audioSampleRate; + + if ((m_channelFrequencyOffset != channelFrequencyOffset) || + (m_channelSampleRate != channelSampleRate) || force) + { + m_nco.setFreq(-channelFrequencyOffset, channelSampleRate); + } + + if ((m_channelSampleRate != channelSampleRate) || force) + { + m_interpolator.create(16, channelSampleRate, m_settings.m_rfBandwidth / 2.2f); + m_interpolatorDistanceRemain = 0; + m_interpolatorDistance = (Real) channelSampleRate / (Real) m_audioSampleRate; + } + + m_channelSampleRate = channelSampleRate; + m_channelFrequencyOffset = channelFrequencyOffset; +} + +void AMDemodSink::applySettings(const AMDemodSettings& settings, bool force) +{ + qDebug() << "AMDemodSink::applySettings:" + << " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset + << " m_rfBandwidth: " << settings.m_rfBandwidth + << " m_volume: " << settings.m_volume + << " m_squelch: " << settings.m_squelch + << " m_audioMute: " << settings.m_audioMute + << " m_bandpassEnable: " << settings.m_bandpassEnable + << " m_audioDeviceName: " << settings.m_audioDeviceName + << " m_pll: " << settings.m_pll + << " m_syncAMOperation: " << (int) settings.m_syncAMOperation + << " force: " << force; + + if((m_settings.m_rfBandwidth != settings.m_rfBandwidth) || + (m_settings.m_bandpassEnable != settings.m_bandpassEnable) || force) + { + m_interpolator.create(16, m_channelSampleRate, settings.m_rfBandwidth / 2.2f); + m_interpolatorDistanceRemain = 0; + m_interpolatorDistance = (Real) m_channelSampleRate / (Real) m_audioSampleRate; + m_bandpass.create(301, m_audioSampleRate, 300.0, settings.m_rfBandwidth / 2.0f); + m_lowpass.create(301, m_audioSampleRate, settings.m_rfBandwidth / 2.0f); + DSBFilter->create_dsb_filter((2.0f * settings.m_rfBandwidth) / (float) m_audioSampleRate); + } + + if ((m_settings.m_squelch != settings.m_squelch) || force) { + m_squelchLevel = CalcDb::powerFromdB(settings.m_squelch); + } + + if ((m_settings.m_pll != settings.m_pll) || force) + { + if (settings.m_pll) + { + m_volumeAGC.resizeNew(m_audioSampleRate/4, 0.003); + m_syncAMBuffIndex = 0; + } + else + { + m_volumeAGC.resizeNew(m_audioSampleRate/10, 0.003); + } + } + + if ((m_settings.m_syncAMOperation != settings.m_syncAMOperation) || force) { + m_syncAMBuffIndex = 0; + } + + m_settings = settings; +} + +void AMDemodSink::applyAudioSampleRate(int sampleRate) +{ + qDebug("AMDemodSink::applyAudioSampleRate: sampleRate: %d m_channelSampleRate: %d", sampleRate, m_channelSampleRate); + + m_interpolator.create(16, m_channelSampleRate, m_settings.m_rfBandwidth / 2.2f); + m_interpolatorDistanceRemain = 0; + m_interpolatorDistance = (Real) m_channelSampleRate / (Real) sampleRate; + m_bandpass.create(301, sampleRate, 300.0, m_settings.m_rfBandwidth / 2.0f); + m_lowpass.create(301, sampleRate, m_settings.m_rfBandwidth / 2.0f); + m_audioFifo.setSize(sampleRate); + m_squelchDelayLine.resize(sampleRate/5); + DSBFilter->create_dsb_filter((2.0f * m_settings.m_rfBandwidth) / (float) sampleRate); + m_pllFilt.create(101, sampleRate, 200.0); + + if (m_settings.m_pll) { + m_volumeAGC.resizeNew(sampleRate, 0.003); + } else { + m_volumeAGC.resizeNew(sampleRate/10, 0.003); + } + + m_syncAMAGC.resize(sampleRate/4, sampleRate/8, 0.1); + m_pll.setSampleRate(sampleRate); + + m_audioSampleRate = sampleRate; +} diff --git a/plugins/channelrx/demodam/amdemodsink.h b/plugins/channelrx/demodam/amdemodsink.h new file mode 100644 index 000000000..753552dd6 --- /dev/null +++ b/plugins/channelrx/demodam/amdemodsink.h @@ -0,0 +1,127 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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 // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License 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_AMDEMODSINK_H +#define INCLUDE_AMDEMODSINK_H + +#include "dsp/channelsamplesink.h" +#include "dsp/nco.h" +#include "dsp/interpolator.h" +#include "dsp/agc.h" +#include "dsp/bandpass.h" +#include "dsp/lowpass.h" +#include "dsp/phaselockcomplex.h" +#include "audio/audiofifo.h" +#include "util/movingaverage.h" +#include "util/doublebufferfifo.h" + +#include "amdemodsettings.h" + +class fftfilt; + +class AMDemodSink : public ChannelSampleSink { +public: + AMDemodSink(); + ~AMDemodSink(); + + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); + + void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false); + void applySettings(const AMDemodSettings& settings, bool force = false); + void applyAudioSampleRate(int sampleRate); + + 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(); } + Real getPllFrequency() const { return m_pll.getFreq(); } + AudioFifo *getAudioFifo() { return &m_audioFifo; } + + void getMagSqLevels(double& avg, double& peak, int& nbSamples) + { + if (m_magsqCount > 0) + { + m_magsq = m_magsqSum / m_magsqCount; + m_magSqLevelStore.m_magsq = m_magsq; + m_magSqLevelStore.m_magsqPeak = m_magsqPeak; + } + + avg = m_magSqLevelStore.m_magsq; + peak = m_magSqLevelStore.m_magsqPeak; + nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; + + m_magsqSum = 0.0f; + m_magsqPeak = 0.0f; + m_magsqCount = 0; + } + +private: + struct MagSqLevelsStore + { + MagSqLevelsStore() : + m_magsq(1e-12), + m_magsqPeak(1e-12) + {} + double m_magsq; + double m_magsqPeak; + }; + + enum RateState { + RSInitialFill, + RSRunning + }; + + int m_channelSampleRate; + int m_channelFrequencyOffset; + AMDemodSettings m_settings; + uint32_t m_audioSampleRate; + + 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; + MagSqLevelsStore m_magSqLevelStore; + + MovingAverageUtil m_movingAverage; + SimpleAGC<4800> m_volumeAGC; + Bandpass m_bandpass; + Lowpass m_lowpass; + 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; + AudioFifo m_audioFifo; + uint32_t m_audioBufferFill; + + void processOneSample(Complex &ci); +}; + +#endif // INCLUDE_AMDEMODSINK_H \ No newline at end of file diff --git a/plugins/channelrx/demoddsd/CMakeLists.txt b/plugins/channelrx/demoddsd/CMakeLists.txt index 6e9bfd903..89ef936b1 100644 --- a/plugins/channelrx/demoddsd/CMakeLists.txt +++ b/plugins/channelrx/demoddsd/CMakeLists.txt @@ -5,6 +5,8 @@ set(dsddemod_SOURCES dsddemodplugin.cpp dsddemodbaudrates.cpp dsddemodsettings.cpp + dsddemodsink.cpp + dsddemodbaseband.cpp dsddemodwebapiadapter.cpp dsddecoder.cpp ) @@ -14,6 +16,8 @@ set(dsddemod_HEADERS dsddemodplugin.h dsddemodbaudrates.h dsddemodsettings.h + dsddemodsink.h + dsddemodbaseband.h dsddemodwebapiadapter.h dsddecoder.h ) @@ -29,7 +33,6 @@ if(NOT SERVER_MODE) ${dsddemod_SOURCES} dsddemodgui.cpp dsdstatustextdialog.cpp - dsddemodgui.ui dsdstatustextdialog.ui ) @@ -59,12 +62,12 @@ if(ENABLE_EXTERNAL_LIBRARIES) endif() target_link_libraries(${TARGET_NAME} - Qt5::Core - ${TARGET_LIB} + Qt5::Core + ${TARGET_LIB} sdrbase ${TARGET_LIB_GUI} - ${LIBDSDCC_LIBRARIES} - ${LIBMBE_LIBRARY} + ${LIBDSDCC_LIBRARIES} + ${LIBMBE_LIBRARY} ) install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER}) diff --git a/plugins/channelrx/demoddsd/dsddemod.cpp b/plugins/channelrx/demoddsd/dsddemod.cpp index ed1c62946..41ec8daf4 100644 --- a/plugins/channelrx/demoddsd/dsddemod.cpp +++ b/plugins/channelrx/demoddsd/dsddemod.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "SWGChannelSettings.h" #include "SWGDSDDemodSettings.h" @@ -33,20 +34,14 @@ #include "SWGDSDDemodReport.h" #include "SWGRDSReport.h" -#include "audio/audiooutput.h" #include "dsp/dspengine.h" -#include "dsp/threadedbasebandsamplesink.h" -#include "dsp/downchannelizer.h" #include "dsp/dspcommands.h" -#include "dsp/devicesamplemimo.h" #include "device/deviceapi.h" #include "util/db.h" #include "dsddemod.h" -MESSAGE_CLASS_DEFINITION(DSDDemod::MsgConfigureChannelizer, Message) MESSAGE_CLASS_DEFINITION(DSDDemod::MsgConfigureDSDDemod, Message) -MESSAGE_CLASS_DEFINITION(DSDDemod::MsgConfigureMyPosition, Message) const QString DSDDemod::m_channelIdURI = "sdrangel.channel.dsddemod"; const QString DSDDemod::m_channelId = "DSDDemod"; @@ -55,50 +50,17 @@ const int DSDDemod::m_udpBlockSize = 512; DSDDemod::DSDDemod(DeviceAPI *deviceAPI) : ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink), m_deviceAPI(deviceAPI), - m_inputSampleRate(48000), - m_inputFrequencyOffset(0), - m_interpolatorDistance(0.0f), - m_interpolatorDistanceRemain(0.0f), - m_sampleCount(0), - m_squelchCount(0), - m_squelchGate(0), - m_squelchLevel(1e-4), - m_squelchOpen(false), - m_squelchDelayLine(24000), - m_audioFifo1(48000), - m_audioFifo2(48000), - m_scopeXY(0), - m_scopeEnabled(true), - m_dsdDecoder(), - m_signalFormat(signalFormatNone), - m_settingsMutex(QMutex::Recursive) + m_basebandSampleRate(0) { + qDebug("DSDDemod::DSDDemod"); setObjectName(m_channelId); - m_audioBuffer.resize(1<<14); - m_audioBufferFill = 0; + m_thread = new QThread(this); + m_basebandSink = new DSDDemodBaseband(); + m_basebandSink->moveToThread(m_thread); - m_sampleBuffer = new FixReal[1<<17]; // 128 kS - m_sampleBufferIndex = 0; - m_scaleFromShort = SDR_RX_SAMP_SZ < sizeof(short)*8 ? 1 : 1<<(SDR_RX_SAMP_SZ - sizeof(short)*8); - - m_magsq = 0.0f; - m_magsqSum = 0.0f; - m_magsqPeak = 0.0f; - m_magsqCount = 0; - - DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo1, getInputMessageQueue()); - DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo2, getInputMessageQueue()); - m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate(); - - applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); applySettings(m_settings, true); - m_channelizer = new DownChannelizer(this); - m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); - m_deviceAPI->addChannelSink(m_threadedChannelizer); - m_deviceAPI->addChannelSinkAPI(this); - m_networkManager = new QNetworkAccessManager(); connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); } @@ -107,20 +69,10 @@ DSDDemod::~DSDDemod() { disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); delete m_networkManager; - delete[] m_sampleBuffer; - DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo1); - DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo2); - - m_deviceAPI->removeChannelSinkAPI(this); - m_deviceAPI->removeChannelSink(m_threadedChannelizer); - delete m_threadedChannelizer; - delete m_channelizer; -} - -void DSDDemod::configureMyPosition(MessageQueue* messageQueue, float myLatitude, float myLongitude) -{ - Message* cmd = MsgConfigureMyPosition::create(myLatitude, myLongitude); - messageQueue->push(cmd); + m_deviceAPI->removeChannelSinkAPI(this); + m_deviceAPI->removeChannelSink(this); + delete m_basebandSink; + delete m_thread; } uint32_t DSDDemod::getNumberOfDeviceStreams() const @@ -131,269 +83,33 @@ uint32_t DSDDemod::getNumberOfDeviceStreams() const void DSDDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst) { (void) firstOfBurst; - Complex ci; - int samplesPerSymbol = m_dsdDecoder.getSamplesPerSymbol(); - - m_settingsMutex.lock(); - m_scopeSampleBuffer.clear(); - - m_dsdDecoder.enableMbelib(!DSPEngine::instance()->hasDVSerialSupport()); // disable mbelib if DV serial support is present and activated else enable it - - for (SampleVector::const_iterator it = begin; it != end; ++it) - { - Complex c(it->real(), it->imag()); - c *= m_nco.nextIQ(); - - if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci)) - { - FixReal sample, delayedSample; - qint16 sampleDSD; - - Real re = ci.real() / SDR_RX_SCALED; - Real im = ci.imag() / SDR_RX_SCALED; - Real magsq = re*re + im*im; - m_movingAverage(magsq); - - m_magsqSum += magsq; - - if (magsq > m_magsqPeak) - { - m_magsqPeak = magsq; - } - - m_magsqCount++; - - Real demod = m_phaseDiscri.phaseDiscriminator(ci) * m_settings.m_demodGain; // [-1.0:1.0] - m_sampleCount++; - - // AF processing - - if (m_movingAverage.asDouble() > m_squelchLevel) - { - if (m_squelchGate > 0) - { - - if (m_squelchCount < m_squelchGate*2) { - m_squelchCount++; - } - - m_squelchDelayLine.write(demod); - m_squelchOpen = m_squelchCount > m_squelchGate; - } - else - { - m_squelchOpen = true; - } - } - else - { - if (m_squelchGate > 0) - { - if (m_squelchCount > 0) { - m_squelchCount--; - } - - m_squelchDelayLine.write(0); - m_squelchOpen = m_squelchCount > m_squelchGate; - } - else - { - m_squelchOpen = false; - } - } - - if (m_squelchOpen) - { - if (m_squelchGate > 0) - { - sampleDSD = m_squelchDelayLine.readBack(m_squelchGate) * 32768.0f; // DSD decoder takes int16 samples - sample = m_squelchDelayLine.readBack(m_squelchGate) * SDR_RX_SCALEF; // scale to sample size - } - else - { - sampleDSD = demod * 32768.0f; // DSD decoder takes int16 samples - sample = demod * SDR_RX_SCALEF; // scale to sample size - } - } - else - { - sampleDSD = 0; - sample = 0; - } - - m_dsdDecoder.pushSample(sampleDSD); - - if (m_settings.m_enableCosineFiltering) { // show actual input to FSK demod - sample = m_dsdDecoder.getFilteredSample() * m_scaleFromShort; - } - - if (m_sampleBufferIndex < (1<<17)-1) { - m_sampleBufferIndex++; - } else { - m_sampleBufferIndex = 0; - } - - m_sampleBuffer[m_sampleBufferIndex] = sample; - - if (m_sampleBufferIndex < samplesPerSymbol) { - delayedSample = m_sampleBuffer[(1<<17) - samplesPerSymbol + m_sampleBufferIndex]; // wrap - } else { - delayedSample = m_sampleBuffer[m_sampleBufferIndex - samplesPerSymbol]; - } - - if (m_settings.m_syncOrConstellation) - { - Sample s(sample, m_dsdDecoder.getSymbolSyncSample() * m_scaleFromShort * 0.84); - m_scopeSampleBuffer.push_back(s); - } - else - { - Sample s(sample, delayedSample); // I=signal, Q=signal delayed by 20 samples (2400 baud: lowest rate) - m_scopeSampleBuffer.push_back(s); - } - - if (DSPEngine::instance()->hasDVSerialSupport()) - { - if ((m_settings.m_slot1On) && m_dsdDecoder.mbeDVReady1()) - { - if (!m_settings.m_audioMute) - { - DSPEngine::instance()->pushMbeFrame( - m_dsdDecoder.getMbeDVFrame1(), - m_dsdDecoder.getMbeRateIndex(), - m_settings.m_volume * 10.0, - m_settings.m_tdmaStereo ? 1 : 3, // left or both channels - m_settings.m_highPassFilter, - m_audioSampleRate/8000, // upsample from native 8k - &m_audioFifo1); - } - - m_dsdDecoder.resetMbeDV1(); - } - - if ((m_settings.m_slot2On) && m_dsdDecoder.mbeDVReady2()) - { - if (!m_settings.m_audioMute) - { - DSPEngine::instance()->pushMbeFrame( - m_dsdDecoder.getMbeDVFrame2(), - m_dsdDecoder.getMbeRateIndex(), - m_settings.m_volume * 10.0, - m_settings.m_tdmaStereo ? 2 : 3, // right or both channels - m_settings.m_highPassFilter, - m_audioSampleRate/8000, // upsample from native 8k - &m_audioFifo2); - } - - m_dsdDecoder.resetMbeDV2(); - } - } - -// if (DSPEngine::instance()->hasDVSerialSupport() && m_dsdDecoder.mbeDVReady1()) -// { -// if (!m_settings.m_audioMute) -// { -// DSPEngine::instance()->pushMbeFrame(m_dsdDecoder.getMbeDVFrame1(), m_dsdDecoder.getMbeRateIndex(), m_settings.m_volume, &m_audioFifo1); -// } -// -// m_dsdDecoder.resetMbeDV1(); -// } - - m_interpolatorDistanceRemain += m_interpolatorDistance; - } - } - - if (!DSPEngine::instance()->hasDVSerialSupport()) - { - if (m_settings.m_slot1On) - { - int nbAudioSamples; - short *dsdAudio = m_dsdDecoder.getAudio1(nbAudioSamples); - - if (nbAudioSamples > 0) - { - if (!m_settings.m_audioMute) { - m_audioFifo1.write((const quint8*) dsdAudio, nbAudioSamples); - } - - m_dsdDecoder.resetAudio1(); - } - } - - if (m_settings.m_slot2On) - { - int nbAudioSamples; - short *dsdAudio = m_dsdDecoder.getAudio2(nbAudioSamples); - - if (nbAudioSamples > 0) - { - if (!m_settings.m_audioMute) { - m_audioFifo2.write((const quint8*) dsdAudio, nbAudioSamples); - } - - m_dsdDecoder.resetAudio2(); - } - } - -// int nbAudioSamples; -// short *dsdAudio = m_dsdDecoder.getAudio1(nbAudioSamples); -// -// if (nbAudioSamples > 0) -// { -// if (!m_settings.m_audioMute) { -// uint res = m_audioFifo1.write((const quint8*) dsdAudio, nbAudioSamples, 10); -// } -// -// m_dsdDecoder.resetAudio1(); -// } - } - - if ((m_scopeXY != 0) && (m_scopeEnabled)) - { - m_scopeXY->feed(m_scopeSampleBuffer.begin(), m_scopeSampleBuffer.end(), true); // true = real samples for what it's worth - } - - m_settingsMutex.unlock(); + m_basebandSink->feed(begin, end); } void DSDDemod::start() { - m_audioFifo1.clear(); - m_audioFifo2.clear(); - m_phaseDiscri.reset(); - applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); + qDebug() << "DSDDemod::start"; + + if (m_basebandSampleRate != 0) { + m_basebandSink->setBasebandSampleRate(m_basebandSampleRate); + } + + m_basebandSink->reset(); + m_thread->start(); } void DSDDemod::stop() { + qDebug() << "DSDDemod::stop"; + m_thread->exit(); + m_thread->wait(); } bool DSDDemod::handleMessage(const Message& cmd) { qDebug() << "DSDDemod::handleMessage"; - if (DownChannelizer::MsgChannelizerNotification::match(cmd)) - { - DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd; - qDebug() << "DSDDemod::handleMessage: MsgChannelizerNotification: inputSampleRate: " << notif.getSampleRate() - << " inputFrequencyOffset: " << notif.getFrequencyOffset(); - - applyChannelSettings(notif.getSampleRate(), notif.getFrequencyOffset()); - - return true; - } - else if (MsgConfigureChannelizer::match(cmd)) - { - MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; - qDebug("DSDDemod::handleMessage: MsgConfigureChannelizer"); - - m_channelizer->configure(m_channelizer->getInputMessageQueue(), - cfg.getSampleRate(), - cfg.getCenterFrequency()); - - return true; - } - else if (MsgConfigureDSDDemod::match(cmd)) + if (MsgConfigureDSDDemod::match(cmd)) { MsgConfigureDSDDemod& cfg = (MsgConfigureDSDDemod&) cmd; qDebug("DSDDemod::handleMessage: MsgConfigureDSDDemod: m_rfBandwidth"); @@ -402,33 +118,16 @@ bool DSDDemod::handleMessage(const Message& cmd) return true; } - else if (MsgConfigureMyPosition::match(cmd)) - { - MsgConfigureMyPosition& cfg = (MsgConfigureMyPosition&) cmd; - m_dsdDecoder.setMyPoint(cfg.getMyLatitude(), cfg.getMyLongitude()); - return true; - } - else if (DSPConfigureAudio::match(cmd)) - { - DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd; - uint32_t sampleRate = cfg.getSampleRate(); - - qDebug() << "DSDDemod::handleMessage: DSPConfigureAudio:" - << " sampleRate: " << sampleRate; - - if (sampleRate != m_audioSampleRate) { - applyAudioSampleRate(sampleRate); - } - - return true; - } - else if (BasebandSampleSink::MsgThreadedSink::match(cmd)) - { - return true; - } else if (DSPSignalNotification::match(cmd)) { - return true; + DSPSignalNotification& notif = (DSPSignalNotification&) cmd; + m_basebandSampleRate = notif.getSampleRate(); + // Forward to the sink + DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy + qDebug() << "DSDDemod::handleMessage: DSPSignalNotification"; + m_basebandSink->getInputMessageQueue()->push(rep); + + return true; } else { @@ -436,45 +135,6 @@ bool DSDDemod::handleMessage(const Message& cmd) } } -void DSDDemod::applyAudioSampleRate(int sampleRate) -{ - int upsampling = sampleRate / 8000; - - qDebug("DSDDemod::applyAudioSampleRate: audio rate: %d upsample by %d", sampleRate, upsampling); - - if (sampleRate % 8000 != 0) { - qDebug("DSDDemod::applyAudioSampleRate: audio will sound best with sample rates that are integer multiples of 8 kS/s"); - } - - m_dsdDecoder.setUpsampling(upsampling); - m_audioSampleRate = sampleRate; -} - -void DSDDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force) -{ - qDebug() << "DSDDemod::applyChannelSettings:" - << " inputSampleRate: " << inputSampleRate - << " inputFrequencyOffset: " << inputFrequencyOffset; - - if ((inputFrequencyOffset != m_inputFrequencyOffset) || - (inputSampleRate != m_inputSampleRate) || force) - { - m_nco.setFreq(-inputFrequencyOffset, inputSampleRate); - } - - if ((inputSampleRate != m_inputSampleRate) || force) - { - m_settingsMutex.lock(); - m_interpolator.create(16, inputSampleRate, (m_settings.m_rfBandwidth) / 2.2); - m_interpolatorDistanceRemain = 0; - m_interpolatorDistance = (Real) inputSampleRate / (Real) 48000; - m_settingsMutex.unlock(); - } - - m_inputSampleRate = inputSampleRate; - m_inputFrequencyOffset = inputFrequencyOffset; -} - void DSDDemod::applySettings(const DSDDemodSettings& settings, bool force) { qDebug() << "DSDDemod::applySettings: " @@ -527,87 +187,38 @@ void DSDDemod::applySettings(const DSDDemodSettings& settings, bool force) if ((settings.m_traceLengthMutliplier != m_settings.m_traceLengthMutliplier) || force) { reverseAPIKeys.append("traceLengthMutliplier"); } - - if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) - { + if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) { reverseAPIKeys.append("rfBandwidth"); - m_settingsMutex.lock(); - m_interpolator.create(16, m_inputSampleRate, (settings.m_rfBandwidth) / 2.2); - m_interpolatorDistanceRemain = 0; - m_interpolatorDistance = (Real) m_inputSampleRate / (Real) 48000; - //m_phaseDiscri.setFMScaling((float) settings.m_rfBandwidth / (float) settings.m_fmDeviation); - m_settingsMutex.unlock(); } - - if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) - { + if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) { reverseAPIKeys.append("fmDeviation"); - m_phaseDiscri.setFMScaling(48000.0f / (2.0f*settings.m_fmDeviation)); } - - if ((settings.m_squelchGate != m_settings.m_squelchGate) || force) - { + if ((settings.m_squelchGate != m_settings.m_squelchGate) || force) { reverseAPIKeys.append("squelchGate"); - m_squelchGate = 480 * settings.m_squelchGate; // gate is given in 10s of ms at 48000 Hz audio sample rate - m_squelchCount = 0; // reset squelch open counter } - - if ((settings.m_squelch != m_settings.m_squelch) || force) - { + if ((settings.m_squelch != m_settings.m_squelch) || force) { reverseAPIKeys.append("squelch"); - // input is a value in dB - m_squelchLevel = std::pow(10.0, settings.m_squelch / 10.0); } - - if ((settings.m_volume != m_settings.m_volume) || force) - { + if ((settings.m_volume != m_settings.m_volume) || force) { reverseAPIKeys.append("volume"); - m_dsdDecoder.setAudioGain(settings.m_volume); } - - if ((settings.m_baudRate != m_settings.m_baudRate) || force) - { + if ((settings.m_baudRate != m_settings.m_baudRate) || force) { reverseAPIKeys.append("baudRate"); - m_dsdDecoder.setBaudRate(settings.m_baudRate); } - - if ((settings.m_enableCosineFiltering != m_settings.m_enableCosineFiltering) || force) - { + if ((settings.m_enableCosineFiltering != m_settings.m_enableCosineFiltering) || force) { reverseAPIKeys.append("enableCosineFiltering"); - m_dsdDecoder.enableCosineFiltering(settings.m_enableCosineFiltering); } - - if ((settings.m_tdmaStereo != m_settings.m_tdmaStereo) || force) - { + if ((settings.m_tdmaStereo != m_settings.m_tdmaStereo) || force) { reverseAPIKeys.append("tdmaStereo"); - m_dsdDecoder.setTDMAStereo(settings.m_tdmaStereo); } - - if ((settings.m_pllLock != m_settings.m_pllLock) || force) - { + if ((settings.m_pllLock != m_settings.m_pllLock) || force) { reverseAPIKeys.append("pllLock"); - m_dsdDecoder.setSymbolPLLLock(settings.m_pllLock); } - - if ((settings.m_highPassFilter != m_settings.m_highPassFilter) || force) - { + if ((settings.m_highPassFilter != m_settings.m_highPassFilter) || force) { reverseAPIKeys.append("highPassFilter"); - m_dsdDecoder.useHPMbelib(settings.m_highPassFilter); } - - if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) - { + if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) { reverseAPIKeys.append("audioDeviceName"); - AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); - int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName); - //qDebug("AMDemod::applySettings: audioDeviceName: %s audioDeviceIndex: %d", qPrintable(settings.m_audioDeviceName), audioDeviceIndex); - audioDeviceManager->addAudioSink(&m_audioFifo1, getInputMessageQueue(), audioDeviceIndex); - audioDeviceManager->addAudioSink(&m_audioFifo2, getInputMessageQueue(), audioDeviceIndex); - uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex); - - if (m_audioSampleRate != audioSampleRate) { - applyAudioSampleRate(audioSampleRate); - } } if (m_settings.m_streamIndex != settings.m_streamIndex) @@ -615,16 +226,17 @@ void DSDDemod::applySettings(const DSDDemodSettings& settings, bool force) if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only { m_deviceAPI->removeChannelSinkAPI(this, m_settings.m_streamIndex); - m_deviceAPI->removeChannelSink(m_threadedChannelizer, m_settings.m_streamIndex); - m_deviceAPI->addChannelSink(m_threadedChannelizer, settings.m_streamIndex); + m_deviceAPI->removeChannelSink(this, m_settings.m_streamIndex); + m_deviceAPI->addChannelSink(this, settings.m_streamIndex); m_deviceAPI->addChannelSinkAPI(this, settings.m_streamIndex); - // apply stream sample rate to itself - applyChannelSettings(m_deviceAPI->getSampleMIMO()->getSourceSampleRate(settings.m_streamIndex), m_inputFrequencyOffset); } reverseAPIKeys.append("streamIndex"); } + DSDDemodBaseband::MsgConfigureDSDDemodBaseband *msg = DSDDemodBaseband::MsgConfigureDSDDemodBaseband::create(settings, force); + m_basebandSink->getInputMessageQueue()->push(msg); + if (settings.m_useReverseAPI) { bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || @@ -660,195 +272,6 @@ bool DSDDemod::deserialize(const QByteArray& data) } } -const char *DSDDemod::updateAndGetStatusText() -{ - formatStatusText(); - return m_formatStatusText; -} - -void DSDDemod::formatStatusText() -{ - switch (getDecoder().getSyncType()) - { - case DSDcc::DSDDecoder::DSDSyncDMRDataMS: - case DSDcc::DSDDecoder::DSDSyncDMRDataP: - case DSDcc::DSDDecoder::DSDSyncDMRVoiceMS: - case DSDcc::DSDDecoder::DSDSyncDMRVoiceP: - if (m_signalFormat != signalFormatDMR) - { - strcpy(m_formatStatusText, "Sta: __ S1: __________________________ S2: __________________________"); - } - - switch (getDecoder().getStationType()) - { - case DSDcc::DSDDecoder::DSDBaseStation: - memcpy(&m_formatStatusText[5], "BS ", 3); - break; - case DSDcc::DSDDecoder::DSDMobileStation: - memcpy(&m_formatStatusText[5], "MS ", 3); - break; - default: - memcpy(&m_formatStatusText[5], "NA ", 3); - break; - } - - memcpy(&m_formatStatusText[12], getDecoder().getDMRDecoder().getSlot0Text(), 26); - memcpy(&m_formatStatusText[43], getDecoder().getDMRDecoder().getSlot1Text(), 26); - m_signalFormat = signalFormatDMR; - break; - case DSDcc::DSDDecoder::DSDSyncDStarHeaderN: - case DSDcc::DSDDecoder::DSDSyncDStarHeaderP: - case DSDcc::DSDDecoder::DSDSyncDStarN: - case DSDcc::DSDDecoder::DSDSyncDStarP: - if (m_signalFormat != signalFormatDStar) - { - // 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 - // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0.. - strcpy(m_formatStatusText, "________/____>________|________>________|____________________|______:___/_____._"); - // MY UR RPT1 RPT2 Info Loc Target - } - - { - const std::string& rpt1 = getDecoder().getDStarDecoder().getRpt1(); - const std::string& rpt2 = getDecoder().getDStarDecoder().getRpt2(); - const std::string& mySign = getDecoder().getDStarDecoder().getMySign(); - const std::string& yrSign = getDecoder().getDStarDecoder().getYourSign(); - - if (rpt1.length() > 0) { // 0 or 8 - memcpy(&m_formatStatusText[23], rpt1.c_str(), 8); - } - if (rpt2.length() > 0) { // 0 or 8 - memcpy(&m_formatStatusText[32], rpt2.c_str(), 8); - } - if (yrSign.length() > 0) { // 0 or 8 - memcpy(&m_formatStatusText[14], yrSign.c_str(), 8); - } - if (mySign.length() > 0) { // 0 or 13 - memcpy(&m_formatStatusText[0], mySign.c_str(), 13); - } - memcpy(&m_formatStatusText[41], getDecoder().getDStarDecoder().getInfoText(), 20); - memcpy(&m_formatStatusText[62], getDecoder().getDStarDecoder().getLocator(), 6); - snprintf(&m_formatStatusText[69], 82-69, "%03d/%07.1f", - getDecoder().getDStarDecoder().getBearing(), - getDecoder().getDStarDecoder().getDistance()); - } - - m_formatStatusText[82] = '\0'; - m_signalFormat = signalFormatDStar; - break; - case DSDcc::DSDDecoder::DSDSyncDPMR: - snprintf(m_formatStatusText, 82, "%s CC: %04d OI: %08d CI: %08d", - DSDcc::DSDdPMR::dpmrFrameTypes[(int) getDecoder().getDPMRDecoder().getFrameType()], - getDecoder().getDPMRDecoder().getColorCode(), - getDecoder().getDPMRDecoder().getOwnId(), - getDecoder().getDPMRDecoder().getCalledId()); - m_signalFormat = signalFormatDPMR; - break; - case DSDcc::DSDDecoder::DSDSyncNXDNP: - case DSDcc::DSDDecoder::DSDSyncNXDNN: - if (getDecoder().getNXDNDecoder().getRFChannel() == DSDcc::DSDNXDN::NXDNRCCH) - { - // 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 - // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0.. - // RC r cc mm llllll ssss - snprintf(m_formatStatusText, 82, "RC %s %02d %02X %06X %02X", - getDecoder().getNXDNDecoder().isFullRate() ? "F" : "H", - getDecoder().getNXDNDecoder().getRAN(), - getDecoder().getNXDNDecoder().getMessageType(), - getDecoder().getNXDNDecoder().getLocationId(), - getDecoder().getNXDNDecoder().getServicesFlag()); - } - else if ((getDecoder().getNXDNDecoder().getRFChannel() == DSDcc::DSDNXDN::NXDNRTCH) - || (getDecoder().getNXDNDecoder().getRFChannel() == DSDcc::DSDNXDN::NXDNRDCH)) - { - if (getDecoder().getNXDNDecoder().isIdle()) { - snprintf(m_formatStatusText, 82, "%s IDLE", getDecoder().getNXDNDecoder().getRFChannelStr()); - } - else - { - // 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 - // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0.. - // Rx r cc mm sssss>gddddd - snprintf(m_formatStatusText, 82, "%s %s %02d %02X %05d>%c%05d", - getDecoder().getNXDNDecoder().getRFChannelStr(), - getDecoder().getNXDNDecoder().isFullRate() ? "F" : "H", - getDecoder().getNXDNDecoder().getRAN(), - getDecoder().getNXDNDecoder().getMessageType(), - getDecoder().getNXDNDecoder().getSourceId(), - getDecoder().getNXDNDecoder().isGroupCall() ? 'G' : 'I', - getDecoder().getNXDNDecoder().getDestinationId()); - } - } - else - { - // 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 - // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0.. - // RU - snprintf(m_formatStatusText, 82, "RU"); - } - m_signalFormat = signalFormatNXDN; - break; - case DSDcc::DSDDecoder::DSDSyncYSF: - // 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 - // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0.. - // C V2 RI 0:7 WL000|ssssssssss>dddddddddd |UUUUUUUUUU>DDDDDDDDDD|44444 - if (getDecoder().getYSFDecoder().getFICHError() == DSDcc::DSDYSF::FICHNoError) - { - snprintf(m_formatStatusText, 82, "%s ", DSDcc::DSDYSF::ysfChannelTypeText[(int) getDecoder().getYSFDecoder().getFICH().getFrameInformation()]); - } - else - { - snprintf(m_formatStatusText, 82, "%d ", (int) getDecoder().getYSFDecoder().getFICHError()); - } - - snprintf(&m_formatStatusText[2], 80, "%s %s %d:%d %c%c", - DSDcc::DSDYSF::ysfDataTypeText[(int) getDecoder().getYSFDecoder().getFICH().getDataType()], - DSDcc::DSDYSF::ysfCallModeText[(int) getDecoder().getYSFDecoder().getFICH().getCallMode()], - getDecoder().getYSFDecoder().getFICH().getBlockTotal(), - getDecoder().getYSFDecoder().getFICH().getFrameTotal(), - (getDecoder().getYSFDecoder().getFICH().isNarrowMode() ? 'N' : 'W'), - (getDecoder().getYSFDecoder().getFICH().isInternetPath() ? 'I' : 'L')); - - if (getDecoder().getYSFDecoder().getFICH().isSquelchCodeEnabled()) - { - snprintf(&m_formatStatusText[14], 82-14, "%03d", getDecoder().getYSFDecoder().getFICH().getSquelchCode()); - } - else - { - strncpy(&m_formatStatusText[14], "---", 82-14); - } - - char dest[13]; - - if (getDecoder().getYSFDecoder().radioIdMode()) - { - snprintf(dest, 12, "%-5s:%-5s", - getDecoder().getYSFDecoder().getDestId(), - getDecoder().getYSFDecoder().getSrcId()); - } - else - { - snprintf(dest, 11, "%-10s", getDecoder().getYSFDecoder().getDest()); - } - - snprintf(&m_formatStatusText[17], 82-17, "|%-10s>%s|%-10s>%-10s|%-5s", - getDecoder().getYSFDecoder().getSrc(), - dest, - getDecoder().getYSFDecoder().getUplink(), - getDecoder().getYSFDecoder().getDownlink(), - getDecoder().getYSFDecoder().getRem4()); - - m_signalFormat = signalFormatYSF; - break; - default: - m_signalFormat = signalFormatNone; - m_formatStatusText[0] = '\0'; - break; - } - - m_formatStatusText[82] = '\0'; // guard -} - int DSDDemod::webapiSettingsGet( SWGSDRangel::SWGChannelSettings& response, QString& errorMessage) @@ -870,13 +293,6 @@ int DSDDemod::webapiSettingsPutPatch( DSDDemodSettings settings = m_settings; webapiUpdateChannelSettings(settings, channelSettingsKeys, response); - if (settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) - { - MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( - m_audioSampleRate, settings.m_inputFrequencyOffset); - m_inputMessageQueue.push(channelConfigMsg); - } - MsgConfigureDSDDemod *msg = MsgConfigureDSDDemod::create(settings, force); m_inputMessageQueue.push(msg); @@ -1051,9 +467,9 @@ void DSDDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples); response.getDsdDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg)); - response.getDsdDemodReport()->setAudioSampleRate(m_audioSampleRate); - response.getDsdDemodReport()->setChannelSampleRate(m_inputSampleRate); - response.getDsdDemodReport()->setSquelch(m_squelchOpen ? 1 : 0); + response.getDsdDemodReport()->setAudioSampleRate(m_basebandSink->getAudioSampleRate()); + response.getDsdDemodReport()->setChannelSampleRate(m_basebandSink->getChannelSampleRate()); + response.getDsdDemodReport()->setSquelch(m_basebandSink->getSquelchOpen() ? 1 : 0); response.getDsdDemodReport()->setPllLocked(getDecoder().getSymbolPLLLocked() ? 1 : 0); response.getDsdDemodReport()->setSlot1On(getDecoder().getVoice1On() ? 1 : 0); response.getDsdDemodReport()->setSlot2On(getDecoder().getVoice2On() ? 1 : 0); diff --git a/plugins/channelrx/demoddsd/dsddemod.h b/plugins/channelrx/demoddsd/dsddemod.h index 6f4674c41..e438e992e 100644 --- a/plugins/channelrx/demoddsd/dsddemod.h +++ b/plugins/channelrx/demoddsd/dsddemod.h @@ -21,31 +21,20 @@ #include -#include #include #include "dsp/basebandsamplesink.h" #include "channel/channelapi.h" -#include "dsp/phasediscri.h" -#include "dsp/nco.h" -#include "dsp/interpolator.h" -#include "dsp/lowpass.h" -#include "dsp/bandpass.h" -#include "dsp/afsquelch.h" -#include "util/movingaverage.h" -#include "dsp/afsquelch.h" -#include "audio/audiofifo.h" #include "util/message.h" -#include "util/doublebufferfifo.h" #include "dsddemodsettings.h" #include "dsddecoder.h" +#include "dsddemodbaseband.h" class QNetworkAccessManager; class QNetworkReply; -class DeviceAPI; -class ThreadedBasebandSampleSink; -class DownChannelizer; +class QThread; +class DownSampleChannelizer; class DSDDemod : public BasebandSampleSink, public ChannelAPI { Q_OBJECT @@ -73,35 +62,9 @@ public: { } }; - 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) - { } - }; - DSDDemod(DeviceAPI *deviceAPI); ~DSDDemod(); virtual void destroy() { delete this; } - void setScopeXYSink(BasebandSampleSink* sampleSink) { m_scopeXY = sampleSink; } - - void configureMyPosition(MessageQueue* messageQueue, float myLatitude, float myLongitude); virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool po); virtual void start(); @@ -125,31 +88,6 @@ public: return m_settings.m_inputFrequencyOffset; } - double getMagSq() { return m_magsq; } - bool getSquelchOpen() const { return m_squelchOpen; } - - const DSDDecoder& getDecoder() const { return m_dsdDecoder; } - - void getMagSqLevels(double& avg, double& peak, int& nbSamples) - { - if (m_magsqCount > 0) - { - m_magsq = m_magsqSum / m_magsqCount; - m_magSqLevelStore.m_magsq = m_magsq; - m_magSqLevelStore.m_magsqPeak = m_magsqPeak; - } - - avg = m_magSqLevelStore.m_magsq; - peak = m_magSqLevelStore.m_magsqPeak; - nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; - - m_magsqSum = 0.0f; - m_magsqPeak = 0.0f; - m_magsqCount = 0; - } - - const char *updateAndGetStatusText(); - virtual int webapiSettingsGet( SWGSDRangel::SWGChannelSettings& response, QString& errorMessage); @@ -174,115 +112,30 @@ public: SWGSDRangel::SWGChannelSettings& response); uint32_t getNumberOfDeviceStreams() const; + void setScopeXYSink(BasebandSampleSink* sampleSink) { m_basebandSink->setScopeXYSink(sampleSink); } + void configureMyPosition(float myLatitude, float myLongitude) { m_basebandSink->configureMyPosition(myLatitude, myLongitude); } + double getMagSq() { return m_basebandSink->getMagSq(); } + bool getSquelchOpen() const { return m_basebandSink->getSquelchOpen(); } + const DSDDecoder& getDecoder() const { return m_basebandSink->getDecoder(); } + void getMagSqLevels(double& avg, double& peak, int& nbSamples) { m_basebandSink->getMagSqLevels(avg, peak, nbSamples); } + const char *updateAndGetStatusText() { return m_basebandSink->updateAndGetStatusText(); } static const QString m_channelIdURI; static const QString m_channelId; private: - struct MagSqLevelsStore - { - MagSqLevelsStore() : - m_magsq(1e-12), - m_magsqPeak(1e-12) - {} - double m_magsq; - double m_magsqPeak; - }; - - typedef enum - { - signalFormatNone, - signalFormatDMR, - signalFormatDStar, - signalFormatDPMR, - signalFormatYSF, - signalFormatNXDN - } SignalFormat; //!< Used for status text formatting - - class MsgConfigureMyPosition : public Message { - MESSAGE_CLASS_DECLARATION - - public: - float getMyLatitude() const { return m_myLatitude; } - float getMyLongitude() const { return m_myLongitude; } - - static MsgConfigureMyPosition* create(float myLatitude, float myLongitude) - { - return new MsgConfigureMyPosition(myLatitude, myLongitude); - } - - private: - float m_myLatitude; - float m_myLongitude; - - MsgConfigureMyPosition(float myLatitude, float myLongitude) : - m_myLatitude(myLatitude), - m_myLongitude(myLongitude) - {} - }; - - enum RateState { - RSInitialFill, - RSRunning - }; - DeviceAPI *m_deviceAPI; - ThreadedBasebandSampleSink* m_threadedChannelizer; - DownChannelizer* m_channelizer; - - int m_inputSampleRate; - int m_inputFrequencyOffset; + QThread *m_thread; + DSDDemodBaseband *m_basebandSink; DSDDemodSettings m_settings; - quint32 m_audioSampleRate; - - NCO m_nco; - Interpolator m_interpolator; - Real m_interpolatorDistance; - Real m_interpolatorDistanceRemain; - int m_sampleCount; - int m_squelchCount; - int m_squelchGate; - double m_squelchLevel; - bool m_squelchOpen; - DoubleBufferFIFO m_squelchDelayLine; - - MovingAverageUtil m_movingAverage; - double m_magsq; - double m_magsqSum; - double m_magsqPeak; - int m_magsqCount; - MagSqLevelsStore m_magSqLevelStore; - - SampleVector m_scopeSampleBuffer; - AudioVector m_audioBuffer; - uint m_audioBufferFill; - FixReal *m_sampleBuffer; //!< samples ring buffer - int m_sampleBufferIndex; - int m_scaleFromShort; - - AudioFifo m_audioFifo1; - AudioFifo m_audioFifo2; - BasebandSampleSink* m_scopeXY; - bool m_scopeEnabled; - - DSDDecoder m_dsdDecoder; - - char m_formatStatusText[82+1]; //!< Fixed signal format dependent status text - SignalFormat m_signalFormat; //!< Used to keep formatting during successive calls for the same standard type - PhaseDiscriminators m_phaseDiscri; + int m_basebandSampleRate; //!< stored from device message used when starting baseband sink QNetworkAccessManager *m_networkManager; QNetworkRequest m_networkRequest; - QMutex m_settingsMutex; - static const int m_udpBlockSize; - void applyAudioSampleRate(int sampleRate); - void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); - void applySettings(const DSDDemodSettings& settings, bool force = false); - void formatStatusText(); - + void applySettings(const DSDDemodSettings& settings, bool force = false); void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); void webapiReverseSendSettings(QList& channelSettingsKeys, const DSDDemodSettings& settings, bool force); diff --git a/plugins/channelrx/demoddsd/dsddemodbaseband.cpp b/plugins/channelrx/demoddsd/dsddemodbaseband.cpp new file mode 100644 index 000000000..be18c39dc --- /dev/null +++ b/plugins/channelrx/demoddsd/dsddemodbaseband.cpp @@ -0,0 +1,174 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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 // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "dsp/downsamplechannelizer.h" + +#include "dsddemodbaseband.h" + +MESSAGE_CLASS_DEFINITION(DSDDemodBaseband::MsgConfigureDSDDemodBaseband, Message) + +DSDDemodBaseband::DSDDemodBaseband() : + m_mutex(QMutex::Recursive) +{ + qDebug("DSDDemodBaseband::DSDDemodBaseband"); + m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000)); + m_channelizer = new DownSampleChannelizer(&m_sink); + + QObject::connect( + &m_sampleFifo, + &SampleSinkFifo::dataReady, + this, + &DSDDemodBaseband::handleData, + Qt::QueuedConnection + ); + + DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(m_sink.getAudioFifo1(), getInputMessageQueue()); + m_sink.applyAudioSampleRate(DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate()); + DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(m_sink.getAudioFifo2(), getInputMessageQueue()); + m_sink.applyAudioSampleRate(DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate()); + + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); +} + +DSDDemodBaseband::~DSDDemodBaseband() +{ + DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(m_sink.getAudioFifo1()); + DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(m_sink.getAudioFifo2()); + delete m_channelizer; +} + +void DSDDemodBaseband::reset() +{ + QMutexLocker mutexLocker(&m_mutex); + m_sampleFifo.reset(); +} + +void DSDDemodBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) +{ + m_sampleFifo.write(begin, end); +} + +void DSDDemodBaseband::handleData() +{ + while ((m_sampleFifo.fill() > 0) && (m_inputMessageQueue.size() == 0)) + { + SampleVector::iterator part1begin; + SampleVector::iterator part1end; + SampleVector::iterator part2begin; + SampleVector::iterator part2end; + + std::size_t count = m_sampleFifo.readBegin(m_sampleFifo.fill(), &part1begin, &part1end, &part2begin, &part2end); + + // first part of FIFO data + if (part1begin != part1end) { + m_channelizer->feed(part1begin, part1end); + } + + // second part of FIFO data (used when block wraps around) + if(part2begin != part2end) { + m_channelizer->feed(part2begin, part2end); + } + + m_sampleFifo.readCommit((unsigned int) count); + } +} + +void DSDDemodBaseband::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != nullptr) + { + if (handleMessage(*message)) { + delete message; + } + } +} + +bool DSDDemodBaseband::handleMessage(const Message& cmd) +{ + if (MsgConfigureDSDDemodBaseband::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + MsgConfigureDSDDemodBaseband& cfg = (MsgConfigureDSDDemodBaseband&) cmd; + qDebug() << "DSDDemodBaseband::handleMessage: MsgConfigureDSDDemodBaseband"; + + applySettings(cfg.getSettings(), cfg.getForce()); + + return true; + } + else if (DSPSignalNotification::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + DSPSignalNotification& notif = (DSPSignalNotification&) cmd; + qDebug() << "DSDDemodBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate(); + m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate())); + m_channelizer->setBasebandSampleRate(notif.getSampleRate()); + m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); + + return true; + } + else + { + return false; + } +} + +void DSDDemodBaseband::applySettings(const DSDDemodSettings& settings, bool force) +{ + if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) + { + m_channelizer->setChannelization(m_sink.getAudioSampleRate(), settings.m_inputFrequencyOffset); + m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); + } + + if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) + { + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName); + //qDebug("AMDemod::applySettings: audioDeviceName: %s audioDeviceIndex: %d", qPrintable(settings.m_audioDeviceName), audioDeviceIndex); + audioDeviceManager->addAudioSink(m_sink.getAudioFifo1(), getInputMessageQueue(), audioDeviceIndex); + audioDeviceManager->addAudioSink(m_sink.getAudioFifo2(), getInputMessageQueue(), audioDeviceIndex); + uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex); + + if (m_sink.getAudioSampleRate() != audioSampleRate) + { + m_sink.applyAudioSampleRate(audioSampleRate); + m_channelizer->setChannelization(audioSampleRate, settings.m_inputFrequencyOffset); + m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); + } + } + + m_sink.applySettings(settings, force); + + m_settings = settings; +} + +int DSDDemodBaseband::getChannelSampleRate() const +{ + return m_channelizer->getChannelSampleRate(); +} + + +void DSDDemodBaseband::setBasebandSampleRate(int sampleRate) +{ + m_channelizer->setBasebandSampleRate(sampleRate); +} \ No newline at end of file diff --git a/plugins/channelrx/demoddsd/dsddemodbaseband.h b/plugins/channelrx/demoddsd/dsddemodbaseband.h new file mode 100644 index 000000000..a5acc9c8b --- /dev/null +++ b/plugins/channelrx/demoddsd/dsddemodbaseband.h @@ -0,0 +1,91 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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 // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License 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_DSDDEMODBASEBAND_H +#define INCLUDE_DSDDEMODBASEBAND_H + +#include +#include + +#include "dsp/samplesinkfifo.h" +#include "util/message.h" +#include "util/messagequeue.h" + +#include "dsddemodsink.h" + +class DownSampleChannelizer; + +class DSDDemodBaseband : public QObject +{ + Q_OBJECT +public: + class MsgConfigureDSDDemodBaseband : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const DSDDemodSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureDSDDemodBaseband* create(const DSDDemodSettings& settings, bool force) + { + return new MsgConfigureDSDDemodBaseband(settings, force); + } + + private: + DSDDemodSettings m_settings; + bool m_force; + + MsgConfigureDSDDemodBaseband(const DSDDemodSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + DSDDemodBaseband(); + ~DSDDemodBaseband(); + void reset(); + void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); + MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication + int getChannelSampleRate() const; + unsigned int getAudioSampleRate() const { return m_sink.getAudioSampleRate(); } + double getMagSq() { return m_sink.getMagSq(); } + void getMagSqLevels(double& avg, double& peak, int& nbSamples) { m_sink.getMagSqLevels(avg, peak, nbSamples); } + bool getSquelchOpen() const { return m_sink.getSquelchOpen(); } + void setBasebandSampleRate(int sampleRate); + void setScopeXYSink(BasebandSampleSink* scopeSink) { m_sink.setScopeXYSink(scopeSink); } + void configureMyPosition(float myLatitude, float myLongitude) { m_sink.configureMyPosition(myLatitude, myLongitude); } + const DSDDecoder& getDecoder() const { return m_sink.getDecoder(); } + const char *updateAndGetStatusText() { return m_sink.updateAndGetStatusText(); } + +private: + SampleSinkFifo m_sampleFifo; + DownSampleChannelizer *m_channelizer; + DSDDemodSink m_sink; + MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication + DSDDemodSettings m_settings; + QMutex m_mutex; + + bool handleMessage(const Message& cmd); + void applySettings(const DSDDemodSettings& settings, bool force = false); + +private slots: + void handleInputMessages(); + void handleData(); //!< Handle data when samples have to be processed +}; + +#endif // INCLUDE_DSDDEMODBASEBAND_H \ No newline at end of file diff --git a/plugins/channelrx/demoddsd/dsddemodgui.cpp b/plugins/channelrx/demoddsd/dsddemodgui.cpp index 8cb29ed28..532cc0532 100644 --- a/plugins/channelrx/demoddsd/dsddemodgui.cpp +++ b/plugins/channelrx/demoddsd/dsddemodgui.cpp @@ -417,7 +417,7 @@ void DSDDemodGUI::updateMyPosition() if ((m_myLatitude != latitude) || (m_myLongitude != longitude)) { - m_dsdDemod->configureMyPosition(m_dsdDemod->getInputMessageQueue(), latitude, longitude); + m_dsdDemod->configureMyPosition(latitude, longitude); m_myLatitude = latitude; m_myLongitude = longitude; } @@ -500,10 +500,6 @@ void DSDDemodGUI::applySettings(bool force) { qDebug() << "DSDDemodGUI::applySettings"; - DSDDemod::MsgConfigureChannelizer* channelConfigMsg = DSDDemod::MsgConfigureChannelizer::create( - 48000, m_channelMarker.getCenterFrequency()); - m_dsdDemod->getInputMessageQueue()->push(channelConfigMsg); - DSDDemod::MsgConfigureDSDDemod* message = DSDDemod::MsgConfigureDSDDemod::create( m_settings, force); m_dsdDemod->getInputMessageQueue()->push(message); } diff --git a/plugins/channelrx/demoddsd/dsddemodsink.cpp b/plugins/channelrx/demoddsd/dsddemodsink.cpp new file mode 100644 index 000000000..91f08f08b --- /dev/null +++ b/plugins/channelrx/demoddsd/dsddemodsink.cpp @@ -0,0 +1,595 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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 // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "SWGChannelSettings.h" +#include "SWGDSDDemodSettings.h" +#include "SWGChannelReport.h" +#include "SWGDSDDemodReport.h" +#include "SWGRDSReport.h" + +#include "dsp/dspengine.h" +#include "dsp/basebandsamplesink.h" +#include "audio/audiooutput.h" +#include "util/db.h" + +#include "dsddemodsink.h" + +DSDDemodSink::DSDDemodSink() : + m_channelSampleRate(48000), + m_channelFrequencyOffset(0), + m_interpolatorDistance(0.0f), + m_interpolatorDistanceRemain(0.0f), + m_sampleCount(0), + m_squelchCount(0), + m_squelchGate(0), + m_squelchLevel(1e-4), + m_squelchOpen(false), + m_squelchDelayLine(24000), + m_audioFifo1(48000), + m_audioFifo2(48000), + m_scopeXY(0), + m_scopeEnabled(true), + m_dsdDecoder(), + m_signalFormat(signalFormatNone) +{ + m_audioBuffer.resize(1<<14); + m_audioBufferFill = 0; + + m_sampleBuffer = new FixReal[1<<17]; // 128 kS + m_sampleBufferIndex = 0; + m_scaleFromShort = SDR_RX_SAMP_SZ < sizeof(short)*8 ? 1 : 1<<(SDR_RX_SAMP_SZ - sizeof(short)*8); + + m_magsq = 0.0f; + m_magsqSum = 0.0f; + m_magsqPeak = 0.0f; + m_magsqCount = 0; + + applySettings(m_settings, true); + applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true); +} + +DSDDemodSink::~DSDDemodSink() +{ + delete[] m_sampleBuffer; +} + +void DSDDemodSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) +{ Complex ci; + int samplesPerSymbol = m_dsdDecoder.getSamplesPerSymbol(); + + m_scopeSampleBuffer.clear(); + + m_dsdDecoder.enableMbelib(!DSPEngine::instance()->hasDVSerialSupport()); // disable mbelib if DV serial support is present and activated else enable it + + for (SampleVector::const_iterator it = begin; it != end; ++it) + { + Complex c(it->real(), it->imag()); + c *= m_nco.nextIQ(); + + if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci)) + { + FixReal sample, delayedSample; + qint16 sampleDSD; + + Real re = ci.real() / SDR_RX_SCALED; + Real im = ci.imag() / SDR_RX_SCALED; + Real magsq = re*re + im*im; + m_movingAverage(magsq); + + m_magsqSum += magsq; + + if (magsq > m_magsqPeak) + { + m_magsqPeak = magsq; + } + + m_magsqCount++; + + Real demod = m_phaseDiscri.phaseDiscriminator(ci) * m_settings.m_demodGain; // [-1.0:1.0] + m_sampleCount++; + + // AF processing + + if (m_movingAverage.asDouble() > m_squelchLevel) + { + if (m_squelchGate > 0) + { + + if (m_squelchCount < m_squelchGate*2) { + m_squelchCount++; + } + + m_squelchDelayLine.write(demod); + m_squelchOpen = m_squelchCount > m_squelchGate; + } + else + { + m_squelchOpen = true; + } + } + else + { + if (m_squelchGate > 0) + { + if (m_squelchCount > 0) { + m_squelchCount--; + } + + m_squelchDelayLine.write(0); + m_squelchOpen = m_squelchCount > m_squelchGate; + } + else + { + m_squelchOpen = false; + } + } + + if (m_squelchOpen) + { + if (m_squelchGate > 0) + { + sampleDSD = m_squelchDelayLine.readBack(m_squelchGate) * 32768.0f; // DSD decoder takes int16 samples + sample = m_squelchDelayLine.readBack(m_squelchGate) * SDR_RX_SCALEF; // scale to sample size + } + else + { + sampleDSD = demod * 32768.0f; // DSD decoder takes int16 samples + sample = demod * SDR_RX_SCALEF; // scale to sample size + } + } + else + { + sampleDSD = 0; + sample = 0; + } + + m_dsdDecoder.pushSample(sampleDSD); + + if (m_settings.m_enableCosineFiltering) { // show actual input to FSK demod + sample = m_dsdDecoder.getFilteredSample() * m_scaleFromShort; + } + + if (m_sampleBufferIndex < (1<<17)-1) { + m_sampleBufferIndex++; + } else { + m_sampleBufferIndex = 0; + } + + m_sampleBuffer[m_sampleBufferIndex] = sample; + + if (m_sampleBufferIndex < samplesPerSymbol) { + delayedSample = m_sampleBuffer[(1<<17) - samplesPerSymbol + m_sampleBufferIndex]; // wrap + } else { + delayedSample = m_sampleBuffer[m_sampleBufferIndex - samplesPerSymbol]; + } + + if (m_settings.m_syncOrConstellation) + { + Sample s(sample, m_dsdDecoder.getSymbolSyncSample() * m_scaleFromShort * 0.84); + m_scopeSampleBuffer.push_back(s); + } + else + { + Sample s(sample, delayedSample); // I=signal, Q=signal delayed by 20 samples (2400 baud: lowest rate) + m_scopeSampleBuffer.push_back(s); + } + + if (DSPEngine::instance()->hasDVSerialSupport()) + { + if ((m_settings.m_slot1On) && m_dsdDecoder.mbeDVReady1()) + { + if (!m_settings.m_audioMute) + { + DSPEngine::instance()->pushMbeFrame( + m_dsdDecoder.getMbeDVFrame1(), + m_dsdDecoder.getMbeRateIndex(), + m_settings.m_volume * 10.0, + m_settings.m_tdmaStereo ? 1 : 3, // left or both channels + m_settings.m_highPassFilter, + m_audioSampleRate/8000, // upsample from native 8k + &m_audioFifo1); + } + + m_dsdDecoder.resetMbeDV1(); + } + + if ((m_settings.m_slot2On) && m_dsdDecoder.mbeDVReady2()) + { + if (!m_settings.m_audioMute) + { + DSPEngine::instance()->pushMbeFrame( + m_dsdDecoder.getMbeDVFrame2(), + m_dsdDecoder.getMbeRateIndex(), + m_settings.m_volume * 10.0, + m_settings.m_tdmaStereo ? 2 : 3, // right or both channels + m_settings.m_highPassFilter, + m_audioSampleRate/8000, // upsample from native 8k + &m_audioFifo2); + } + + m_dsdDecoder.resetMbeDV2(); + } + } + + m_interpolatorDistanceRemain += m_interpolatorDistance; + } + } + + if (!DSPEngine::instance()->hasDVSerialSupport()) + { + if (m_settings.m_slot1On) + { + int nbAudioSamples; + short *dsdAudio = m_dsdDecoder.getAudio1(nbAudioSamples); + + if (nbAudioSamples > 0) + { + if (!m_settings.m_audioMute) { + m_audioFifo1.write((const quint8*) dsdAudio, nbAudioSamples); + } + + m_dsdDecoder.resetAudio1(); + } + } + + if (m_settings.m_slot2On) + { + int nbAudioSamples; + short *dsdAudio = m_dsdDecoder.getAudio2(nbAudioSamples); + + if (nbAudioSamples > 0) + { + if (!m_settings.m_audioMute) { + m_audioFifo2.write((const quint8*) dsdAudio, nbAudioSamples); + } + + m_dsdDecoder.resetAudio2(); + } + } + } + + if ((m_scopeXY != 0) && (m_scopeEnabled)) + { + m_scopeXY->feed(m_scopeSampleBuffer.begin(), m_scopeSampleBuffer.end(), true); // true = real samples for what it's worth + } +} + +void DSDDemodSink::applyAudioSampleRate(int sampleRate) +{ + int upsampling = sampleRate / 8000; + + qDebug("DSDDemodSink::applyAudioSampleRate: audio rate: %d upsample by %d", sampleRate, upsampling); + + if (sampleRate % 8000 != 0) { + qDebug("DSDDemodSink::applyAudioSampleRate: audio will sound best with sample rates that are integer multiples of 8 kS/s"); + } + + m_dsdDecoder.setUpsampling(upsampling); + m_audioSampleRate = sampleRate; +} + +void DSDDemodSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force) +{ + qDebug() << "DSDDemodSink::applyChannelSettings:" + << " channelSampleRate: " << channelSampleRate + << " inputFrequencyOffset: " << channelFrequencyOffset; + + if ((channelFrequencyOffset != m_channelFrequencyOffset) || + (channelSampleRate != m_channelSampleRate) || force) + { + m_nco.setFreq(-channelFrequencyOffset, channelSampleRate); + } + + if ((channelSampleRate != m_channelSampleRate) || force) + { + m_interpolator.create(16, channelSampleRate, (m_settings.m_rfBandwidth) / 2.2); + m_interpolatorDistanceRemain = 0; + m_interpolatorDistance = (Real) channelSampleRate / (Real) 48000; + } + + m_channelSampleRate = channelSampleRate; + m_channelFrequencyOffset = channelFrequencyOffset; +} + + +void DSDDemodSink::applySettings(const DSDDemodSettings& settings, bool force) +{ + qDebug() << "DSDDemodSink::applySettings: " + << " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset + << " m_rfBandwidth: " << settings.m_rfBandwidth + << " m_fmDeviation: " << settings.m_fmDeviation + << " m_demodGain: " << settings.m_demodGain + << " m_volume: " << settings.m_volume + << " m_baudRate: " << settings.m_baudRate + << " m_squelchGate" << settings.m_squelchGate + << " m_squelch: " << settings.m_squelch + << " m_audioMute: " << settings.m_audioMute + << " m_enableCosineFiltering: " << settings.m_enableCosineFiltering + << " m_syncOrConstellation: " << settings.m_syncOrConstellation + << " m_slot1On: " << settings.m_slot1On + << " m_slot2On: " << settings.m_slot2On + << " m_tdmaStereo: " << settings.m_tdmaStereo + << " m_pllLock: " << settings.m_pllLock + << " m_highPassFilter: "<< settings.m_highPassFilter + << " m_audioDeviceName: " << settings.m_audioDeviceName + << " m_traceLengthMutliplier: " << settings.m_traceLengthMutliplier + << " m_traceStroke: " << settings.m_traceStroke + << " m_traceDecay: " << settings.m_traceDecay + << " m_streamIndex: " << settings.m_streamIndex + << " force: " << force; + + if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) + { + m_interpolator.create(16, m_channelSampleRate, (settings.m_rfBandwidth) / 2.2); + m_interpolatorDistanceRemain = 0; + m_interpolatorDistance = (Real) m_channelSampleRate / (Real) 48000; + //m_phaseDiscri.setFMScaling((float) settings.m_rfBandwidth / (float) settings.m_fmDeviation); + } + + if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) + { + m_phaseDiscri.setFMScaling(48000.0f / (2.0f*settings.m_fmDeviation)); + } + + if ((settings.m_squelchGate != m_settings.m_squelchGate) || force) + { + m_squelchGate = 480 * settings.m_squelchGate; // gate is given in 10s of ms at 48000 Hz audio sample rate + m_squelchCount = 0; // reset squelch open counter + } + + if ((settings.m_squelch != m_settings.m_squelch) || force) + { + // input is a value in dB + m_squelchLevel = std::pow(10.0, settings.m_squelch / 10.0); + } + + if ((settings.m_volume != m_settings.m_volume) || force) + { + m_dsdDecoder.setAudioGain(settings.m_volume); + } + + if ((settings.m_baudRate != m_settings.m_baudRate) || force) + { + m_dsdDecoder.setBaudRate(settings.m_baudRate); + } + + if ((settings.m_enableCosineFiltering != m_settings.m_enableCosineFiltering) || force) + { + m_dsdDecoder.enableCosineFiltering(settings.m_enableCosineFiltering); + } + + if ((settings.m_tdmaStereo != m_settings.m_tdmaStereo) || force) + { + m_dsdDecoder.setTDMAStereo(settings.m_tdmaStereo); + } + + if ((settings.m_pllLock != m_settings.m_pllLock) || force) + { + m_dsdDecoder.setSymbolPLLLock(settings.m_pllLock); + } + + if ((settings.m_highPassFilter != m_settings.m_highPassFilter) || force) + { + m_dsdDecoder.useHPMbelib(settings.m_highPassFilter); + } + + m_settings = settings; +} + +void DSDDemodSink::configureMyPosition(float myLatitude, float myLongitude) +{ + m_dsdDecoder.setMyPoint(myLatitude, myLongitude); +} + +const char *DSDDemodSink::updateAndGetStatusText() +{ + formatStatusText(); + return m_formatStatusText; +} + +void DSDDemodSink::formatStatusText() +{ + switch (getDecoder().getSyncType()) + { + case DSDcc::DSDDecoder::DSDSyncDMRDataMS: + case DSDcc::DSDDecoder::DSDSyncDMRDataP: + case DSDcc::DSDDecoder::DSDSyncDMRVoiceMS: + case DSDcc::DSDDecoder::DSDSyncDMRVoiceP: + if (m_signalFormat != signalFormatDMR) + { + strcpy(m_formatStatusText, "Sta: __ S1: __________________________ S2: __________________________"); + } + + switch (getDecoder().getStationType()) + { + case DSDcc::DSDDecoder::DSDBaseStation: + memcpy(&m_formatStatusText[5], "BS ", 3); + break; + case DSDcc::DSDDecoder::DSDMobileStation: + memcpy(&m_formatStatusText[5], "MS ", 3); + break; + default: + memcpy(&m_formatStatusText[5], "NA ", 3); + break; + } + + memcpy(&m_formatStatusText[12], getDecoder().getDMRDecoder().getSlot0Text(), 26); + memcpy(&m_formatStatusText[43], getDecoder().getDMRDecoder().getSlot1Text(), 26); + m_signalFormat = signalFormatDMR; + break; + case DSDcc::DSDDecoder::DSDSyncDStarHeaderN: + case DSDcc::DSDDecoder::DSDSyncDStarHeaderP: + case DSDcc::DSDDecoder::DSDSyncDStarN: + case DSDcc::DSDDecoder::DSDSyncDStarP: + if (m_signalFormat != signalFormatDStar) + { + // 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 + // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0.. + strcpy(m_formatStatusText, "________/____>________|________>________|____________________|______:___/_____._"); + // MY UR RPT1 RPT2 Info Loc Target + } + + { + const std::string& rpt1 = getDecoder().getDStarDecoder().getRpt1(); + const std::string& rpt2 = getDecoder().getDStarDecoder().getRpt2(); + const std::string& mySign = getDecoder().getDStarDecoder().getMySign(); + const std::string& yrSign = getDecoder().getDStarDecoder().getYourSign(); + + if (rpt1.length() > 0) { // 0 or 8 + memcpy(&m_formatStatusText[23], rpt1.c_str(), 8); + } + if (rpt2.length() > 0) { // 0 or 8 + memcpy(&m_formatStatusText[32], rpt2.c_str(), 8); + } + if (yrSign.length() > 0) { // 0 or 8 + memcpy(&m_formatStatusText[14], yrSign.c_str(), 8); + } + if (mySign.length() > 0) { // 0 or 13 + memcpy(&m_formatStatusText[0], mySign.c_str(), 13); + } + memcpy(&m_formatStatusText[41], getDecoder().getDStarDecoder().getInfoText(), 20); + memcpy(&m_formatStatusText[62], getDecoder().getDStarDecoder().getLocator(), 6); + snprintf(&m_formatStatusText[69], 82-69, "%03d/%07.1f", + getDecoder().getDStarDecoder().getBearing(), + getDecoder().getDStarDecoder().getDistance()); + } + + m_formatStatusText[82] = '\0'; + m_signalFormat = signalFormatDStar; + break; + case DSDcc::DSDDecoder::DSDSyncDPMR: + snprintf(m_formatStatusText, 82, "%s CC: %04d OI: %08d CI: %08d", + DSDcc::DSDdPMR::dpmrFrameTypes[(int) getDecoder().getDPMRDecoder().getFrameType()], + getDecoder().getDPMRDecoder().getColorCode(), + getDecoder().getDPMRDecoder().getOwnId(), + getDecoder().getDPMRDecoder().getCalledId()); + m_signalFormat = signalFormatDPMR; + break; + case DSDcc::DSDDecoder::DSDSyncNXDNP: + case DSDcc::DSDDecoder::DSDSyncNXDNN: + if (getDecoder().getNXDNDecoder().getRFChannel() == DSDcc::DSDNXDN::NXDNRCCH) + { + // 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 + // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0.. + // RC r cc mm llllll ssss + snprintf(m_formatStatusText, 82, "RC %s %02d %02X %06X %02X", + getDecoder().getNXDNDecoder().isFullRate() ? "F" : "H", + getDecoder().getNXDNDecoder().getRAN(), + getDecoder().getNXDNDecoder().getMessageType(), + getDecoder().getNXDNDecoder().getLocationId(), + getDecoder().getNXDNDecoder().getServicesFlag()); + } + else if ((getDecoder().getNXDNDecoder().getRFChannel() == DSDcc::DSDNXDN::NXDNRTCH) + || (getDecoder().getNXDNDecoder().getRFChannel() == DSDcc::DSDNXDN::NXDNRDCH)) + { + if (getDecoder().getNXDNDecoder().isIdle()) { + snprintf(m_formatStatusText, 82, "%s IDLE", getDecoder().getNXDNDecoder().getRFChannelStr()); + } + else + { + // 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 + // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0.. + // Rx r cc mm sssss>gddddd + snprintf(m_formatStatusText, 82, "%s %s %02d %02X %05d>%c%05d", + getDecoder().getNXDNDecoder().getRFChannelStr(), + getDecoder().getNXDNDecoder().isFullRate() ? "F" : "H", + getDecoder().getNXDNDecoder().getRAN(), + getDecoder().getNXDNDecoder().getMessageType(), + getDecoder().getNXDNDecoder().getSourceId(), + getDecoder().getNXDNDecoder().isGroupCall() ? 'G' : 'I', + getDecoder().getNXDNDecoder().getDestinationId()); + } + } + else + { + // 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 + // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0.. + // RU + snprintf(m_formatStatusText, 82, "RU"); + } + m_signalFormat = signalFormatNXDN; + break; + case DSDcc::DSDDecoder::DSDSyncYSF: + // 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 + // 0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0.. + // C V2 RI 0:7 WL000|ssssssssss>dddddddddd |UUUUUUUUUU>DDDDDDDDDD|44444 + if (getDecoder().getYSFDecoder().getFICHError() == DSDcc::DSDYSF::FICHNoError) + { + snprintf(m_formatStatusText, 82, "%s ", DSDcc::DSDYSF::ysfChannelTypeText[(int) getDecoder().getYSFDecoder().getFICH().getFrameInformation()]); + } + else + { + snprintf(m_formatStatusText, 82, "%d ", (int) getDecoder().getYSFDecoder().getFICHError()); + } + + snprintf(&m_formatStatusText[2], 80, "%s %s %d:%d %c%c", + DSDcc::DSDYSF::ysfDataTypeText[(int) getDecoder().getYSFDecoder().getFICH().getDataType()], + DSDcc::DSDYSF::ysfCallModeText[(int) getDecoder().getYSFDecoder().getFICH().getCallMode()], + getDecoder().getYSFDecoder().getFICH().getBlockTotal(), + getDecoder().getYSFDecoder().getFICH().getFrameTotal(), + (getDecoder().getYSFDecoder().getFICH().isNarrowMode() ? 'N' : 'W'), + (getDecoder().getYSFDecoder().getFICH().isInternetPath() ? 'I' : 'L')); + + if (getDecoder().getYSFDecoder().getFICH().isSquelchCodeEnabled()) + { + snprintf(&m_formatStatusText[14], 82-14, "%03d", getDecoder().getYSFDecoder().getFICH().getSquelchCode()); + } + else + { + strncpy(&m_formatStatusText[14], "---", 82-14); + } + + char dest[13]; + + if (getDecoder().getYSFDecoder().radioIdMode()) + { + snprintf(dest, 12, "%-5s:%-5s", + getDecoder().getYSFDecoder().getDestId(), + getDecoder().getYSFDecoder().getSrcId()); + } + else + { + snprintf(dest, 11, "%-10s", getDecoder().getYSFDecoder().getDest()); + } + + snprintf(&m_formatStatusText[17], 82-17, "|%-10s>%s|%-10s>%-10s|%-5s", + getDecoder().getYSFDecoder().getSrc(), + dest, + getDecoder().getYSFDecoder().getUplink(), + getDecoder().getYSFDecoder().getDownlink(), + getDecoder().getYSFDecoder().getRem4()); + + m_signalFormat = signalFormatYSF; + break; + default: + m_signalFormat = signalFormatNone; + m_formatStatusText[0] = '\0'; + break; + } + + m_formatStatusText[82] = '\0'; // guard +} diff --git a/plugins/channelrx/demoddsd/dsddemodsink.h b/plugins/channelrx/demoddsd/dsddemodsink.h new file mode 100644 index 000000000..f242833e3 --- /dev/null +++ b/plugins/channelrx/demoddsd/dsddemodsink.h @@ -0,0 +1,150 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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 // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License 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_DSDDEMODSINK_H +#define INCLUDE_DSDDEMODSINK_H + +#include "dsp/channelsamplesink.h" +#include "dsp/phasediscri.h" +#include "dsp/nco.h" +#include "dsp/interpolator.h" +#include "dsp/lowpass.h" +#include "dsp/bandpass.h" +#include "dsp/afsquelch.h" +#include "dsp/afsquelch.h" +#include "audio/audiofifo.h" +#include "util/movingaverage.h" +#include "util/doublebufferfifo.h" + +#include "dsddemodsettings.h" +#include "dsddecoder.h" + +class BasebandSampleSink; + +class DSDDemodSink : public ChannelSampleSink { +public: + DSDDemodSink(); + ~DSDDemodSink(); + + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); + + void applyAudioSampleRate(int sampleRate); + void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false); + void applySettings(const DSDDemodSettings& settings, bool force = false); + AudioFifo *getAudioFifo1() { return &m_audioFifo1; } + AudioFifo *getAudioFifo2() { return &m_audioFifo2; } + unsigned int getAudioSampleRate() const { return m_audioSampleRate; } + + void setScopeXYSink(BasebandSampleSink* scopeSink) { m_scopeXY = scopeSink; } + void configureMyPosition(float myLatitude, float myLongitude); + + double getMagSq() { return m_magsq; } + bool getSquelchOpen() const { return m_squelchOpen; } + + const DSDDecoder& getDecoder() const { return m_dsdDecoder; } + + void getMagSqLevels(double& avg, double& peak, int& nbSamples) + { + if (m_magsqCount > 0) + { + m_magsq = m_magsqSum / m_magsqCount; + m_magSqLevelStore.m_magsq = m_magsq; + m_magSqLevelStore.m_magsqPeak = m_magsqPeak; + } + + avg = m_magSqLevelStore.m_magsq; + peak = m_magSqLevelStore.m_magsqPeak; + nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; + + m_magsqSum = 0.0f; + m_magsqPeak = 0.0f; + m_magsqCount = 0; + } + + const char *updateAndGetStatusText(); + +private: + struct MagSqLevelsStore + { + MagSqLevelsStore() : + m_magsq(1e-12), + m_magsqPeak(1e-12) + {} + double m_magsq; + double m_magsqPeak; + }; + + typedef enum + { + signalFormatNone, + signalFormatDMR, + signalFormatDStar, + signalFormatDPMR, + signalFormatYSF, + signalFormatNXDN + } SignalFormat; //!< Used for status text formatting + + enum RateState { + RSInitialFill, + RSRunning + }; + + int m_channelSampleRate; + int m_channelFrequencyOffset; + DSDDemodSettings m_settings; + quint32 m_audioSampleRate; + + NCO m_nco; + Interpolator m_interpolator; + Real m_interpolatorDistance; + Real m_interpolatorDistanceRemain; + int m_sampleCount; + int m_squelchCount; + int m_squelchGate; + double m_squelchLevel; + bool m_squelchOpen; + DoubleBufferFIFO m_squelchDelayLine; + + MovingAverageUtil m_movingAverage; + double m_magsq; + double m_magsqSum; + double m_magsqPeak; + int m_magsqCount; + MagSqLevelsStore m_magSqLevelStore; + + SampleVector m_scopeSampleBuffer; + AudioVector m_audioBuffer; + uint m_audioBufferFill; + FixReal *m_sampleBuffer; //!< samples ring buffer + int m_sampleBufferIndex; + int m_scaleFromShort; + + AudioFifo m_audioFifo1; + AudioFifo m_audioFifo2; + BasebandSampleSink* m_scopeXY; + bool m_scopeEnabled; + + DSDDecoder m_dsdDecoder; + + char m_formatStatusText[82+1]; //!< Fixed signal format dependent status text + SignalFormat m_signalFormat; //!< Used to keep formatting during successive calls for the same standard type + PhaseDiscriminators m_phaseDiscri; + + void formatStatusText(); +}; + +#endif // INCLUDE_DSDDEMODSINK_H \ No newline at end of file diff --git a/plugins/channelrx/demodnfm/CMakeLists.txt b/plugins/channelrx/demodnfm/CMakeLists.txt index 7ab2e4dd3..450fbe549 100644 --- a/plugins/channelrx/demodnfm/CMakeLists.txt +++ b/plugins/channelrx/demodnfm/CMakeLists.txt @@ -4,14 +4,20 @@ set(nfm_SOURCES nfmdemod.cpp nfmdemodsettings.cpp nfmdemodwebapiadapter.cpp - nfmplugin.cpp + nfmplugin.cpp + nfmdemodreport.cpp + nfmdemodsink.cpp + nfmdemodbaseband.cpp ) set(nfm_HEADERS nfmdemod.h nfmdemodsettings.h nfmdemodwebapiadapter.h - nfmplugin.h + nfmplugin.h + nfmdemodreport.h + nfmdemodsink.h + nfmdemodbaseband.h ) include_directories( diff --git a/plugins/channelrx/demodnfm/nfmdemod.cpp b/plugins/channelrx/demodnfm/nfmdemod.cpp index 10787cf65..caeb18ba7 100644 --- a/plugins/channelrx/demodnfm/nfmdemod.cpp +++ b/plugins/channelrx/demodnfm/nfmdemod.cpp @@ -24,81 +24,43 @@ #include #include #include +#include #include "SWGChannelSettings.h" #include "SWGNFMDemodSettings.h" #include "SWGChannelReport.h" #include "SWGNFMDemodReport.h" -#include "dsp/downchannelizer.h" -#include "util/stepfunctions.h" -#include "util/db.h" -#include "audio/audiooutput.h" #include "dsp/dspengine.h" -#include "dsp/threadedbasebandsamplesink.h" #include "dsp/dspcommands.h" #include "dsp/devicesamplemimo.h" #include "device/deviceapi.h" +#include "util/db.h" #include "nfmdemod.h" MESSAGE_CLASS_DEFINITION(NFMDemod::MsgConfigureNFMDemod, Message) -MESSAGE_CLASS_DEFINITION(NFMDemod::MsgConfigureChannelizer, Message) -MESSAGE_CLASS_DEFINITION(NFMDemod::MsgReportCTCSSFreq, Message) const QString NFMDemod::m_channelIdURI = "sdrangel.channel.nfmdemod"; const QString NFMDemod::m_channelId = "NFMDemod"; -static const double afSqTones[2] = {1000.0, 6000.0}; // {1200.0, 8000.0}; -static const double afSqTones_lowrate[2] = {1000.0, 3500.0}; const int NFMDemod::m_udpBlockSize = 512; NFMDemod::NFMDemod(DeviceAPI *devieAPI) : ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink), m_deviceAPI(devieAPI), - m_inputSampleRate(48000), - m_inputFrequencyOffset(0), - m_running(false), - m_ctcssIndex(0), - m_sampleCount(0), - m_squelchCount(0), - m_squelchGate(4800), - m_squelchLevel(-990), - m_squelchOpen(false), - m_afSquelchOpen(false), - m_magsq(0.0f), - m_magsqSum(0.0f), - m_magsqPeak(0.0f), - m_magsqCount(0), - m_afSquelch(), - m_squelchDelayLine(24000), - m_audioFifo(48000), - m_settingsMutex(QMutex::Recursive) + m_basebandSampleRate(0) { qDebug("NFMDemod::NFMDemod"); setObjectName(m_channelId); - m_audioBuffer.resize(1<<14); - m_audioBufferFill = 0; + m_thread = new QThread(this); + m_basebandSink = new NFMDemodBaseband(); + m_basebandSink->moveToThread(m_thread); - m_agcLevel = 1.0; - - DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo, getInputMessageQueue()); - m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate(); - m_discriCompensation = (m_audioSampleRate/48000.0f); - m_discriCompensation *= sqrt(m_discriCompensation); - - m_ctcssDetector.setCoefficients(m_audioSampleRate/16, m_audioSampleRate/8.0f); // 0.5s / 2 Hz resolution - m_afSquelch.setCoefficients(m_audioSampleRate/2000, 600, m_audioSampleRate, 200, 0, afSqTones); // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay - - m_ctcssLowpass.create(301, m_audioSampleRate, 250.0); - - applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); applySettings(m_settings, true); - m_channelizer = new DownChannelizer(this); - m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); - m_deviceAPI->addChannelSink(m_threadedChannelizer); + m_deviceAPI->addChannelSink(this); m_deviceAPI->addChannelSinkAPI(this); m_networkManager = new QNetworkAccessManager(); @@ -109,43 +71,10 @@ NFMDemod::~NFMDemod() { disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); delete m_networkManager; - DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo); m_deviceAPI->removeChannelSinkAPI(this); - m_deviceAPI->removeChannelSink(m_threadedChannelizer); - delete m_threadedChannelizer; - delete m_channelizer; -} - -float arctan2(Real y, Real x) -{ - Real coeff_1 = M_PI / 4; - Real coeff_2 = 3 * coeff_1; - Real abs_y = fabs(y) + 1e-10; // kludge to prevent 0/0 condition - Real angle; - if( x>= 0) { - Real r = (x - abs_y) / (x + abs_y); - angle = coeff_1 - coeff_1 * r; - } else { - Real r = (x + abs_y) / (abs_y - x); - angle = coeff_2 - coeff_1 * r; - } - if(y < 0) { - return(-angle); - } else { - return(angle); - } -} - -Real angleDist(Real a, Real b) -{ - Real dist = b - a; - - while(dist <= M_PI) - dist += 2 * M_PI; - while(dist >= M_PI) - dist -= 2 * M_PI; - - return dist; + m_deviceAPI->removeChannelSink(this); + delete m_basebandSink; + delete m_thread; } uint32_t NFMDemod::getNumberOfDeviceStreams() const @@ -156,245 +85,31 @@ uint32_t NFMDemod::getNumberOfDeviceStreams() const void NFMDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst) { (void) firstOfBurst; - Complex ci; - - if (!m_running) { - return; - } - - m_settingsMutex.lock(); - - for (SampleVector::const_iterator it = begin; it != end; ++it) - { - Complex c(it->real(), it->imag()); - c *= m_nco.nextIQ(); - - if (m_interpolatorDistance < 1.0f) // interpolate - { - while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci)) - { - processOneSample(ci); - m_interpolatorDistanceRemain += m_interpolatorDistance; - } - } - else // decimate - { - if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci)) - { - processOneSample(ci); - m_interpolatorDistanceRemain += m_interpolatorDistance; - } - } - } - - m_settingsMutex.unlock(); -} - -void NFMDemod::processOneSample(Complex &ci) -{ - qint16 sample; - - double magsqRaw; // = ci.real()*ci.real() + c.imag()*c.imag(); - Real deviation; - - Real demod = m_phaseDiscri.phaseDiscriminatorDelta(ci, magsqRaw, deviation); - - Real magsq = magsqRaw / (SDR_RX_SCALED*SDR_RX_SCALED); - m_movingAverage(magsq); - m_magsqSum += magsq; - - if (magsq > m_magsqPeak) - { - m_magsqPeak = magsq; - } - - m_magsqCount++; - m_sampleCount++; - - // AF processing - - if (m_settings.m_deltaSquelch) - { - if (m_afSquelch.analyze(demod * m_discriCompensation)) - { - m_afSquelchOpen = m_afSquelch.evaluate(); // ? m_squelchGate + m_squelchDecay : 0; - - if (!m_afSquelchOpen) { - m_squelchDelayLine.zeroBack(m_audioSampleRate/10); // zero out evaluation period - } - } - - if (m_afSquelchOpen) - { - m_squelchDelayLine.write(demod * m_discriCompensation); - - if (m_squelchCount < 2*m_squelchGate) { - m_squelchCount++; - } - } - else - { - m_squelchDelayLine.write(0); - - if (m_squelchCount > 0) { - m_squelchCount--; - } - } - } - else - { - if ((Real) m_movingAverage < m_squelchLevel) - { - m_squelchDelayLine.write(0); - - if (m_squelchCount > 0) { - m_squelchCount--; - } - } - else - { - m_squelchDelayLine.write(demod * m_discriCompensation); - - if (m_squelchCount < 2*m_squelchGate) { - m_squelchCount++; - } - } - } - - m_squelchOpen = (m_squelchCount > m_squelchGate); - - if (m_settings.m_audioMute) - { - sample = 0; - } - else - { - if (m_squelchOpen) - { - if (m_settings.m_ctcssOn) - { - Real ctcss_sample = m_ctcssLowpass.filter(demod * m_discriCompensation); - - if ((m_sampleCount & 7) == 7) // decimate 48k -> 6k - { - if (m_ctcssDetector.analyze(&ctcss_sample)) - { - int maxToneIndex; - - if (m_ctcssDetector.getDetectedTone(maxToneIndex)) - { - if (maxToneIndex+1 != m_ctcssIndex) - { - if (getMessageQueueToGUI()) { - MsgReportCTCSSFreq *msg = MsgReportCTCSSFreq::create(m_ctcssDetector.getToneSet()[maxToneIndex]); - getMessageQueueToGUI()->push(msg); - } - m_ctcssIndex = maxToneIndex+1; - } - } - else - { - if (m_ctcssIndex != 0) - { - if (getMessageQueueToGUI()) { - MsgReportCTCSSFreq *msg = MsgReportCTCSSFreq::create(0); - getMessageQueueToGUI()->push(msg); - } - m_ctcssIndex = 0; - } - } - } - } - } - - if (m_settings.m_ctcssOn && m_ctcssIndexSelected && (m_ctcssIndexSelected != m_ctcssIndex)) - { - sample = 0; - } - else - { - if (m_settings.m_highPass) { - sample = m_bandpass.filter(m_squelchDelayLine.readBack(m_squelchGate)) * m_settings.m_volume; - } else { - sample = m_lowpass.filter(m_squelchDelayLine.readBack(m_squelchGate)) * m_settings.m_volume * 301.0f; - } - } - } - else - { - if (m_ctcssIndex != 0) - { - if (getMessageQueueToGUI()) { - MsgReportCTCSSFreq *msg = MsgReportCTCSSFreq::create(0); - getMessageQueueToGUI()->push(msg); - } - - m_ctcssIndex = 0; - } - - 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); - - if (res != m_audioBufferFill) { - qDebug("NFMDemod::feed: %u/%u audio samples written", res, m_audioBufferFill); - } - - m_audioBufferFill = 0; - } + m_basebandSink->feed(begin, end); } void NFMDemod::start() { qDebug() << "NFMDemod::start"; - m_squelchCount = 0; - m_audioFifo.clear(); - m_phaseDiscri.reset(); - applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); - m_running = true; + + if (m_basebandSampleRate != 0) { + m_basebandSink->setBasebandSampleRate(m_basebandSampleRate); + } + + m_basebandSink->reset(); + m_thread->start(); } void NFMDemod::stop() { qDebug() << "NFMDemod::stop"; - m_running = false; + m_thread->exit(); + m_thread->wait(); } bool NFMDemod::handleMessage(const Message& cmd) { - if (DownChannelizer::MsgChannelizerNotification::match(cmd)) - { - DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd; - qDebug() << "NFMDemod::handleMessage: DownChannelizer::MsgChannelizerNotification"; - - applyChannelSettings(notif.getSampleRate(), notif.getFrequencyOffset()); - - return true; - } - else if (MsgConfigureChannelizer::match(cmd)) - { - MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; - - qDebug() << "NFMDemod::handleMessage: MsgConfigureChannelizer:" - << " sampleRate: " << cfg.getSampleRate() - << " centerFrequency: " << cfg.getCenterFrequency(); - - m_channelizer->configure(m_channelizer->getInputMessageQueue(), - cfg.getSampleRate(), - cfg.getCenterFrequency()); - - return true; - } - else if (MsgConfigureNFMDemod::match(cmd)) + if (MsgConfigureNFMDemod::match(cmd)) { MsgConfigureNFMDemod& cfg = (MsgConfigureNFMDemod&) cmd; qDebug() << "NFMDemod::handleMessage: MsgConfigureNFMDemod"; @@ -403,29 +118,15 @@ bool NFMDemod::handleMessage(const Message& cmd) return true; } - else if (BasebandSampleSink::MsgThreadedSink::match(cmd)) - { - BasebandSampleSink::MsgThreadedSink& cfg = (BasebandSampleSink::MsgThreadedSink&) cmd; - const QThread *thread = cfg.getThread(); - qDebug("NFMDemod::handleMessage: BasebandSampleSink::MsgThreadedSink: %p", thread); - return true; - } - else if (DSPConfigureAudio::match(cmd)) - { - DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd; - uint32_t sampleRate = cfg.getSampleRate(); - - qDebug() << "NFMDemod::handleMessage: DSPConfigureAudio:" - << " sampleRate: " << sampleRate; - - if (sampleRate != m_audioSampleRate) { - applyAudioSampleRate(sampleRate); - } - - return true; - } else if (DSPSignalNotification::match(cmd)) { + DSPSignalNotification& notif = (DSPSignalNotification&) cmd; + m_basebandSampleRate = notif.getSampleRate(); + // Forward to the sink + DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy + qDebug() << "NFMDemod::handleMessage: DSPSignalNotification"; + m_basebandSink->getInputMessageQueue()->push(rep); + return true; } else @@ -434,70 +135,6 @@ bool NFMDemod::handleMessage(const Message& cmd) } } -void NFMDemod::applyAudioSampleRate(int sampleRate) -{ - qDebug("NFMDemod::applyAudioSampleRate: %d", sampleRate); - - MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( - sampleRate, m_settings.m_inputFrequencyOffset); - m_inputMessageQueue.push(channelConfigMsg); - - m_settingsMutex.lock(); - - m_interpolator.create(16, m_inputSampleRate, m_settings.m_rfBandwidth / 2.2f); - m_interpolatorDistanceRemain = 0; - m_interpolatorDistance = (Real) m_inputSampleRate / (Real) sampleRate; - m_ctcssLowpass.create(301, sampleRate, 250.0); - m_bandpass.create(301, sampleRate, 300.0, m_settings.m_afBandwidth); - m_lowpass.create(301, sampleRate, m_settings.m_afBandwidth); - m_squelchGate = (sampleRate / 100) * m_settings.m_squelchGate; // gate is given in 10s of ms at 48000 Hz audio sample rate - m_squelchCount = 0; // reset squelch open counter - m_ctcssDetector.setCoefficients(sampleRate/16, sampleRate/8.0f); // 0.5s / 2 Hz resolution - - if (sampleRate < 16000) { - m_afSquelch.setCoefficients(sampleRate/2000, 600, sampleRate, 200, 0, afSqTones_lowrate); // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay - - } else { - m_afSquelch.setCoefficients(sampleRate/2000, 600, sampleRate, 200, 0, afSqTones); // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay - } - - m_discriCompensation = (sampleRate/48000.0f); - m_discriCompensation *= sqrt(m_discriCompensation); - - m_phaseDiscri.setFMScaling(sampleRate / static_cast(m_settings.m_fmDeviation)); - m_audioFifo.setSize(sampleRate); - m_squelchDelayLine.resize(sampleRate/2); - - m_settingsMutex.unlock(); - - m_audioSampleRate = sampleRate; -} - -void NFMDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force) -{ - qDebug() << "NFMDemod::applyChannelSettings:" - << " inputSampleRate: " << inputSampleRate - << " inputFrequencyOffset: " << inputFrequencyOffset; - - if ((inputFrequencyOffset != m_inputFrequencyOffset) || - (inputSampleRate != m_inputSampleRate) || force) - { - m_nco.setFreq(-inputFrequencyOffset, inputSampleRate); - } - - if ((inputSampleRate != m_inputSampleRate) || force) - { - m_settingsMutex.lock(); - m_interpolator.create(16, inputSampleRate, m_settings.m_rfBandwidth / 2.2f); - m_interpolatorDistanceRemain = 0; - m_interpolatorDistance = (Real) inputSampleRate / (Real) m_audioSampleRate; - m_settingsMutex.unlock(); - } - - m_inputSampleRate = inputSampleRate; - m_inputFrequencyOffset = inputFrequencyOffset; -} - void NFMDemod::applySettings(const NFMDemodSettings& settings, bool force) { qDebug() << "NFMDemod::applySettings:" @@ -542,86 +179,32 @@ void NFMDemod::applySettings(const NFMDemodSettings& settings, bool force) if ((settings.m_title != m_settings.m_title) || force) { reverseAPIKeys.append("title"); } - - if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) - { + if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) { reverseAPIKeys.append("rfBandwidth"); - m_settingsMutex.lock(); - m_interpolator.create(16, m_inputSampleRate, settings.m_rfBandwidth / 2.2); - m_interpolatorDistanceRemain = 0; - m_interpolatorDistance = (Real) m_inputSampleRate / (Real) m_audioSampleRate; - m_settingsMutex.unlock(); } - - if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) - { + if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) { reverseAPIKeys.append("fmDeviation"); - m_phaseDiscri.setFMScaling((8.0f*m_audioSampleRate) / static_cast(settings.m_fmDeviation)); // integrate 4x factor } - - if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || force) - { + if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || force) { reverseAPIKeys.append("afBandwidth"); - m_settingsMutex.lock(); - m_bandpass.create(301, m_audioSampleRate, 300.0, settings.m_afBandwidth); - m_lowpass.create(301, m_audioSampleRate, settings.m_afBandwidth); - m_settingsMutex.unlock(); } - - if ((settings.m_squelchGate != m_settings.m_squelchGate) || force) - { + if ((settings.m_squelchGate != m_settings.m_squelchGate) || force) { reverseAPIKeys.append("squelchGate"); - m_squelchGate = (m_audioSampleRate / 100) * settings.m_squelchGate; // gate is given in 10s of ms at 48000 Hz audio sample rate - m_squelchCount = 0; // reset squelch open counter } - if ((settings.m_squelch != m_settings.m_squelch) || force) { reverseAPIKeys.append("squelch"); } if ((settings.m_deltaSquelch != m_settings.m_deltaSquelch) || force) { reverseAPIKeys.append("deltaSquelch"); } - - if ((settings.m_squelch != m_settings.m_squelch) || - (settings.m_deltaSquelch != m_settings.m_deltaSquelch) || force) - { - if (settings.m_deltaSquelch) - { // input is a value in negative centis - m_squelchLevel = (- settings.m_squelch) / 100.0; - m_afSquelch.setThreshold(m_squelchLevel); - m_afSquelch.reset(); - } - else - { // input is a value in deci-Bels - m_squelchLevel = std::pow(10.0, settings.m_squelch / 10.0); - m_movingAverage.reset(); - } - - m_squelchCount = 0; // reset squelch open counter - } - - if ((settings.m_ctcssIndex != m_settings.m_ctcssIndex) || force) - { + if ((settings.m_ctcssIndex != m_settings.m_ctcssIndex) || force) { reverseAPIKeys.append("ctcssIndex"); - setSelectedCtcssIndex(settings.m_ctcssIndex); } - if ((settings.m_highPass != m_settings.m_highPass) || force) { reverseAPIKeys.append("highPass"); } - - if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) - { + if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) { reverseAPIKeys.append("audioDeviceName"); - AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); - int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName); - //qDebug("AMDemod::applySettings: audioDeviceName: %s audioDeviceIndex: %d", qPrintable(settings.m_audioDeviceName), audioDeviceIndex); - audioDeviceManager->addAudioSink(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex); - uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex); - - if (m_audioSampleRate != audioSampleRate) { - applyAudioSampleRate(audioSampleRate); - } } if (m_settings.m_streamIndex != settings.m_streamIndex) @@ -629,16 +212,18 @@ void NFMDemod::applySettings(const NFMDemodSettings& settings, bool force) if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only { m_deviceAPI->removeChannelSinkAPI(this, m_settings.m_streamIndex); - m_deviceAPI->removeChannelSink(m_threadedChannelizer, m_settings.m_streamIndex); - m_deviceAPI->addChannelSink(m_threadedChannelizer, settings.m_streamIndex); + m_deviceAPI->removeChannelSink(this, m_settings.m_streamIndex); + m_deviceAPI->addChannelSink(this, settings.m_streamIndex); m_deviceAPI->addChannelSinkAPI(this, settings.m_streamIndex); - // apply stream sample rate to itself - applyChannelSettings(m_deviceAPI->getSampleMIMO()->getSourceSampleRate(settings.m_streamIndex), m_inputFrequencyOffset); } reverseAPIKeys.append("streamIndex"); } + + NFMDemodBaseband::MsgConfigureNFMDemodBaseband *msg = NFMDemodBaseband::MsgConfigureNFMDemodBaseband::create(settings, force); + m_basebandSink->getInputMessageQueue()->push(msg); + if (settings.m_useReverseAPI) { bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || @@ -667,10 +252,6 @@ bool NFMDemod::deserialize(const QByteArray& data) success = false; } - NFMDemod::MsgConfigureChannelizer* channelConfigMsg = NFMDemod::MsgConfigureChannelizer::create( - m_audioSampleRate, m_settings.m_inputFrequencyOffset); - m_inputMessageQueue.push(channelConfigMsg); - MsgConfigureNFMDemod *msg = MsgConfigureNFMDemod::create(m_settings, true); m_inputMessageQueue.push(msg); @@ -698,13 +279,6 @@ int NFMDemod::webapiSettingsPutPatch( NFMDemodSettings settings = m_settings; webapiUpdateChannelSettings(settings, channelSettingsKeys, response); - if (settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) - { - MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( - m_audioSampleRate, settings.m_inputFrequencyOffset); - m_inputMessageQueue.push(channelConfigMsg); - } - MsgConfigureNFMDemod *msg = MsgConfigureNFMDemod::create(settings, force); m_inputMessageQueue.push(msg); @@ -849,10 +423,20 @@ void NFMDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples); response.getNfmDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg)); - response.getNfmDemodReport()->setCtcssTone(m_settings.m_ctcssOn ? (m_ctcssIndex ? 0 : m_ctcssDetector.getToneSet()[m_ctcssIndex-1]) : 0); - response.getNfmDemodReport()->setSquelch(m_squelchOpen ? 1 : 0); - response.getNfmDemodReport()->setAudioSampleRate(m_audioSampleRate); - response.getNfmDemodReport()->setChannelSampleRate(m_inputSampleRate); + int nbCtcssToneFrequencies; + const Real *ctcssToneFrequencies = m_basebandSink->getCtcssToneSet(nbCtcssToneFrequencies); + response.getNfmDemodReport()->setCtcssTone( + m_settings.m_ctcssOn ? + m_settings.m_ctcssIndex < 0 ? + 0 + : m_settings.m_ctcssIndex < nbCtcssToneFrequencies ? + ctcssToneFrequencies[m_settings.m_ctcssIndex-1] + : 0 + : 0 + ); + response.getNfmDemodReport()->setSquelch(m_basebandSink->getSquelchOpen() ? 1 : 0); + response.getNfmDemodReport()->setAudioSampleRate(m_basebandSink->getAudioSampleRate()); + response.getNfmDemodReport()->setChannelSampleRate(m_basebandSink->getChannelSampleRate()); } void NFMDemod::webapiReverseSendSettings(QList& channelSettingsKeys, const NFMDemodSettings& settings, bool force) diff --git a/plugins/channelrx/demodnfm/nfmdemod.h b/plugins/channelrx/demodnfm/nfmdemod.h index 3b03d5cb2..b09ce6e13 100644 --- a/plugins/channelrx/demodnfm/nfmdemod.h +++ b/plugins/channelrx/demodnfm/nfmdemod.h @@ -16,36 +16,24 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// -#ifndef INCLUDE_NFMDEMOD_H -#define INCLUDE_NFMDEMOD_H +#ifndef INCLUDE_NFMTESTDEMOD_H +#define INCLUDE_NFMTESTDEMOD_H #include -#include #include #include "dsp/basebandsamplesink.h" #include "channel/channelapi.h" -#include "dsp/phasediscri.h" -#include "dsp/nco.h" -#include "dsp/interpolator.h" -#include "dsp/lowpass.h" -#include "dsp/bandpass.h" -#include "dsp/afsquelch.h" -#include "dsp/agc.h" -#include "dsp/ctcssdetector.h" -#include "audio/audiofifo.h" #include "util/message.h" -#include "util/movingaverage.h" -#include "util/doublebufferfifo.h" +#include "nfmdemodbaseband.h" #include "nfmdemodsettings.h" class QNetworkAccessManager; class QNetworkReply; +class QThread; class DeviceAPI; -class ThreadedBasebandSampleSink; -class DownChannelizer; class NFMDemod : public BasebandSampleSink, public ChannelAPI { Q_OBJECT @@ -73,54 +61,11 @@ public: { } }; - 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) - { } - }; - - class MsgReportCTCSSFreq : public Message { - MESSAGE_CLASS_DECLARATION - - public: - Real getFrequency() const { return m_freq; } - - static MsgReportCTCSSFreq* create(Real freq) - { - return new MsgReportCTCSSFreq(freq); - } - - private: - Real m_freq; - - MsgReportCTCSSFreq(Real freq) : - Message(), - m_freq(freq) - { } - }; - NFMDemod(DeviceAPI *deviceAPI); ~NFMDemod(); virtual void destroy() { delete this; } - virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool po); + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positive); virtual void start(); virtual void stop(); virtual bool handleMessage(const Message& cmd); @@ -165,35 +110,11 @@ public: const QStringList& channelSettingsKeys, SWGSDRangel::SWGChannelSettings& response); - const Real *getCtcssToneSet(int& nbTones) const { - nbTones = m_ctcssDetector.getNTones(); - return m_ctcssDetector.getToneSet(); - } - - void setSelectedCtcssIndex(int selectedCtcssIndex) { - m_ctcssIndexSelected = selectedCtcssIndex; - } - - Real getMag() { return m_magsq; } - bool getSquelchOpen() const { return m_squelchOpen; } - - void getMagSqLevels(double& avg, double& peak, int& nbSamples) - { - if (m_magsqCount > 0) - { - m_magsq = m_magsqSum / m_magsqCount; - m_magSqLevelStore.m_magsq = m_magsq; - m_magSqLevelStore.m_magsqPeak = m_magsqPeak; - } - - avg = m_magSqLevelStore.m_magsq; - peak = m_magSqLevelStore.m_magsqPeak; - nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; - - m_magsqSum = 0.0f; - m_magsqPeak = 0.0f; - m_magsqCount = 0; - } + const Real *getCtcssToneSet(int& nbTones) const { m_basebandSink->getCtcssToneSet(nbTones); } + void setSelectedCtcssIndex(int selectedCtcssIndex) { m_basebandSink->setSelectedCtcssIndex(selectedCtcssIndex); } + bool getSquelchOpen() const { return m_basebandSink->getSquelchOpen(); } + void getMagSqLevels(double& avg, double& peak, int& nbSamples) { m_basebandSink->getMagSqLevels(avg, peak, nbSamples); } + void propagateMessageQueueToGUI() { m_basebandSink->setMessageQueueToGUI(getMessageQueueToGUI()); } uint32_t getNumberOfDeviceStreams() const; @@ -201,82 +122,26 @@ public: static const QString m_channelId; private: - struct MagSqLevelsStore - { - MagSqLevelsStore() : - m_magsq(1e-12), - m_magsqPeak(1e-12) - {} - double m_magsq; - double m_magsqPeak; + enum RateState { + RSInitialFill, + RSRunning }; - enum RateState { - RSInitialFill, - RSRunning - }; - DeviceAPI* m_deviceAPI; - ThreadedBasebandSampleSink* m_threadedChannelizer; - DownChannelizer* m_channelizer; - - int m_inputSampleRate; - int m_inputFrequencyOffset; + QThread *m_thread; + NFMDemodBaseband* m_basebandSink; NFMDemodSettings m_settings; - uint32_t m_audioSampleRate; - float m_discriCompensation; //!< compensation factor that depends on audio rate (1 for 48 kS/s) - bool m_running; - - NCO m_nco; - Interpolator m_interpolator; - Real m_interpolatorDistance; - Real m_interpolatorDistanceRemain; - Lowpass m_ctcssLowpass; - Bandpass m_bandpass; - Lowpass m_lowpass; - CTCSSDetector m_ctcssDetector; - int m_ctcssIndex; // 0 for nothing detected - int m_ctcssIndexSelected; - int m_sampleCount; - int m_squelchCount; - int m_squelchGate; - - Real m_squelchLevel; - bool m_squelchOpen; - bool m_afSquelchOpen; - double m_magsq; //!< displayed averaged value - double m_magsqSum; - double m_magsqPeak; - int m_magsqCount; - MagSqLevelsStore m_magSqLevelStore; - - MovingAverageUtil m_movingAverage; - AFSquelch m_afSquelch; - Real m_agcLevel; // AGC will aim to this level - DoubleBufferFIFO m_squelchDelayLine; - - AudioVector m_audioBuffer; - uint m_audioBufferFill; - AudioFifo m_audioFifo; - - QMutex m_settingsMutex; - - PhaseDiscriminators m_phaseDiscri; + int m_basebandSampleRate; //!< stored from device message used when starting baseband sink QNetworkAccessManager *m_networkManager; QNetworkRequest m_networkRequest; static const int m_udpBlockSize; -// void apply(bool force = false); - void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); void applySettings(const NFMDemodSettings& settings, bool force = false); - void applyAudioSampleRate(int sampleRate); void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); void webapiReverseSendSettings(QList& channelSettingsKeys, const NFMDemodSettings& settings, bool force); - void processOneSample(Complex &ci); - private slots: void networkManagerFinished(QNetworkReply *reply); }; diff --git a/plugins/channelrx/demodnfm/nfmdemodbaseband.cpp b/plugins/channelrx/demodnfm/nfmdemodbaseband.cpp new file mode 100644 index 000000000..099fa12ed --- /dev/null +++ b/plugins/channelrx/demodnfm/nfmdemodbaseband.cpp @@ -0,0 +1,170 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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 // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "dsp/downsamplechannelizer.h" + +#include "nfmdemodbaseband.h" + +MESSAGE_CLASS_DEFINITION(NFMDemodBaseband::MsgConfigureNFMDemodBaseband, Message) + +NFMDemodBaseband::NFMDemodBaseband() : + m_mutex(QMutex::Recursive) +{ + m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000)); + m_channelizer = new DownSampleChannelizer(&m_sink); + + qDebug("NFMDemodBaseband::NFMDemodBaseband"); + QObject::connect( + &m_sampleFifo, + &SampleSinkFifo::dataReady, + this, + &NFMDemodBaseband::handleData, + Qt::QueuedConnection + ); + + DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(m_sink.getAudioFifo(), getInputMessageQueue()); + m_sink.applyAudioSampleRate(DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate()); + + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); +} + +NFMDemodBaseband::~NFMDemodBaseband() +{ + DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(m_sink.getAudioFifo()); + delete m_channelizer; +} + +void NFMDemodBaseband::reset() +{ + QMutexLocker mutexLocker(&m_mutex); + m_sampleFifo.reset(); +} + +void NFMDemodBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) +{ + m_sampleFifo.write(begin, end); +} + +void NFMDemodBaseband::handleData() +{ + while ((m_sampleFifo.fill() > 0) && (m_inputMessageQueue.size() == 0)) + { + SampleVector::iterator part1begin; + SampleVector::iterator part1end; + SampleVector::iterator part2begin; + SampleVector::iterator part2end; + + std::size_t count = m_sampleFifo.readBegin(m_sampleFifo.fill(), &part1begin, &part1end, &part2begin, &part2end); + + // first part of FIFO data + if (part1begin != part1end) { + m_channelizer->feed(part1begin, part1end); + } + + // second part of FIFO data (used when block wraps around) + if(part2begin != part2end) { + m_channelizer->feed(part2begin, part2end); + } + + m_sampleFifo.readCommit((unsigned int) count); + } +} + +void NFMDemodBaseband::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != nullptr) + { + if (handleMessage(*message)) { + delete message; + } + } +} + +bool NFMDemodBaseband::handleMessage(const Message& cmd) +{ + if (MsgConfigureNFMDemodBaseband::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + MsgConfigureNFMDemodBaseband& cfg = (MsgConfigureNFMDemodBaseband&) cmd; + qDebug() << "NFMDemodBaseband::handleMessage: MsgConfigureNFMDemodBaseband"; + + applySettings(cfg.getSettings(), cfg.getForce()); + + return true; + } + else if (DSPSignalNotification::match(cmd)) + { + QMutexLocker mutexLocker(&m_mutex); + DSPSignalNotification& notif = (DSPSignalNotification&) cmd; + qDebug() << "NFMDemodBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate(); + m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate())); + m_channelizer->setBasebandSampleRate(notif.getSampleRate()); + m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); + + return true; + } + else + { + return false; + } +} + +void NFMDemodBaseband::applySettings(const NFMDemodSettings& settings, bool force) +{ + if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) + { + m_channelizer->setChannelization(m_sink.getAudioSampleRate(), settings.m_inputFrequencyOffset); + m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); + } + + if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) + { + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName); + //qDebug("AMDemod::applySettings: audioDeviceName: %s audioDeviceIndex: %d", qPrintable(settings.m_audioDeviceName), audioDeviceIndex); + audioDeviceManager->addAudioSink(m_sink.getAudioFifo(), getInputMessageQueue(), audioDeviceIndex); + uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex); + + if (m_sink.getAudioSampleRate() != audioSampleRate) + { + m_sink.applyAudioSampleRate(audioSampleRate); + m_channelizer->setChannelization(audioSampleRate, settings.m_inputFrequencyOffset); + m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset()); + } + } + + m_sink.applySettings(settings, force); + + m_settings = settings; +} + +int NFMDemodBaseband::getChannelSampleRate() const +{ + return m_channelizer->getChannelSampleRate(); +} + + +void NFMDemodBaseband::setBasebandSampleRate(int sampleRate) +{ + m_channelizer->setBasebandSampleRate(sampleRate); +} \ No newline at end of file diff --git a/plugins/channelrx/demodnfm/nfmdemodbaseband.h b/plugins/channelrx/demodnfm/nfmdemodbaseband.h new file mode 100644 index 000000000..e52127705 --- /dev/null +++ b/plugins/channelrx/demodnfm/nfmdemodbaseband.h @@ -0,0 +1,89 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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 // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License 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_NFMDEMODBASEBAND_H +#define INCLUDE_NFMDEMODBASEBAND_H + +#include +#include + +#include "dsp/samplesinkfifo.h" +#include "util/message.h" +#include "util/messagequeue.h" + +#include "nfmdemodsink.h" + +class DownSampleChannelizer; + +class NFMDemodBaseband : public QObject +{ + Q_OBJECT +public: + class MsgConfigureNFMDemodBaseband : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const NFMDemodSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureNFMDemodBaseband* create(const NFMDemodSettings& settings, bool force) + { + return new MsgConfigureNFMDemodBaseband(settings, force); + } + + private: + NFMDemodSettings m_settings; + bool m_force; + + MsgConfigureNFMDemodBaseband(const NFMDemodSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + NFMDemodBaseband(); + ~NFMDemodBaseband(); + void reset(); + void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); + MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication + int getChannelSampleRate() const; + void getMagSqLevels(double& avg, double& peak, int& nbSamples) { m_sink.getMagSqLevels(avg, peak, nbSamples); } + void setSelectedCtcssIndex(int selectedCtcssIndex) { m_sink.setSelectedCtcssIndex(selectedCtcssIndex); } + bool getSquelchOpen() const { return m_sink.getSquelchOpen(); } + const Real *getCtcssToneSet(int& nbTones) const { return m_sink.getCtcssToneSet(nbTones); } + void setMessageQueueToGUI(MessageQueue *messageQueue) { m_sink.setMessageQueueToGUI(messageQueue); } + unsigned int getAudioSampleRate() const { return m_sink.getAudioSampleRate(); } + void setBasebandSampleRate(int sampleRate); + +private: + SampleSinkFifo m_sampleFifo; + DownSampleChannelizer *m_channelizer; + NFMDemodSink m_sink; + MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication + NFMDemodSettings m_settings; + QMutex m_mutex; + + bool handleMessage(const Message& cmd); + void applySettings(const NFMDemodSettings& settings, bool force = false); + +private slots: + void handleInputMessages(); + void handleData(); //!< Handle data when samples have to be processed +}; + +#endif // INCLUDE_NFMDEMODBASEBAND_H \ No newline at end of file diff --git a/plugins/channelrx/demodnfm/nfmdemodgui.cpp b/plugins/channelrx/demodnfm/nfmdemodgui.cpp index 7f474a314..baba5bb51 100644 --- a/plugins/channelrx/demodnfm/nfmdemodgui.cpp +++ b/plugins/channelrx/demodnfm/nfmdemodgui.cpp @@ -1,5 +1,3 @@ -#include "nfmdemodgui.h" - #include "device/deviceuiset.h" #include #include @@ -15,7 +13,10 @@ #include "gui/audioselectdialog.h" #include "dsp/dspengine.h" #include "mainwindow.h" + +#include "nfmdemodreport.h" #include "nfmdemod.h" +#include "nfmdemodgui.h" NFMDemodGUI* NFMDemodGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) { @@ -75,12 +76,10 @@ bool NFMDemodGUI::deserialize(const QByteArray& data) bool NFMDemodGUI::handleMessage(const Message& message) { - if (NFMDemod::MsgReportCTCSSFreq::match(message)) + if (NFMDemodReport::MsgReportCTCSSFreq::match(message)) { - //qDebug("NFMDemodGUI::handleMessage: NFMDemod::MsgReportCTCSSFreq"); - NFMDemod::MsgReportCTCSSFreq& report = (NFMDemod::MsgReportCTCSSFreq&) message; + NFMDemodReport::MsgReportCTCSSFreq& report = (NFMDemodReport::MsgReportCTCSSFreq&) message; setCtcssFreq(report.getFrequency()); - //qDebug("NFMDemodGUI::handleMessage: MsgReportCTCSSFreq: %f", report.getFrequency()); return true; } else if (NFMDemod::MsgConfigureNFMDemod::match(message)) @@ -366,10 +365,6 @@ void NFMDemodGUI::applySettings(bool force) { qDebug() << "NFMDemodGUI::applySettings"; - NFMDemod::MsgConfigureChannelizer* channelConfigMsg = NFMDemod::MsgConfigureChannelizer::create( - 48000, m_channelMarker.getCenterFrequency()); - m_nfmDemod->getInputMessageQueue()->push(channelConfigMsg); - NFMDemod::MsgConfigureNFMDemod* message = NFMDemod::MsgConfigureNFMDemod::create( m_settings, force); m_nfmDemod->getInputMessageQueue()->push(message); } diff --git a/plugins/channelrx/demodnfm/nfmdemodreport.cpp b/plugins/channelrx/demodnfm/nfmdemodreport.cpp new file mode 100644 index 000000000..b72c40730 --- /dev/null +++ b/plugins/channelrx/demodnfm/nfmdemodreport.cpp @@ -0,0 +1,26 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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 // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "nfmdemodreport.h" + +MESSAGE_CLASS_DEFINITION(NFMDemodReport::MsgReportCTCSSFreq, Message) + +NFMDemodReport::NFMDemodReport() +{ } + +NFMDemodReport::~NFMDemodReport() +{ } \ No newline at end of file diff --git a/plugins/channelrx/demodnfm/nfmdemodreport.h b/plugins/channelrx/demodnfm/nfmdemodreport.h new file mode 100644 index 000000000..8b5c49c9c --- /dev/null +++ b/plugins/channelrx/demodnfm/nfmdemodreport.h @@ -0,0 +1,55 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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 // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_CHANNELRX_DEMONFM_NFMDEMODREPORT_H_ +#define PLUGINS_CHANNELRX_DEMONFM_NFMDEMODREPORT_H_ + +#include + +#include "dsp/dsptypes.h" +#include "util/message.h" + +class NFMDemodReport : public QObject +{ + Q_OBJECT +public: + class MsgReportCTCSSFreq : public Message { + MESSAGE_CLASS_DECLARATION + + public: + Real getFrequency() const { return m_freq; } + + static MsgReportCTCSSFreq* create(Real freq) + { + return new MsgReportCTCSSFreq(freq); + } + + private: + Real m_freq; + + MsgReportCTCSSFreq(Real freq) : + Message(), + m_freq(freq) + { } + }; + +public: + NFMDemodReport(); + ~NFMDemodReport(); +}; + +#endif // PLUGINS_CHANNELRX_DEMONFM_NFMDEMODREPORT_H_ diff --git a/plugins/channelrx/demodnfm/nfmdemodsink.cpp b/plugins/channelrx/demodnfm/nfmdemodsink.cpp new file mode 100644 index 000000000..1a9f09b12 --- /dev/null +++ b/plugins/channelrx/demodnfm/nfmdemodsink.cpp @@ -0,0 +1,385 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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 // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include +#include + +#include "util/stepfunctions.h" +#include "util/db.h" +#include "audio/audiooutput.h" +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "dsp/devicesamplemimo.h" +#include "device/deviceapi.h" + +#include "nfmdemodreport.h" +#include "nfmdemodsink.h" + +const double NFMDemodSink::afSqTones[] = {1000.0, 6000.0}; // {1200.0, 8000.0}; +const double NFMDemodSink::afSqTones_lowrate[] = {1000.0, 3500.0}; + +NFMDemodSink::NFMDemodSink() : + m_channelSampleRate(48000), + m_channelFrequencyOffset(0), + m_audioSampleRate(48000), + m_audioBufferFill(0), + m_audioFifo(48000), + m_ctcssIndex(0), + m_sampleCount(0), + m_squelchCount(0), + m_squelchGate(4800), + m_squelchLevel(-990), + m_squelchOpen(false), + m_afSquelchOpen(false), + m_magsq(0.0f), + m_magsqSum(0.0f), + m_magsqPeak(0.0f), + m_magsqCount(0), + m_afSquelch(), + m_squelchDelayLine(24000), + m_messageQueueToGUI(nullptr) +{ + m_agcLevel = 1.0; + m_audioBuffer.resize(1<<14); + + applySettings(m_settings, true); + applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true); +} + +NFMDemodSink::~NFMDemodSink() +{ +} + +void NFMDemodSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) +{ + Complex ci; + + for (SampleVector::const_iterator it = begin; it != end; ++it) + { + Complex c(it->real(), it->imag()); + c *= m_nco.nextIQ(); + + if (m_interpolatorDistance < 1.0f) // interpolate + { + while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci)) + { + processOneSample(ci); + m_interpolatorDistanceRemain += m_interpolatorDistance; + } + } + else // decimate + { + if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci)) + { + processOneSample(ci); + m_interpolatorDistanceRemain += m_interpolatorDistance; + } + } + } + +} + +void NFMDemodSink::processOneSample(Complex &ci) +{ + qint16 sample; + + double magsqRaw; // = ci.real()*ci.real() + c.imag()*c.imag(); + Real deviation; + + Real demod = m_phaseDiscri.phaseDiscriminatorDelta(ci, magsqRaw, deviation); + + Real magsq = magsqRaw / (SDR_RX_SCALED*SDR_RX_SCALED); + m_movingAverage(magsq); + m_magsqSum += magsq; + + if (magsq > m_magsqPeak) + { + m_magsqPeak = magsq; + } + + m_magsqCount++; + m_sampleCount++; + + // AF processing + + if (m_settings.m_deltaSquelch) + { + if (m_afSquelch.analyze(demod * m_discriCompensation)) + { + m_afSquelchOpen = m_afSquelch.evaluate(); // ? m_squelchGate + m_squelchDecay : 0; + + if (!m_afSquelchOpen) { + m_squelchDelayLine.zeroBack(m_audioSampleRate/10); // zero out evaluation period + } + } + + if (m_afSquelchOpen) + { + m_squelchDelayLine.write(demod * m_discriCompensation); + + if (m_squelchCount < 2*m_squelchGate) { + m_squelchCount++; + } + } + else + { + m_squelchDelayLine.write(0); + + if (m_squelchCount > 0) { + m_squelchCount--; + } + } + } + else + { + if ((Real) m_movingAverage < m_squelchLevel) + { + m_squelchDelayLine.write(0); + + if (m_squelchCount > 0) { + m_squelchCount--; + } + } + else + { + m_squelchDelayLine.write(demod * m_discriCompensation); + + if (m_squelchCount < 2*m_squelchGate) { + m_squelchCount++; + } + } + } + + m_squelchOpen = (m_squelchCount > m_squelchGate); + + if (m_settings.m_audioMute) + { + sample = 0; + } + else + { + if (m_squelchOpen) + { + if (m_settings.m_ctcssOn) + { + Real ctcss_sample = m_ctcssLowpass.filter(demod * m_discriCompensation); + + if ((m_sampleCount & 7) == 7) // decimate 48k -> 6k + { + if (m_ctcssDetector.analyze(&ctcss_sample)) + { + int maxToneIndex; + + if (m_ctcssDetector.getDetectedTone(maxToneIndex)) + { + if (maxToneIndex+1 != m_ctcssIndex) + { + if (getMessageQueueToGUI()) + { + NFMDemodReport::MsgReportCTCSSFreq *msg = NFMDemodReport::MsgReportCTCSSFreq::create(m_ctcssDetector.getToneSet()[maxToneIndex]); + getMessageQueueToGUI()->push(msg); + } + + m_ctcssIndex = maxToneIndex+1; + } + } + else + { + if (m_ctcssIndex != 0) + { + if (getMessageQueueToGUI()) + { + NFMDemodReport::MsgReportCTCSSFreq *msg = NFMDemodReport::MsgReportCTCSSFreq::create(0); + getMessageQueueToGUI()->push(msg); + } + + m_ctcssIndex = 0; + } + } + } + } + } + + if (m_settings.m_ctcssOn && m_ctcssIndexSelected && (m_ctcssIndexSelected != m_ctcssIndex)) + { + sample = 0; + } + else + { + if (m_settings.m_highPass) { + sample = m_bandpass.filter(m_squelchDelayLine.readBack(m_squelchGate)) * m_settings.m_volume; + } else { + sample = m_lowpass.filter(m_squelchDelayLine.readBack(m_squelchGate)) * m_settings.m_volume * 301.0f; + } + } + } + else + { + if (m_ctcssIndex != 0) + { + if (getMessageQueueToGUI()) + { + NFMDemodReport::MsgReportCTCSSFreq *msg = NFMDemodReport::MsgReportCTCSSFreq::create(0); + getMessageQueueToGUI()->push(msg); + } + + m_ctcssIndex = 0; + } + + 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); + + if (res != m_audioBufferFill) + { + qDebug("NFMDemodSink::feed: %u/%u audio samples written", res, m_audioBufferFill); + qDebug("NFMDemodSink::feed: m_audioSampleRate: %u m_channelSampleRate: %d", m_audioSampleRate, m_channelSampleRate); + } + + m_audioBufferFill = 0; + } +} + + +void NFMDemodSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force) +{ + qDebug() << "NFMDemodSink::applyChannelSettings:" + << " channelSampleRate: " << channelSampleRate + << " channelFrequencyOffset: " << channelFrequencyOffset; + + if ((channelFrequencyOffset != m_channelFrequencyOffset) || + (channelSampleRate != m_channelSampleRate) || force) + { + m_nco.setFreq(-channelFrequencyOffset, channelSampleRate); + } + + if ((channelSampleRate != m_channelSampleRate) || force) + { + m_interpolator.create(16, channelSampleRate, m_settings.m_rfBandwidth / 2.2); + m_interpolatorDistanceRemain = 0; + m_interpolatorDistance = (Real) channelSampleRate / (Real) m_audioSampleRate; + } + + m_channelSampleRate = channelSampleRate; + m_channelFrequencyOffset = channelFrequencyOffset; +} + +void NFMDemodSink::applySettings(const NFMDemodSettings& settings, bool force) +{ + qDebug() << "NFMDemodSink::applySettings:" + << " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset + << " m_rfBandwidth: " << settings.m_rfBandwidth + << " m_afBandwidth: " << settings.m_afBandwidth + << " m_fmDeviation: " << settings.m_fmDeviation + << " m_volume: " << settings.m_volume + << " m_squelchGate: " << settings.m_squelchGate + << " m_deltaSquelch: " << settings.m_deltaSquelch + << " m_squelch: " << settings.m_squelch + << " m_ctcssIndex: " << settings.m_ctcssIndex + << " m_ctcssOn: " << settings.m_ctcssOn + << " m_highPass: " << settings.m_highPass + << " m_audioMute: " << settings.m_audioMute + << " m_audioDeviceName: " << settings.m_audioDeviceName + << " force: " << force; + + if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) + { + m_interpolator.create(16, m_channelSampleRate, settings.m_rfBandwidth / 2.2); + m_interpolatorDistanceRemain = 0; + m_interpolatorDistance = (Real) m_channelSampleRate / (Real) m_audioSampleRate; + } + + if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) + { + m_phaseDiscri.setFMScaling((8.0f*m_audioSampleRate) / static_cast(settings.m_fmDeviation)); // integrate 4x factor + } + + if ((settings.m_afBandwidth != m_settings.m_afBandwidth) || force) + { + m_bandpass.create(301, m_audioSampleRate, 300.0, settings.m_afBandwidth); + m_lowpass.create(301, m_audioSampleRate, settings.m_afBandwidth); + } + + if ((settings.m_squelchGate != m_settings.m_squelchGate) || force) + { + m_squelchGate = (m_audioSampleRate / 100) * settings.m_squelchGate; // gate is given in 10s of ms at 48000 Hz audio sample rate + m_squelchCount = 0; // reset squelch open counter + } + + if ((settings.m_squelch != m_settings.m_squelch) || + (settings.m_deltaSquelch != m_settings.m_deltaSquelch) || force) + { + if (settings.m_deltaSquelch) + { // input is a value in negative centis + m_squelchLevel = (- settings.m_squelch) / 100.0; + m_afSquelch.setThreshold(m_squelchLevel); + m_afSquelch.reset(); + } + else + { // input is a value in deci-Bels + m_squelchLevel = std::pow(10.0, settings.m_squelch / 10.0); + m_movingAverage.reset(); + } + + m_squelchCount = 0; // reset squelch open counter + } + + if ((settings.m_ctcssIndex != m_settings.m_ctcssIndex) || force) { + setSelectedCtcssIndex(settings.m_ctcssIndex); + } + + m_settings = settings; +} + +void NFMDemodSink::applyAudioSampleRate(unsigned int sampleRate) +{ + qDebug("NFMDemodSink::applyAudioSampleRate: %u m_channelSampleRate: %d", sampleRate, m_channelSampleRate); + + m_ctcssLowpass.create(301, sampleRate, 250.0); + m_bandpass.create(301, sampleRate, 300.0, m_settings.m_afBandwidth); + m_lowpass.create(301, sampleRate, m_settings.m_afBandwidth); + m_squelchGate = (sampleRate / 100) * m_settings.m_squelchGate; // gate is given in 10s of ms at 48000 Hz audio sample rate + m_squelchCount = 0; // reset squelch open counter + m_ctcssDetector.setCoefficients(sampleRate/16, sampleRate/8.0f); // 0.5s / 2 Hz resolution + + if (sampleRate < 16000) { + m_afSquelch.setCoefficients(sampleRate/2000, 600, sampleRate, 200, 0, afSqTones_lowrate); // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay + + } else { + m_afSquelch.setCoefficients(sampleRate/2000, 600, sampleRate, 200, 0, afSqTones); // 0.5ms test period, 300ms average span, audio SR, 100ms attack, no decay + } + + m_discriCompensation = (sampleRate/48000.0f); + m_discriCompensation *= sqrt(m_discriCompensation); + + m_phaseDiscri.setFMScaling(sampleRate / static_cast(m_settings.m_fmDeviation)); + m_audioFifo.setSize(sampleRate); + m_squelchDelayLine.resize(sampleRate/2); + + m_audioSampleRate = sampleRate; +} \ No newline at end of file diff --git a/plugins/channelrx/demodnfm/nfmdemodsink.h b/plugins/channelrx/demodnfm/nfmdemodsink.h new file mode 100644 index 000000000..4ab646694 --- /dev/null +++ b/plugins/channelrx/demodnfm/nfmdemodsink.h @@ -0,0 +1,180 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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 // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License 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_NFMDEMODSINK_H +#define INCLUDE_NFMDEMODSINK_H + +#include + +#include "dsp/channelsamplesink.h" +#include "dsp/phasediscri.h" +#include "dsp/nco.h" +#include "dsp/interpolator.h" +#include "dsp/lowpass.h" +#include "dsp/bandpass.h" +#include "dsp/afsquelch.h" +#include "dsp/agc.h" +#include "dsp/ctcssdetector.h" +#include "util/movingaverage.h" +#include "util/doublebufferfifo.h" +#include "audio/audiofifo.h" + +#include "nfmdemodsettings.h" + +class NFMDemodSink : public ChannelSampleSink { +public: + NFMDemodSink(); + ~NFMDemodSink(); + + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); + + const Real *getCtcssToneSet(int& nbTones) const { + nbTones = m_ctcssDetector.getNTones(); + return m_ctcssDetector.getToneSet(); + } + + void setSelectedCtcssIndex(int selectedCtcssIndex) { + m_ctcssIndexSelected = selectedCtcssIndex; + } + + bool getSquelchOpen() const { return m_squelchOpen; } + + void getMagSqLevels(double& avg, double& peak, int& nbSamples) + { + if (m_magsqCount > 0) + { + m_magsq = m_magsqSum / m_magsqCount; + m_magSqLevelStore.m_magsq = m_magsq; + m_magSqLevelStore.m_magsqPeak = m_magsqPeak; + } + + avg = m_magSqLevelStore.m_magsq; + peak = m_magSqLevelStore.m_magsqPeak; + nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; + + m_magsqSum = 0.0f; + m_magsqPeak = 0.0f; + m_magsqCount = 0; + } + + void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false); + void applySettings(const NFMDemodSettings& settings, bool force = false); + void setMessageQueueToGUI(MessageQueue *messageQueue) { m_messageQueueToGUI = messageQueue; } + + AudioFifo *getAudioFifo() { return &m_audioFifo; } + void applyAudioSampleRate(unsigned int sampleRate); + unsigned int getAudioSampleRate() const { return m_audioSampleRate; } + +private: + struct MagSqLevelsStore + { + MagSqLevelsStore() : + m_magsq(1e-12), + m_magsqPeak(1e-12) + {} + double m_magsq; + double m_magsqPeak; + }; + + enum RateState { + RSInitialFill, + RSRunning + }; + + int m_channelSampleRate; + int m_channelFrequencyOffset; + NFMDemodSettings m_settings; + + quint32 m_audioSampleRate; + AudioVector m_audioBuffer; + uint m_audioBufferFill; + AudioFifo m_audioFifo; + + float m_discriCompensation; //!< compensation factor that depends on audio rate (1 for 48 kS/s) + + NCO m_nco; + Interpolator m_interpolator; + Real m_interpolatorDistance; + Real m_interpolatorDistanceRemain; + Lowpass m_ctcssLowpass; + Bandpass m_bandpass; + Lowpass m_lowpass; + CTCSSDetector m_ctcssDetector; + int m_ctcssIndex; // 0 for nothing detected + int m_ctcssIndexSelected; + int m_sampleCount; + int m_squelchCount; + int m_squelchGate; + + Real m_squelchLevel; + bool m_squelchOpen; + bool m_afSquelchOpen; + double m_magsq; //!< displayed averaged value + double m_magsqSum; + double m_magsqPeak; + int m_magsqCount; + MagSqLevelsStore m_magSqLevelStore; + + MovingAverageUtil m_movingAverage; + AFSquelch m_afSquelch; + Real m_agcLevel; // AGC will aim to this level + DoubleBufferFIFO m_squelchDelayLine; + + PhaseDiscriminators m_phaseDiscri; + MessageQueue *m_messageQueueToGUI; + + static const double afSqTones[]; + static const double afSqTones_lowrate[]; + + void processOneSample(Complex &ci); + MessageQueue *getMessageQueueToGUI() { return m_messageQueueToGUI; } + + inline float arctan2(Real y, Real x) + { + Real coeff_1 = M_PI / 4; + Real coeff_2 = 3 * coeff_1; + Real abs_y = fabs(y) + 1e-10; // kludge to prevent 0/0 condition + Real angle; + if( x>= 0) { + Real r = (x - abs_y) / (x + abs_y); + angle = coeff_1 - coeff_1 * r; + } else { + Real r = (x + abs_y) / (abs_y - x); + angle = coeff_2 - coeff_1 * r; + } + if(y < 0) { + return(-angle); + } else { + return(angle); + } + } + + inline Real angleDist(Real a, Real b) + { + Real dist = b - a; + + while(dist <= M_PI) + dist += 2 * M_PI; + while(dist >= M_PI) + dist -= 2 * M_PI; + + return dist; + } + +}; + +#endif // INCLUDE_NFMDEMODSINK_H diff --git a/plugins/channelrx/demodnfm/nfmplugin.cpp b/plugins/channelrx/demodnfm/nfmplugin.cpp index d4238a433..50df6b86c 100644 --- a/plugins/channelrx/demodnfm/nfmplugin.cpp +++ b/plugins/channelrx/demodnfm/nfmplugin.cpp @@ -11,7 +11,7 @@ const PluginDescriptor NFMPlugin::m_pluginDescriptor = { QString("NFM Demodulator"), - QString("4.11.6"), + QString("4.12.2"), QString("(c) Edouard Griffiths, F4EXB"), QString("https://github.com/f4exb/sdrangel"), true, diff --git a/sdrbase/CMakeLists.txt b/sdrbase/CMakeLists.txt index c6b5364b3..087f70ca8 100644 --- a/sdrbase/CMakeLists.txt +++ b/sdrbase/CMakeLists.txt @@ -63,9 +63,11 @@ set(sdrbase_SOURCES dsp/afsquelch.cpp dsp/agc.cpp dsp/downchannelizer.cpp + dsp/downsamplechannelizer.cpp dsp/upchannelizer.cpp dsp/channelmarker.cpp dsp/ctcssdetector.cpp + dsp/channelsamplesink.cpp dsp/channelsamplesource.cpp dsp/cwkeyer.cpp dsp/cwkeyersettings.cpp @@ -178,8 +180,10 @@ set(sdrbase_HEADERS dsp/afsquelch.h dsp/autocorrector.h dsp/downchannelizer.h + dsp/downsamplechannelizer.h dsp/upchannelizer.h dsp/channelmarker.h + dsp/channelsamplesink.h dsp/channelsamplesource.h dsp/complex.h dsp/cwkeyer.h diff --git a/sdrbase/device/deviceapi.cpp b/sdrbase/device/deviceapi.cpp index 35e8e8f72..7ce06727e 100644 --- a/sdrbase/device/deviceapi.cpp +++ b/sdrbase/device/deviceapi.cpp @@ -107,6 +107,24 @@ void DeviceAPI::removeChannelSink(ThreadedBasebandSampleSink* sink, int streamIn } } +void DeviceAPI::addChannelSink(BasebandSampleSink* sink, int streamIndex) +{ + if (m_deviceSourceEngine) { + m_deviceSourceEngine->addSink(sink); + } else if (m_deviceMIMOEngine) { + m_deviceMIMOEngine->addChannelSink(sink); + } +} + +void DeviceAPI::removeChannelSink(BasebandSampleSink* sink, int streamIndex) +{ + (void) streamIndex; + + if (m_deviceSourceEngine) { + m_deviceSourceEngine->removeSink(sink); + } +} + void DeviceAPI::addChannelSource(BasebandSampleSource* source, int streamIndex) { if (m_deviceSinkEngine) { diff --git a/sdrbase/device/deviceapi.h b/sdrbase/device/deviceapi.h index d1d168edb..fd006b45f 100644 --- a/sdrbase/device/deviceapi.h +++ b/sdrbase/device/deviceapi.h @@ -73,6 +73,8 @@ public: void addChannelSink(ThreadedBasebandSampleSink* sink, int streamIndex = 0); //!< Add a channel sink (Rx) void removeChannelSink(ThreadedBasebandSampleSink* sink, int streamIndex = 0); //!< Remove a channel sink (Rx) + void addChannelSink(BasebandSampleSink* sink, int streamIndex = 0); //!< Add a channel sink (Rx) + void removeChannelSink(BasebandSampleSink* sink, int streamIndex = 0); //!< Remove a channel sink (Rx) void addChannelSource(BasebandSampleSource* sink, int streamIndex = 0); //!< Add a channel source (Tx) void removeChannelSource(BasebandSampleSource* sink, int streamIndex = 0); //!< Remove a channel source (Tx) void addMIMOChannel(MIMOChannel* channel); //!< Add a MIMO channel (n Rx and m Tx combination) diff --git a/sdrbase/dsp/channelsamplesink.cpp b/sdrbase/dsp/channelsamplesink.cpp new file mode 100644 index 000000000..d4736cf6b --- /dev/null +++ b/sdrbase/dsp/channelsamplesink.cpp @@ -0,0 +1,25 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 F4EXB // +// written by Edouard Griffiths // +// // +// 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 // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "channelsamplesink.h" + +ChannelSampleSink::ChannelSampleSink() +{} + +ChannelSampleSink::~ChannelSampleSink() +{} \ No newline at end of file diff --git a/sdrbase/dsp/channelsamplesink.h b/sdrbase/dsp/channelsamplesink.h new file mode 100644 index 000000000..e5a12ed56 --- /dev/null +++ b/sdrbase/dsp/channelsamplesink.h @@ -0,0 +1,37 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 F4EXB // +// written by Edouard Griffiths // +// // +// 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 // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +// This is a lightweight channel sample source interface + +#ifndef SDRBASE_DSP_CHANNELSAMPLESINK_H_ +#define SDRBASE_DSP_CHANNELSAMPLESINK_H_ + +#include "export.h" +#include "dsptypes.h" + +class Message; + +class SDRBASE_API ChannelSampleSink { +public: + ChannelSampleSink(); + virtual ~ChannelSampleSink(); + + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) = 0; +}; + +#endif // SDRBASE_DSP_CHANNELSAMPLESINK_H_ diff --git a/sdrbase/dsp/downsamplechannelizer.cpp b/sdrbase/dsp/downsamplechannelizer.cpp new file mode 100644 index 000000000..20f0790c2 --- /dev/null +++ b/sdrbase/dsp/downsamplechannelizer.cpp @@ -0,0 +1,322 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016-2019 F4EXB // +// written by Edouard Griffiths // +// // +// 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 // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "dsp/inthalfbandfilter.h" +#include "dsp/dspcommands.h" +#include "dsp/hbfilterchainconverter.h" +#include "downsamplechannelizer.h" + +DownSampleChannelizer::DownSampleChannelizer(ChannelSampleSink* sampleSink) : + m_filterChainSetMode(false), + m_sampleSink(sampleSink), + m_basebandSampleRate(0), + m_requestedOutputSampleRate(0), + m_requestedCenterFrequency(0), + m_channelSampleRate(0), + m_channelFrequencyOffset(0), + m_log2Decim(0), + m_filterChainHash(0) +{ +} + +DownSampleChannelizer::~DownSampleChannelizer() +{ + freeFilterChain(); +} + +void DownSampleChannelizer::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) +{ + if (m_sampleSink == 0) + { + m_sampleBuffer.clear(); + return; + } + + if (m_filterStages.size() == 0) // optimization when no downsampling is done anyway + { + m_sampleSink->feed(begin, end); + } + else + { + for (SampleVector::const_iterator sample = begin; sample != end; ++sample) + { + Sample s(*sample); + FilterStages::iterator stage = m_filterStages.begin(); + + for (; stage != m_filterStages.end(); ++stage) + { +#ifndef SDR_RX_SAMPLE_24BIT + s.m_real /= 2; // avoid saturation on 16 bit samples + s.m_imag /= 2; +#endif + if (!(*stage)->work(&s)) { + break; + } + } + + if(stage == m_filterStages.end()) + { +#ifdef SDR_RX_SAMPLE_24BIT + s.m_real /= (1<<(m_filterStages.size())); // on 32 bit samples there is enough headroom to just divide the final result + s.m_imag /= (1<<(m_filterStages.size())); +#endif + m_sampleBuffer.push_back(s); + } + } + + m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end()); + m_sampleBuffer.clear(); + } +} + +void DownSampleChannelizer::setChannelization(int requestedSampleRate, qint64 requestedCenterFrequency) +{ + m_requestedOutputSampleRate = requestedSampleRate; + m_requestedCenterFrequency = requestedCenterFrequency; + applyChannelization(); +} + +void DownSampleChannelizer::setBasebandSampleRate(int basebandSampleRate, bool decim) +{ + m_basebandSampleRate = basebandSampleRate; + + if (decim) { + applyDecimation(); + } else { + applyChannelization(); + } +} + +void DownSampleChannelizer::applyChannelization() +{ + m_filterChainSetMode = false; + + if (m_basebandSampleRate == 0) + { + qDebug() << "DownSampleChannelizer::applyChannelization: aborting (in=0)" + << " in (baseband):" << m_basebandSampleRate + << " req:" << m_requestedOutputSampleRate + << " out (channel):" << m_channelSampleRate + << " fc:" << m_channelFrequencyOffset; + return; + } + + freeFilterChain(); + + m_channelFrequencyOffset = createFilterChain( + m_basebandSampleRate / -2, m_basebandSampleRate / 2, + m_requestedCenterFrequency - m_requestedOutputSampleRate / 2, m_requestedCenterFrequency + m_requestedOutputSampleRate / 2); + + m_channelSampleRate = m_basebandSampleRate / (1 << m_filterStages.size()); + + qDebug() << "DownChannelizer::applyChannelization done:" + << " in (baseband):" << m_basebandSampleRate + << " req:" << m_requestedOutputSampleRate + << " out (channel):" << m_channelSampleRate + << " fc:" << m_channelFrequencyOffset; +} + +void DownSampleChannelizer::setDecimation(unsigned int log2Decim, unsigned int filterChainHash) +{ + m_log2Decim = log2Decim; + m_filterChainHash = filterChainHash; + applyDecimation(); +} + +void DownSampleChannelizer::applyDecimation() +{ + m_filterChainSetMode = true; + std::vector stageIndexes; + m_channelFrequencyOffset = m_basebandSampleRate * HBFilterChainConverter::convertToIndexes(m_log2Decim, m_filterChainHash, stageIndexes); + m_requestedCenterFrequency = m_channelFrequencyOffset; + + freeFilterChain(); + + m_channelFrequencyOffset = m_basebandSampleRate * setFilterChain(stageIndexes); + m_channelSampleRate = m_basebandSampleRate / (1 << m_filterStages.size()); + m_requestedOutputSampleRate = m_channelSampleRate; + + qDebug() << "UpChannelizer::applyInterpolation:" + << " m_log2Interp:" << m_log2Decim + << " m_filterChainHash:" << m_filterChainHash + << " out:" << m_basebandSampleRate + << " in:" << m_channelSampleRate + << " fc:" << m_channelFrequencyOffset; +} + +#ifdef SDR_RX_SAMPLE_24BIT +DownSampleChannelizer::FilterStage::FilterStage(Mode mode) : + m_filter(new IntHalfbandFilterEO), + m_workFunction(0), + m_mode(mode), + m_sse(true) +{ + switch(mode) { + case ModeCenter: + m_workFunction = &IntHalfbandFilterEO::workDecimateCenter; + break; + + case ModeLowerHalf: + m_workFunction = &IntHalfbandFilterEO::workDecimateLowerHalf; + break; + + case ModeUpperHalf: + m_workFunction = &IntHalfbandFilterEO::workDecimateUpperHalf; + break; + } +} +#else +DownSampleChannelizer::FilterStage::FilterStage(Mode mode) : + m_filter(new IntHalfbandFilterEO), + m_workFunction(0), + m_mode(mode), + m_sse(true) +{ + switch(mode) { + case ModeCenter: + m_workFunction = &IntHalfbandFilterEO::workDecimateCenter; + break; + + case ModeLowerHalf: + m_workFunction = &IntHalfbandFilterEO::workDecimateLowerHalf; + break; + + case ModeUpperHalf: + m_workFunction = &IntHalfbandFilterEO::workDecimateUpperHalf; + break; + } +} +#endif + +DownSampleChannelizer::FilterStage::~FilterStage() +{ + delete m_filter; +} + +bool DownSampleChannelizer::signalContainsChannel(Real sigStart, Real sigEnd, Real chanStart, Real chanEnd) const +{ + //qDebug(" testing signal [%f, %f], channel [%f, %f]", sigStart, sigEnd, chanStart, chanEnd); + if(sigEnd <= sigStart) + return false; + if(chanEnd <= chanStart) + return false; + return (sigStart <= chanStart) && (sigEnd >= chanEnd); +} + +Real DownSampleChannelizer::createFilterChain(Real sigStart, Real sigEnd, Real chanStart, Real chanEnd) +{ + Real sigBw = sigEnd - sigStart; + Real rot = sigBw / 4; + + //qDebug("DownChannelizer::createFilterChain: Signal [%.1f, %.1f] (BW %.1f), Channel [%.1f, %.1f], Rot %.1f", sigStart, sigEnd, sigBw, chanStart, chanEnd, rot); + + // check if it fits into the left half + if(signalContainsChannel(sigStart, sigStart + sigBw / 2.0, chanStart, chanEnd)) + { + //qDebug("DownChannelizer::createFilterChain: -> take left half (rotate by +1/4 and decimate by 2)"); + m_filterStages.push_back(new FilterStage(FilterStage::ModeLowerHalf)); + return createFilterChain(sigStart, sigStart + sigBw / 2.0, chanStart, chanEnd); + } + + // check if it fits into the right half + if(signalContainsChannel(sigEnd - sigBw / 2.0f, sigEnd, chanStart, chanEnd)) + { + //qDebug("DownChannelizer::createFilterChain: -> take right half (rotate by -1/4 and decimate by 2)"); + m_filterStages.push_back(new FilterStage(FilterStage::ModeUpperHalf)); + return createFilterChain(sigEnd - sigBw / 2.0f, sigEnd, chanStart, chanEnd); + } + + // check if it fits into the center + if(signalContainsChannel(sigStart + rot, sigEnd - rot, chanStart, chanEnd)) + { + //qDebug("DownChannelizer::createFilterChain: -> take center half (decimate by 2)"); + m_filterStages.push_back(new FilterStage(FilterStage::ModeCenter)); + return createFilterChain(sigStart + rot, sigEnd - rot, chanStart, chanEnd); + } + + Real ofs = ((chanEnd - chanStart) / 2.0 + chanStart) - ((sigEnd - sigStart) / 2.0 + sigStart); + //qDebug("DownChannelizer::createFilterChain: -> complete (final BW %.1f, frequency offset %.1f)", sigBw, ofs); + return ofs; +} + +double DownSampleChannelizer::setFilterChain(const std::vector& stageIndexes) +{ + // filters are described from lower to upper level but the chain is constructed the other way round + std::vector::const_reverse_iterator rit = stageIndexes.rbegin(); + double ofs = 0.0, ofs_stage = 0.25; + + // Each index is a base 3 number with 0 = low, 1 = center, 2 = high + // Functions at upper level will convert a number to base 3 to describe the filter chain. Common converting + // algorithms will go from LSD to MSD. This explains the reverse order. + for (; rit != stageIndexes.rend(); ++rit) + { + if (*rit == 0) + { + m_filterStages.push_back(new FilterStage(FilterStage::ModeLowerHalf)); + ofs -= ofs_stage; + qDebug("DownSampleChannelizer::setFilterChain: lower half: ofs: %f", ofs); + } + else if (*rit == 1) + { + m_filterStages.push_back(new FilterStage(FilterStage::ModeCenter)); + qDebug("DownSampleChannelizer::setFilterChain: center: ofs: %f", ofs); + } + else if (*rit == 2) + { + m_filterStages.push_back(new FilterStage(FilterStage::ModeUpperHalf)); + ofs += ofs_stage; + qDebug("DownSampleChannelizer::setFilterChain: upper half: ofs: %f", ofs); + } + } + + return ofs; +} + +void DownSampleChannelizer::freeFilterChain() +{ + for(FilterStages::iterator it = m_filterStages.begin(); it != m_filterStages.end(); ++it) + delete *it; + m_filterStages.clear(); +} + +void DownSampleChannelizer::debugFilterChain() +{ + qDebug("DownChannelizer::debugFilterChain: %lu stages", m_filterStages.size()); + + for(FilterStages::iterator it = m_filterStages.begin(); it != m_filterStages.end(); ++it) + { + switch ((*it)->m_mode) + { + case FilterStage::ModeCenter: + qDebug("DownChannelizer::debugFilterChain: center %s", (*it)->m_sse ? "sse" : "no_sse"); + break; + case FilterStage::ModeLowerHalf: + qDebug("DownChannelizer::debugFilterChain: lower %s", (*it)->m_sse ? "sse" : "no_sse"); + break; + case FilterStage::ModeUpperHalf: + qDebug("DownChannelizer::debugFilterChain: upper %s", (*it)->m_sse ? "sse" : "no_sse"); + break; + default: + qDebug("DownChannelizer::debugFilterChain: none %s", (*it)->m_sse ? "sse" : "no_sse"); + break; + } + } +} diff --git a/sdrbase/dsp/downsamplechannelizer.h b/sdrbase/dsp/downsamplechannelizer.h new file mode 100644 index 000000000..09c14bf5a --- /dev/null +++ b/sdrbase/dsp/downsamplechannelizer.h @@ -0,0 +1,96 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016-2019 F4EXB // +// written by Edouard Griffiths // +// // +// 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 // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRBASE_DSP_DOWNSAMPLECHANNELIZER_H +#define SDRBASE_DSP_DOWNSAMPLECHANNELIZER_H + +#include +#include + +#include "export.h" +#include "util/message.h" +#include "dsp/inthalfbandfiltereo.h" + +#include "channelsamplesink.h" + +#define DOWNCHANNELIZER_HB_FILTER_ORDER 48 + +class SDRBASE_API DownSampleChannelizer : public ChannelSampleSink { +public: + DownSampleChannelizer(ChannelSampleSink* sampleSink); + virtual ~DownSampleChannelizer(); + + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); + + void setDecimation(unsigned int log2Decim, unsigned int filterChainHash); //!< Define channelizer with decimation factor and filter chain definition + void setChannelization(int requestedSampleRate, qint64 requestedCenterFrequency); //!< Define channelizer with requested sample rate and center frequency (shift in the baseband) + void setBasebandSampleRate(int basebandSampleRate, bool decim = false); //!< decim: true => use direct decimation false => use channel configuration + int getChannelSampleRate() const { return m_channelSampleRate; } + int getChannelFrequencyOffset() const { return m_channelFrequencyOffset; } + +protected: + struct FilterStage { + enum Mode { + ModeCenter, + ModeLowerHalf, + ModeUpperHalf + }; + +#ifdef SDR_RX_SAMPLE_24BIT + typedef bool (IntHalfbandFilterEO::*WorkFunction)(Sample* s); + IntHalfbandFilterEO* m_filter; +#else + typedef bool (IntHalfbandFilterEO::*WorkFunction)(Sample* s); + IntHalfbandFilterEO* m_filter; +#endif + + WorkFunction m_workFunction; + Mode m_mode; + bool m_sse; + + FilterStage(Mode mode); + ~FilterStage(); + + bool work(Sample* sample) + { + return (m_filter->*m_workFunction)(sample); + } + }; + typedef std::list FilterStages; + FilterStages m_filterStages; + bool m_filterChainSetMode; + ChannelSampleSink* m_sampleSink; //!< Demodulator + int m_basebandSampleRate; + int m_requestedOutputSampleRate; + int m_requestedCenterFrequency; + int m_channelSampleRate; + int m_channelFrequencyOffset; + unsigned int m_log2Decim; + unsigned int m_filterChainHash; + SampleVector m_sampleBuffer; + + void applyChannelization(); + void applyDecimation(); + bool signalContainsChannel(Real sigStart, Real sigEnd, Real chanStart, Real chanEnd) const; + Real createFilterChain(Real sigStart, Real sigEnd, Real chanStart, Real chanEnd); + double setFilterChain(const std::vector& stageIndexes); + void freeFilterChain(); + void debugFilterChain(); +}; + +#endif // SDRBASE_DSP_DOWNCHANNELIZER_H diff --git a/sdrbase/dsp/dspdevicemimoengine.cpp b/sdrbase/dsp/dspdevicemimoengine.cpp index 736292e31..27d130a63 100644 --- a/sdrbase/dsp/dspdevicemimoengine.cpp +++ b/sdrbase/dsp/dspdevicemimoengine.cpp @@ -191,6 +191,26 @@ void DSPDeviceMIMOEngine::removeChannelSink(ThreadedBasebandSampleSink* sink, in m_syncMessenger.sendWait(cmd); } +void DSPDeviceMIMOEngine::addChannelSink(BasebandSampleSink* sink, int index) +{ + qDebug() << "DSPDeviceMIMOEngine::addChannelSink: " + << sink->objectName().toStdString().c_str() + << " at: " + << index; + AddBasebandSampleSink cmd(sink, index); + m_syncMessenger.sendWait(cmd); +} + +void DSPDeviceMIMOEngine::removeChannelSink(BasebandSampleSink* sink, int index) +{ + qDebug() << "DSPDeviceMIMOEngine::removeChannelSink: " + << sink->objectName().toStdString().c_str() + << " at: " + << index; + RemoveBasebandSampleSink cmd(sink, index); + m_syncMessenger.sendWait(cmd); +} + void DSPDeviceMIMOEngine::addMIMOChannel(MIMOChannel *channel) { qDebug() << "DSPDeviceMIMOEngine::addMIMOChannel: " diff --git a/sdrbase/dsp/dspdevicemimoengine.h b/sdrbase/dsp/dspdevicemimoengine.h index 71275088d..760d99aa0 100644 --- a/sdrbase/dsp/dspdevicemimoengine.h +++ b/sdrbase/dsp/dspdevicemimoengine.h @@ -262,6 +262,8 @@ public: void addChannelSource(BasebandSampleSource* source, int index = 0); //!< Add a channel source void removeChannelSource(BasebandSampleSource* source, int index = 0); //!< Remove a channel source + void addChannelSink(BasebandSampleSink* sink, int index = 0); //!< Add a channel sink + void removeChannelSink(BasebandSampleSink* sink, int index = 0); //!< Remove a channel sink void addChannelSink(ThreadedBasebandSampleSink* sink, int index = 0); //!< Add a channel sink that will run on its own thread void removeChannelSink(ThreadedBasebandSampleSink* sink, int index = 0); //!< Remove a channel sink that runs on its own thread void addMIMOChannel(MIMOChannel *channel); //!< Add a MIMO channel diff --git a/sdrbase/dsp/samplesinkfifo.cpp b/sdrbase/dsp/samplesinkfifo.cpp index 3282c7084..66a3d4287 100644 --- a/sdrbase/dsp/samplesinkfifo.cpp +++ b/sdrbase/dsp/samplesinkfifo.cpp @@ -31,6 +31,14 @@ void SampleSinkFifo::create(unsigned int s) m_size = m_data.size(); } +void SampleSinkFifo::reset() +{ + m_suppressed = -1; + m_fill = 0; + m_head = 0; + m_tail = 0; +} + SampleSinkFifo::SampleSinkFifo(QObject* parent) : QObject(parent), m_data() @@ -274,3 +282,8 @@ unsigned int SampleSinkFifo::readCommit(unsigned int count) return count; } + +unsigned int SampleSinkFifo::getSizePolicy(unsigned int sampleRate) +{ + return (sampleRate/100)*64; // .64s +} \ No newline at end of file diff --git a/sdrbase/dsp/samplesinkfifo.h b/sdrbase/dsp/samplesinkfifo.h index 922da4ec1..ad3db9f95 100644 --- a/sdrbase/dsp/samplesinkfifo.h +++ b/sdrbase/dsp/samplesinkfifo.h @@ -49,6 +49,7 @@ public: ~SampleSinkFifo(); bool setSize(int size); + void reset(); inline unsigned int size() const { return m_size; } inline unsigned int fill() { QMutexLocker mutexLocker(&m_mutex); unsigned int fill = m_fill; return fill; } @@ -61,6 +62,7 @@ public: SampleVector::iterator* part1Begin, SampleVector::iterator* part1End, SampleVector::iterator* part2Begin, SampleVector::iterator* part2End); unsigned int readCommit(unsigned int count); + static unsigned int getSizePolicy(unsigned int sampleRate); signals: void dataReady();