diff --git a/Readme.md b/Readme.md
index b63502d84..f63c97d9a 100644
--- a/Readme.md
+++ b/Readme.md
@@ -77,14 +77,12 @@ Done since the fork
- Make the sidebands appear correctly on SSB channel overlay. Limit to +/- 6 kHz to fit channel spectrum analyzer window
- SSB bandwidth can now be tuned in steps of 100 Hz
- NFM and SSB receiver in focus trigger the display of the central frequency line on the spectrum frequency scale thus facilitating its identification
-
+ - Added AM demod so now you can listen to air traffic!
=====
To Do
=====
-
- - AM demod. What about the air band?!
- Add the possibility to change the brightness and/or color of the grid. Sometimes it is barely visible yet useful
- Possibility to completely undock the receiver in a separate window. Useful when there are many receivers
- Larger decimation capability for narrowband and very narrowband work (32, 64, ...)
diff --git a/plugins/channel/CMakeLists.txt b/plugins/channel/CMakeLists.txt
index a57fa036d..1700e8b72 100644
--- a/plugins/channel/CMakeLists.txt
+++ b/plugins/channel/CMakeLists.txt
@@ -1,6 +1,7 @@
project(demod)
add_subdirectory(lora)
+add_subdirectory(am)
add_subdirectory(nfm)
add_subdirectory(ssb)
add_subdirectory(tcpsrc)
diff --git a/plugins/channel/am/CMakeLists.txt b/plugins/channel/am/CMakeLists.txt
new file mode 100644
index 000000000..77bbbdfd5
--- /dev/null
+++ b/plugins/channel/am/CMakeLists.txt
@@ -0,0 +1,47 @@
+project(am)
+
+set(am_SOURCES
+ amdemod.cpp
+ amdemodgui.cpp
+ amplugin.cpp
+)
+
+set(am_HEADERS
+ amdemod.h
+ amdemodgui.h
+ amplugin.h
+)
+
+set(am_FORMS
+ amdemodgui.ui
+)
+
+include_directories(
+ .
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CMAKE_SOURCE_DIR}/include
+ ${CMAKE_SOURCE_DIR}/include-gpl
+ ${OPENGL_INCLUDE_DIR}
+)
+
+#include(${QT_USE_FILE})
+add_definitions(${QT_DEFINITIONS})
+add_definitions(-DQT_PLUGIN)
+add_definitions(-DQT_SHARED)
+
+#qt5_wrap_cpp(nfm_HEADERS_MOC ${nfm_HEADERS})
+qt5_wrap_ui(am_FORMS_HEADERS ${am_FORMS})
+
+add_library(demodam SHARED
+ ${am_SOURCES}
+ ${am_HEADERS_MOC}
+ ${am_FORMS_HEADERS}
+)
+
+target_link_libraries(demodam
+ ${QT_LIBRARIES}
+ ${OPENGL_LIBRARIES}
+ sdrbase
+)
+
+qt5_use_modules(demodam Core Widgets OpenGL Multimedia)
\ No newline at end of file
diff --git a/plugins/channel/am/amdemod.cpp b/plugins/channel/am/amdemod.cpp
new file mode 100644
index 000000000..edaaaa0cd
--- /dev/null
+++ b/plugins/channel/am/amdemod.cpp
@@ -0,0 +1,244 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2015 Edouard Griffiths, F4EXB. //
+// //
+// This program is free software; you can redistribute it and/or modify //
+// it under the terms of the GNU General Public License as published by //
+// the Free Software Foundation as version 3 of the License, or //
+// //
+// This program is distributed in the hope that it will be useful, //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
+// GNU General Public License V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include "amdemod.h"
+
+#include
+#include
+#include
+#include "audio/audiooutput.h"
+#include "dsp/dspcommands.h"
+#include "dsp/pidcontroller.h"
+
+MESSAGE_CLASS_DEFINITION(AMDemod::MsgConfigureAMDemod, Message)
+
+AMDemod::AMDemod(AudioFifo* audioFifo, SampleSink* sampleSink) :
+ m_sampleSink(sampleSink),
+ m_audioFifo(audioFifo)
+{
+ m_config.m_inputSampleRate = 96000;
+ m_config.m_inputFrequencyOffset = 0;
+ m_config.m_rfBandwidth = 12500;
+ m_config.m_afBandwidth = 3000;
+ m_config.m_squelch = -40.0;
+ m_config.m_volume = 2.0;
+ m_config.m_audioSampleRate = 48000;
+
+ apply();
+
+ m_audioBuffer.resize(16384);
+ m_audioBufferFill = 0;
+
+ m_movingAverage.resize(16, 0);
+}
+
+AMDemod::~AMDemod()
+{
+}
+
+void AMDemod::configure(MessageQueue* messageQueue, Real rfBandwidth, Real afBandwidth, Real volume, Real squelch)
+{
+ Message* cmd = MsgConfigureAMDemod::create(rfBandwidth, afBandwidth, volume, squelch);
+ cmd->submit(messageQueue, this);
+}
+
+float arctan2(Real y, Real x)
+{
+ Real coeff_1 = M_PI / 4;
+ Real coeff_2 = 3 * coeff_1;
+ Real abs_y = fabs(y) + 1e-10; // kludge to prevent 0/0 condition
+ Real angle;
+ if( x>= 0) {
+ Real r = (x - abs_y) / (x + abs_y);
+ angle = coeff_1 - coeff_1 * r;
+ } else {
+ Real r = (x + abs_y) / (abs_y - x);
+ angle = coeff_2 - coeff_1 * r;
+ }
+ if(y < 0)
+ return(-angle);
+ else return(angle);
+}
+
+Real angleDist(Real a, Real b)
+{
+ Real dist = b - a;
+
+ while(dist <= M_PI)
+ dist += 2 * M_PI;
+ while(dist >= M_PI)
+ dist -= 2 * M_PI;
+
+ return dist;
+}
+
+void AMDemod::feed(SampleVector::const_iterator begin, SampleVector::const_iterator end, bool firstOfBurst)
+{
+ Complex ci;
+
+ if(m_audioFifo->size() <= 0)
+ return;
+
+ for(SampleVector::const_iterator it = begin; it != end; ++it) {
+ Complex c(it->real() / 32768.0, it->imag() / 32768.0);
+ c *= m_nco.nextIQ();
+
+ {
+ if(m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci)) {
+ m_sampleBuffer.push_back(Sample(ci.real() * 32767.0, ci.imag() * 32767.0));
+
+ Real magsq = ci.real() * ci.real() + ci.imag() * ci.imag();
+ m_movingAverage.feed(magsq);
+ if(m_movingAverage.average() >= m_squelchLevel)
+ m_squelchState = m_running.m_audioSampleRate/ 20;
+
+ qint16 sample;
+ if(m_squelchState > 0) {
+ m_squelchState--;
+ /*
+ Real argument = arg(ci);
+ Real demod = argument - m_lastArgument;
+ m_lastArgument = argument;
+ */
+
+ /* NFM demod:
+ Complex d = conj(m_lastSample) * ci;
+ m_lastSample = ci;
+ Real demod = atan2(d.imag(), d.real());
+ //Real demod = arctan2(d.imag(), d.real());
+ */
+
+/*
+ Real argument1 = arg(ci);//atan2(ci.imag(), ci.real());
+ Real argument2 = m_lastSample.real();
+ Real demod = angleDist(argument2, argument1);
+ m_lastSample = Complex(argument1, 0);
+*/
+ Real demod = sqrt(magsq);
+
+ //demod /= M_PI;
+
+ demod = m_lowpass.filter(demod);
+
+ if(demod < -1)
+ demod = -1;
+ else if(demod > 1)
+ demod = 1;
+
+ demod *= m_running.m_volume;
+ sample = demod * 32700;
+
+ } else {
+ sample = 0;
+ }
+
+ m_audioBuffer[m_audioBufferFill].l = sample;
+ m_audioBuffer[m_audioBufferFill].r = sample;
+ ++m_audioBufferFill;
+ if(m_audioBufferFill >= m_audioBuffer.size()) {
+ uint res = m_audioFifo->write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 1);
+ if(res != m_audioBufferFill)
+ qDebug("lost %u audio samples", m_audioBufferFill - res);
+ m_audioBufferFill = 0;
+ }
+
+ m_interpolatorDistanceRemain += m_interpolatorDistance;
+ }
+ }
+ }
+ if(m_audioBufferFill > 0) {
+ uint res = m_audioFifo->write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 1);
+ if(res != m_audioBufferFill)
+ qDebug("lost %u samples", m_audioBufferFill - res);
+ m_audioBufferFill = 0;
+ }
+
+ if(m_sampleSink != NULL)
+ m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), false);
+ m_sampleBuffer.clear();
+}
+
+void AMDemod::start()
+{
+ m_squelchState = 0;
+ m_audioFifo->clear();
+ m_interpolatorRegulation = 0.9999;
+ m_interpolatorDistance = 1.0;
+ m_interpolatorDistanceRemain = 0.0;
+ m_lastSample = 0;
+}
+
+void AMDemod::stop()
+{
+}
+
+bool AMDemod::handleMessage(Message* cmd)
+{
+ if(DSPSignalNotification::match(cmd)) {
+ DSPSignalNotification* signal = (DSPSignalNotification*)cmd;
+
+ m_config.m_inputSampleRate = signal->getSampleRate();
+ m_config.m_inputFrequencyOffset = signal->getFrequencyOffset();
+ apply();
+ cmd->completed();
+ return true;
+ } else if(MsgConfigureAMDemod::match(cmd)) {
+ MsgConfigureAMDemod* cfg = (MsgConfigureAMDemod*)cmd;
+ m_config.m_rfBandwidth = cfg->getRFBandwidth();
+ m_config.m_afBandwidth = cfg->getAFBandwidth();
+ m_config.m_volume = cfg->getVolume();
+ m_config.m_squelch = cfg->getSquelch();
+ apply();
+ return true;
+ } else {
+ if(m_sampleSink != NULL)
+ return m_sampleSink->handleMessage(cmd);
+ else return false;
+ }
+}
+
+void AMDemod::apply()
+{
+
+ if((m_config.m_inputFrequencyOffset != m_running.m_inputFrequencyOffset) ||
+ (m_config.m_inputSampleRate != m_running.m_inputSampleRate)) {
+ m_nco.setFreq(-m_config.m_inputFrequencyOffset, m_config.m_inputSampleRate);
+ }
+
+ if((m_config.m_inputSampleRate != m_running.m_inputSampleRate) ||
+ (m_config.m_rfBandwidth != m_running.m_rfBandwidth)) {
+ m_interpolator.create(16, m_config.m_inputSampleRate, m_config.m_rfBandwidth / 2.2);
+ m_interpolatorDistanceRemain = 0;
+ m_interpolatorDistance = m_config.m_inputSampleRate / m_config.m_audioSampleRate;
+ }
+
+ if((m_config.m_afBandwidth != m_running.m_afBandwidth) ||
+ (m_config.m_audioSampleRate != m_running.m_audioSampleRate)) {
+ m_lowpass.create(21, m_config.m_audioSampleRate, m_config.m_afBandwidth);
+ }
+
+ if(m_config.m_squelch != m_running.m_squelch) {
+ m_squelchLevel = pow(10.0, m_config.m_squelch / 20.0);
+ m_squelchLevel *= m_squelchLevel;
+ }
+
+ m_running.m_inputSampleRate = m_config.m_inputSampleRate;
+ m_running.m_inputFrequencyOffset = m_config.m_inputFrequencyOffset;
+ m_running.m_rfBandwidth = m_config.m_rfBandwidth;
+ m_running.m_squelch = m_config.m_squelch;
+ m_running.m_volume = m_config.m_volume;
+ m_running.m_audioSampleRate = m_config.m_audioSampleRate;
+}
diff --git a/plugins/channel/am/amdemod.h b/plugins/channel/am/amdemod.h
new file mode 100644
index 000000000..2557970e4
--- /dev/null
+++ b/plugins/channel/am/amdemod.h
@@ -0,0 +1,131 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2015 Edouard Griffiths, F4EXB. //
+// //
+// This program is free software; you can redistribute it and/or modify //
+// it under the terms of the GNU General Public License as published by //
+// the Free Software Foundation as version 3 of the License, or //
+// //
+// This program is distributed in the hope that it will be useful, //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
+// GNU General Public License V3 for more details. //
+// //
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_AMDEMOD_H
+#define INCLUDE_AMDEMOD_H
+
+#include
+#include "dsp/samplesink.h"
+#include "dsp/nco.h"
+#include "dsp/interpolator.h"
+#include "dsp/lowpass.h"
+#include "dsp/movingaverage.h"
+#include "audio/audiofifo.h"
+#include "util/message.h"
+
+class AudioFifo;
+
+class AMDemod : public SampleSink {
+public:
+ AMDemod(AudioFifo* audioFifo, SampleSink* sampleSink);
+ ~AMDemod();
+
+ void configure(MessageQueue* messageQueue, Real rfBandwidth, Real afBandwidth, Real volume, Real squelch);
+
+ void feed(SampleVector::const_iterator begin, SampleVector::const_iterator end, bool po);
+ void start();
+ void stop();
+ bool handleMessage(Message* cmd);
+
+private:
+ class MsgConfigureAMDemod : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ Real getRFBandwidth() const { return m_rfBandwidth; }
+ Real getAFBandwidth() const { return m_afBandwidth; }
+ Real getVolume() const { return m_volume; }
+ Real getSquelch() const { return m_squelch; }
+
+ static MsgConfigureAMDemod* create(Real rfBandwidth, Real afBandwidth, Real volume, Real squelch)
+ {
+ return new MsgConfigureAMDemod(rfBandwidth, afBandwidth, volume, squelch);
+ }
+
+ private:
+ Real m_rfBandwidth;
+ Real m_afBandwidth;
+ Real m_volume;
+ Real m_squelch;
+
+ MsgConfigureAMDemod(Real rfBandwidth, Real afBandwidth, Real volume, Real squelch) :
+ Message(),
+ m_rfBandwidth(rfBandwidth),
+ m_afBandwidth(afBandwidth),
+ m_volume(volume),
+ m_squelch(squelch)
+ { }
+ };
+
+ struct AudioSample {
+ qint16 l;
+ qint16 r;
+ };
+ typedef std::vector AudioVector;
+
+ enum RateState {
+ RSInitialFill,
+ RSRunning
+ };
+
+ struct Config {
+ int m_inputSampleRate;
+ qint64 m_inputFrequencyOffset;
+ Real m_rfBandwidth;
+ Real m_afBandwidth;
+ Real m_squelch;
+ Real m_volume;
+ quint32 m_audioSampleRate;
+
+ Config() :
+ m_inputSampleRate(-1),
+ m_inputFrequencyOffset(0),
+ m_rfBandwidth(-1),
+ m_afBandwidth(-1),
+ m_squelch(0),
+ m_volume(0),
+ m_audioSampleRate(0)
+ { }
+ };
+
+ Config m_config;
+ Config m_running;
+
+ NCO m_nco;
+ Real m_interpolatorRegulation;
+ Interpolator m_interpolator;
+ Real m_interpolatorDistance;
+ Real m_interpolatorDistanceRemain;
+ Lowpass m_lowpass;
+
+ Real m_squelchLevel;
+ int m_squelchState;
+
+ Real m_lastArgument;
+ Complex m_lastSample;
+ MovingAverage m_movingAverage;
+
+ AudioVector m_audioBuffer;
+ uint m_audioBufferFill;
+
+ SampleSink* m_sampleSink;
+ AudioFifo* m_audioFifo;
+ SampleVector m_sampleBuffer;
+
+ void apply();
+};
+
+#endif // INCLUDE_AMDEMOD_H
diff --git a/plugins/channel/am/amdemodgui.cpp b/plugins/channel/am/amdemodgui.cpp
new file mode 100644
index 000000000..6c4460bda
--- /dev/null
+++ b/plugins/channel/am/amdemodgui.cpp
@@ -0,0 +1,236 @@
+#include "amdemodgui.h"
+
+#include
+#include
+#include "ui_amdemodgui.h"
+#include "dsp/threadedsamplesink.h"
+#include "dsp/channelizer.h"
+#include "dsp/nullsink.h"
+#include "gui/glspectrum.h"
+#include "plugin/pluginapi.h"
+#include "util/simpleserializer.h"
+#include "gui/basicchannelsettingswidget.h"
+
+#include
+#include "amdemod.h"
+
+const int AMDemodGUI::m_rfBW[] = {
+ 5000, 6250, 8330, 10000, 12500, 15000, 20000, 25000, 40000
+};
+
+AMDemodGUI* AMDemodGUI::create(PluginAPI* pluginAPI)
+{
+ AMDemodGUI* gui = new AMDemodGUI(pluginAPI);
+ return gui;
+}
+
+void AMDemodGUI::destroy()
+{
+ delete this;
+}
+
+void AMDemodGUI::setName(const QString& name)
+{
+ setObjectName(name);
+}
+
+void AMDemodGUI::resetToDefaults()
+{
+ ui->rfBW->setValue(4);
+ ui->afBW->setValue(3);
+ ui->volume->setValue(20);
+ ui->squelch->setValue(-40);
+ ui->deltaFrequency->setValue(0);
+ applySettings();
+}
+
+QByteArray AMDemodGUI::serialize() const
+{
+ SimpleSerializer s(1);
+ s.writeS32(1, m_channelMarker->getCenterFrequency());
+ s.writeS32(2, ui->rfBW->value());
+ s.writeS32(3, ui->afBW->value());
+ s.writeS32(4, ui->volume->value());
+ s.writeS32(5, ui->squelch->value());
+ //s.writeBlob(6, ui->spectrumGUI->serialize());
+ s.writeU32(7, m_channelMarker->getColor().rgb());
+ return s.final();
+}
+
+bool AMDemodGUI::deserialize(const QByteArray& data)
+{
+ SimpleDeserializer d(data);
+
+ if(!d.isValid()) {
+ resetToDefaults();
+ return false;
+ }
+
+ if(d.getVersion() == 1) {
+ QByteArray bytetmp;
+ quint32 u32tmp;
+ qint32 tmp;
+ d.readS32(1, &tmp, 0);
+ m_channelMarker->setCenterFrequency(tmp);
+ d.readS32(2, &tmp, 4);
+ ui->rfBW->setValue(tmp);
+ d.readS32(3, &tmp, 3);
+ ui->afBW->setValue(tmp);
+ d.readS32(4, &tmp, 20);
+ ui->volume->setValue(tmp);
+ d.readS32(5, &tmp, -40);
+ ui->squelch->setValue(tmp);
+ //d.readBlob(6, &bytetmp);
+ //ui->spectrumGUI->deserialize(bytetmp);
+ if(d.readU32(7, &u32tmp))
+ m_channelMarker->setColor(u32tmp);
+ applySettings();
+ return true;
+ } else {
+ resetToDefaults();
+ return false;
+ }
+}
+
+bool AMDemodGUI::handleMessage(Message* message)
+{
+ return false;
+}
+
+void AMDemodGUI::viewChanged()
+{
+ applySettings();
+}
+
+void AMDemodGUI::on_deltaMinus_clicked(bool minus)
+{
+ int deltaFrequency = m_channelMarker->getCenterFrequency();
+ bool minusDelta = (deltaFrequency < 0);
+
+ if (minus ^ minusDelta) // sign change
+ {
+ m_channelMarker->setCenterFrequency(-deltaFrequency);
+ }
+}
+
+void AMDemodGUI::on_deltaFrequency_changed(quint64 value)
+{
+ if (ui->deltaMinus->isChecked()) {
+ m_channelMarker->setCenterFrequency(-value);
+ } else {
+ m_channelMarker->setCenterFrequency(value);
+ }
+}
+
+void AMDemodGUI::on_rfBW_valueChanged(int value)
+{
+ ui->rfBWText->setText(QString("%1 kHz").arg(m_rfBW[value] / 1000.0));
+ m_channelMarker->setBandwidth(m_rfBW[value]);
+ applySettings();
+}
+
+void AMDemodGUI::on_afBW_valueChanged(int value)
+{
+ ui->afBWText->setText(QString("%1 kHz").arg(value));
+ applySettings();
+}
+
+void AMDemodGUI::on_volume_valueChanged(int value)
+{
+ ui->volumeText->setText(QString("%1").arg(value / 10.0, 0, 'f', 1));
+ applySettings();
+}
+
+void AMDemodGUI::on_squelch_valueChanged(int value)
+{
+ ui->squelchText->setText(QString("%1 dB").arg(value));
+ applySettings();
+}
+
+
+void AMDemodGUI::onWidgetRolled(QWidget* widget, bool rollDown)
+{
+ /*
+ if((widget == ui->spectrumContainer) && (m_nfmDemod != NULL))
+ m_nfmDemod->setSpectrum(m_threadedSampleSink->getMessageQueue(), rollDown);
+ */
+}
+
+void AMDemodGUI::onMenuDoubleClicked()
+{
+ if(!m_basicSettingsShown) {
+ m_basicSettingsShown = true;
+ BasicChannelSettingsWidget* bcsw = new BasicChannelSettingsWidget(m_channelMarker, this);
+ bcsw->show();
+ }
+}
+
+AMDemodGUI::AMDemodGUI(PluginAPI* pluginAPI, QWidget* parent) :
+ RollupWidget(parent),
+ ui(new Ui::AMDemodGUI),
+ m_pluginAPI(pluginAPI),
+ m_basicSettingsShown(false)
+{
+ ui->setupUi(this);
+ setAttribute(Qt::WA_DeleteOnClose, true);
+ connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
+ connect(this, SIGNAL(menuDoubleClickEvent()), this, SLOT(onMenuDoubleClicked()));
+
+ m_audioFifo = new AudioFifo(4, 48000);
+ m_nullSink = new NullSink();
+ m_amDemod = new AMDemod(m_audioFifo, m_nullSink);
+ m_channelizer = new Channelizer(m_amDemod);
+ m_threadedSampleSink = new ThreadedSampleSink(m_channelizer);
+ m_pluginAPI->addAudioSource(m_audioFifo);
+ m_pluginAPI->addSampleSink(m_threadedSampleSink);
+
+ m_channelMarker = new ChannelMarker(this);
+ m_channelMarker->setColor(Qt::yellow);
+ m_channelMarker->setBandwidth(12500);
+ m_channelMarker->setCenterFrequency(0);
+ m_channelMarker->setVisible(true);
+ connect(m_channelMarker, SIGNAL(changed()), this, SLOT(viewChanged()));
+ m_pluginAPI->addChannelMarker(m_channelMarker);
+
+ applySettings();
+}
+
+AMDemodGUI::~AMDemodGUI()
+{
+ m_pluginAPI->removeChannelInstance(this);
+ m_pluginAPI->removeAudioSource(m_audioFifo);
+ m_pluginAPI->removeSampleSink(m_threadedSampleSink);
+ delete m_threadedSampleSink;
+ delete m_channelizer;
+ delete m_amDemod;
+ delete m_nullSink;
+ delete m_audioFifo;
+ delete m_channelMarker;
+ delete ui;
+}
+
+void AMDemodGUI::applySettings()
+{
+ setTitleColor(m_channelMarker->getColor());
+ m_channelizer->configure(m_threadedSampleSink->getMessageQueue(),
+ 48000,
+ m_channelMarker->getCenterFrequency());
+ ui->deltaFrequency->setValue(abs(m_channelMarker->getCenterFrequency()));
+ ui->deltaMinus->setChecked(m_channelMarker->getCenterFrequency() < 0);
+ m_amDemod->configure(m_threadedSampleSink->getMessageQueue(),
+ m_rfBW[ui->rfBW->value()],
+ ui->afBW->value() * 1000.0,
+ ui->volume->value() / 10.0,
+ ui->squelch->value());
+}
+
+void AMDemodGUI::leaveEvent(QEvent*)
+{
+ m_channelMarker->setHighlighted(false);
+}
+
+void AMDemodGUI::enterEvent(QEvent*)
+{
+ m_channelMarker->setHighlighted(true);
+}
+
diff --git a/plugins/channel/am/amdemodgui.h b/plugins/channel/am/amdemodgui.h
new file mode 100644
index 000000000..0ee51e09b
--- /dev/null
+++ b/plugins/channel/am/amdemodgui.h
@@ -0,0 +1,69 @@
+#ifndef INCLUDE_AMDEMODGUI_H
+#define INCLUDE_AMDEMODGUI_H
+
+#include "gui/rollupwidget.h"
+#include "plugin/plugingui.h"
+
+class PluginAPI;
+class ChannelMarker;
+
+class AudioFifo;
+class ThreadedSampleSink;
+class Channelizer;
+class AMDemod;
+class NullSink;
+
+namespace Ui {
+ class AMDemodGUI;
+}
+
+class AMDemodGUI : public RollupWidget, public PluginGUI {
+ Q_OBJECT
+
+public:
+ static AMDemodGUI* create(PluginAPI* pluginAPI);
+ void destroy();
+
+ void setName(const QString& name);
+
+ void resetToDefaults();
+ QByteArray serialize() const;
+ bool deserialize(const QByteArray& data);
+
+ bool handleMessage(Message* message);
+
+private slots:
+ void viewChanged();
+ void on_deltaFrequency_changed(quint64 value);
+ void on_deltaMinus_clicked(bool minus);
+ void on_rfBW_valueChanged(int value);
+ void on_afBW_valueChanged(int value);
+ void on_volume_valueChanged(int value);
+ void on_squelch_valueChanged(int value);
+ void onWidgetRolled(QWidget* widget, bool rollDown);
+ void onMenuDoubleClicked();
+
+private:
+ Ui::AMDemodGUI* ui;
+ PluginAPI* m_pluginAPI;
+ ChannelMarker* m_channelMarker;
+ bool m_basicSettingsShown;
+
+ AudioFifo* m_audioFifo;
+ ThreadedSampleSink* m_threadedSampleSink;
+ Channelizer* m_channelizer;
+ AMDemod* m_amDemod;
+ NullSink *m_nullSink;
+
+ static const int m_rfBW[];
+
+ explicit AMDemodGUI(PluginAPI* pluginAPI, QWidget* parent = NULL);
+ ~AMDemodGUI();
+
+ void applySettings();
+
+ void leaveEvent(QEvent*);
+ void enterEvent(QEvent*);
+};
+
+#endif // INCLUDE_AMDEMODGUI_H
diff --git a/plugins/channel/am/amdemodgui.ui b/plugins/channel/am/amdemodgui.ui
new file mode 100644
index 000000000..280576f06
--- /dev/null
+++ b/plugins/channel/am/amdemodgui.ui
@@ -0,0 +1,270 @@
+
+
+ AMDemodGUI
+
+
+
+ 0
+ 0
+ 302
+ 138
+
+
+
+ Qt::StrongFocus
+
+
+ AM Demodulator
+
+
+
+
+ 35
+ 35
+ 242
+ 96
+
+
+
+ Settings
+
+
+
+ 2
+
+
+ 2
+
+
+ 2
+
+
+ 2
+
+
+ 3
+
+ -
+
+
+ 100
+
+
+ 20
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ 8
+
+
+ 1
+
+
+ 4
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ Qt::RightToLeft
+
+
+ Minus
+
+
+
+ -
+
+
+ Squelch
+
+
+
+ -
+
+
+ AF Bandwidth
+
+
+
+ -
+
+
+ -100
+
+
+ 0
+
+
+ -40
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+
+ 50
+ 0
+
+
+
+ 12.5kHz
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ RF Bandwidth
+
+
+
+ -
+
+
+
+ 50
+ 0
+
+
+
+ 3 kHz
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ 1
+
+
+ 20
+
+
+ 1
+
+
+ 3
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+
+ 50
+ 0
+
+
+
+ -40dB
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ Volume
+
+
+
+ -
+
+
+
+ 50
+ 0
+
+
+
+ 2.0
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 32
+ 16
+
+
+
+
+ Monospace
+ 12
+
+
+
+ SizeVerCursor
+
+
+ Qt::StrongFocus
+
+
+ Demod shift frequency from center in Hz
+
+
+
+ -
+
+
+ Hz
+
+
+
+
+
+
+
+
+ RollupWidget
+ QWidget
+
+ 1
+
+
+ ValueDial
+ QWidget
+
+ 1
+
+
+
+
+
diff --git a/plugins/channel/am/amplugin.cpp b/plugins/channel/am/amplugin.cpp
new file mode 100644
index 000000000..fc888a1a9
--- /dev/null
+++ b/plugins/channel/am/amplugin.cpp
@@ -0,0 +1,54 @@
+#include "amplugin.h"
+
+#include
+#include
+#include "plugin/pluginapi.h"
+#include "amdemodgui.h"
+
+const PluginDescriptor AMPlugin::m_pluginDescriptor = {
+ QString("AM Demodulator"),
+ QString("---"),
+ QString("(c) Edouard Griffiths, F4EXB"),
+ QString("https://github.com/f4exb/rtl-sdrangelove/tree/f4exb"),
+ true,
+ QString("https://github.com/f4exb/rtl-sdrangelove/tree/f4exb")
+};
+
+AMPlugin::AMPlugin(QObject* parent) :
+ QObject(parent)
+{
+}
+
+const PluginDescriptor& AMPlugin::getPluginDescriptor() const
+{
+ return m_pluginDescriptor;
+}
+
+void AMPlugin::initPlugin(PluginAPI* pluginAPI)
+{
+ m_pluginAPI = pluginAPI;
+
+ // register AM demodulator
+ QAction* action = new QAction(tr("&AM Demodulator"), this);
+ connect(action, SIGNAL(triggered()), this, SLOT(createInstanceAM()));
+ m_pluginAPI->registerChannel("de.maintech.sdrangelove.channel.am", this, action);
+}
+
+PluginGUI* AMPlugin::createChannel(const QString& channelName)
+{
+ if(channelName == "de.maintech.sdrangelove.channel.am") {
+ AMDemodGUI* gui = AMDemodGUI::create(m_pluginAPI);
+ m_pluginAPI->registerChannelInstance("de.maintech.sdrangelove.channel.am", gui);
+ m_pluginAPI->addChannelRollup(gui);
+ return gui;
+ } else {
+ return NULL;
+ }
+}
+
+void AMPlugin::createInstanceAM()
+{
+ AMDemodGUI* gui = AMDemodGUI::create(m_pluginAPI);
+ m_pluginAPI->registerChannelInstance("de.maintech.sdrangelove.channel.am", gui);
+ m_pluginAPI->addChannelRollup(gui);
+}
diff --git a/plugins/channel/am/amplugin.h b/plugins/channel/am/amplugin.h
new file mode 100644
index 000000000..3c974f310
--- /dev/null
+++ b/plugins/channel/am/amplugin.h
@@ -0,0 +1,29 @@
+#ifndef INCLUDE_AMPLUGIN_H
+#define INCLUDE_AMPLUGIN_H
+
+#include
+#include "plugin/plugininterface.h"
+
+class AMPlugin : public QObject, PluginInterface {
+ Q_OBJECT
+ Q_INTERFACES(PluginInterface)
+ Q_PLUGIN_METADATA(IID "de.maintech.sdrangelove.channel.am")
+
+public:
+ explicit AMPlugin(QObject* parent = NULL);
+
+ const PluginDescriptor& getPluginDescriptor() const;
+ void initPlugin(PluginAPI* pluginAPI);
+
+ PluginGUI* createChannel(const QString& channelName);
+
+private:
+ static const PluginDescriptor m_pluginDescriptor;
+
+ PluginAPI* m_pluginAPI;
+
+private slots:
+ void createInstanceAM();
+};
+
+#endif // INCLUDE_AMPLUGIN_H