From 793d8b9f493541b4183fd29975ca978533f8bc5f Mon Sep 17 00:00:00 2001
From: f4exb <f4exb06@gmail.com>
Date: Mon, 21 Dec 2020 02:30:29 +0100
Subject: [PATCH] Demod Analyzer: implementation for the rest of planned
 plugins

---
 plugins/channelrx/demoddsd/dsddemodplugin.cpp |  2 +-
 plugins/channelrx/demodnfm/nfmplugin.cpp      |  2 +-
 plugins/channelrx/demodssb/ssbdemod.cpp       |  1 +
 .../channelrx/demodssb/ssbdemodbaseband.cpp   |  7 ++-
 plugins/channelrx/demodssb/ssbdemodbaseband.h |  4 +-
 plugins/channelrx/demodssb/ssbdemodsink.cpp   | 38 +++++++++++++++
 plugins/channelrx/demodssb/ssbdemodsink.h     | 10 +++-
 plugins/channelrx/demodssb/ssbplugin.cpp      |  2 +-
 plugins/channelrx/demodwfm/wfmdemod.cpp       |  1 +
 .../channelrx/demodwfm/wfmdemodbaseband.cpp   |  5 ++
 plugins/channelrx/demodwfm/wfmdemodbaseband.h |  2 +
 plugins/channelrx/demodwfm/wfmdemodsink.cpp   | 48 +++++++++++++++++--
 plugins/channelrx/demodwfm/wfmdemodsink.h     |  9 +++-
 plugins/channelrx/demodwfm/wfmplugin.cpp      |  2 +-
 plugins/channeltx/modam/ammodsource.cpp       |  1 -
 plugins/channeltx/modam/ammodsource.h         |  1 -
 plugins/channeltx/modnfm/nfmmod.cpp           |  1 +
 plugins/channeltx/modnfm/nfmmodbaseband.cpp   |  7 ++-
 plugins/channeltx/modnfm/nfmmodbaseband.h     |  2 +
 plugins/channeltx/modnfm/nfmmodplugin.cpp     |  2 +-
 plugins/channeltx/modnfm/nfmmodsource.cpp     | 47 ++++++++++++++++--
 plugins/channeltx/modnfm/nfmmodsource.h       |  8 ++++
 plugins/channeltx/modpacket/packetmod.cpp     |  1 +
 .../channeltx/modpacket/packetmodbaseband.cpp |  5 ++
 .../channeltx/modpacket/packetmodbaseband.h   |  3 +-
 .../channeltx/modpacket/packetmodplugin.cpp   |  2 +-
 .../channeltx/modpacket/packetmodsource.cpp   | 39 +++++++++++++++
 plugins/channeltx/modpacket/packetmodsource.h |  7 +++
 plugins/channeltx/modssb/ssbmod.cpp           |  1 +
 plugins/channeltx/modssb/ssbmodbaseband.cpp   |  5 ++
 plugins/channeltx/modssb/ssbmodbaseband.h     |  2 +
 plugins/channeltx/modssb/ssbmodplugin.cpp     |  2 +-
 plugins/channeltx/modssb/ssbmodsource.cpp     | 40 ++++++++++++++++
 plugins/channeltx/modssb/ssbmodsource.h       |  7 +++
 plugins/channeltx/modwfm/wfmmod.cpp           |  1 +
 plugins/channeltx/modwfm/wfmmodbaseband.cpp   |  7 ++-
 plugins/channeltx/modwfm/wfmmodbaseband.h     |  2 +
 plugins/channeltx/modwfm/wfmmodplugin.cpp     |  2 +-
 plugins/channeltx/modwfm/wfmmodsource.cpp     | 42 +++++++++++++++-
 plugins/channeltx/modwfm/wfmmodsource.h       |  8 ++++
 .../demodanalyzer/demodanalyzersettings.cpp   | 12 +++++
 41 files changed, 363 insertions(+), 27 deletions(-)

diff --git a/plugins/channelrx/demoddsd/dsddemodplugin.cpp b/plugins/channelrx/demoddsd/dsddemodplugin.cpp
index 4f10b0a35..b63b55742 100644
--- a/plugins/channelrx/demoddsd/dsddemodplugin.cpp
+++ b/plugins/channelrx/demoddsd/dsddemodplugin.cpp
@@ -30,7 +30,7 @@
 const PluginDescriptor DSDDemodPlugin::m_pluginDescriptor = {
     DSDDemod::m_channelId,
 	QStringLiteral("DSD Demodulator"),
-    QStringLiteral("6.3.3"),
+    QStringLiteral("6.4.0"),
 	QStringLiteral("(c) Edouard Griffiths, F4EXB"),
 	QStringLiteral("https://github.com/f4exb/sdrangel"),
 	true,
diff --git a/plugins/channelrx/demodnfm/nfmplugin.cpp b/plugins/channelrx/demodnfm/nfmplugin.cpp
index e357d7d47..e8eabc0ca 100644
--- a/plugins/channelrx/demodnfm/nfmplugin.cpp
+++ b/plugins/channelrx/demodnfm/nfmplugin.cpp
@@ -12,7 +12,7 @@
 const PluginDescriptor NFMPlugin::m_pluginDescriptor = {
     NFMDemod::m_channelId,
 	QStringLiteral("NFM Demodulator"),
-	QStringLiteral("6.3.3"),
+	QStringLiteral("6.4.0"),
 	QStringLiteral("(c) Edouard Griffiths, F4EXB"),
 	QStringLiteral("https://github.com/f4exb/sdrangel"),
 	true,
diff --git a/plugins/channelrx/demodssb/ssbdemod.cpp b/plugins/channelrx/demodssb/ssbdemod.cpp
index 7bf61f081..1e7beaefd 100644
--- a/plugins/channelrx/demodssb/ssbdemod.cpp
+++ b/plugins/channelrx/demodssb/ssbdemod.cpp
@@ -58,6 +58,7 @@ SSBDemod::SSBDemod(DeviceAPI *deviceAPI) :
     m_thread = new QThread(this);
     m_basebandSink = new SSBDemodBaseband();
     m_basebandSink->setSpectrumSink(&m_spectrumVis);
+    m_basebandSink->setChannel(this);
     m_basebandSink->moveToThread(m_thread);
 
 	applySettings(m_settings, true);
diff --git a/plugins/channelrx/demodssb/ssbdemodbaseband.cpp b/plugins/channelrx/demodssb/ssbdemodbaseband.cpp
index c44798740..7f19ee1e1 100644
--- a/plugins/channelrx/demodssb/ssbdemodbaseband.cpp
+++ b/plugins/channelrx/demodssb/ssbdemodbaseband.cpp
@@ -63,6 +63,11 @@ void SSBDemodBaseband::reset()
     m_sampleFifo.reset();
 }
 
+void SSBDemodBaseband::setChannel(ChannelAPI *channel)
+{
+    m_sink.setChannel(channel);
+}
+
 void SSBDemodBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
 {
     m_sampleFifo.write(begin, end);
@@ -198,4 +203,4 @@ void SSBDemodBaseband::setBasebandSampleRate(int sampleRate)
 {
     m_channelizer->setBasebandSampleRate(sampleRate);
     m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
-}
\ No newline at end of file
+}
diff --git a/plugins/channelrx/demodssb/ssbdemodbaseband.h b/plugins/channelrx/demodssb/ssbdemodbaseband.h
index 6cc518827..2d5402b53 100644
--- a/plugins/channelrx/demodssb/ssbdemodbaseband.h
+++ b/plugins/channelrx/demodssb/ssbdemodbaseband.h
@@ -28,6 +28,7 @@
 #include "ssbdemodsink.h"
 
 class DownChannelizer;
+class ChannelAPI;
 
 class SSBDemodBaseband : public QObject
 {
@@ -69,6 +70,7 @@ public:
     bool getAudioActive() const { return m_sink.getAudioActive(); }
     void setBasebandSampleRate(int sampleRate);
     void setMessageQueueToGUI(MessageQueue *messageQueue) { m_messageQueueToGUI = messageQueue; }
+    void setChannel(ChannelAPI *channel);
 
 private:
     SampleSinkFifo m_sampleFifo;
@@ -90,4 +92,4 @@ private slots:
     void handleData(); //!< Handle data when samples have to be processed
 };
 
-#endif // INCLUDE_SSBDEMODBASEBAND_H
\ No newline at end of file
+#endif // INCLUDE_SSBDEMODBASEBAND_H
diff --git a/plugins/channelrx/demodssb/ssbdemodsink.cpp b/plugins/channelrx/demodssb/ssbdemodsink.cpp
index 06a4a5add..fde3c5814 100644
--- a/plugins/channelrx/demodssb/ssbdemodsink.cpp
+++ b/plugins/channelrx/demodssb/ssbdemodsink.cpp
@@ -25,8 +25,11 @@
 #include "dsp/dspcommands.h"
 #include "dsp/devicesamplemimo.h"
 #include "dsp/basebandsamplesink.h"
+#include "dsp/datafifo.h"
 #include "device/deviceapi.h"
 #include "util/db.h"
+#include "util/messagequeue.h"
+#include "maincore.h"
 
 #include "ssbdemodsink.h"
 
@@ -62,6 +65,9 @@ SSBDemodSink::SSBDemodSink() :
 	m_undersampleCount = 0;
 	m_sum = 0;
 
+    m_demodBuffer.resize(1<<12);
+    m_demodBufferFill = 0;
+
 	m_usb = true;
 	m_magsq = 0.0f;
 	m_magsqSum = 0.0f;
@@ -194,6 +200,25 @@ void SSBDemodSink::processOneSample(Complex &ci)
                 m_audioBuffer[m_audioBufferFill].l = sample;
                 m_audioBuffer[m_audioBufferFill].r = sample;
             }
