diff --git a/plugins/channelrx/CMakeLists.txt b/plugins/channelrx/CMakeLists.txt
index ff9814290..796249a08 100644
--- a/plugins/channelrx/CMakeLists.txt
+++ b/plugins/channelrx/CMakeLists.txt
@@ -30,6 +30,11 @@ if(CM256CC_FOUND)
add_subdirectory(remotesink)
endif(CM256CC_FOUND)
+find_package(Codec2)
+if (CODEC2_FOUND)
+ add_subdirectory(demodfreedv)
+endif(CODEC2_FOUND)
+
if (BUILD_DEBIAN)
add_subdirectory(demoddsd)
add_subdirectory(remotesink)
diff --git a/plugins/channelrx/demodfreedv/CMakeLists.txt b/plugins/channelrx/demodfreedv/CMakeLists.txt
new file mode 100644
index 000000000..4b41d26ce
--- /dev/null
+++ b/plugins/channelrx/demodfreedv/CMakeLists.txt
@@ -0,0 +1,49 @@
+project(demodfreedv)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
+
+set(freedv_SOURCES
+ freedvdemod.cpp
+ freedvdemodgui.cpp
+ freedvdemodsettings.cpp
+ freedvplugin.cpp
+)
+
+set(freedv_HEADERS
+ freedvdemod.h
+ freedvdemodgui.h
+ freedvdemodsettings.h
+ freedvplugin.h
+)
+
+set(freedv_FORMS
+ freedvdemodgui.ui
+)
+
+include_directories(
+ .
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
+)
+
+add_definitions(${QT_DEFINITIONS})
+add_definitions(-DQT_PLUGIN)
+add_definitions(-DQT_SHARED)
+
+qt5_wrap_ui(freedv_FORMS_HEADERS ${freedv_FORMS})
+
+add_library(demodfreedv SHARED
+ ${freedv_SOURCES}
+ ${freedv_HEADERS_MOC}
+ ${freedv_FORMS_HEADERS}
+)
+
+target_link_libraries(demodfreedv
+ ${QT_LIBRARIES}
+ sdrbase
+ sdrgui
+)
+
+target_link_libraries(demodfreedv Qt5::Core Qt5::Widgets)
+
+install(TARGETS demodfreedv DESTINATION lib/plugins/channelrx)
diff --git a/plugins/channelrx/demodfreedv/freedvdemod.cpp b/plugins/channelrx/demodfreedv/freedvdemod.cpp
new file mode 100644
index 000000000..1129d5b84
--- /dev/null
+++ b/plugins/channelrx/demodfreedv/freedvdemod.cpp
@@ -0,0 +1,898 @@
+///////////////////////////////////////////////////////////////////////////////////
+// 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 //
+// //
+// 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 "SWGChannelSettings.h"
+#include "SWGSSBDemodSettings.h"
+#include "SWGChannelReport.h"
+#include "SWGSSBDemodReport.h"
+
+#include "audio/audiooutput.h"
+#include "dsp/dspengine.h"
+#include "dsp/downchannelizer.h"
+#include "dsp/threadedbasebandsamplesink.h"
+#include "dsp/dspcommands.h"
+#include "device/devicesourceapi.h"
+#include "util/db.h"
+
+#include "freedvdemod.h"
+
+MESSAGE_CLASS_DEFINITION(FreeDVDemod::MsgConfigureFreeDVDemod, Message)
+MESSAGE_CLASS_DEFINITION(FreeDVDemod::MsgConfigureFreeDVDemodPrivate, Message)
+MESSAGE_CLASS_DEFINITION(FreeDVDemod::MsgConfigureChannelizer, Message)
+
+const QString FreeDVDemod::m_channelIdURI = "sdrangel.channel.freedvdemod";
+const QString FreeDVDemod::m_channelId = "FreeDVDemod";
+
+FreeDVDemod::FreeDVDemod(DeviceSourceAPI *deviceAPI) :
+ ChannelSinkAPI(m_channelIdURI),
+ m_deviceAPI(deviceAPI),
+ m_audioBinaual(false),
+ m_audioFlipChannels(false),
+ m_dsb(false),
+ m_audioMute(false),
+ m_agc(12000, agcTarget, 1e-2),
+ m_agcActive(false),
+ m_agcClamping(false),
+ m_agcNbSamples(12000),
+ m_agcPowerThreshold(1e-2),
+ m_agcThresholdGate(0),
+ m_squelchDelayLine(2*48000),
+ m_audioActive(false),
+ m_sampleSink(0),
+ m_audioFifo(24000),
+ m_settingsMutex(QMutex::Recursive)
+{
+ setObjectName(m_channelId);
+
+ m_Bandwidth = 5000;
+ m_LowCutoff = 300;
+ m_volume = 2.0;
+ m_spanLog2 = 3;
+ m_inputSampleRate = 48000;
+ m_inputFrequencyOffset = 0;
+
+ DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo, getInputMessageQueue());
+ m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate();
+
+ m_audioBuffer.resize(1<<14);
+ m_audioBufferFill = 0;
+ m_undersampleCount = 0;
+ m_sum = 0;
+
+ m_usb = true;
+ m_magsq = 0.0f;
+ m_magsqSum = 0.0f;
+ m_magsqPeak = 0.0f;
+ m_magsqCount = 0;
+
+ m_agc.setClampMax(SDR_RX_SCALED/100.0);
+ m_agc.setClamping(m_agcClamping);
+
+ SSBFilter = new fftfilt(m_LowCutoff / m_audioSampleRate, m_Bandwidth / m_audioSampleRate, ssbFftLen);
+ DSBFilter = new fftfilt((2.0f * m_Bandwidth) / m_audioSampleRate, 2 * ssbFftLen);
+
+ applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true);
+ applySettings(m_settings, true);
+
+ m_channelizer = new DownChannelizer(this);
+ m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this);
+ m_deviceAPI->addThreadedSink(m_threadedChannelizer);
+ m_deviceAPI->addChannelAPI(this);
+
+ m_networkManager = new QNetworkAccessManager();
+ connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
+}
+
+FreeDVDemod::~FreeDVDemod()
+{
+ disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
+ delete m_networkManager;
+ DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo);
+
+ m_deviceAPI->removeChannelAPI(this);
+ m_deviceAPI->removeThreadedSink(m_threadedChannelizer);
+ delete m_threadedChannelizer;
+ delete m_channelizer;
+ delete SSBFilter;
+ delete DSBFilter;
+}
+
+void FreeDVDemod::configure(MessageQueue* messageQueue,
+ Real Bandwidth,
+ Real LowCutoff,
+ Real volume,
+ int spanLog2,
+ bool audioBinaural,
+ bool audioFlipChannel,
+ bool dsb,
+ bool audioMute,
+ bool agc,
+ bool agcClamping,
+ int agcTimeLog2,
+ int agcPowerThreshold,
+ int agcThresholdGate)
+{
+ Message* cmd = MsgConfigureFreeDVDemodPrivate::create(
+ Bandwidth,
+ LowCutoff,
+ volume,
+ spanLog2,
+ audioBinaural,
+ audioFlipChannel,
+ dsb,
+ audioMute,
+ agc,
+ agcClamping,
+ agcTimeLog2,
+ agcPowerThreshold,
+ agcThresholdGate);
+ messageQueue->push(cmd);
+}
+
+void FreeDVDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly)
+{
+ (void) positiveOnly;
+ Complex ci;
+ fftfilt::cmplx *sideband;
+ int n_out;
+
+ m_settingsMutex.lock();
+
+ int decim = 1<<(m_spanLog2 - 1);
+ unsigned char decim_mask = decim - 1; // counter LSB bit mask for decimation by 2^(m_scaleLog2 - 1)
+
+ 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))
+ {
+ if (m_dsb)
+ {
+ n_out = DSBFilter->runDSB(ci, &sideband);
+ }
+ else
+ {
+ n_out = SSBFilter->runSSB(ci, &sideband, m_usb);
+ }
+
+ m_interpolatorDistanceRemain += m_interpolatorDistance;
+ }
+ else
+ {
+ n_out = 0;
+ }
+
+ for (int i = 0; i < n_out; i++)
+ {
+ // Downsample by 2^(m_scaleLog2 - 1) for SSB band spectrum display
+ // smart decimation with bit gain using float arithmetic (23 bits significand)
+
+ m_sum += sideband[i];
+
+ if (!(m_undersampleCount++ & decim_mask))
+ {
+ Real avgr = m_sum.real() / decim;
+ Real avgi = m_sum.imag() / decim;
+ m_magsq = (avgr * avgr + avgi * avgi) / (SDR_RX_SCALED*SDR_RX_SCALED);
+
+ m_magsqSum += m_magsq;
+
+ if (m_magsq > m_magsqPeak)
+ {
+ m_magsqPeak = m_magsq;
+ }
+
+ m_magsqCount++;
+
+ if (!m_dsb & !m_usb)
+ { // invert spectrum for LSB
+ m_sampleBuffer.push_back(Sample(avgi, avgr));
+ }
+ else
+ {
+ m_sampleBuffer.push_back(Sample(avgr, avgi));
+ }
+
+ m_sum.real(0.0);
+ m_sum.imag(0.0);
+ }
+
+ float agcVal = m_agcActive ? m_agc.feedAndGetValue(sideband[i]) : 10.0; // 10.0 for 3276.8, 1.0 for 327.68
+ fftfilt::cmplx& delayedSample = m_squelchDelayLine.readBack(m_agc.getStepDownDelay());
+ m_audioActive = delayedSample.real() != 0.0;
+ m_squelchDelayLine.write(sideband[i]*agcVal);
+
+ if (m_audioMute)
+ {
+ m_audioBuffer[m_audioBufferFill].r = 0;
+ m_audioBuffer[m_audioBufferFill].l = 0;
+ }
+ else
+ {
+ fftfilt::cmplx z = delayedSample * m_agc.getStepValue();
+
+ if (m_audioBinaual)
+ {
+ if (m_audioFlipChannels)
+ {
+ m_audioBuffer[m_audioBufferFill].r = (qint16)(z.imag() * m_volume);
+ m_audioBuffer[m_audioBufferFill].l = (qint16)(z.real() * m_volume);
+ }
+ else
+ {
+ m_audioBuffer[m_audioBufferFill].r = (qint16)(z.real() * m_volume);
+ m_audioBuffer[m_audioBufferFill].l = (qint16)(z.imag() * m_volume);
+ }
+ }
+ else
+ {
+ Real demod = (z.real() + z.imag()) * 0.7;
+ qint16 sample = (qint16)(demod * m_volume);
+ 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("FreeDVDemod::feed: %u/%u samples written", res, m_audioBufferFill);
+ }
+
+ m_audioBufferFill = 0;
+ }
+ }
+ }
+
+ uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill);
+
+ if (res != m_audioBufferFill)
+ {
+ qDebug("FreeDVDemod::feed: %u/%u tail samples written", res, m_audioBufferFill);
+ }
+
+ m_audioBufferFill = 0;
+
+ if (m_sampleSink != 0)
+ {
+ m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), !m_dsb);
+ }
+
+ m_sampleBuffer.clear();
+
+ m_settingsMutex.unlock();
+}
+
+void FreeDVDemod::start()
+{
+ applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true);
+}
+
+void FreeDVDemod::stop()
+{
+}
+
+bool FreeDVDemod::handleMessage(const Message& cmd)
+{
+ if (DownChannelizer::MsgChannelizerNotification::match(cmd))
+ {
+ DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd;
+ qDebug("FreeDVDemod::handleMessage: MsgChannelizerNotification: m_sampleRate");
+
+ applyChannelSettings(notif.getSampleRate(), notif.getFrequencyOffset());
+
+ return true;
+ }
+ else if (MsgConfigureChannelizer::match(cmd))
+ {
+ MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd;
+ qDebug() << "FreeDVDemod::handleMessage: MsgConfigureChannelizer: sampleRate: " << cfg.getSampleRate()
+ << " centerFrequency: " << cfg.getCenterFrequency();
+
+ m_channelizer->configure(m_channelizer->getInputMessageQueue(),
+ cfg.getSampleRate(),
+ cfg.getCenterFrequency());
+
+ return true;
+ }
+ else if (MsgConfigureFreeDVDemod::match(cmd))
+ {
+ MsgConfigureFreeDVDemod& cfg = (MsgConfigureFreeDVDemod&) cmd;
+ qDebug("FreeDVDemod::handleMessage: MsgConfigureFreeDVDemod");
+
+ applySettings(cfg.getSettings(), cfg.getForce());
+
+ return true;
+ }
+ else if (BasebandSampleSink::MsgThreadedSink::match(cmd))
+ {
+ BasebandSampleSink::MsgThreadedSink& cfg = (BasebandSampleSink::MsgThreadedSink&) cmd;
+ const QThread *thread = cfg.getThread();
+ qDebug("FreeDVDemod::handleMessage: BasebandSampleSink::MsgThreadedSink: %p", thread);
+ return true;
+ }
+ else if (DSPConfigureAudio::match(cmd))
+ {
+ DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd;
+ uint32_t sampleRate = cfg.getSampleRate();
+
+ qDebug() << "FreeDVDemod::handleMessage: DSPConfigureAudio:"
+ << " sampleRate: " << sampleRate;
+
+ if (sampleRate != m_audioSampleRate) {
+ applyAudioSampleRate(sampleRate);
+ }
+
+ return true;
+ }
+ else if (DSPSignalNotification::match(cmd))
+ {
+ return true;
+ }
+ else
+ {
+ if(m_sampleSink != 0)
+ {
+ return m_sampleSink->handleMessage(cmd);
+ }
+ else
+ {
+ return false;
+ }
+ }
+}
+
+void FreeDVDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force)
+{
+ qDebug() << "FreeDVDemod::applyChannelSettings:"
+ << " inputSampleRate: " << inputSampleRate
+ << " inputFrequencyOffset: " << inputFrequencyOffset;
+
+ 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_Bandwidth * 1.5f, 2.0f);
+ m_interpolatorDistanceRemain = 0;
+ m_interpolatorDistance = (Real) inputSampleRate / (Real) m_audioSampleRate;
+ m_settingsMutex.unlock();
+ }
+
+ m_inputSampleRate = inputSampleRate;
+ m_inputFrequencyOffset = inputFrequencyOffset;
+}
+
+void FreeDVDemod::applyAudioSampleRate(int sampleRate)
+{
+ qDebug("FreeDVDemod::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_Bandwidth * 1.5f, 2.0f);
+ m_interpolatorDistanceRemain = 0;
+ m_interpolatorDistance = (Real) m_inputSampleRate / (Real) sampleRate;
+
+ SSBFilter->create_filter(m_LowCutoff / (float) sampleRate, m_Bandwidth / (float) sampleRate);
+ DSBFilter->create_dsb_filter((2.0f * m_Bandwidth) / (float) sampleRate);
+
+ int agcNbSamples = (sampleRate / 1000) * (1<push(cfg);
+ }
+}
+
+void FreeDVDemod::applySettings(const FreeDVDemodSettings& settings, bool force)
+{
+ qDebug() << "FreeDVDemod::applySettings:"
+ << " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset
+ << " m_rfBandwidth: " << settings.m_rfBandwidth
+ << " m_lowCutoff: " << settings.m_lowCutoff
+ << " m_volume: " << settings.m_volume
+ << " m_spanLog2: " << settings.m_spanLog2
+ << " m_audioBinaual: " << settings.m_audioBinaural
+ << " m_audioFlipChannels: " << settings.m_audioFlipChannels
+ << " m_dsb: " << settings.m_dsb
+ << " m_audioMute: " << settings.m_audioMute
+ << " m_agcActive: " << settings.m_agc
+ << " m_agcClamping: " << settings.m_agcClamping
+ << " m_agcTimeLog2: " << settings.m_agcTimeLog2
+ << " agcPowerThreshold: " << settings.m_agcPowerThreshold
+ << " agcThresholdGate: " << settings.m_agcThresholdGate
+ << " m_audioDeviceName: " << settings.m_audioDeviceName
+ << " force: " << force;
+
+ QList reverseAPIKeys;
+
+ if((m_settings.m_inputFrequencyOffset != settings.m_inputFrequencyOffset) || force) {
+ reverseAPIKeys.append("inputFrequencyOffset");
+ }
+ if((m_settings.m_rfBandwidth != settings.m_rfBandwidth) || force) {
+ reverseAPIKeys.append("rfBandwidth");
+ }
+ if((m_settings.m_lowCutoff != settings.m_lowCutoff) || force) {
+ reverseAPIKeys.append("lowCutoff");
+ }
+
+ if((m_settings.m_rfBandwidth != settings.m_rfBandwidth) ||
+ (m_settings.m_lowCutoff != settings.m_lowCutoff) || force)
+ {
+ float band, lowCutoff;
+
+ band = settings.m_rfBandwidth;
+ lowCutoff = settings.m_lowCutoff;
+
+ if (band < 0) {
+ band = -band;
+ lowCutoff = -lowCutoff;
+ m_usb = false;
+ } else {
+ m_usb = true;
+ }
+
+ if (band < 100.0f)
+ {
+ band = 100.0f;
+ lowCutoff = 0;
+ }
+
+ m_Bandwidth = band;
+ m_LowCutoff = lowCutoff;
+
+ m_settingsMutex.lock();
+ m_interpolator.create(16, m_inputSampleRate, m_Bandwidth * 1.5f, 2.0f);
+ m_interpolatorDistanceRemain = 0;
+ m_interpolatorDistance = (Real) m_inputSampleRate / (Real) m_audioSampleRate;
+ SSBFilter->create_filter(m_LowCutoff / (float) m_audioSampleRate, m_Bandwidth / (float) m_audioSampleRate);
+ DSBFilter->create_dsb_filter((2.0f * m_Bandwidth) / (float) m_audioSampleRate);
+ m_settingsMutex.unlock();
+ }
+
+ if ((m_settings.m_volume != settings.m_volume) || force)
+ {
+ reverseAPIKeys.append("volume");
+ m_volume = settings.m_volume;
+ m_volume /= 4.0; // for 3276.8
+ }
+
+ if ((m_settings.m_agcTimeLog2 != settings.m_agcTimeLog2) || force) {
+ reverseAPIKeys.append("agcTimeLog2");
+ }
+ if ((m_settings.m_agcPowerThreshold != settings.m_agcPowerThreshold) || force) {
+ reverseAPIKeys.append("agcPowerThreshold");
+ }
+ if ((m_settings.m_agcThresholdGate != settings.m_agcThresholdGate) || force) {
+ reverseAPIKeys.append("agcThresholdGate");
+ }
+ if ((m_settings.m_agcClamping != settings.m_agcClamping) || force) {
+ reverseAPIKeys.append("agcClamping");
+ }
+
+ if ((m_settings.m_agcTimeLog2 != settings.m_agcTimeLog2) ||
+ (m_settings.m_agcPowerThreshold != settings.m_agcPowerThreshold) ||
+ (m_settings.m_agcThresholdGate != settings.m_agcThresholdGate) ||
+ (m_settings.m_agcClamping != settings.m_agcClamping) || force)
+ {
+ int agcNbSamples = (m_audioSampleRate / 1000) * (1<getAudioDeviceManager();
+ int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName);
+ audioDeviceManager->addAudioSink(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex);
+ uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex);
+
+ if (m_audioSampleRate != audioSampleRate) {
+ applyAudioSampleRate(audioSampleRate);
+ }
+ }
+
+ if ((m_settings.m_spanLog2 != settings.m_spanLog2) || force) {
+ reverseAPIKeys.append("spanLog2");
+ }
+ if ((m_settings.m_audioBinaural != settings.m_audioBinaural) || force) {
+ reverseAPIKeys.append("audioBinaural");
+ }
+ if ((m_settings.m_audioFlipChannels != settings.m_audioFlipChannels) || force) {
+ reverseAPIKeys.append("audioFlipChannels");
+ }
+ if ((m_settings.m_dsb != settings.m_dsb) || force) {
+ reverseAPIKeys.append("dsb");
+ }
+ if ((m_settings.m_audioMute != settings.m_audioMute) || force) {
+ reverseAPIKeys.append("audioMute");
+ }
+ if ((m_settings.m_agc != settings.m_agc) || force) {
+ reverseAPIKeys.append("agc");
+ }
+
+ m_spanLog2 = settings.m_spanLog2;
+ m_audioBinaual = settings.m_audioBinaural;
+ m_audioFlipChannels = settings.m_audioFlipChannels;
+ m_dsb = settings.m_dsb;
+ m_audioMute = settings.m_audioMute;
+ m_agcActive = settings.m_agc;
+
+ if (settings.m_useReverseAPI)
+ {
+ bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||
+ (m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) ||
+ (m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) ||
+ (m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex) ||
+ (m_settings.m_reverseAPIChannelIndex != settings.m_reverseAPIChannelIndex);
+ webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force);
+ }
+
+ m_settings = settings;
+}
+
+QByteArray FreeDVDemod::serialize() const
+{
+ return m_settings.serialize();
+}
+
+bool FreeDVDemod::deserialize(const QByteArray& data)
+{
+ if (m_settings.deserialize(data))
+ {
+ MsgConfigureFreeDVDemod *msg = MsgConfigureFreeDVDemod::create(m_settings, true);
+ m_inputMessageQueue.push(msg);
+ return true;
+ }
+ else
+ {
+ m_settings.resetToDefaults();
+ MsgConfigureFreeDVDemod *msg = MsgConfigureFreeDVDemod::create(m_settings, true);
+ m_inputMessageQueue.push(msg);
+ return false;
+ }
+}
+
+int FreeDVDemod::webapiSettingsGet(
+ SWGSDRangel::SWGChannelSettings& response,
+ QString& errorMessage)
+{
+ (void) errorMessage;
+ response.setSsbDemodSettings(new SWGSDRangel::SWGSSBDemodSettings());
+ response.getSsbDemodSettings()->init();
+ webapiFormatChannelSettings(response, m_settings);
+ return 200;
+}
+
+int FreeDVDemod::webapiSettingsPutPatch(
+ bool force,
+ const QStringList& channelSettingsKeys,
+ SWGSDRangel::SWGChannelSettings& response,
+ QString& errorMessage)
+{
+ (void) errorMessage;
+ FreeDVDemodSettings settings = m_settings;
+ bool frequencyOffsetChanged = false;
+
+ if (channelSettingsKeys.contains("inputFrequencyOffset"))
+ {
+ settings.m_inputFrequencyOffset = response.getSsbDemodSettings()->getInputFrequencyOffset();
+ frequencyOffsetChanged = true;
+ }
+ if (channelSettingsKeys.contains("rfBandwidth")) {
+ settings.m_rfBandwidth = response.getSsbDemodSettings()->getRfBandwidth();
+ }
+ if (channelSettingsKeys.contains("lowCutoff")) {
+ settings.m_lowCutoff = response.getSsbDemodSettings()->getLowCutoff();
+ }
+ if (channelSettingsKeys.contains("volume")) {
+ settings.m_volume = response.getSsbDemodSettings()->getVolume();
+ }
+ if (channelSettingsKeys.contains("spanLog2")) {
+ settings.m_spanLog2 = response.getSsbDemodSettings()->getSpanLog2();
+ }
+ if (channelSettingsKeys.contains("audioBinaural")) {
+ settings.m_audioBinaural = response.getSsbDemodSettings()->getAudioBinaural() != 0;
+ }
+ if (channelSettingsKeys.contains("audioFlipChannels")) {
+ settings.m_audioFlipChannels = response.getSsbDemodSettings()->getAudioFlipChannels() != 0;
+ }
+ if (channelSettingsKeys.contains("dsb")) {
+ settings.m_dsb = response.getSsbDemodSettings()->getDsb() != 0;
+ }
+ if (channelSettingsKeys.contains("audioMute")) {
+ settings.m_audioMute = response.getSsbDemodSettings()->getAudioMute() != 0;
+ }
+ if (channelSettingsKeys.contains("agc")) {
+ settings.m_agc = response.getSsbDemodSettings()->getAgc() != 0;
+ }
+ if (channelSettingsKeys.contains("agcClamping")) {
+ settings.m_agcClamping = response.getSsbDemodSettings()->getAgcClamping() != 0;
+ }
+ if (channelSettingsKeys.contains("agcTimeLog2")) {
+ settings.m_agcTimeLog2 = response.getSsbDemodSettings()->getAgcTimeLog2();
+ }
+ if (channelSettingsKeys.contains("agcPowerThreshold")) {
+ settings.m_agcPowerThreshold = response.getSsbDemodSettings()->getAgcPowerThreshold();
+ }
+ if (channelSettingsKeys.contains("agcThresholdGate")) {
+ settings.m_agcThresholdGate = response.getSsbDemodSettings()->getAgcThresholdGate();
+ }
+ if (channelSettingsKeys.contains("rgbColor")) {
+ settings.m_rgbColor = response.getSsbDemodSettings()->getRgbColor();
+ }
+ if (channelSettingsKeys.contains("title")) {
+ settings.m_title = *response.getSsbDemodSettings()->getTitle();
+ }
+ if (channelSettingsKeys.contains("audioDeviceName")) {
+ settings.m_audioDeviceName = *response.getSsbDemodSettings()->getAudioDeviceName();
+ }
+
+ if (frequencyOffsetChanged)
+ {
+ MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create(
+ m_audioSampleRate, settings.m_inputFrequencyOffset);
+ m_inputMessageQueue.push(channelConfigMsg);
+ }
+
+ MsgConfigureFreeDVDemod *msg = MsgConfigureFreeDVDemod::create(settings, force);
+ m_inputMessageQueue.push(msg);
+
+ qDebug("FreeDVDemod::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue);
+ if (m_guiMessageQueue) // forward to GUI if any
+ {
+ MsgConfigureFreeDVDemod *msgToGUI = MsgConfigureFreeDVDemod::create(settings, force);
+ m_guiMessageQueue->push(msgToGUI);
+ }
+
+ webapiFormatChannelSettings(response, settings);
+
+ return 200;
+}
+
+int FreeDVDemod::webapiReportGet(
+ SWGSDRangel::SWGChannelReport& response,
+ QString& errorMessage)
+{
+ (void) errorMessage;
+ response.setSsbDemodReport(new SWGSDRangel::SWGSSBDemodReport());
+ response.getSsbDemodReport()->init();
+ webapiFormatChannelReport(response);
+ return 200;
+}
+
+void FreeDVDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const FreeDVDemodSettings& settings)
+{
+ response.getSsbDemodSettings()->setAudioMute(settings.m_audioMute ? 1 : 0);
+ response.getSsbDemodSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
+ response.getSsbDemodSettings()->setRfBandwidth(settings.m_rfBandwidth);
+ response.getSsbDemodSettings()->setLowCutoff(settings.m_lowCutoff);
+ response.getSsbDemodSettings()->setVolume(settings.m_volume);
+ response.getSsbDemodSettings()->setSpanLog2(settings.m_spanLog2);
+ response.getSsbDemodSettings()->setAudioBinaural(settings.m_audioBinaural ? 1 : 0);
+ response.getSsbDemodSettings()->setAudioFlipChannels(settings.m_audioFlipChannels ? 1 : 0);
+ response.getSsbDemodSettings()->setDsb(settings.m_dsb ? 1 : 0);
+ response.getSsbDemodSettings()->setAudioMute(settings.m_audioMute ? 1 : 0);
+ response.getSsbDemodSettings()->setAgc(settings.m_agc ? 1 : 0);
+ response.getSsbDemodSettings()->setAgcClamping(settings.m_agcClamping ? 1 : 0);
+ response.getSsbDemodSettings()->setAgcTimeLog2(settings.m_agcTimeLog2);
+ response.getSsbDemodSettings()->setAgcPowerThreshold(settings.m_agcPowerThreshold);
+ response.getSsbDemodSettings()->setAgcThresholdGate(settings.m_agcThresholdGate);
+ response.getSsbDemodSettings()->setRgbColor(settings.m_rgbColor);
+
+ if (response.getSsbDemodSettings()->getTitle()) {
+ *response.getSsbDemodSettings()->getTitle() = settings.m_title;
+ } else {
+ response.getSsbDemodSettings()->setTitle(new QString(settings.m_title));
+ }
+
+ if (response.getSsbDemodSettings()->getAudioDeviceName()) {
+ *response.getSsbDemodSettings()->getAudioDeviceName() = settings.m_audioDeviceName;
+ } else {
+ response.getSsbDemodSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName));
+ }
+}
+
+void FreeDVDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response)
+{
+ double magsqAvg, magsqPeak;
+ int nbMagsqSamples;
+ getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples);
+
+ response.getSsbDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg));
+ response.getSsbDemodReport()->setSquelch(m_audioActive ? 1 : 0);
+ response.getSsbDemodReport()->setAudioSampleRate(m_audioSampleRate);
+ response.getSsbDemodReport()->setChannelSampleRate(m_inputSampleRate);
+}
+
+void FreeDVDemod::webapiReverseSendSettings(QList& channelSettingsKeys, const FreeDVDemodSettings& settings, bool force)
+{
+ SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings();
+ swgChannelSettings->setTx(0);
+ swgChannelSettings->setChannelType(new QString("SSBDemod"));
+ swgChannelSettings->setSsbDemodSettings(new SWGSDRangel::SWGSSBDemodSettings());
+ SWGSDRangel::SWGSSBDemodSettings *swgSSBDemodSettings = swgChannelSettings->getSsbDemodSettings();
+
+ // transfer data that has been modified. When force is on transfer all data except reverse API data
+
+ if (channelSettingsKeys.contains("inputFrequencyOffset") || force) {
+ swgSSBDemodSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
+ }
+ if (channelSettingsKeys.contains("rfBandwidth") || force) {
+ swgSSBDemodSettings->setRfBandwidth(settings.m_rfBandwidth);
+ }
+ if (channelSettingsKeys.contains("lowCutoff") || force) {
+ swgSSBDemodSettings->setLowCutoff(settings.m_lowCutoff);
+ }
+ if (channelSettingsKeys.contains("volume") || force) {
+ swgSSBDemodSettings->setVolume(settings.m_volume);
+ }
+ if (channelSettingsKeys.contains("spanLog2") || force) {
+ swgSSBDemodSettings->setSpanLog2(settings.m_spanLog2);
+ }
+ if (channelSettingsKeys.contains("audioBinaural") || force) {
+ swgSSBDemodSettings->setAudioBinaural(settings.m_audioBinaural ? 1 : 0);
+ }
+ if (channelSettingsKeys.contains("audioFlipChannels") || force) {
+ swgSSBDemodSettings->setAudioFlipChannels(settings.m_audioFlipChannels ? 1 : 0);
+ }
+ if (channelSettingsKeys.contains("dsb") || force) {
+ swgSSBDemodSettings->setDsb(settings.m_dsb ? 1 : 0);
+ }
+ if (channelSettingsKeys.contains("audioMute") || force) {
+ swgSSBDemodSettings->setAudioMute(settings.m_audioMute ? 1 : 0);
+ }
+ if (channelSettingsKeys.contains("agc") || force) {
+ swgSSBDemodSettings->setAgc(settings.m_agc ? 1 : 0);
+ }
+ if (channelSettingsKeys.contains("agcClamping") || force) {
+ swgSSBDemodSettings->setAgcClamping(settings.m_agcClamping ? 1 : 0);
+ }
+ if (channelSettingsKeys.contains("agcTimeLog2") || force) {
+ swgSSBDemodSettings->setAgcTimeLog2(settings.m_agcTimeLog2);
+ }
+ if (channelSettingsKeys.contains("agcPowerThreshold") || force) {
+ swgSSBDemodSettings->setAgcPowerThreshold(settings.m_agcPowerThreshold);
+ }
+ if (channelSettingsKeys.contains("agcThresholdGate") || force) {
+ swgSSBDemodSettings->setAgcThresholdGate(settings.m_agcThresholdGate);
+ }
+ if (channelSettingsKeys.contains("rgbColor") || force) {
+ swgSSBDemodSettings->setRgbColor(settings.m_rgbColor);
+ }
+ if (channelSettingsKeys.contains("title") || force) {
+ swgSSBDemodSettings->setTitle(new QString(settings.m_title));
+ }
+ if (channelSettingsKeys.contains("audioDeviceName") || force) {
+ swgSSBDemodSettings->setAudioDeviceName(new QString(settings.m_audioDeviceName));
+ }
+
+ QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings")
+ .arg(settings.m_reverseAPIAddress)
+ .arg(settings.m_reverseAPIPort)
+ .arg(settings.m_reverseAPIDeviceIndex)
+ .arg(settings.m_reverseAPIChannelIndex);
+ m_networkRequest.setUrl(QUrl(channelSettingsURL));
+ m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
+
+ QBuffer *buffer=new QBuffer();
+ buffer->open((QBuffer::ReadWrite));
+ buffer->write(swgChannelSettings->asJson().toUtf8());
+ buffer->seek(0);
+
+ // Always use PATCH to avoid passing reverse API settings
+ m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
+
+ delete swgChannelSettings;
+}
+
+void FreeDVDemod::networkManagerFinished(QNetworkReply *reply)
+{
+ QNetworkReply::NetworkError replyError = reply->error();
+
+ if (replyError)
+ {
+ qWarning() << "FreeDVDemod::networkManagerFinished:"
+ << " error(" << (int) replyError
+ << "): " << replyError
+ << ": " << reply->errorString();
+ return;
+ }
+
+ QString answer = reply->readAll();
+ answer.chop(1); // remove last \n
+ qDebug("FreeDVDemod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
+}
diff --git a/plugins/channelrx/demodfreedv/freedvdemod.h b/plugins/channelrx/demodfreedv/freedvdemod.h
new file mode 100644
index 000000000..4eec60f21
--- /dev/null
+++ b/plugins/channelrx/demodfreedv/freedvdemod.h
@@ -0,0 +1,333 @@
+///////////////////////////////////////////////////////////////////////////////////
+// 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 //
+// //
+// 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_FREEDVDEMOD_H
+#define INCLUDE_FREEDVDEMOD_H
+
+#include
+
+#include
+#include
+
+#include "dsp/basebandsamplesink.h"
+#include "channel/channelsinkapi.h"
+#include "dsp/ncof.h"
+#include "dsp/interpolator.h"
+#include "dsp/fftfilt.h"
+#include "dsp/agc.h"
+#include "audio/audiofifo.h"
+#include "util/message.h"
+#include "util/doublebufferfifo.h"
+
+#include "freedvdemodsettings.h"
+
+#define ssbFftLen 1024
+#define agcTarget 3276.8 // -10 dB amplitude => -20 dB power: center of normal signal
+
+class QNetworkAccessManager;
+class QNetworkReply;
+class DeviceSourceAPI;
+class ThreadedBasebandSampleSink;
+class DownChannelizer;
+
+class FreeDVDemod : public BasebandSampleSink, public ChannelSinkAPI {
+ Q_OBJECT
+public:
+ class MsgConfigureFreeDVDemod : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ const FreeDVDemodSettings& getSettings() const { return m_settings; }
+ bool getForce() const { return m_force; }
+
+ static MsgConfigureFreeDVDemod* create(const FreeDVDemodSettings& settings, bool force)
+ {
+ return new MsgConfigureFreeDVDemod(settings, force);
+ }
+
+ private:
+ FreeDVDemodSettings m_settings;
+ bool m_force;
+
+ MsgConfigureFreeDVDemod(const FreeDVDemodSettings& settings, bool force) :
+ Message(),
+ m_settings(settings),
+ m_force(force)
+ { }
+ };
+
+ class MsgConfigureChannelizer : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ int getSampleRate() const { return m_sampleRate; }
+ int getCenterFrequency() const { return m_centerFrequency; }
+
+ static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency)
+ {
+ return new MsgConfigureChannelizer(sampleRate, centerFrequency);
+ }
+
+ private:
+ int m_sampleRate;
+ int m_centerFrequency;
+
+ MsgConfigureChannelizer(int sampleRate, int centerFrequency) :
+ Message(),
+ m_sampleRate(sampleRate),
+ m_centerFrequency(centerFrequency)
+ { }
+ };
+
+ FreeDVDemod(DeviceSourceAPI *deviceAPI);
+ virtual ~FreeDVDemod();
+ virtual void destroy() { delete this; }
+ void setSampleSink(BasebandSampleSink* sampleSink) { m_sampleSink = sampleSink; }
+
+ void configure(MessageQueue* messageQueue,
+ Real Bandwidth,
+ Real LowCutoff,
+ Real volume,
+ int spanLog2,
+ bool audioBinaural,
+ bool audioFlipChannels,
+ bool dsb,
+ bool audioMute,
+ bool agc,
+ bool agcClamping,
+ int agcTimeLog2,
+ int agcPowerThreshold,
+ int agcThresholdGate);
+
+ virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly);
+ virtual void start();
+ virtual void stop();
+ virtual bool handleMessage(const Message& cmd);
+
+ virtual void getIdentifier(QString& id) { id = objectName(); }
+ virtual void getTitle(QString& title) { title = m_settings.m_title; }
+ virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; }
+
+ virtual QByteArray serialize() const;
+ virtual bool deserialize(const QByteArray& data);
+
+ uint32_t getAudioSampleRate() const { return m_audioSampleRate; }
+ double getMagSq() const { return m_magsq; }
+ bool getAudioActive() const { return m_audioActive; }
+
+ 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;
+ }
+
+ virtual int webapiSettingsGet(
+ SWGSDRangel::SWGChannelSettings& response,
+ QString& errorMessage);
+
+ virtual int webapiSettingsPutPatch(
+ bool force,
+ const QStringList& channelSettingsKeys,
+ SWGSDRangel::SWGChannelSettings& response,
+ QString& errorMessage);
+
+ virtual int webapiReportGet(
+ SWGSDRangel::SWGChannelReport& response,
+ QString& errorMessage);
+
+ 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;
+ };
+
+ class MsgConfigureFreeDVDemodPrivate : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ Real getBandwidth() const { return m_Bandwidth; }
+ Real getLoCutoff() const { return m_LowCutoff; }
+ Real getVolume() const { return m_volume; }
+ int getSpanLog2() const { return m_spanLog2; }
+ bool getAudioBinaural() const { return m_audioBinaural; }
+ bool getAudioFlipChannels() const { return m_audioFlipChannels; }
+ bool getDSB() const { return m_dsb; }
+ bool getAudioMute() const { return m_audioMute; }
+ bool getAGC() const { return m_agc; }
+ bool getAGCClamping() const { return m_agcClamping; }
+ int getAGCTimeLog2() const { return m_agcTimeLog2; }
+ int getAGCPowerThershold() const { return m_agcPowerThreshold; }
+ int getAGCThersholdGate() const { return m_agcThresholdGate; }
+
+ static MsgConfigureFreeDVDemodPrivate* create(Real Bandwidth,
+ Real LowCutoff,
+ Real volume,
+ int spanLog2,
+ bool audioBinaural,
+ bool audioFlipChannels,
+ bool dsb,
+ bool audioMute,
+ bool agc,
+ bool agcClamping,
+ int agcTimeLog2,
+ int agcPowerThreshold,
+ int agcThresholdGate)
+ {
+ return new MsgConfigureFreeDVDemodPrivate(
+ Bandwidth,
+ LowCutoff,
+ volume,
+ spanLog2,
+ audioBinaural,
+ audioFlipChannels,
+ dsb,
+ audioMute,
+ agc,
+ agcClamping,
+ agcTimeLog2,
+ agcPowerThreshold,
+ agcThresholdGate);
+ }
+
+ private:
+ Real m_Bandwidth;
+ Real m_LowCutoff;
+ Real m_volume;
+ int m_spanLog2;
+ bool m_audioBinaural;
+ bool m_audioFlipChannels;
+ bool m_dsb;
+ bool m_audioMute;
+ bool m_agc;
+ bool m_agcClamping;
+ int m_agcTimeLog2;
+ int m_agcPowerThreshold;
+ int m_agcThresholdGate;
+
+ MsgConfigureFreeDVDemodPrivate(Real Bandwidth,
+ Real LowCutoff,
+ Real volume,
+ int spanLog2,
+ bool audioBinaural,
+ bool audioFlipChannels,
+ bool dsb,
+ bool audioMute,
+ bool agc,
+ bool agcClamping,
+ int agcTimeLog2,
+ int agcPowerThreshold,
+ int agcThresholdGate) :
+ Message(),
+ m_Bandwidth(Bandwidth),
+ m_LowCutoff(LowCutoff),
+ m_volume(volume),
+ m_spanLog2(spanLog2),
+ m_audioBinaural(audioBinaural),
+ m_audioFlipChannels(audioFlipChannels),
+ m_dsb(dsb),
+ m_audioMute(audioMute),
+ m_agc(agc),
+ m_agcClamping(agcClamping),
+ m_agcTimeLog2(agcTimeLog2),
+ m_agcPowerThreshold(agcPowerThreshold),
+ m_agcThresholdGate(agcThresholdGate)
+ { }
+ };
+
+ DeviceSourceAPI *m_deviceAPI;
+ ThreadedBasebandSampleSink* m_threadedChannelizer;
+ DownChannelizer* m_channelizer;
+ FreeDVDemodSettings m_settings;
+
+ Real m_Bandwidth;
+ Real m_LowCutoff;
+ Real m_volume;
+ int m_spanLog2;
+ fftfilt::cmplx m_sum;
+ int m_undersampleCount;
+ int m_inputSampleRate;
+ int m_inputFrequencyOffset;
+ bool m_audioBinaual;
+ bool m_audioFlipChannels;
+ bool m_usb;
+ bool m_dsb;
+ bool m_audioMute;
+ double m_magsq;
+ double m_magsqSum;
+ double m_magsqPeak;
+ int m_magsqCount;
+ MagSqLevelsStore m_magSqLevelStore;
+ MagAGC m_agc;
+ bool m_agcActive;
+ bool m_agcClamping;
+ int m_agcNbSamples; //!< number of audio (48 kHz) samples for AGC averaging
+ double m_agcPowerThreshold; //!< AGC power threshold (linear)
+ int m_agcThresholdGate; //!< Gate length in number of samples befor threshold triggers
+ DoubleBufferFIFO m_squelchDelayLine;
+ bool m_audioActive; //!< True if an audio signal is produced (no AGC or AGC and above threshold)
+
+ NCOF m_nco;
+ Interpolator m_interpolator;
+ Real m_interpolatorDistance;
+ Real m_interpolatorDistanceRemain;
+ fftfilt* SSBFilter;
+ fftfilt* DSBFilter;
+
+ BasebandSampleSink* m_sampleSink;
+ SampleVector m_sampleBuffer;
+
+ AudioVector m_audioBuffer;
+ uint m_audioBufferFill;
+ AudioFifo m_audioFifo;
+ quint32 m_audioSampleRate;
+
+ QNetworkAccessManager *m_networkManager;
+ QNetworkRequest m_networkRequest;
+
+ QMutex m_settingsMutex;
+
+ void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false);
+ void applySettings(const FreeDVDemodSettings& settings, bool force = false);
+ void applyAudioSampleRate(int sampleRate);
+ void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const FreeDVDemodSettings& settings);
+ void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response);
+ void webapiReverseSendSettings(QList& channelSettingsKeys, const FreeDVDemodSettings& settings, bool force);
+
+private slots:
+ void networkManagerFinished(QNetworkReply *reply);
+};
+
+#endif // INCLUDE_FREEDVDEMOD_H
diff --git a/plugins/channelrx/demodfreedv/freedvdemodgui.cpp b/plugins/channelrx/demodfreedv/freedvdemodgui.cpp
new file mode 100644
index 000000000..7e2c731ef
--- /dev/null
+++ b/plugins/channelrx/demodfreedv/freedvdemodgui.cpp
@@ -0,0 +1,638 @@
+///////////////////////////////////////////////////////////////////////////////////
+// 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 //
+// //
+// 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 "freedvdemodgui.h"
+
+#include "device/devicesourceapi.h"
+#include "device/deviceuiset.h"
+
+#include "ui_freedvdemodgui.h"
+#include "dsp/spectrumvis.h"
+#include "dsp/dspengine.h"
+#include "dsp/dspcommands.h"
+#include "gui/glspectrum.h"
+#include "gui/basicchannelsettingsdialog.h"
+#include "plugin/pluginapi.h"
+#include "util/simpleserializer.h"
+#include "util/db.h"
+#include "gui/crightclickenabler.h"
+#include "gui/audioselectdialog.h"
+#include "mainwindow.h"
+#include "freedvdemod.h"
+
+FreeDVDemodGUI* FreeDVDemodGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel)
+{
+ FreeDVDemodGUI* gui = new FreeDVDemodGUI(pluginAPI, deviceUISet, rxChannel);
+ return gui;
+}
+
+void FreeDVDemodGUI::destroy()
+{
+ delete this;
+}
+
+void FreeDVDemodGUI::setName(const QString& name)
+{
+ setObjectName(name);
+}
+
+QString FreeDVDemodGUI::getName() const
+{
+ return objectName();
+}
+
+qint64 FreeDVDemodGUI::getCenterFrequency() const
+{
+ return m_channelMarker.getCenterFrequency();
+}
+
+void FreeDVDemodGUI::setCenterFrequency(qint64 centerFrequency)
+{
+ m_channelMarker.setCenterFrequency(centerFrequency);
+ m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
+ applySettings();
+}
+
+void FreeDVDemodGUI::resetToDefaults()
+{
+ m_settings.resetToDefaults();
+}
+
+QByteArray FreeDVDemodGUI::serialize() const
+{
+ return m_settings.serialize();
+}
+
+bool FreeDVDemodGUI::deserialize(const QByteArray& data)
+{
+ if(m_settings.deserialize(data))
+ {
+ displaySettings();
+ applySettings(true); // will have true
+ return true;
+ }
+ else
+ {
+ m_settings.resetToDefaults();
+ displaySettings();
+ applySettings(true); // will have true
+ return false;
+ }
+}
+
+bool FreeDVDemodGUI::handleMessage(const Message& message)
+{
+ if (FreeDVDemod::MsgConfigureFreeDVDemod::match(message))
+ {
+ qDebug("FreeDVDemodGUI::handleMessage: FreeDVDemodGUI::MsgConfigureFreeDVDemod");
+ const FreeDVDemod::MsgConfigureFreeDVDemod& cfg = (FreeDVDemod::MsgConfigureFreeDVDemod&) message;
+ m_settings = cfg.getSettings();
+ blockApplySettings(true);
+ displaySettings();
+ blockApplySettings(false);
+ return true;
+ }
+ else if (DSPConfigureAudio::match(message))
+ {
+ qDebug("FreeDVDemodGUI::handleMessage: DSPConfigureAudio: %d", m_freeDVDemod->getAudioSampleRate());
+ applyBandwidths(5 - ui->spanLog2->value()); // will update spectrum details with new sample rate
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+void FreeDVDemodGUI::handleInputMessages()
+{
+ Message* message;
+
+ while ((message = getInputMessageQueue()->pop()) != 0)
+ {
+ if (handleMessage(*message))
+ {
+ delete message;
+ }
+ }
+}
+
+void FreeDVDemodGUI::channelMarkerChangedByCursor()
+{
+ ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
+ m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
+ applySettings();
+}
+
+void FreeDVDemodGUI::channelMarkerHighlightedByCursor()
+{
+ setHighlighted(m_channelMarker.getHighlighted());
+}
+
+void FreeDVDemodGUI::on_audioBinaural_toggled(bool binaural)
+{
+ m_audioBinaural = binaural;
+ m_settings.m_audioBinaural = binaural;
+ applySettings();
+}
+
+void FreeDVDemodGUI::on_audioFlipChannels_toggled(bool flip)
+{
+ m_audioFlipChannels = flip;
+ m_settings.m_audioFlipChannels = flip;
+ applySettings();
+}
+
+void FreeDVDemodGUI::on_dsb_toggled(bool dsb)
+{
+ ui->flipSidebands->setEnabled(!dsb);
+ applyBandwidths(5 - ui->spanLog2->value());
+}
+
+void FreeDVDemodGUI::on_deltaFrequency_changed(qint64 value)
+{
+ m_channelMarker.setCenterFrequency(value);
+ m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
+ applySettings();
+}
+
+void FreeDVDemodGUI::on_BW_valueChanged(int value)
+{
+ (void) value;
+ applyBandwidths(5 - ui->spanLog2->value());
+}
+
+void FreeDVDemodGUI::on_lowCut_valueChanged(int value)
+{
+ (void) value;
+ applyBandwidths(5 - ui->spanLog2->value());
+}
+
+void FreeDVDemodGUI::on_volume_valueChanged(int value)
+{
+ ui->volumeText->setText(QString("%1").arg(value / 10.0, 0, 'f', 1));
+ m_settings.m_volume = value / 10.0;
+ applySettings();
+}
+
+void FreeDVDemodGUI::on_agc_toggled(bool checked)
+{
+ m_settings.m_agc = checked;
+ applySettings();
+}
+
+void FreeDVDemodGUI::on_agcClamping_toggled(bool checked)
+{
+ m_settings.m_agcClamping = checked;
+ applySettings();
+}
+
+void FreeDVDemodGUI::on_agcTimeLog2_valueChanged(int value)
+{
+ QString s = QString::number((1<agcTimeText->setText(s);
+ m_settings.m_agcTimeLog2 = value;
+ applySettings();
+}
+
+void FreeDVDemodGUI::on_agcPowerThreshold_valueChanged(int value)
+{
+ displayAGCPowerThreshold(value);
+ m_settings.m_agcPowerThreshold = value;
+ applySettings();
+}
+
+void FreeDVDemodGUI::on_agcThresholdGate_valueChanged(int value)
+{
+ QString s = QString::number(value, 'f', 0);
+ ui->agcThresholdGateText->setText(s);
+ m_settings.m_agcThresholdGate = value;
+ applySettings();
+}
+
+void FreeDVDemodGUI::on_audioMute_toggled(bool checked)
+{
+ m_audioMute = checked;
+ m_settings.m_audioMute = checked;
+ applySettings();
+}
+
+void FreeDVDemodGUI::on_spanLog2_valueChanged(int value)
+{
+ if ((value < 0) || (value > 4)) {
+ return;
+ }
+
+ applyBandwidths(5 - ui->spanLog2->value());
+}
+
+void FreeDVDemodGUI::on_flipSidebands_clicked(bool checked)
+{
+ (void) checked;
+ int bwValue = ui->BW->value();
+ int lcValue = ui->lowCut->value();
+ ui->BW->setValue(-bwValue);
+ ui->lowCut->setValue(-lcValue);
+}
+
+void FreeDVDemodGUI::onMenuDialogCalled(const QPoint &p)
+{
+ BasicChannelSettingsDialog dialog(&m_channelMarker, this);
+ dialog.setUseReverseAPI(m_settings.m_useReverseAPI);
+ dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress);
+ dialog.setReverseAPIPort(m_settings.m_reverseAPIPort);
+ dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex);
+ dialog.setReverseAPIChannelIndex(m_settings.m_reverseAPIChannelIndex);
+
+ dialog.move(p);
+ dialog.exec();
+
+ m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
+ m_settings.m_rgbColor = m_channelMarker.getColor().rgb();
+ m_settings.m_title = m_channelMarker.getTitle();
+ m_settings.m_useReverseAPI = dialog.useReverseAPI();
+ m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress();
+ m_settings.m_reverseAPIPort = dialog.getReverseAPIPort();
+ m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex();
+ m_settings.m_reverseAPIChannelIndex = dialog.getReverseAPIChannelIndex();
+
+ setWindowTitle(m_settings.m_title);
+ setTitleColor(m_settings.m_rgbColor);
+
+ applySettings();
+}
+
+void FreeDVDemodGUI::onWidgetRolled(QWidget* widget, bool rollDown)
+{
+ (void) widget;
+ (void) rollDown;
+}
+
+FreeDVDemodGUI::FreeDVDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) :
+ RollupWidget(parent),
+ ui(new Ui::FreeDVDemodGUI),
+ m_pluginAPI(pluginAPI),
+ m_deviceUISet(deviceUISet),
+ m_channelMarker(this),
+ m_doApplySettings(true),
+ m_spectrumRate(6000),
+ m_audioBinaural(false),
+ m_audioFlipChannels(false),
+ m_audioMute(false),
+ m_squelchOpen(false)
+{
+ ui->setupUi(this);
+ setAttribute(Qt::WA_DeleteOnClose, true);
+ connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
+ connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
+
+ m_spectrumVis = new SpectrumVis(SDR_RX_SCALEF, ui->glSpectrum);
+ m_freeDVDemod = (FreeDVDemod*) rxChannel;
+ m_freeDVDemod->setSampleSink(m_spectrumVis);
+ m_freeDVDemod->setMessageQueueToGUI(getInputMessageQueue());
+
+ resetToDefaults();
+
+ ui->glSpectrum->setCenterFrequency(m_spectrumRate/2);
+ ui->glSpectrum->setSampleRate(m_spectrumRate);
+ ui->glSpectrum->setDisplayWaterfall(true);
+ ui->glSpectrum->setDisplayMaxHold(true);
+ ui->glSpectrum->setSsbSpectrum(true);
+ ui->glSpectrum->connectTimer(MainWindow::getInstance()->getMasterTimer());
+
+ connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick()));
+
+ CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->audioMute);
+ connect(audioMuteRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(audioSelect()));
+
+ ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03)));
+ ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
+ ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999);
+ ui->channelPowerMeter->setColorTheme(LevelMeterSignalDB::ColorGreenAndBlue);
+
+ m_channelMarker.setVisible(true); // activate signal on the last setting only
+
+ m_settings.setChannelMarker(&m_channelMarker);
+ m_settings.setSpectrumGUI(ui->spectrumGUI);
+
+ m_deviceUISet->registerRxChannelInstance(FreeDVDemod::m_channelIdURI, this);
+ m_deviceUISet->addChannelMarker(&m_channelMarker);
+ m_deviceUISet->addRollupWidget(this);
+
+ connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor()));
+ connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor()));
+ connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
+
+ ui->spectrumGUI->setBuddies(m_spectrumVis->getInputMessageQueue(), m_spectrumVis, ui->glSpectrum);
+
+ m_iconDSBUSB.addPixmap(QPixmap("://dsb.png"), QIcon::Normal, QIcon::On);
+ m_iconDSBUSB.addPixmap(QPixmap("://usb.png"), QIcon::Normal, QIcon::Off);
+ m_iconDSBLSB.addPixmap(QPixmap("://dsb.png"), QIcon::Normal, QIcon::On);
+ m_iconDSBLSB.addPixmap(QPixmap("://lsb.png"), QIcon::Normal, QIcon::Off);
+
+ displaySettings();
+ applyBandwidths(5 - ui->spanLog2->value(), true); // does applySettings(true)
+}
+
+FreeDVDemodGUI::~FreeDVDemodGUI()
+{
+ m_deviceUISet->removeRxChannelInstance(this);
+ delete m_freeDVDemod; // TODO: check this: when the GUI closes it has to delete the demodulator
+ delete m_spectrumVis;
+ delete ui;
+}
+
+bool FreeDVDemodGUI::blockApplySettings(bool block)
+{
+ bool ret = !m_doApplySettings;
+ m_doApplySettings = !block;
+ return ret;
+}
+
+void FreeDVDemodGUI::applySettings(bool force)
+{
+ if (m_doApplySettings)
+ {
+ FreeDVDemod::MsgConfigureChannelizer* channelConfigMsg = FreeDVDemod::MsgConfigureChannelizer::create(
+ m_freeDVDemod->getAudioSampleRate(), m_channelMarker.getCenterFrequency());
+ m_freeDVDemod->getInputMessageQueue()->push(channelConfigMsg);
+
+ FreeDVDemod::MsgConfigureFreeDVDemod* message = FreeDVDemod::MsgConfigureFreeDVDemod::create( m_settings, force);
+ m_freeDVDemod->getInputMessageQueue()->push(message);
+ }
+}
+
+void FreeDVDemodGUI::applyBandwidths(int spanLog2, bool force)
+{
+ bool dsb = ui->dsb->isChecked();
+ //int spanLog2 = ui->spanLog2->value();
+ m_spectrumRate = m_freeDVDemod->getAudioSampleRate() / (1<BW->value();
+ int lw = ui->lowCut->value();
+ int bwMax = m_freeDVDemod->getAudioSampleRate() / (100*(1<BW->setTickInterval(tickInterval);
+ ui->lowCut->setTickInterval(tickInterval);
+
+ bw = bw < -bwMax ? -bwMax : bw > bwMax ? bwMax : bw;
+
+ if (bw < 0) {
+ lw = lw < bw+1 ? bw+1 : lw < 0 ? lw : 0;
+ } else if (bw > 0) {
+ lw = lw > bw-1 ? bw-1 : lw < 0 ? 0 : lw;
+ } else {
+ lw = 0;
+ }
+
+ if (dsb)
+ {
+ bw = bw < 0 ? -bw : bw;
+ lw = 0;
+ }
+
+ QString spanStr = QString::number(bwMax/10.0, 'f', 1);
+ QString bwStr = QString::number(bw/10.0, 'f', 1);
+ QString lwStr = QString::number(lw/10.0, 'f', 1);
+
+ if (dsb)
+ {
+ ui->BWText->setText(tr("%1%2k").arg(QChar(0xB1, 0x00)).arg(bwStr));
+ ui->spanText->setText(tr("%1%2k").arg(QChar(0xB1, 0x00)).arg(spanStr));
+ ui->scaleMinus->setText("0");
+ ui->scaleCenter->setText("");
+ ui->scalePlus->setText(tr("%1").arg(QChar(0xB1, 0x00)));
+ ui->lsbLabel->setText("");
+ ui->usbLabel->setText("");
+ ui->glSpectrum->setCenterFrequency(0);
+ ui->glSpectrum->setSampleRate(2*m_spectrumRate);
+ ui->glSpectrum->setSsbSpectrum(false);
+ ui->glSpectrum->setLsbDisplay(false);
+ }
+ else
+ {
+ ui->BWText->setText(tr("%1k").arg(bwStr));
+ ui->spanText->setText(tr("%1k").arg(spanStr));
+ ui->scaleMinus->setText("-");
+ ui->scaleCenter->setText("0");
+ ui->scalePlus->setText("+");
+ ui->lsbLabel->setText("LSB");
+ ui->usbLabel->setText("USB");
+ ui->glSpectrum->setCenterFrequency(m_spectrumRate/2);
+ ui->glSpectrum->setSampleRate(m_spectrumRate);
+ ui->glSpectrum->setSsbSpectrum(true);
+ ui->glSpectrum->setLsbDisplay(bw < 0);
+ }
+
+ ui->lowCutText->setText(tr("%1k").arg(lwStr));
+
+
+ ui->BW->blockSignals(true);
+ ui->lowCut->blockSignals(true);
+
+ ui->BW->setMaximum(bwMax);
+ ui->BW->setMinimum(dsb ? 0 : -bwMax);
+ ui->BW->setValue(bw);
+
+ ui->lowCut->setMaximum(dsb ? 0 : bwMax);
+ ui->lowCut->setMinimum(dsb ? 0 : -bwMax);
+ ui->lowCut->setValue(lw);
+
+ ui->lowCut->blockSignals(false);
+ ui->BW->blockSignals(false);
+
+ ui->channelPowerMeter->setRange(FreeDVDemodSettings::m_minPowerThresholdDB, 0);
+
+ m_settings.m_dsb = dsb;
+ m_settings.m_spanLog2 = spanLog2;
+ m_settings.m_rfBandwidth = bw * 100;
+ m_settings.m_lowCutoff = lw * 100;
+
+ applySettings(force);
+
+ bool wasBlocked = blockApplySettings(true);
+ m_channelMarker.setBandwidth(bw * 200);
+ m_channelMarker.setSidebands(dsb ? ChannelMarker::dsb : bw < 0 ? ChannelMarker::lsb : ChannelMarker::usb);
+ ui->dsb->setIcon(bw < 0 ? m_iconDSBLSB: m_iconDSBUSB);
+ if (!dsb) { m_channelMarker.setLowCutoff(lw * 100); }
+ blockApplySettings(wasBlocked);
+}
+
+void FreeDVDemodGUI::displaySettings()
+{
+ m_channelMarker.blockSignals(true);
+ m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset);
+ m_channelMarker.setBandwidth(m_settings.m_rfBandwidth * 2);
+ m_channelMarker.setTitle(m_settings.m_title);
+ m_channelMarker.setLowCutoff(m_settings.m_lowCutoff);
+
+ ui->flipSidebands->setEnabled(!m_settings.m_dsb);
+
+ if (m_settings.m_dsb) {
+ m_channelMarker.setSidebands(ChannelMarker::dsb);
+ } else {
+ if (m_settings.m_rfBandwidth < 0) {
+ m_channelMarker.setSidebands(ChannelMarker::lsb);
+ ui->dsb->setIcon(m_iconDSBLSB);
+ } else {
+ m_channelMarker.setSidebands(ChannelMarker::usb);
+ ui->dsb->setIcon(m_iconDSBUSB);
+ }
+ }
+
+ m_channelMarker.blockSignals(false);
+ m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only
+
+ setTitleColor(m_settings.m_rgbColor);
+ setWindowTitle(m_channelMarker.getTitle());
+
+ blockApplySettings(true);
+
+ ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
+
+ ui->agc->setChecked(m_settings.m_agc);
+ ui->agcClamping->setChecked(m_settings.m_agcClamping);
+ ui->audioBinaural->setChecked(m_settings.m_audioBinaural);
+ ui->audioFlipChannels->setChecked(m_settings.m_audioFlipChannels);
+ ui->audioMute->setChecked(m_settings.m_audioMute);
+ ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
+
+ // Prevent uncontrolled triggering of applyBandwidths
+ ui->spanLog2->blockSignals(true);
+ ui->dsb->blockSignals(true);
+ ui->BW->blockSignals(true);
+
+ ui->dsb->setChecked(m_settings.m_dsb);
+ ui->spanLog2->setValue(5 - m_settings.m_spanLog2);
+
+ ui->BW->setValue(m_settings.m_rfBandwidth / 100.0);
+ QString s = QString::number(m_settings.m_rfBandwidth/1000.0, 'f', 1);
+
+ if (m_settings.m_dsb)
+ {
+ ui->BWText->setText(tr("%1%2k").arg(QChar(0xB1, 0x00)).arg(s));
+ }
+ else
+ {
+ ui->BWText->setText(tr("%1k").arg(s));
+ }
+
+ ui->spanLog2->blockSignals(false);
+ ui->dsb->blockSignals(false);
+ ui->BW->blockSignals(false);
+
+ // The only one of the four signals triggering applyBandwidths will trigger it once only with all other values
+ // set correctly and therefore validate the settings and apply them to dependent widgets
+ ui->lowCut->setValue(m_settings.m_lowCutoff / 100.0);
+ ui->lowCutText->setText(tr("%1k").arg(m_settings.m_lowCutoff / 1000.0));
+
+ ui->volume->setValue(m_settings.m_volume * 10.0);
+ ui->volumeText->setText(QString("%1").arg(m_settings.m_volume, 0, 'f', 1));
+
+ ui->agcTimeLog2->setValue(m_settings.m_agcTimeLog2);
+ s = QString::number((1<agcTimeLog2->value()), 'f', 0);
+ ui->agcTimeText->setText(s);
+
+ ui->agcPowerThreshold->setValue(m_settings.m_agcPowerThreshold);
+ displayAGCPowerThreshold(ui->agcPowerThreshold->value());
+
+ ui->agcThresholdGate->setValue(m_settings.m_agcThresholdGate);
+ s = QString::number(ui->agcThresholdGate->value(), 'f', 0);
+ ui->agcThresholdGateText->setText(s);
+
+ blockApplySettings(false);
+}
+
+void FreeDVDemodGUI::displayAGCPowerThreshold(int value)
+{
+ if (value == FreeDVDemodSettings::m_minPowerThresholdDB)
+ {
+ ui->agcPowerThresholdText->setText("---");
+ }
+ else
+ {
+ QString s = QString::number(value, 'f', 0);
+ ui->agcPowerThresholdText->setText(s);
+ }
+}
+
+void FreeDVDemodGUI::leaveEvent(QEvent*)
+{
+ m_channelMarker.setHighlighted(false);
+}
+
+void FreeDVDemodGUI::enterEvent(QEvent*)
+{
+ m_channelMarker.setHighlighted(true);
+}
+
+void FreeDVDemodGUI::audioSelect()
+{
+ qDebug("FreeDVDemodGUI::audioSelect");
+ AudioSelectDialog audioSelect(DSPEngine::instance()->getAudioDeviceManager(), m_settings.m_audioDeviceName);
+ audioSelect.exec();
+
+ if (audioSelect.m_selected)
+ {
+ m_settings.m_audioDeviceName = audioSelect.m_audioDeviceName;
+ applySettings();
+ }
+}
+
+void FreeDVDemodGUI::tick()
+{
+ double magsqAvg, magsqPeak;
+ int nbMagsqSamples;
+ m_freeDVDemod->getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples);
+ double powDbAvg = CalcDb::dbPower(magsqAvg);
+ double powDbPeak = CalcDb::dbPower(magsqPeak);
+
+ ui->channelPowerMeter->levelChanged(
+ (FreeDVDemodSettings::m_mminPowerThresholdDBf + powDbAvg) / FreeDVDemodSettings::m_mminPowerThresholdDBf,
+ (FreeDVDemodSettings::m_mminPowerThresholdDBf + powDbPeak) / FreeDVDemodSettings::m_mminPowerThresholdDBf,
+ nbMagsqSamples);
+
+ if (m_tickCount % 4 == 0) {
+ ui->channelPower->setText(tr("%1 dB").arg(powDbAvg, 0, 'f', 1));
+ }
+
+ bool squelchOpen = m_freeDVDemod->getAudioActive();
+
+ if (squelchOpen != m_squelchOpen)
+ {
+ if (squelchOpen) {
+ ui->audioMute->setStyleSheet("QToolButton { background-color : green; }");
+ } else {
+ ui->audioMute->setStyleSheet("QToolButton { background:rgb(79,79,79); }");
+ }
+
+ m_squelchOpen = squelchOpen;
+ }
+
+ m_tickCount++;
+}
diff --git a/plugins/channelrx/demodfreedv/freedvdemodgui.h b/plugins/channelrx/demodfreedv/freedvdemodgui.h
new file mode 100644
index 000000000..fd2dc0828
--- /dev/null
+++ b/plugins/channelrx/demodfreedv/freedvdemodgui.h
@@ -0,0 +1,120 @@
+///////////////////////////////////////////////////////////////////////////////////
+// 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 //
+// //
+// 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_FREEDVDEMODGUI_H
+#define INCLUDE_FREEDVDEMODGUI_H
+
+#include
+
+#include "plugin/plugininstancegui.h"
+#include "gui/rollupwidget.h"
+#include "dsp/channelmarker.h"
+#include "dsp/movingaverage.h"
+#include "util/messagequeue.h"
+#include "freedvdemodsettings.h"
+
+class PluginAPI;
+class DeviceUISet;
+
+class AudioFifo;
+class FreeDVDemod;
+class SpectrumVis;
+class BasebandSampleSink;
+
+namespace Ui {
+ class FreeDVDemodGUI;
+}
+
+class FreeDVDemodGUI : public RollupWidget, public PluginInstanceGUI {
+ Q_OBJECT
+
+public:
+ static FreeDVDemodGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel);
+ virtual void destroy();
+
+ void setName(const QString& name);
+ QString getName() const;
+ virtual qint64 getCenterFrequency() const;
+ virtual void setCenterFrequency(qint64 centerFrequency);
+
+ void resetToDefaults();
+ QByteArray serialize() const;
+ bool deserialize(const QByteArray& data);
+ virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
+ virtual bool handleMessage(const Message& message);
+
+public slots:
+ void channelMarkerChangedByCursor();
+ void channelMarkerHighlightedByCursor();
+
+private:
+ Ui::FreeDVDemodGUI* ui;
+ PluginAPI* m_pluginAPI;
+ DeviceUISet* m_deviceUISet;
+ ChannelMarker m_channelMarker;
+ FreeDVDemodSettings m_settings;
+ bool m_doApplySettings;
+ int m_spectrumRate;
+ bool m_audioBinaural;
+ bool m_audioFlipChannels;
+ bool m_audioMute;
+ bool m_squelchOpen;
+ uint32_t m_tickCount;
+
+ FreeDVDemod* m_freeDVDemod;
+ SpectrumVis* m_spectrumVis;
+ MessageQueue m_inputMessageQueue;
+
+ QIcon m_iconDSBUSB;
+ QIcon m_iconDSBLSB;
+
+ explicit FreeDVDemodGUI(PluginAPI* pluginAPI, DeviceUISet* deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0);
+ virtual ~FreeDVDemodGUI();
+
+ bool blockApplySettings(bool block);
+ void applySettings(bool force = false);
+ void applyBandwidths(int spanLog2, bool force = false);
+ void displaySettings();
+
+ void displayAGCPowerThreshold(int value);
+
+ void leaveEvent(QEvent*);
+ void enterEvent(QEvent*);
+
+private slots:
+ void on_deltaFrequency_changed(qint64 value);
+ void on_audioBinaural_toggled(bool binaural);
+ void on_audioFlipChannels_toggled(bool flip);
+ void on_dsb_toggled(bool dsb);
+ void on_BW_valueChanged(int value);
+ void on_lowCut_valueChanged(int value);
+ void on_volume_valueChanged(int value);
+ void on_agc_toggled(bool checked);
+ void on_agcClamping_toggled(bool checked);
+ void on_agcTimeLog2_valueChanged(int value);
+ void on_agcPowerThreshold_valueChanged(int value);
+ void on_agcThresholdGate_valueChanged(int value);
+ void on_audioMute_toggled(bool checked);
+ void on_spanLog2_valueChanged(int value);
+ void on_flipSidebands_clicked(bool checked);
+ void onWidgetRolled(QWidget* widget, bool rollDown);
+ void onMenuDialogCalled(const QPoint& p);
+ void handleInputMessages();
+ void audioSelect();
+ void tick();
+};
+
+#endif // INCLUDE_FREEDVDEMODGUI_H
diff --git a/plugins/channelrx/demodfreedv/freedvdemodgui.ui b/plugins/channelrx/demodfreedv/freedvdemodgui.ui
new file mode 100644
index 000000000..f28c54b12
--- /dev/null
+++ b/plugins/channelrx/demodfreedv/freedvdemodgui.ui
@@ -0,0 +1,999 @@
+
+
+ FreeDVDemodGUI
+
+
+
+ 0
+ 0
+ 412
+ 190
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 412
+ 0
+
+
+
+
+ Liberation Sans
+ 9
+
+
+
+ SSB Demodulator
+
+
+
+
+ 0
+ 0
+ 410
+ 171
+
+
+
+
+ 410
+ 0
+
+
+
+ Settings
+
+
+
+ 3
+
+
+ 2
+
+
+ 2
+
+
+ 2
+
+
+ 2
+
+ -
+
+
+ 2
+
+
-
+
+
-
+
+
+
+ 16
+ 0
+
+
+
+ Df
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 125
+ 16
+
+
+
+
+ Liberation Mono
+ 12
+
+
+
+ PointingHandCursor
+
+
+ Qt::StrongFocus
+
+
+ Demod shift frequency from center in Hz
+
+
+
+ -
+
+
+ Hz
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+
+ 60
+ 0
+
+
+
+ Channel power
+
+
+ Qt::RightToLeft
+
+
+ -100.0 dB
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
-
+
+
+ Toggle btw Mono and Binaural I/Q audio
+
+
+ ...
+
+
+
+ :/mono.png
+ :/stereo.png:/mono.png
+
+
+ true
+
+
+
+ -
+
+
+ Flip left/right audio channels
+
+
+ ...
+
+
+
+ :/flip_lr.png
+ :/flip_rl.png:/flip_lr.png
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
+ Flip sideband in SSB mode (LSB->USB or USB->LSB)
+
+
+
+
+
+
+ :/flip_sidebands.png:/flip_sidebands.png
+
+
+
+ -
+
+
+ DSB/SSB toggle
+
+
+ ...
+
+
+
+ :/usb.png
+ :/dsb.png:/usb.png
+
+
+ true
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+ dB
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 24
+
+
+
+
+ Liberation Mono
+ 8
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 50
+ 0
+
+
+
+
+ 50
+ 16777215
+
+
+
+ Span
+
+
+
+ -
+
+
+ Demod frequency span
+
+
+ 0
+
+
+ 4
+
+
+ 1
+
+
+ 2
+
+
+ 2
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+
+ 50
+ 0
+
+
+
+
+ 50
+ 16777215
+
+
+
+ 6.0k
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 50
+ 0
+
+
+
+
+ 50
+ 16777215
+
+
+
+ Low cut
+
+
+
+ -
+
+
+
+ 16777215
+ 16
+
+
+
+ Highpass filter cutoff frequency (SSB)
+
+
+ -60
+
+
+ 60
+
+
+ 1
+
+
+ 3
+
+
+ Qt::Horizontal
+
+
+ false
+
+
+ QSlider::NoTicks
+
+
+ 5
+
+
+
+ -
+
+
+
+ 50
+ 0
+
+
+
+
+ 50
+ 16777215
+
+
+
+ 0.3k
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 50
+ 0
+
+
+
+
+ 50
+ 16777215
+
+
+
+ Hi cut
+
+
+
+ -
+
+
+
+ 16777215
+ 16
+
+
+
+ Lowpass filter cutoff frequency
+
+
+ -60
+
+
+ 60
+
+
+ 1
+
+
+ 30
+
+
+ Qt::Horizontal
+
+
+ QSlider::TicksBelow
+
+
+ 5
+
+
+
+ -
+
+
+
+ 50
+ 0
+
+
+
+
+ 50
+ 16777215
+
+
+
+ 3.0k
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 50
+ 0
+
+
+
+
+ 50
+ 10
+
+
+
+
+ 8
+
+
+
+ f:
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 10
+ 10
+
+
+
+
+ 8
+
+
+
+ -
+
+
+
+ -
+
+
+
+ 8
+
+
+
+ LSB
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+
+ 12
+ 10
+
+
+
+
+ 8
+
+
+
+ 0
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+
+ 8
+
+
+
+ USB
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+
+ 10
+ 10
+
+
+
+
+ 8
+
+
+
+ +
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 50
+ 0
+
+
+
+
+ 50
+ 10
+
+
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
-
+
+
+ Vol
+
+
+
+ -
+
+
+
+ 24
+ 24
+
+
+
+
+ 24
+ 24
+
+
+
+ 1
+
+
+
+ -
+
+
+
+ 30
+ 16777215
+
+
+
+ 2.0
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
+ Toggle AGC
+
+
+ AGC
+
+
+ true
+
+
+
+ -
+
+
+ Toggle AGC clamping to maximum allowable signal
+
+
+ CL
+
+
+ true
+
+
+
+ -
+
+
+
+ 24
+ 24
+
+
+
+ AGC time constant (ms in log2 steps)
+
+
+ 4
+
+
+ 11
+
+
+ 1
+
+
+ 7
+
+
+
+ -
+
+
+
+ 35
+ 0
+
+
+
+ AGC time constant (ms)
+
+
+ 0000
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 24
+ 24
+
+
+
+ Power threshold (dB)
+
+
+ -120
+
+
+ 0
+
+
+ 1
+
+
+ -40
+
+
+
+ -
+
+
+
+ 26
+ 0
+
+
+
+ Power threshold (dB)
+
+
+ -000
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 24
+ 24
+
+
+
+ Power threshold gate (ms)
+
+
+ 20
+
+
+ 1
+
+
+ 4
+
+
+
+ -
+
+
+
+ 16
+ 0
+
+
+
+ Power threshold gate (ms)
+
+
+ 00
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Mute/Unmute audio
+
+
+
+
+
+
+ :/sound_on.png
+ :/sound_off.png:/sound_on.png
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+ 10
+ 170
+ 218
+ 284
+
+
+
+ Channel Spectrum
+
+
+
+ 2
+
+
+ 3
+
+
+ 3
+
+
+ 3
+
+
+ 3
+
+ -
+
+
+
+ 200
+ 250
+
+
+
+
+ Liberation Mono
+ 8
+
+
+
+
+ -
+
+
+
+
+
+
+
+ RollupWidget
+ QWidget
+
+ 1
+
+
+ LevelMeterSignalDB
+ QWidget
+
+ 1
+
+
+ GLSpectrum
+ QWidget
+
+ 1
+
+
+ GLSpectrumGUI
+ QWidget
+
+ 1
+
+
+ ValueDialZ
+ QWidget
+
+ 1
+
+
+ ButtonSwitch
+ QToolButton
+
+
+
+ TickedSlider
+ QSlider
+
+
+
+
+
+
+
+
diff --git a/plugins/channelrx/demodfreedv/freedvdemodsettings.cpp b/plugins/channelrx/demodfreedv/freedvdemodsettings.cpp
new file mode 100644
index 000000000..c56522742
--- /dev/null
+++ b/plugins/channelrx/demodfreedv/freedvdemodsettings.cpp
@@ -0,0 +1,216 @@
+///////////////////////////////////////////////////////////////////////////////////
+// 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 //
+// //
+// 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 "util/simpleserializer.h"
+#include "settings/serializable.h"
+#include "freedvdemodsettings.h"
+
+#ifdef SDR_RX_SAMPLE_24BIT
+const int FreeDVDemodSettings::m_minPowerThresholdDB = -120;
+const float FreeDVDemodSettings::m_mminPowerThresholdDBf = 120.0f;
+#else
+const int FreeDVDemodSettings::m_minPowerThresholdDB = -100;
+const float FreeDVDemodSettings::m_mminPowerThresholdDBf = 100.0f;
+#endif
+
+FreeDVDemodSettings::FreeDVDemodSettings() :
+ m_channelMarker(0),
+ m_spectrumGUI(0)
+{
+ resetToDefaults();
+}
+
+void FreeDVDemodSettings::resetToDefaults()
+{
+ m_audioBinaural = false;
+ m_audioFlipChannels = false;
+ m_dsb = false;
+ m_audioMute = false;
+ m_agc = false;
+ m_agcClamping = false;
+ m_agcPowerThreshold = -40;
+ m_agcThresholdGate = 4;
+ m_agcTimeLog2 = 7;
+ m_rfBandwidth = 3000;
+ m_lowCutoff = 300;
+ m_volume = 3.0;
+ m_spanLog2 = 3;
+ m_inputFrequencyOffset = 0;
+ m_rgbColor = QColor(0, 255, 204).rgb();
+ m_title = "FreeDV Demodulator";
+ m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName;
+ m_freeDVMode = FreeDVMode2400A;
+ m_useReverseAPI = false;
+ m_reverseAPIAddress = "127.0.0.1";
+ m_reverseAPIPort = 8888;
+ m_reverseAPIDeviceIndex = 0;
+ m_reverseAPIChannelIndex = 0;
+}
+
+QByteArray FreeDVDemodSettings::serialize() const
+{
+ SimpleSerializer s(1);
+ s.writeS32(1, m_inputFrequencyOffset);
+ s.writeS32(2, m_rfBandwidth / 100.0);
+ s.writeS32(3, m_volume * 10.0);
+
+ if (m_spectrumGUI) {
+ s.writeBlob(4, m_spectrumGUI->serialize());
+ }
+
+ s.writeU32(5, m_rgbColor);
+ s.writeS32(6, m_lowCutoff / 100.0);
+ s.writeS32(7, m_spanLog2);
+ s.writeBool(8, m_audioBinaural);
+ s.writeBool(9, m_audioFlipChannels);
+ s.writeBool(10, m_dsb);
+ s.writeBool(11, m_agc);
+ s.writeS32(12, m_agcTimeLog2);
+ s.writeS32(13, m_agcPowerThreshold);
+ s.writeS32(14, m_agcThresholdGate);
+ s.writeBool(15, m_agcClamping);
+ s.writeString(16, m_title);
+ s.writeString(17, m_audioDeviceName);
+ s.writeBool(18, m_useReverseAPI);
+ s.writeString(19, m_reverseAPIAddress);
+ s.writeU32(20, m_reverseAPIPort);
+ s.writeU32(21, m_reverseAPIDeviceIndex);
+ s.writeU32(22, m_reverseAPIChannelIndex);
+ s.writeS32(23, (int) m_freeDVMode);
+
+ return s.final();
+}
+
+bool FreeDVDemodSettings::deserialize(const QByteArray& data)
+{
+ SimpleDeserializer d(data);
+
+ if(!d.isValid())
+ {
+ resetToDefaults();
+ return false;
+ }
+
+ if(d.getVersion() == 1)
+ {
+ QByteArray bytetmp;
+ qint32 tmp;
+ uint32_t utmp;
+ QString strtmp;
+
+ d.readS32(1, &m_inputFrequencyOffset, 0);
+ d.readS32(2, &tmp, 30);
+ m_rfBandwidth = tmp * 100.0;
+ d.readS32(3, &tmp, 30);
+ m_volume = tmp / 10.0;
+
+ if (m_spectrumGUI) {
+ d.readBlob(4, &bytetmp);
+ m_spectrumGUI->deserialize(bytetmp);
+ }
+
+ d.readU32(5, &m_rgbColor);
+ d.readS32(6, &tmp, 30);
+ m_lowCutoff = tmp * 100.0;
+ d.readS32(7, &m_spanLog2, 3);
+ d.readBool(8, &m_audioBinaural, false);
+ d.readBool(9, &m_audioFlipChannels, false);
+ d.readBool(10, &m_dsb, false);
+ d.readBool(11, &m_agc, false);
+ d.readS32(12, &m_agcTimeLog2, 7);
+ d.readS32(13, &m_agcPowerThreshold, -40);
+ d.readS32(14, &m_agcThresholdGate, 4);
+ d.readBool(15, &m_agcClamping, false);
+ d.readString(16, &m_title, "SSB Demodulator");
+ d.readString(17, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName);
+ d.readBool(18, &m_useReverseAPI, false);
+ d.readString(19, &m_reverseAPIAddress, "127.0.0.1");
+ d.readU32(20, &utmp, 0);
+
+ if ((utmp > 1023) && (utmp < 65535)) {
+ m_reverseAPIPort = utmp;
+ } else {
+ m_reverseAPIPort = 8888;
+ }
+
+ d.readU32(21, &utmp, 0);
+ m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp;
+ d.readU32(22, &utmp, 0);
+ m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp;
+
+ d.readS32(23, &tmp, 0);
+ if ((tmp < 0) || (tmp > (int) FreeDVMode::FreeDVMode700D)) {
+ m_freeDVMode = FreeDVMode::FreeDVMode2400A;
+ } else {
+ m_freeDVMode = (FreeDVMode) tmp;
+ }
+
+ return true;
+ }
+ else
+ {
+ resetToDefaults();
+ return false;
+ }
+}
+
+int FreeDVDemodSettings::getHiCutoff(FreeDVMode freeDVMode)
+{
+ switch(freeDVMode)
+ {
+ case FreeDVMode800XA: // C4FM NB
+ return 2400.0;
+ break;
+ case FreeDVMode700D: // OFDM
+ case FreeDVMode1600: // OFDM
+ return 2200.0;
+ break;
+ case FreeDVMode2400A: // C4FM WB
+ default:
+ return 6000.0;
+ break;
+ }
+}
+
+int FreeDVDemodSettings::getLowCutoff(FreeDVMode freeDVMode)
+{
+ switch(freeDVMode)
+ {
+ case FreeDVMode800XA: // C4FM NB
+ return 400.0;
+ break;
+ case FreeDVMode700D: // OFDM
+ case FreeDVMode1600: // OFDM
+ return 800.0;
+ break;
+ case FreeDVMode2400A: // C4FM WB
+ default:
+ return 0.0;
+ break;
+ }
+}
+
+int FreeDVDemodSettings::getModSampleRate(FreeDVMode freeDVMode)
+{
+ if (freeDVMode == FreeDVMode2400A) {
+ return 48000;
+ } else {
+ return 8000;
+ }
+}
diff --git a/plugins/channelrx/demodfreedv/freedvdemodsettings.h b/plugins/channelrx/demodfreedv/freedvdemodsettings.h
new file mode 100644
index 000000000..f05ca1b36
--- /dev/null
+++ b/plugins/channelrx/demodfreedv/freedvdemodsettings.h
@@ -0,0 +1,79 @@
+///////////////////////////////////////////////////////////////////////////////////
+// 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 //
+// //
+// 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_DEMODFREEDV_FREEDVDEMODSETTINGS_H_
+#define PLUGINS_CHANNELRX_DEMODFREEDV_FREEDVDEMODSETTINGS_H_
+
+#include
+#include
+#include
+
+class Serializable;
+
+struct FreeDVDemodSettings
+{
+ typedef enum
+ {
+ FreeDVMode2400A,
+ FreeDVMode1600,
+ FreeDVMode800XA,
+ FreeDVMode700D
+ } FreeDVMode;
+
+ qint32 m_inputFrequencyOffset;
+ Real m_rfBandwidth;
+ Real m_lowCutoff;
+ Real m_volume;
+ int m_spanLog2;
+ bool m_audioBinaural;
+ bool m_audioFlipChannels;
+ bool m_dsb;
+ bool m_audioMute;
+ bool m_agc;
+ bool m_agcClamping;
+ int m_agcTimeLog2;
+ int m_agcPowerThreshold;
+ int m_agcThresholdGate;
+ quint32 m_rgbColor;
+ QString m_title;
+ QString m_audioDeviceName;
+ FreeDVMode m_freeDVMode;
+ bool m_useReverseAPI;
+ QString m_reverseAPIAddress;
+ uint16_t m_reverseAPIPort;
+ uint16_t m_reverseAPIDeviceIndex;
+ uint16_t m_reverseAPIChannelIndex;
+
+ Serializable *m_channelMarker;
+ Serializable *m_spectrumGUI;
+
+ FreeDVDemodSettings();
+ void resetToDefaults();
+ void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; }
+ void setSpectrumGUI(Serializable *spectrumGUI) { m_spectrumGUI = spectrumGUI; }
+ QByteArray serialize() const;
+ bool deserialize(const QByteArray& data);
+
+ static const int m_minPowerThresholdDB;
+ static const float m_mminPowerThresholdDBf;
+
+ static int getHiCutoff(FreeDVMode freeDVMode);
+ static int getLowCutoff(FreeDVMode freeDVMode);
+ static int getModSampleRate(FreeDVMode freeDVMode);
+};
+
+
+#endif /* PLUGINS_CHANNELRX_DEMODFREEDV_FREEDVDEMODSETTINGS_H_ */
diff --git a/plugins/channelrx/demodfreedv/freedvplugin.cpp b/plugins/channelrx/demodfreedv/freedvplugin.cpp
new file mode 100644
index 000000000..1c0c009aa
--- /dev/null
+++ b/plugins/channelrx/demodfreedv/freedvplugin.cpp
@@ -0,0 +1,78 @@
+///////////////////////////////////////////////////////////////////////////////////
+// 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 //
+// //
+// 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 "device/devicesourceapi.h"
+#include "plugin/pluginapi.h"
+#ifndef SERVER_MODE
+#include "freedvdemodgui.h"
+#endif
+#include "freedvdemod.h"
+#include "freedvplugin.h"
+
+const PluginDescriptor FreeDVPlugin::m_pluginDescriptor = {
+ QString("FreeDV Demodulator"),
+ QString("4.5.0"),
+ QString("(c) Edouard Griffiths, F4EXB"),
+ QString("https://github.com/f4exb/sdrangel"),
+ true,
+ QString("https://github.com/f4exb/sdrangel")
+};
+
+FreeDVPlugin::FreeDVPlugin(QObject* parent) :
+ QObject(parent),
+ m_pluginAPI(0)
+{
+}
+
+const PluginDescriptor& FreeDVPlugin::getPluginDescriptor() const
+{
+ return m_pluginDescriptor;
+}
+
+void FreeDVPlugin::initPlugin(PluginAPI* pluginAPI)
+{
+ m_pluginAPI = pluginAPI;
+
+ // register demodulator
+ m_pluginAPI->registerRxChannel(FreeDVDemod::m_channelIdURI, FreeDVDemod::m_channelId, this);
+}
+
+#ifdef SERVER_MODE
+PluginInstanceGUI* FreeDVPlugin::createRxChannelGUI(
+ DeviceUISet *deviceUISet __attribute__((unused)),
+ BasebandSampleSink *rxChannel __attribute__((unused)))
+{
+ return 0;
+}
+#else
+PluginInstanceGUI* FreeDVPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel)
+{
+ return FreeDVDemodGUI::create(m_pluginAPI, deviceUISet, rxChannel);
+}
+#endif
+
+BasebandSampleSink* FreeDVPlugin::createRxChannelBS(DeviceSourceAPI *deviceAPI)
+{
+ return new FreeDVDemod(deviceAPI);
+}
+
+ChannelSinkAPI* FreeDVPlugin::createRxChannelCS(DeviceSourceAPI *deviceAPI)
+{
+ return new FreeDVDemod(deviceAPI);
+}
+
diff --git a/plugins/channelrx/demodfreedv/freedvplugin.h b/plugins/channelrx/demodfreedv/freedvplugin.h
new file mode 100644
index 000000000..78375d750
--- /dev/null
+++ b/plugins/channelrx/demodfreedv/freedvplugin.h
@@ -0,0 +1,47 @@
+///////////////////////////////////////////////////////////////////////////////////
+// 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 //
+// //
+// 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_FREEDVPLUGIN_H
+#define INCLUDE_FREEDVPLUGIN_H
+
+#include
+#include "plugin/plugininterface.h"
+
+class DeviceUISet;
+class BasebandSampleSink;
+
+class FreeDVPlugin : public QObject, PluginInterface {
+ Q_OBJECT
+ Q_INTERFACES(PluginInterface)
+ Q_PLUGIN_METADATA(IID "sdrangel.channel.freedvdemod")
+
+public:
+ explicit FreeDVPlugin(QObject* parent = NULL);
+
+ const PluginDescriptor& getPluginDescriptor() const;
+ void initPlugin(PluginAPI* pluginAPI);
+
+ virtual PluginInstanceGUI* createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel);
+ virtual BasebandSampleSink* createRxChannelBS(DeviceSourceAPI *deviceAPI);
+ virtual ChannelSinkAPI* createRxChannelCS(DeviceSourceAPI *deviceAPI);
+
+private:
+ static const PluginDescriptor m_pluginDescriptor;
+
+ PluginAPI* m_pluginAPI;
+};
+
+#endif // INCLUDE_FREEDVPLUGIN_H
diff --git a/plugins/channelrx/demodssb/ssbdemodgui.cpp b/plugins/channelrx/demodssb/ssbdemodgui.cpp
index ca4702b0b..94fc7e5f4 100644
--- a/plugins/channelrx/demodssb/ssbdemodgui.cpp
+++ b/plugins/channelrx/demodssb/ssbdemodgui.cpp
@@ -1,6 +1,5 @@
#include
-#include "ssbdemodgui.h"
#include "ssbdemodgui.h"
#include