+
+            m_demodBuffer[m_demodBufferFill] = (z.real() + z.imag()) * 0.7;
+            ++m_demodBufferFill;
+
+            if (m_demodBufferFill >= m_demodBuffer.size())
+            {
+                QList<DataFifo*> *dataFifos = MainCore::instance()->getDataPipes().getFifos(m_channel, "demod");
+
+                if (dataFifos)
+                {
+                    QList<DataFifo*>::iterator it = dataFifos->begin();
+
+                    for (; it != dataFifos->end(); ++it) {
+                        (*it)->write((quint8*) &m_demodBuffer[0], m_demodBuffer.size() * sizeof(qint16));
+                    }
+                }
+
+                m_demodBufferFill = 0;
+            }
         }
 
         ++m_audioBufferFill;
@@ -279,6 +304,19 @@ void SSBDemodSink::applyAudioSampleRate(int sampleRate)
 
     m_audioFifo.setSize(sampleRate);
     m_audioSampleRate = sampleRate;
+
+    QList<MessageQueue*> *messageQueues = MainCore::instance()->getMessagePipes().getMessageQueues(m_channel, "reportdemod");
+
+    if (messageQueues)
+    {
+        QList<MessageQueue*>::iterator it = messageQueues->begin();
+
+        for (; it != messageQueues->end(); ++it)
+        {
+            MainCore::MsgChannelDemodReport *msg = MainCore::MsgChannelDemodReport::create(m_channel, sampleRate);
+            (*it)->push(msg);
+        }
+    }
 }
 
 void SSBDemodSink::applySettings(const SSBDemodSettings& settings, bool force)
diff --git a/plugins/channelrx/demodssb/ssbdemodsink.h b/plugins/channelrx/demodssb/ssbdemodsink.h
index 407458a00..6514b990c 100644
--- a/plugins/channelrx/demodssb/ssbdemodsink.h
+++ b/plugins/channelrx/demodssb/ssbdemodsink.h
@@ -18,7 +18,7 @@
 #ifndef INCLUDE_SSBDEMODSINK_H
 #define INCLUDE_SSBDEMODSINK_H
 
-#include <vector>
+#include <QVector>
 
 #include "dsp/channelsamplesink.h"
 #include "dsp/ncof.h"
@@ -31,6 +31,7 @@
 #include "ssbdemodsettings.h"
 
 class BasebandSampleSink;
+class ChannelAPI;
 
 class SSBDemodSink : public ChannelSampleSink {
 public:
@@ -47,6 +48,7 @@ public:
     AudioFifo *getAudioFifo() { return &m_audioFifo; }
     double getMagSq() const { return m_magsq; }
 	bool getAudioActive() const { return m_audioActive; }
+    void setChannel(ChannelAPI *channel) { m_channel = channel; }
 
     void getMagSqLevels(double& avg, double& peak, int& nbSamples)
     {
@@ -78,6 +80,7 @@ private:
     };
 
     SSBDemodSettings m_settings;
+    ChannelAPI *m_channel;
 
 	Real m_Bandwidth;
 	Real m_LowCutoff;
@@ -121,10 +124,13 @@ private:
 	AudioFifo m_audioFifo;
 	quint32 m_audioSampleRate;
 
+    QVector<qint16> m_demodBuffer;
+    int m_demodBufferFill;
+
 	static const int m_ssbFftLen;
 	static const int m_agcTarget;
 
     void processOneSample(Complex &ci);
 };
 
-#endif // INCLUDE_SSBDEMODSINK_H
\ No newline at end of file
+#endif // INCLUDE_SSBDEMODSINK_H
diff --git a/plugins/channelrx/demodssb/ssbplugin.cpp b/plugins/channelrx/demodssb/ssbplugin.cpp
index 22e288c37..a4e79f62c 100644
--- a/plugins/channelrx/demodssb/ssbplugin.cpp
+++ b/plugins/channelrx/demodssb/ssbplugin.cpp
@@ -12,7 +12,7 @@
 const PluginDescriptor SSBPlugin::m_pluginDescriptor = {
     SSBDemod::m_channelId,
 	QStringLiteral("SSB Demodulator"),
-	QStringLiteral("6.3.3"),
+	QStringLiteral("6.4.0"),
 	QStringLiteral("(c) Edouard Griffiths, F4EXB"),
 	QStringLiteral("https://github.com/f4exb/sdrangel"),
 	true,
diff --git a/plugins/channelrx/demodwfm/wfmdemod.cpp b/plugins/channelrx/demodwfm/wfmdemod.cpp
index 630e94863..64deec0f3 100644
--- a/plugins/channelrx/demodwfm/wfmdemod.cpp
+++ b/plugins/channelrx/demodwfm/wfmdemod.cpp
@@ -59,6 +59,7 @@ WFMDemod::WFMDemod(DeviceAPI* deviceAPI) :
 
     m_thread = new QThread(this);
     m_basebandSink = new WFMDemodBaseband();
+    m_basebandSink->setChannel(this);
     m_basebandSink->moveToThread(m_thread);
 
 	applySettings(m_settings, true);
diff --git a/plugins/channelrx/demodwfm/wfmdemodbaseband.cpp b/plugins/channelrx/demodwfm/wfmdemodbaseband.cpp
index 15ad153e8..dd78d933d 100644
--- a/plugins/channelrx/demodwfm/wfmdemodbaseband.cpp
+++ b/plugins/channelrx/demodwfm/wfmdemodbaseband.cpp
@@ -58,6 +58,11 @@ void WFMDemodBaseband::reset()
     m_sampleFifo.reset();
 }
 
+void WFMDemodBaseband::setChannel(ChannelAPI *channel)
+{
+    m_sink.setChannel(channel);
+}
+
 void WFMDemodBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
 {
     m_sampleFifo.write(begin, end);
diff --git a/plugins/channelrx/demodwfm/wfmdemodbaseband.h b/plugins/channelrx/demodwfm/wfmdemodbaseband.h
index b79e289a2..5a5a5b561 100644
--- a/plugins/channelrx/demodwfm/wfmdemodbaseband.h
+++ b/plugins/channelrx/demodwfm/wfmdemodbaseband.h
@@ -28,6 +28,7 @@
 #include "wfmdemodsink.h"
 
 class DownChannelizer;
+class ChannelAPI;
 
 class WFMDemodBaseband : public QObject
 {
@@ -69,6 +70,7 @@ public:
     bool getSquelchOpen() const { return m_sink.getSquelchOpen(); }
     int getSquelchState() const { return m_sink.getSquelchState(); }
     void getMagSqLevels(double& avg, double& peak, int& nbSamples) { m_sink.getMagSqLevels(avg, peak, nbSamples); }
+    void setChannel(ChannelAPI *channel);
 
 private:
     SampleSinkFifo m_sampleFifo;
diff --git a/plugins/channelrx/demodwfm/wfmdemodsink.cpp b/plugins/channelrx/demodwfm/wfmdemodsink.cpp
index 302a9cc8c..ed2127cf4 100644
--- a/plugins/channelrx/demodwfm/wfmdemodsink.cpp
+++ b/plugins/channelrx/demodwfm/wfmdemodsink.cpp
@@ -25,7 +25,10 @@
 #include "dsp/dspengine.h"
 #include "dsp/dspcommands.h"
 #include "dsp/devicesamplemimo.h"
+#include "dsp/datafifo.h"
 #include "util/db.h"
+#include "util/messagequeue.h"
+#include "maincore.h"
 
 #include "wfmdemodsink.h"
 
@@ -35,6 +38,7 @@ WFMDemodSink::WFMDemodSink() :
     m_channelSampleRate(384000),
     m_channelFrequencyOffset(0),
     m_audioSampleRate(48000),
+    m_squelchState(0),
     m_squelchOpen(false),
     m_magsq(0.0f),
     m_magsqSum(0.0f),
@@ -48,6 +52,9 @@ WFMDemodSink::WFMDemodSink() :
 	m_audioBuffer.resize(16384);
 	m_audioBufferFill = 0;
 
+    m_demodBuffer.resize(1<<12);
+    m_demodBufferFill = 0;
+
 	applySettings(m_settings, true);
     applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
 }
@@ -130,6 +137,24 @@ void WFMDemodSink::feed(const SampleVector::const_iterator& begin, const SampleV
 				}
 
 				m_interpolatorDistanceRemain += m_interpolatorDistance;
+                m_demodBuffer[m_demodBufferFill] = sample;
+                ++m_demodBufferFill;
+
+                if (m_demodBufferFill >= m_demodBuffer.size())
+                {
+                    QList<DataFifo*> *dataFifos = MainCore::instance()->getDataPipes().getFifos(m_channel, "demod");
+
+                    if (dataFifos)
+                    {
+                        QList<DataFifo*>::iterator it = dataFifos->begin();
+
+                        for (; it != dataFifos->end(); ++it) {
+                            (*it)->write((quint8*) &m_demodBuffer[0], m_demodBuffer.size() * sizeof(qint16));
+                        }
+                    }
+
+                    m_demodBufferFill = 0;
+                }
 			}
 		}
 	}
@@ -162,6 +187,19 @@ void WFMDemodSink::applyAudioSampleRate(int sampleRate)
     m_interpolatorDistanceRemain = (Real) m_channelSampleRate / sampleRate;
     m_interpolatorDistance =  (Real) m_channelSampleRate / (Real) sampleRate;
     m_audioSampleRate = sampleRate;
+
+    QList<MessageQueue*> *messageQueues = MainCore::instance()->getMessagePipes().getMessageQueues(m_channel, "reportdemod");
+
+    if (messageQueues)
+    {
+        QList<MessageQueue*>::iterator it = messageQueues->begin();
+
+        for (; it != messageQueues->end(); ++it)
+        {
+            MainCore::MsgChannelDemodReport *msg = MainCore::MsgChannelDemodReport::create(m_channel, sampleRate);
+            (*it)->push(msg);
+        }
+    }
 }
 
 void WFMDemodSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
@@ -186,8 +224,8 @@ void WFMDemodSink::applyChannelSettings(int channelSampleRate, int channelFreque
         Real lowCut = -(m_settings.m_rfBandwidth / 2.0) / channelSampleRate;
         Real hiCut  = (m_settings.m_rfBandwidth / 2.0) / channelSampleRate;
         m_rfFilter->create_filter(lowCut, hiCut);
-        m_fmExcursion = m_settings.m_rfBandwidth / (Real) channelSampleRate;
-        m_phaseDiscri.setFMScaling(1.0f/m_fmExcursion);
+        //m_fmExcursion = m_settings.m_rfBandwidth / (Real) channelSampleRate;
+        m_phaseDiscri.setFMScaling((float) channelSampleRate / ((float) 2*m_fmExcursion));
         qDebug("WFMDemod::applySettings: m_fmExcursion: %f", m_fmExcursion);
     }
 
@@ -224,8 +262,10 @@ void WFMDemodSink::applySettings(const WFMDemodSettings& settings, bool force)
         Real lowCut = -(settings.m_rfBandwidth / 2.0) / m_channelSampleRate;
         Real hiCut  = (settings.m_rfBandwidth / 2.0) / m_channelSampleRate;
         m_rfFilter->create_filter(lowCut, hiCut);
-        m_fmExcursion = settings.m_rfBandwidth / (Real) m_channelSampleRate;
-        m_phaseDiscri.setFMScaling(1.0f/m_fmExcursion);
+        m_fmExcursion = (settings.m_rfBandwidth / 2) - m_settings.m_afBandwidth;
+        m_fmExcursion = m_fmExcursion < 2500 ? 2500 : m_fmExcursion;
+        //m_fmExcursion = settings.m_rfBandwidth / (Real) m_channelSampleRate;
+        m_phaseDiscri.setFMScaling((float) m_channelSampleRate / ((float) 2*m_fmExcursion));
         qDebug("WFMDemodSink::applySettings: m_fmExcursion: %f", m_fmExcursion);
     }
 
diff --git a/plugins/channelrx/demodwfm/wfmdemodsink.h b/plugins/channelrx/demodwfm/wfmdemodsink.h
index 46a1a27d8..ad3018538 100644
--- a/plugins/channelrx/demodwfm/wfmdemodsink.h
+++ b/plugins/channelrx/demodwfm/wfmdemodsink.h
@@ -18,7 +18,7 @@
 #ifndef INCLUDE_WFMDEMODSINK_H
 #define INCLUDE_WFMDEMODSINK_H
 
-#include <vector>
+#include <QVector>
 
 #include "dsp/channelsamplesink.h"
 #include "dsp/nco.h"
@@ -32,6 +32,8 @@
 
 #include "wfmdemodsettings.h"
 
+class ChannelAPI;
+
 class WFMDemodSink : public ChannelSampleSink {
 public:
     WFMDemodSink();
@@ -67,6 +69,7 @@ public:
     AudioFifo *getAudioFifo() { return &m_audioFifo; }
     void applyAudioSampleRate(int sampleRate);
     int getAudioSampleRate() const { return m_audioSampleRate; }
+    void setChannel(ChannelAPI *channel) { m_channel = channel; }
 
 private:
     struct MagSqLevelsStore
@@ -87,6 +90,7 @@ private:
     int m_channelSampleRate;
     int m_channelFrequencyOffset;
     WFMDemodSettings m_settings;
+    ChannelAPI *m_channel;
 
     int m_audioSampleRate;
 
@@ -115,6 +119,9 @@ private:
 	SampleVector m_sampleBuffer;
 	PhaseDiscriminators m_phaseDiscri;
 
+    QVector<qint16> m_demodBuffer;
+    int m_demodBufferFill;
+
     static const unsigned int m_rfFilterFftLength;
 };
 
diff --git a/plugins/channelrx/demodwfm/wfmplugin.cpp b/plugins/channelrx/demodwfm/wfmplugin.cpp
index a45d4f67f..4864b9f41 100644
--- a/plugins/channelrx/demodwfm/wfmplugin.cpp
+++ b/plugins/channelrx/demodwfm/wfmplugin.cpp
@@ -13,7 +13,7 @@
 const PluginDescriptor WFMPlugin::m_pluginDescriptor = {
     WFMDemod::m_channelId,
 	QStringLiteral("WFM Demodulator"),
-	QStringLiteral("6.3.3"),
+	QStringLiteral("6.4.0"),
 	QStringLiteral("(c) Edouard Griffiths, F4EXB"),
 	QStringLiteral("https://github.com/f4exb/sdrangel"),
 	true,
diff --git a/plugins/channeltx/modam/ammodsource.cpp b/plugins/channeltx/modam/ammodsource.cpp
index 734eb662f..77b7c9649 100644
--- a/plugins/channeltx/modam/ammodsource.cpp
+++ b/plugins/channeltx/modam/ammodsource.cpp
@@ -343,7 +343,6 @@ void AMModSource::applyFeedbackAudioSampleRate(int sampleRate)
     qDebug("AMModSource::applyFeedbackAudioSampleRate: %u", sampleRate);
 
     m_feedbackInterpolatorDistanceRemain = 0;
-    m_feedbackInterpolatorConsumed = false;
     m_feedbackInterpolatorDistance = (Real) sampleRate / (Real) m_audioSampleRate;
     Real cutoff = std::min(sampleRate, m_audioSampleRate) / 2.2f;
     m_feedbackInterpolator.create(48, sampleRate, cutoff, 3.0);
diff --git a/plugins/channeltx/modam/ammodsource.h b/plugins/channeltx/modam/ammodsource.h
index 528819526..af97dde02 100644
--- a/plugins/channeltx/modam/ammodsource.h
+++ b/plugins/channeltx/modam/ammodsource.h
@@ -85,7 +85,6 @@ private:
     Interpolator m_feedbackInterpolator;
     Real m_feedbackInterpolatorDistance;
     Real m_feedbackInterpolatorDistanceRemain;
-    bool m_feedbackInterpolatorConsumed;
 
     double m_magsq;
     MovingAverageUtil<double, double, 16> m_movingAverage;
diff --git a/plugins/channeltx/modnfm/nfmmod.cpp b/plugins/channeltx/modnfm/nfmmod.cpp
index 1d6b9210d..4d362b6d0 100644
--- a/plugins/channeltx/modnfm/nfmmod.cpp
+++ b/plugins/channeltx/modnfm/nfmmod.cpp
@@ -66,6 +66,7 @@ NFMMod::NFMMod(DeviceAPI *deviceAPI) :
     m_thread = new QThread(this);
     m_basebandSource = new NFMModBaseband();
     m_basebandSource->setInputFileStream(&m_ifstream);
+    m_basebandSource->setChannel(this);
     m_basebandSource->moveToThread(m_thread);
 
     applySettings(m_settings, true);
diff --git a/plugins/channeltx/modnfm/nfmmodbaseband.cpp b/plugins/channeltx/modnfm/nfmmodbaseband.cpp
index d5fd7ffe9..fc821e8da 100644
--- a/plugins/channeltx/modnfm/nfmmodbaseband.cpp
+++ b/plugins/channeltx/modnfm/nfmmodbaseband.cpp
@@ -62,6 +62,11 @@ void NFMModBaseband::reset()
     m_sampleFifo.reset();
 }
 
+void NFMModBaseband::setChannel(ChannelAPI *channel)
+{
+    m_source.setChannel(channel);
+}
+
 void NFMModBaseband::pull(const SampleVector::iterator& begin, unsigned int nbSamples)
 {
     unsigned int part1Begin, part1End, part2Begin, part2End;
@@ -226,4 +231,4 @@ void NFMModBaseband::applySettings(const NFMModSettings& settings, bool force)
 int NFMModBaseband::getChannelSampleRate() const
 {
     return m_channelizer->getChannelSampleRate();
-}
\ No newline at end of file
+}
diff --git a/plugins/channeltx/modnfm/nfmmodbaseband.h b/plugins/channeltx/modnfm/nfmmodbaseband.h
index 378e10777..0057fd5ff 100644
--- a/plugins/channeltx/modnfm/nfmmodbaseband.h
+++ b/plugins/channeltx/modnfm/nfmmodbaseband.h
@@ -28,6 +28,7 @@
 #include "nfmmodsource.h"
 
 class UpChannelizer;
+class ChannelAPI;
 
 class NFMModBaseband : public QObject
 {
@@ -69,6 +70,7 @@ public:
     void setInputFileStream(std::ifstream *ifstream) { m_source.setInputFileStream(ifstream); }
     AudioFifo *getAudioFifo() { return m_source.getAudioFifo(); }
     AudioFifo *getFeedbackAudioFifo() { return m_source.getFeedbackAudioFifo(); }
+    void setChannel(ChannelAPI *channel);
 
 signals:
 	/**
diff --git a/plugins/channeltx/modnfm/nfmmodplugin.cpp b/plugins/channeltx/modnfm/nfmmodplugin.cpp
index 5c908c06a..df961d521 100644
--- a/plugins/channeltx/modnfm/nfmmodplugin.cpp
+++ b/plugins/channeltx/modnfm/nfmmodplugin.cpp
@@ -28,7 +28,7 @@
 const PluginDescriptor NFMModPlugin::m_pluginDescriptor = {
     NFMMod::m_channelId,
     QStringLiteral("NFM Modulator"),
-    QStringLiteral("6.3.3"),
+    QStringLiteral("6.4.0"),
     QStringLiteral("(c) Edouard Griffiths, F4EXB"),
     QStringLiteral("https://github.com/f4exb/sdrangel"),
     true,
diff --git a/plugins/channeltx/modnfm/nfmmodsource.cpp b/plugins/channeltx/modnfm/nfmmodsource.cpp
index 1eabb40dc..ebe4eb9db 100644
--- a/plugins/channeltx/modnfm/nfmmodsource.cpp
+++ b/plugins/channeltx/modnfm/nfmmodsource.cpp
@@ -17,6 +17,10 @@
 
 #include <QDebug>
 
+#include "dsp/datafifo.h"
+#include "util/messagequeue.h"
+#include "maincore.h"
+
 #include "nfmmodsource.h"
 
 const int NFMModSource::m_levelNbSamples = 480; // every 10ms
@@ -44,6 +48,9 @@ NFMModSource::NFMModSource() :
 	m_feedbackAudioBuffer.resize(1<<14);
 	m_feedbackAudioBufferFill = 0;
 
+    m_demodBuffer.resize(1<<12);
+    m_demodBufferFill = 0;
+
 	m_magsq = 0.0;
 
     applySettings(m_settings, true);
@@ -132,7 +139,7 @@ void NFMModSource::pullAudio(unsigned int nbSamplesAudio)
 
 void NFMModSource::modulateSample()
 {
-	Real t0, t;
+	Real t0, t1, t;
 
     pullAF(t0);
     m_preemphasisFilter.process(t0, t);
@@ -144,11 +151,13 @@ void NFMModSource::modulateSample()
     calculateLevel(t);
 
     if (m_settings.m_ctcssOn) {
-        m_modPhasor += (m_settings.m_fmDeviation / (float) m_audioSampleRate) * (0.85f * m_bandpass.filter(t) + 0.15f * 0.625f * m_ctcssNco.next()) * 1.33f;
+        t1 = (0.85f * m_bandpass.filter(t) + 0.15f * 0.625f * m_ctcssNco.next()) * 1.2f;
     } else {
-        m_modPhasor += (m_settings.m_fmDeviation / (float) m_audioSampleRate) * m_bandpass.filter(t) * 1.33f;
+        t1 = m_bandpass.filter(t) * 1.2f;
     }
 
+    m_modPhasor += (m_settings.m_fmDeviation / (float) m_audioSampleRate) * t1;
+
     // limit phasor range to ]-pi,pi]
     if (m_modPhasor > M_PI) {
         m_modPhasor -= (2.0f * M_PI);
@@ -156,6 +165,25 @@ void NFMModSource::modulateSample()
 
     m_modSample.real(cos(m_modPhasor) * 0.891235351562f * SDR_TX_SCALEF); // -1 dB
     m_modSample.imag(sin(m_modPhasor) * 0.891235351562f * SDR_TX_SCALEF);
+
+    m_demodBuffer[m_demodBufferFill] = t1 * std::numeric_limits<int16_t>::max();
+    ++m_demodBufferFill;
+
+    if (m_demodBufferFill >= m_demodBuffer.size())
+    {
+        QList<DataFifo*> *dataFifos = MainCore::instance()->getDataPipes().getFifos(m_channel, "demod");
+
+        if (dataFifos)
+        {
+            QList<DataFifo*>::iterator it = dataFifos->begin();
+
+            for (; it != dataFifos->end(); ++it) {
+                (*it)->write((quint8*) &m_demodBuffer[0], m_demodBuffer.size() * sizeof(qint16));
+            }
+        }
+
+        m_demodBufferFill = 0;
+    }
 }
 
 void NFMModSource::pullAF(Real& sample)
@@ -321,6 +349,19 @@ void NFMModSource::applyAudioSampleRate(int sampleRate)
     m_preemphasisFilter.configure(m_preemphasis*sampleRate);
     m_audioSampleRate = sampleRate;
     applyFeedbackAudioSampleRate(m_feedbackAudioSampleRate);
+
+    QList<MessageQueue*> *messageQueues = MainCore::instance()->getMessagePipes().getMessageQueues(m_channel, "reportdemod");
+
+    if (messageQueues)
+    {
+        QList<MessageQueue*>::iterator it = messageQueues->begin();
+
+        for (; it != messageQueues->end(); ++it)
+        {
+            MainCore::MsgChannelDemodReport *msg = MainCore::MsgChannelDemodReport::create(m_channel, sampleRate);
+            (*it)->push(msg);
+        }
+    }
 }
 
 void NFMModSource::applyFeedbackAudioSampleRate(int sampleRate)
diff --git a/plugins/channeltx/modnfm/nfmmodsource.h b/plugins/channeltx/modnfm/nfmmodsource.h
index cfad5fe45..73cf14e20 100644
--- a/plugins/channeltx/modnfm/nfmmodsource.h
+++ b/plugins/channeltx/modnfm/nfmmodsource.h
@@ -20,6 +20,7 @@
 
 #include <QObject>
 #include <QMutex>
+#include <QVector>
 
 #include <iostream>
 #include <fstream>
@@ -36,6 +37,8 @@
 
 #include "nfmmodsettings.h"
 
+class ChannelAPI;
+
 class NFMModSource : public QObject, public ChannelSampleSource
 {
     Q_OBJECT
@@ -54,6 +57,7 @@ public:
     void applyFeedbackAudioSampleRate(int sampleRate);
     int getAudioSampleRate() const { return m_audioSampleRate; }
     int getFeedbackAudioSampleRate() const { return m_feedbackAudioSampleRate; }
+    void setChannel(ChannelAPI *channel) { m_channel = channel; }
     CWKeyer& getCWKeyer() { return m_cwKeyer; }
     double getMagSq() const { return m_magsq; }
     void getLevels(qreal& rmsLevel, qreal& peakLevel, int& numSamples) const
@@ -69,6 +73,7 @@ private:
     int m_channelSampleRate;
     int m_channelFrequencyOffset;
     NFMModSettings m_settings;
+    ChannelAPI *m_channel;
 
     NCO m_carrierNco;
     NCOF m_toneNco;
@@ -86,6 +91,9 @@ private:
     Real m_feedbackInterpolatorDistanceRemain;
     bool m_feedbackInterpolatorConsumed;
 
+    QVector<qint16> m_demodBuffer;
+    int m_demodBufferFill;
+
     Lowpass<Real> m_lowpass;
     Bandpass<Real> m_bandpass;
     HighPassFilterRC m_preemphasisFilter;
diff --git a/plugins/channeltx/modpacket/packetmod.cpp b/plugins/channeltx/modpacket/packetmod.cpp
index 57db1e210..b84b73403 100644
--- a/plugins/channeltx/modpacket/packetmod.cpp
+++ b/plugins/channeltx/modpacket/packetmod.cpp
@@ -64,6 +64,7 @@ PacketMod::PacketMod(DeviceAPI *deviceAPI) :
     m_thread = new QThread(this);
     m_basebandSource = new PacketModBaseband();
     m_basebandSource->setSpectrumSampleSink(&m_spectrumVis);
+    m_basebandSource->setChannel(this);
     m_basebandSource->moveToThread(m_thread);
 
     applySettings(m_settings, true);
diff --git a/plugins/channeltx/modpacket/packetmodbaseband.cpp b/plugins/channeltx/modpacket/packetmodbaseband.cpp
index 810cc3807..292ab8bc7 100644
--- a/plugins/channeltx/modpacket/packetmodbaseband.cpp
+++ b/plugins/channeltx/modpacket/packetmodbaseband.cpp
@@ -56,6 +56,11 @@ void PacketModBaseband::reset()
     m_sampleFifo.reset();
 }
 
+void PacketModBaseband::setChannel(ChannelAPI *channel)
+{
+    m_source.setChannel(channel);
+}
+
 void PacketModBaseband::pull(const SampleVector::iterator& begin, unsigned int nbSamples)
 {
     unsigned int part1Begin, part1End, part2Begin, part2End;
diff --git a/plugins/channeltx/modpacket/packetmodbaseband.h b/plugins/channeltx/modpacket/packetmodbaseband.h
index 5ed3388e1..1ae98d066 100644
--- a/plugins/channeltx/modpacket/packetmodbaseband.h
+++ b/plugins/channeltx/modpacket/packetmodbaseband.h
@@ -29,6 +29,7 @@
 #include "packetmodsource.h"
 
 class UpChannelizer;
+class ChannelAPI;
 
 class PacketModBaseband : public QObject
 {
@@ -65,7 +66,7 @@ public:
     double getMagSq() const { return m_source.getMagSq(); }
     int getChannelSampleRate() const;
     void setSpectrumSampleSink(BasebandSampleSink* sampleSink) { m_source.setSpectrumSink(sampleSink); }
-
+    void setChannel(ChannelAPI *channel);
 
 signals:
     /**
diff --git a/plugins/channeltx/modpacket/packetmodplugin.cpp b/plugins/channeltx/modpacket/packetmodplugin.cpp
index c29240a33..ba3c698fe 100644
--- a/plugins/channeltx/modpacket/packetmodplugin.cpp
+++ b/plugins/channeltx/modpacket/packetmodplugin.cpp
@@ -29,7 +29,7 @@
 const PluginDescriptor PacketModPlugin::m_pluginDescriptor = {
     PacketMod::m_channelId,
     QStringLiteral("Packet Modulator"),
-    QStringLiteral("6.3.3"),
+    QStringLiteral("6.4.0"),
     QStringLiteral("(c) Jon Beniston, M7RCE"),
     QStringLiteral("https://github.com/f4exb/sdrangel"),
     true,
diff --git a/plugins/channeltx/modpacket/packetmodsource.cpp b/plugins/channeltx/modpacket/packetmodsource.cpp
index 3b668a0be..f4f8431d1 100644
--- a/plugins/channeltx/modpacket/packetmodsource.cpp
+++ b/plugins/channeltx/modpacket/packetmodsource.cpp
@@ -20,8 +20,11 @@
 #include <QDebug>
 
 #include "dsp/basebandsamplesink.h"
+#include "dsp/datafifo.h"
 #include "packetmodsource.h"
 #include "util/crc.h"
+#include "util/messagequeue.h"
+#include "maincore.h"
 
 PacketModSource::PacketModSource() :
     m_channelSampleRate(48000),
@@ -46,6 +49,10 @@ PacketModSource::PacketModSource() :
     qDebug() << "PacketModSource::PacketModSource creating BPF : " << m_channelSampleRate;
     m_bandpass.create(301, m_channelSampleRate, 800.0, 2600.0);
     m_pulseShape.create(0.5, 6, m_channelSampleRate/9600);
+
+    m_demodBuffer.resize(1<<12);
+    m_demodBufferFill = 0;
+
     applySettings(m_settings, true);
     applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
 }
@@ -266,6 +273,25 @@ void PacketModSource::modulateSample()
         Real s = std::real(m_modSample);
         calculateLevel(s);
     }
+
+    m_demodBuffer[m_demodBufferFill] = audioMod * std::numeric_limits<int16_t>::max();
+    ++m_demodBufferFill;
+
+    if (m_demodBufferFill >= m_demodBuffer.size())
+    {
+        QList<DataFifo*> *dataFifos = MainCore::instance()->getDataPipes().getFifos(m_channel, "demod");
+
+        if (dataFifos)
+        {
+            QList<DataFifo*>::iterator it = dataFifos->begin();
+
+            for (; it != dataFifos->end(); ++it) {
+                (*it)->write((quint8*) &m_demodBuffer[0], m_demodBuffer.size() * sizeof(qint16));
+            }
+        }
+
+        m_demodBufferFill = 0;
+    }
 }
 
 void PacketModSource::calculateLevel(Real& sample)
@@ -383,6 +409,19 @@ void PacketModSource::applyChannelSettings(int channelSampleRate, int channelFre
     qDebug() << "m_samplesPerSymbol: " << m_samplesPerSymbol << " (" << m_channelSampleRate << "/" << m_settings.m_baud << ")";
     // Precalculate FM sensensity to save doing it in the loop
     m_phaseSensitivity = 2.0f * M_PI * m_settings.m_fmDeviation / (double)m_channelSampleRate;
+
+    QList<MessageQueue*> *messageQueues = MainCore::instance()->getMessagePipes().getMessageQueues(m_channel, "reportdemod");
+
+    if (messageQueues)
+    {
+        QList<MessageQueue*>::iterator it = messageQueues->begin();
+
+        for (; it != messageQueues->end(); ++it)
+        {
+            MainCore::MsgChannelDemodReport *msg = MainCore::MsgChannelDemodReport::create(m_channel, m_channelSampleRate);
+            (*it)->push(msg);
+        }
+    }
 }
 
 static uint8_t *ax25_address(uint8_t *p, QString address, uint8_t crrl)
diff --git a/plugins/channeltx/modpacket/packetmodsource.h b/plugins/channeltx/modpacket/packetmodsource.h
index 0a9028dee..bc3eb63f8 100644
--- a/plugins/channeltx/modpacket/packetmodsource.h
+++ b/plugins/channeltx/modpacket/packetmodsource.h
@@ -21,6 +21,7 @@
 
 #include <QMutex>
 #include <QDebug>
+#include <QVector>
 
 #include <iostream>
 #include <fstream>
@@ -44,6 +45,7 @@
 #define AX25_NO_L3      0xf0
 
 class BasebandSampleSink;
+class ChannelAPI;
 
 class PacketModSource : public ChannelSampleSource
 {
@@ -66,12 +68,14 @@ public:
     void applySettings(const PacketModSettings& settings, bool force = false);
     void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false);
     void addTXPacket(QString callsign, QString to, QString via, QString data);
+    void setChannel(ChannelAPI *channel) { m_channel = channel; }
 
 private:
     int m_channelSampleRate;
     int m_channelFrequencyOffset;
     int m_spectrumRate;
     PacketModSettings m_settings;
+    ChannelAPI *m_channel;
 
     NCO m_carrierNco;
     Real m_audioPhase;
@@ -126,6 +130,9 @@ private:
 
     std::ofstream m_audioFile;          // For debug output of baseband waveform
 
+    QVector<qint16> m_demodBuffer;
+    int m_demodBufferFill;
+
     bool bitsValid();                   // Are there and bits to transmit
     int getBit();                       // Get bit from m_bits
     void addBit(int bit);               // Add bit to m_bits, with zero stuffing
diff --git a/plugins/channeltx/modssb/ssbmod.cpp b/plugins/channeltx/modssb/ssbmod.cpp
index 34e929ff3..e5c09f4df 100644
--- a/plugins/channeltx/modssb/ssbmod.cpp
+++ b/plugins/channeltx/modssb/ssbmod.cpp
@@ -67,6 +67,7 @@ SSBMod::SSBMod(DeviceAPI *deviceAPI) :
     m_basebandSource = new SSBModBaseband();
     m_basebandSource->setSpectrumSink(&m_spectrumVis);
     m_basebandSource->setInputFileStream(&m_ifstream);
+    m_basebandSource->setChannel(this);
     m_basebandSource->moveToThread(m_thread);
 
     applySettings(m_settings, true);
diff --git a/plugins/channeltx/modssb/ssbmodbaseband.cpp b/plugins/channeltx/modssb/ssbmodbaseband.cpp
index 803ec15e2..9c10252b8 100644
--- a/plugins/channeltx/modssb/ssbmodbaseband.cpp
+++ b/plugins/channeltx/modssb/ssbmodbaseband.cpp
@@ -63,6 +63,11 @@ void SSBModBaseband::reset()
     m_sampleFifo.reset();
 }
 
+void SSBModBaseband::setChannel(ChannelAPI *channel)
+{
+    m_source.setChannel(channel);
+}
+
 void SSBModBaseband::pull(const SampleVector::iterator& begin, unsigned int nbSamples)
 {
     unsigned int part1Begin, part1End, part2Begin, part2End;
diff --git a/plugins/channeltx/modssb/ssbmodbaseband.h b/plugins/channeltx/modssb/ssbmodbaseband.h
index 6ec4d0faf..b60268a30 100644
--- a/plugins/channeltx/modssb/ssbmodbaseband.h
+++ b/plugins/channeltx/modssb/ssbmodbaseband.h
@@ -30,6 +30,7 @@
 class UpChannelizer;
 class BasebandSampleSink;
 class SpectrumVis;
+class ChannelAPI;
 
 class SSBModBaseband : public QObject
 {
@@ -72,6 +73,7 @@ public:
     AudioFifo *getAudioFifo() { return m_source.getAudioFifo(); }
     AudioFifo *getFeedbackAudioFifo() { return m_source.getFeedbackAudioFifo(); }
     void setSpectrumSink(SpectrumVis *sampleSink) { m_spectrumVis = sampleSink; m_source.setSpectrumSink((BasebandSampleSink *) sampleSink); }
+    void setChannel(ChannelAPI *channel);
 
 signals:
 	/**
diff --git a/plugins/channeltx/modssb/ssbmodplugin.cpp b/plugins/channeltx/modssb/ssbmodplugin.cpp
index 021cab62f..4890b16f1 100644
--- a/plugins/channeltx/modssb/ssbmodplugin.cpp
+++ b/plugins/channeltx/modssb/ssbmodplugin.cpp
@@ -28,7 +28,7 @@
 const PluginDescriptor SSBModPlugin::m_pluginDescriptor = {
     SSBMod::m_channelId,
     QStringLiteral("SSB Modulator"),
-    QStringLiteral("6.3.3"),
+    QStringLiteral("6.4.0"),
     QStringLiteral("(c) Edouard Griffiths, F4EXB"),
     QStringLiteral("https://github.com/f4exb/sdrangel"),
     true,
diff --git a/plugins/channeltx/modssb/ssbmodsource.cpp b/plugins/channeltx/modssb/ssbmodsource.cpp
index 219de61c5..80e96b515 100644
--- a/plugins/channeltx/modssb/ssbmodsource.cpp
+++ b/plugins/channeltx/modssb/ssbmodsource.cpp
@@ -19,6 +19,10 @@
 
 #include "dsp/basebandsamplesink.h"
 #include "dsp/misc.h"
+#include "dsp/datafifo.h"
+#include "util/messagequeue.h"
+#include "maincore.h"
+
 #include "ssbmodsource.h"
 
 const int SSBModSource::m_ssbFftLen = 1024;
@@ -51,6 +55,9 @@ SSBModSource::SSBModSource() :
 	m_feedbackAudioBuffer.resize(1<<14);
 	m_feedbackAudioBufferFill = 0;
 
+    m_demodBuffer.resize(1<<12);
+    m_demodBufferFill = 0;
+
     m_sum.real(0.0f);
     m_sum.imag(0.0f);
     m_undersampleCount = 0;
@@ -163,6 +170,26 @@ void SSBModSource::modulateSample()
     }
 
     calculateLevel(m_modSample);
+
+    // take projection on real axis
+    m_demodBuffer[m_demodBufferFill] = m_modSample.real() * std::numeric_limits<int16_t>::max();
+    ++m_demodBufferFill;
+
+    if (m_demodBufferFill >= m_demodBuffer.size())
+    {
+        QList<DataFifo*> *dataFifos = MainCore::instance()->getDataPipes().getFifos(m_channel, "demod");
+
+        if (dataFifos)
+        {
+            QList<DataFifo*>::iterator it = dataFifos->begin();
+
+            for (; it != dataFifos->end(); ++it) {
+                (*it)->write((quint8*) &m_demodBuffer[0], m_demodBuffer.size() * sizeof(qint16));
+            }
+        }
+
+        m_demodBufferFill = 0;
+    }
 }
 
 void SSBModSource::pullAF(Complex& sample)
@@ -574,6 +601,19 @@ void SSBModSource::applyAudioSampleRate(int sampleRate)
     m_audioSampleRate = sampleRate;
 
     applyFeedbackAudioSampleRate(m_feedbackAudioSampleRate);
+
+    QList<MessageQueue*> *messageQueues = MainCore::instance()->getMessagePipes().getMessageQueues(m_channel, "reportdemod");
+
+    if (messageQueues)
+    {
+        QList<MessageQueue*>::iterator it = messageQueues->begin();
+
+        for (; it != messageQueues->end(); ++it)
+        {
+            MainCore::MsgChannelDemodReport *msg = MainCore::MsgChannelDemodReport::create(m_channel, sampleRate);
+            (*it)->push(msg);
+        }
+    }
 }
 
 void SSBModSource::applyFeedbackAudioSampleRate(int sampleRate)
diff --git a/plugins/channeltx/modssb/ssbmodsource.h b/plugins/channeltx/modssb/ssbmodsource.h
index a9fe7721c..76275cb12 100644
--- a/plugins/channeltx/modssb/ssbmodsource.h
+++ b/plugins/channeltx/modssb/ssbmodsource.h
@@ -20,6 +20,7 @@
 
 #include <QObject>
 #include <QMutex>
+#include <QVector>
 
 #include <iostream>
 #include <fstream>
@@ -36,6 +37,7 @@
 #include "ssbmodsettings.h"
 
 class BasebandSampleSink;
+class ChannelAPI;
 
 class SSBModSource : public QObject, public ChannelSampleSource
 {
@@ -55,6 +57,7 @@ public:
     void applyFeedbackAudioSampleRate(int sampleRate);
     int getAudioSampleRate() const { return m_audioSampleRate; }
     int getFeedbackAudioSampleRate() const { return m_feedbackAudioSampleRate; }
+    void setChannel(ChannelAPI *channel) { m_channel = channel; }
     CWKeyer& getCWKeyer() { return m_cwKeyer; }
     double getMagSq() const { return m_magsq; }
     void getLevels(qreal& rmsLevel, qreal& peakLevel, int& numSamples) const
@@ -71,6 +74,7 @@ private:
     int m_channelSampleRate;
     int m_channelFrequencyOffset;
     SSBModSettings m_settings;
+    ChannelAPI *m_channel;
 
     NCOF m_carrierNco;
     NCOF m_toneNco;
@@ -86,6 +90,9 @@ private:
     Real m_feedbackInterpolatorDistanceRemain;
     bool m_feedbackInterpolatorConsumed;
 
+    QVector<qint16> m_demodBuffer;
+    int m_demodBufferFill;
+
     fftfilt* m_SSBFilter;
 	fftfilt* m_DSBFilter;
 	Complex* m_SSBFilterBuffer;
diff --git a/plugins/channeltx/modwfm/wfmmod.cpp b/plugins/channeltx/modwfm/wfmmod.cpp
index 2efed5c8a..464633777 100644
--- a/plugins/channeltx/modwfm/wfmmod.cpp
+++ b/plugins/channeltx/modwfm/wfmmod.cpp
@@ -64,6 +64,7 @@ WFMMod::WFMMod(DeviceAPI *deviceAPI) :
     m_thread = new QThread(this);
     m_basebandSource = new WFMModBaseband();
     m_basebandSource->setInputFileStream(&m_ifstream);
+    m_basebandSource->setChannel(this);
     m_basebandSource->moveToThread(m_thread);
 
     applySettings(m_settings, true);
diff --git a/plugins/channeltx/modwfm/wfmmodbaseband.cpp b/plugins/channeltx/modwfm/wfmmodbaseband.cpp
index 1aa75a934..ee02b0446 100644
--- a/plugins/channeltx/modwfm/wfmmodbaseband.cpp
+++ b/plugins/channeltx/modwfm/wfmmodbaseband.cpp
@@ -61,6 +61,11 @@ void WFMModBaseband::reset()
     m_sampleFifo.reset();
 }
 
+void WFMModBaseband::setChannel(ChannelAPI *channel)
+{
+    m_source.setChannel(channel);
+}
+
 void WFMModBaseband::pull(const SampleVector::iterator& begin, unsigned int nbSamples)
 {
     unsigned int part1Begin, part1End, part2Begin, part2End;
@@ -209,4 +214,4 @@ void WFMModBaseband::applySettings(const WFMModSettings& settings, bool force)
 int WFMModBaseband::getChannelSampleRate() const
 {
     return m_channelizer->getChannelSampleRate();
-}
\ No newline at end of file
+}
diff --git a/plugins/channeltx/modwfm/wfmmodbaseband.h b/plugins/channeltx/modwfm/wfmmodbaseband.h
index d63f2e4e7..a7ef056cf 100644
--- a/plugins/channeltx/modwfm/wfmmodbaseband.h
+++ b/plugins/channeltx/modwfm/wfmmodbaseband.h
@@ -28,6 +28,7 @@
 #include "wfmmodsource.h"
 
 class UpChannelizer;
+class ChannelAPI;
 
 class WFMModBaseband : public QObject
 {
@@ -68,6 +69,7 @@ public:
     int getChannelSampleRate() const;
     void setInputFileStream(std::ifstream *ifstream) { m_source.setInputFileStream(ifstream); }
     AudioFifo *getAudioFifo() { return m_source.getAudioFifo(); }
+    void setChannel(ChannelAPI *channel);
 
 signals:
 	/**
diff --git a/plugins/channeltx/modwfm/wfmmodplugin.cpp b/plugins/channeltx/modwfm/wfmmodplugin.cpp
index f5729778d..0b903c297 100644
--- a/plugins/channeltx/modwfm/wfmmodplugin.cpp
+++ b/plugins/channeltx/modwfm/wfmmodplugin.cpp
@@ -28,7 +28,7 @@
 const PluginDescriptor WFMModPlugin::m_pluginDescriptor = {
     WFMMod::m_channelId,
     QStringLiteral("WFM Modulator"),
-    QStringLiteral("6.3.3"),
+    QStringLiteral("6.4.0"),
     QStringLiteral("(c) Edouard Griffiths, F4EXB"),
     QStringLiteral("https://github.com/f4exb/sdrangel"),
     true,
diff --git a/plugins/channeltx/modwfm/wfmmodsource.cpp b/plugins/channeltx/modwfm/wfmmodsource.cpp
index d073c3260..732e67052 100644
--- a/plugins/channeltx/modwfm/wfmmodsource.cpp
+++ b/plugins/channeltx/modwfm/wfmmodsource.cpp
@@ -16,6 +16,10 @@
 
 #include <QDebug>
 
+#include "dsp/datafifo.h"
+#include "util/messagequeue.h"
+#include "maincore.h"
+
 #include "wfmmodsource.h"
 
 const int WFMModSource::m_rfFilterFFTLength = 1024;
@@ -46,6 +50,8 @@ WFMModSource::WFMModSource() :
 	m_magsq = 0.0;
 	m_feedbackAudioBuffer.resize(1<<14);
 	m_feedbackAudioBufferFill = 0;
+    m_demodBuffer.resize(1<<12);
+    m_demodBufferFill = 0;
 
     applySettings(m_settings, true);
     applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
@@ -140,6 +146,25 @@ void WFMModSource::pullOne(Sample& sample)
 
 	sample.m_real = (FixReal) ci.real();
 	sample.m_imag = (FixReal) ci.imag();
+
+    m_demodBuffer[m_demodBufferFill] = t * std::numeric_limits<int16_t>::max();
+    ++m_demodBufferFill;
+
+    if (m_demodBufferFill >= m_demodBuffer.size())
+    {
+        QList<DataFifo*> *dataFifos = MainCore::instance()->getDataPipes().getFifos(m_channel, "demod");
+
+        if (dataFifos)
+        {
+            QList<DataFifo*>::iterator it = dataFifos->begin();
+
+            for (; it != dataFifos->end(); ++it) {
+                (*it)->write((quint8*) &m_demodBuffer[0], m_demodBuffer.size() * sizeof(qint16));
+            }
+        }
+
+        m_demodBufferFill = 0;
+    }
 }
 
 void WFMModSource::modulateAudio()
@@ -237,13 +262,13 @@ void WFMModSource::pullAF(Real& sample)
         if (m_cwKeyer.getSample())
         {
             m_cwKeyer.getCWSmoother().getFadeSample(true, fadeFactor);
-            sample = m_cwToneNco.next() * m_settings.m_volumeFactor * fadeFactor;
+            sample = m_cwToneNco.next() * m_settings.m_volumeFactor * fadeFactor * 0.99f;
         }
         else
         {
             if (m_cwKeyer.getCWSmoother().getFadeSample(false, fadeFactor))
             {
-                sample = m_cwToneNco.next() * m_settings.m_volumeFactor * fadeFactor;
+                sample = m_cwToneNco.next() * m_settings.m_volumeFactor * fadeFactor * 0.99f;
             }
             else
             {
@@ -339,6 +364,19 @@ void WFMModSource::applyAudioSampleRate(int sampleRate)
     m_cwKeyer.reset();
     m_audioSampleRate = sampleRate;
     applyFeedbackAudioSampleRate(m_feedbackAudioSampleRate);
+
+    QList<MessageQueue*> *messageQueues = MainCore::instance()->getMessagePipes().getMessageQueues(m_channel, "reportdemod");
+
+    if (messageQueues)
+    {
+        QList<MessageQueue*>::iterator it = messageQueues->begin();
+
+        for (; it != messageQueues->end(); ++it)
+        {
+            MainCore::MsgChannelDemodReport *msg = MainCore::MsgChannelDemodReport::create(m_channel, sampleRate);
+            (*it)->push(msg);
+        }
+    }
 }
 
 void WFMModSource::applyFeedbackAudioSampleRate(int sampleRate)
diff --git a/plugins/channeltx/modwfm/wfmmodsource.h b/plugins/channeltx/modwfm/wfmmodsource.h
index 3b393e623..78fa6e399 100644
--- a/plugins/channeltx/modwfm/wfmmodsource.h
+++ b/plugins/channeltx/modwfm/wfmmodsource.h
@@ -20,6 +20,7 @@
 
 #include <QObject>
 #include <QMutex>
+#include <QVector>
 
 #include <iostream>
 #include <fstream>
@@ -35,6 +36,8 @@
 
 #include "wfmmodsettings.h"
 
+class ChannelAPI;
+
 class WFMModSource : public QObject, public ChannelSampleSource
 {
     Q_OBJECT
@@ -53,6 +56,7 @@ public:
     void applyFeedbackAudioSampleRate(int sampleRate);
     int getAudioSampleRate() const { return m_audioSampleRate; }
     int getFeedbackAudioSampleRate() const { return m_feedbackAudioSampleRate; }
+    void setChannel(ChannelAPI *channel) { m_channel = channel; }
     CWKeyer& getCWKeyer() { return m_cwKeyer; }
     double getMagSq() const { return m_magsq; }
     void getLevels(qreal& rmsLevel, qreal& peakLevel, int& numSamples) const
@@ -68,6 +72,7 @@ private:
     int m_channelSampleRate;
     int m_channelFrequencyOffset;
     WFMModSettings m_settings;
+    ChannelAPI *m_channel;
 
     NCO m_carrierNco;
     NCOF m_toneNco;
@@ -85,6 +90,9 @@ private:
     Real m_feedbackInterpolatorDistanceRemain;
     bool m_feedbackInterpolatorConsumed;
 
+    QVector<qint16> m_demodBuffer;
+    int m_demodBufferFill;
+
     fftfilt* m_rfFilter;
     static const int m_rfFilterFFTLength;
     fftfilt::cmplx *m_rfFilterBuffer;
diff --git a/plugins/feature/demodanalyzer/demodanalyzersettings.cpp b/plugins/feature/demodanalyzer/demodanalyzersettings.cpp
index 3ffe5381d..6004ab2b3 100644
--- a/plugins/feature/demodanalyzer/demodanalyzersettings.cpp
+++ b/plugins/feature/demodanalyzer/demodanalyzersettings.cpp
@@ -27,6 +27,12 @@ const QStringList DemodAnalyzerSettings::m_channelTypes = {
     QStringLiteral("AMMod"),
     QStringLiteral("DSDDemod"),
     QStringLiteral("NFMDemod"),
+    QStringLiteral("NFMMod"),
+    QStringLiteral("PacketMod"),
+    QStringLiteral("SSBDemod"),
+    QStringLiteral("SSBMod"),
+    QStringLiteral("WFMDemod"),
+    QStringLiteral("WFMMod"),
 };
 
 const QStringList DemodAnalyzerSettings::m_channelURIs = {
@@ -34,6 +40,12 @@ const QStringList DemodAnalyzerSettings::m_channelURIs = {
     QStringLiteral("sdrangel.channeltx.modam"),
     QStringLiteral("sdrangel.channel.dsddemod"),
     QStringLiteral("sdrangel.channel.nfmdemod"),
+    QStringLiteral("sdrangel.channeltx.modnfm"),
+    QStringLiteral("sdrangel.channeltx.modpacket"),
+    QStringLiteral("sdrangel.channel.ssbdemod"),
+    QStringLiteral("sdrangel.channeltx.modssb"),
+    QStringLiteral("sdrangel.channel.wfmdemod"),
+    QStringLiteral("sdrangel.channeltx.modwfm"),
 };
 
 DemodAnalyzerSettings::DemodAnalyzerSettings() :