diff --git a/plugins/channeltx/CMakeLists.txt b/plugins/channeltx/CMakeLists.txt
index 90e1a4068..c43165da7 100644
--- a/plugins/channeltx/CMakeLists.txt
+++ b/plugins/channeltx/CMakeLists.txt
@@ -1,3 +1,4 @@
project(mod)
add_subdirectory(modam)
+add_subdirectory(modnfm)
diff --git a/plugins/channeltx/modnfm/CMakeLists.txt b/plugins/channeltx/modnfm/CMakeLists.txt
new file mode 100644
index 000000000..2db4c4c8c
--- /dev/null
+++ b/plugins/channeltx/modnfm/CMakeLists.txt
@@ -0,0 +1,43 @@
+project(modnfm)
+
+set(modnfm_SOURCES
+ nfmmod.cpp
+ nfmmodgui.cpp
+ nfmmodplugin.cpp
+)
+
+set(modnfm_HEADERS
+ nfmmod.h
+ nfmmodgui.h
+ nfmmodplugin.h
+)
+
+set(modnfm_FORMS
+ nfmmodgui.ui
+)
+
+include_directories(
+ .
+ ${CMAKE_CURRENT_BINARY_DIR}
+)
+
+add_definitions(${QT_DEFINITIONS})
+add_definitions(-DQT_PLUGIN)
+add_definitions(-DQT_SHARED)
+
+qt5_wrap_ui(modnfm_FORMS_HEADERS ${modnfm_FORMS})
+
+add_library(modnfm SHARED
+ ${modnfm_SOURCES}
+ ${modnfm_HEADERS_MOC}
+ ${modnfm_FORMS_HEADERS}
+)
+
+target_link_libraries(modnfm
+ ${QT_LIBRARIES}
+ sdrbase
+)
+
+qt5_use_modules(modnfm Core Widgets)
+
+install(TARGETS modnfm DESTINATION lib/plugins/channeltx)
\ No newline at end of file
diff --git a/plugins/channeltx/modnfm/modnfm.pro b/plugins/channeltx/modnfm/modnfm.pro
new file mode 100644
index 000000000..765317ea3
--- /dev/null
+++ b/plugins/channeltx/modnfm/modnfm.pro
@@ -0,0 +1,37 @@
+#--------------------------------------------------------
+#
+# Pro file for Android and Windows builds with Qt Creator
+#
+#--------------------------------------------------------
+
+TEMPLATE = lib
+CONFIG += plugin
+
+QT += core gui widgets multimedia
+
+TARGET = modnfm
+
+DEFINES += USE_SSE2=1
+QMAKE_CXXFLAGS += -msse2
+DEFINES += USE_SSE4_1=1
+QMAKE_CXXFLAGS += -msse4.1
+
+INCLUDEPATH += $$PWD
+INCLUDEPATH += ../../../sdrbase
+
+CONFIG(Release):build_subdir = release
+CONFIG(Debug):build_subdir = debug
+
+SOURCES += nfmmod.cpp\
+ nfmmodgui.cpp\
+ nfmmodplugin.cpp
+
+HEADERS += nfmmod.h\
+ nfmmodgui.h\
+ nfmmodplugin.h
+
+FORMS += nfmmodgui.ui
+
+LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase
+
+RESOURCES = ../../../sdrbase/resources/res.qrc
diff --git a/plugins/channeltx/modnfm/nfmmod.cpp b/plugins/channeltx/modnfm/nfmmod.cpp
new file mode 100644
index 000000000..0ea65a7c9
--- /dev/null
+++ b/plugins/channeltx/modnfm/nfmmod.cpp
@@ -0,0 +1,348 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2016 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 "dsp/dspengine.h"
+#include "dsp/pidcontroller.h"
+#include "nfmmod.h"
+
+MESSAGE_CLASS_DEFINITION(NFMMod::MsgConfigureNFMMod, Message)
+MESSAGE_CLASS_DEFINITION(NFMMod::MsgConfigureFileSourceName, Message)
+MESSAGE_CLASS_DEFINITION(NFMMod::MsgConfigureFileSourceSeek, Message)
+MESSAGE_CLASS_DEFINITION(NFMMod::MsgConfigureAFInput, Message)
+MESSAGE_CLASS_DEFINITION(NFMMod::MsgConfigureFileSourceStreamTiming, Message)
+MESSAGE_CLASS_DEFINITION(NFMMod::MsgReportFileSourceStreamData, Message)
+MESSAGE_CLASS_DEFINITION(NFMMod::MsgReportFileSourceStreamTiming, Message)
+
+
+NFMMod::NFMMod() :
+ m_audioFifo(4, 48000),
+ m_settingsMutex(QMutex::Recursive),
+ m_fileSize(0),
+ m_recordLength(0),
+ m_sampleRate(48000),
+ m_afInput(NFMModInputNone)
+{
+ setObjectName("AMMod");
+
+ m_config.m_outputSampleRate = 48000;
+ m_config.m_inputFrequencyOffset = 0;
+ m_config.m_rfBandwidth = 12500;
+ m_config.m_afBandwidth = 3000;
+ m_config.m_modFactor = 20;
+ m_config.m_audioSampleRate = DSPEngine::instance()->getAudioSampleRate();
+
+ apply();
+
+ //m_audioBuffer.resize(1<<14);
+ //m_audioBufferFill = 0;
+
+ m_movingAverage.resize(16, 0);
+ m_volumeAGC.resize(4096, 0.003, 0);
+ m_magsq = 0.0;
+
+ m_toneNco.setFreq(1000.0, m_config.m_audioSampleRate);
+ DSPEngine::instance()->addAudioSource(&m_audioFifo);
+}
+
+NFMMod::~NFMMod()
+{
+ DSPEngine::instance()->removeAudioSource(&m_audioFifo);
+}
+
+void NFMMod::configure(MessageQueue* messageQueue,
+ Real rfBandwidth,
+ Real afBandwidth,
+ float modFactor,
+ int volumeTenths,
+ bool audioMute,
+ bool playLoop)
+{
+ Message* cmd = MsgConfigureNFMMod::create(rfBandwidth, afBandwidth, modFactor, volumeTenths, audioMute, playLoop);
+ messageQueue->push(cmd);
+}
+
+void NFMMod::pull(Sample& sample)
+{
+ Complex ci;
+ Real t;
+
+ m_settingsMutex.lock();
+
+ if (m_interpolatorDistance > 1.0f) // decimate
+ {
+ pullAF(t);
+ m_modSample.real(((t+1.0f) * m_running.m_modFactor * 16384.0f)); // modulate and scale zero frequency carrier
+ m_modSample.imag(0.0f);
+
+ while (!m_interpolator.decimate(&m_interpolatorDistanceRemain, m_modSample, &ci))
+ {
+ pullAF(t);
+ m_modSample.real(((t+1.0f) * m_running.m_modFactor * 16384.0f)); // modulate and scale zero frequency carrier
+ m_modSample.imag(0.0f);
+ }
+ }
+ else
+ {
+ if (m_interpolator.interpolate(&m_interpolatorDistanceRemain, m_modSample, &ci))
+ {
+ pullAF(t);
+ m_modSample.real(((t+1.0f) * m_running.m_modFactor * 16384.0f)); // modulate and scale zero frequency carrier
+ m_modSample.imag(0.0f);
+ }
+ }
+
+ m_interpolatorDistanceRemain += m_interpolatorDistance;
+
+ ci *= m_carrierNco.nextIQ(); // shift to carrier frequency
+
+ m_settingsMutex.unlock();
+
+ Real magsq = ci.real() * ci.real() + ci.imag() * ci.imag();
+ magsq /= (1<<30);
+ m_movingAverage.feed(magsq);
+ m_magsq = m_movingAverage.average();
+
+ sample.m_real = (FixReal) ci.real();
+ sample.m_imag = (FixReal) ci.imag();
+}
+
+void NFMMod::pullAF(Real& sample)
+{
+ int16_t audioSample[2];
+
+ switch (m_afInput)
+ {
+ case NFMModInputTone:
+ sample = m_toneNco.next();
+ break;
+ case NFMModInputFile:
+ // sox f4exb_call.wav --encoding float --endian little f4exb_call.raw
+ // ffplay -f f32le -ar 48k -ac 1 f4exb_call.raw
+ if (m_ifstream.is_open())
+ {
+ if (m_ifstream.eof())
+ {
+ if (m_running.m_playLoop)
+ {
+ m_ifstream.clear();
+ m_ifstream.seekg(0, std::ios::beg);
+ }
+ }
+
+ if (m_ifstream.eof())
+ {
+ sample = 0.0f;
+ }
+ else
+ {
+ m_ifstream.read(reinterpret_cast(&sample), sizeof(Real));
+ }
+ }
+ else
+ {
+ sample = 0.0f;
+ }
+ break;
+ case NFMModInputAudio:
+ m_audioFifo.read(reinterpret_cast(audioSample), 1, 10);
+ sample = ((audioSample[0] + audioSample[1]) * m_running.m_volumeFactor) / 6553600.0f;
+ break;
+ case NFMModInputNone:
+ default:
+ sample = 0.0f;
+ break;
+ }
+}
+
+void NFMMod::start()
+{
+ qDebug() << "NFMMod::start: m_outputSampleRate: " << m_config.m_outputSampleRate
+ << " m_inputFrequencyOffset: " << m_config.m_inputFrequencyOffset;
+
+ m_audioFifo.clear();
+}
+
+void NFMMod::stop()
+{
+}
+
+bool NFMMod::handleMessage(const Message& cmd)
+{
+ qDebug() << "NFMMod::handleMessage";
+
+ if (UpChannelizer::MsgChannelizerNotification::match(cmd))
+ {
+ UpChannelizer::MsgChannelizerNotification& notif = (UpChannelizer::MsgChannelizerNotification&) cmd;
+
+ m_config.m_outputSampleRate = notif.getSampleRate();
+ m_config.m_inputFrequencyOffset = notif.getFrequencyOffset();
+
+ apply();
+
+ qDebug() << "NFMMod::handleMessage: MsgChannelizerNotification:"
+ << " m_outputSampleRate: " << m_config.m_outputSampleRate
+ << " m_inputFrequencyOffset: " << m_config.m_inputFrequencyOffset;
+
+ return true;
+ }
+ else if (MsgConfigureNFMMod::match(cmd))
+ {
+ MsgConfigureNFMMod& cfg = (MsgConfigureNFMMod&) cmd;
+
+ m_config.m_rfBandwidth = cfg.getRFBandwidth();
+ m_config.m_afBandwidth = cfg.getAFBandwidth();
+ m_config.m_modFactor = cfg.getModFactor();
+ m_config.m_volumeFactor = cfg.getVolumeFactor();
+ m_config.m_audioMute = cfg.getAudioMute();
+ m_config.m_playLoop = cfg.getPlayLoop();
+
+ apply();
+
+ qDebug() << "NFMMod::handleMessage: MsgConfigureAMMod:"
+ << " m_rfBandwidth: " << m_config.m_rfBandwidth
+ << " m_afBandwidth: " << m_config.m_afBandwidth
+ << " m_modFactor: " << m_config.m_modFactor
+ << " m_volumeFactor: " << m_config.m_volumeFactor
+ << " m_audioMute: " << m_config.m_audioMute
+ << " m_playLoop: " << m_config.m_playLoop;
+
+ return true;
+ }
+ else if (MsgConfigureFileSourceName::match(cmd))
+ {
+ MsgConfigureFileSourceName& conf = (MsgConfigureFileSourceName&) cmd;
+ m_fileName = conf.getFileName();
+ openFileStream();
+ return true;
+ }
+ else if (MsgConfigureFileSourceSeek::match(cmd))
+ {
+ MsgConfigureFileSourceSeek& conf = (MsgConfigureFileSourceSeek&) cmd;
+ int seekPercentage = conf.getPercentage();
+ seekFileStream(seekPercentage);
+
+ return true;
+ }
+ else if (MsgConfigureAFInput::match(cmd))
+ {
+ MsgConfigureAFInput& conf = (MsgConfigureAFInput&) cmd;
+ m_afInput = conf.getAFInput();
+
+ return true;
+ }
+ else if (MsgConfigureFileSourceStreamTiming::match(cmd))
+ {
+ std::size_t samplesCount;
+
+ if (m_ifstream.eof()) {
+ samplesCount = m_fileSize / sizeof(Real);
+ } else {
+ samplesCount = m_ifstream.tellg() / sizeof(Real);
+ }
+
+ MsgReportFileSourceStreamTiming *report;
+ report = MsgReportFileSourceStreamTiming::create(samplesCount);
+ getOutputMessageQueue()->push(report);
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+void NFMMod::apply()
+{
+
+ if ((m_config.m_inputFrequencyOffset != m_running.m_inputFrequencyOffset) ||
+ (m_config.m_outputSampleRate != m_running.m_outputSampleRate))
+ {
+ m_settingsMutex.lock();
+ m_carrierNco.setFreq(m_config.m_inputFrequencyOffset, m_config.m_outputSampleRate);
+ m_settingsMutex.unlock();
+ }
+
+ if((m_config.m_outputSampleRate != m_running.m_outputSampleRate) ||
+ (m_config.m_rfBandwidth != m_running.m_rfBandwidth))
+ {
+ m_settingsMutex.lock();
+ m_interpolatorDistanceRemain = 0;
+ m_interpolatorConsumed = false;
+ m_interpolatorDistance = (Real) m_config.m_audioSampleRate / (Real) m_config.m_outputSampleRate;
+ m_interpolator.create(48, m_config.m_audioSampleRate, m_config.m_rfBandwidth / 2.2, 3.0);
+ m_settingsMutex.unlock();
+ }
+
+ if((m_config.m_afBandwidth != m_running.m_afBandwidth) ||
+ (m_config.m_audioSampleRate != m_running.m_audioSampleRate))
+ {
+ m_settingsMutex.lock();
+ m_lowpass.create(21, m_config.m_audioSampleRate, m_config.m_afBandwidth);
+ m_settingsMutex.unlock();
+ }
+
+ m_running.m_outputSampleRate = m_config.m_outputSampleRate;
+ m_running.m_inputFrequencyOffset = m_config.m_inputFrequencyOffset;
+ m_running.m_rfBandwidth = m_config.m_rfBandwidth;
+ m_running.m_afBandwidth = m_config.m_afBandwidth;
+ m_running.m_modFactor = m_config.m_modFactor;
+ m_running.m_volumeFactor = m_config.m_volumeFactor;
+ m_running.m_audioSampleRate = m_config.m_audioSampleRate;
+ m_running.m_audioMute = m_config.m_audioMute;
+ m_running.m_playLoop = m_config.m_playLoop;
+}
+
+void NFMMod::openFileStream()
+{
+ if (m_ifstream.is_open()) {
+ m_ifstream.close();
+ }
+
+ m_ifstream.open(m_fileName.toStdString().c_str(), std::ios::binary | std::ios::ate);
+ m_fileSize = m_ifstream.tellg();
+ m_ifstream.seekg(0,std::ios_base::beg);
+
+ m_sampleRate = 48000; // fixed rate
+ m_recordLength = m_fileSize / (sizeof(Real) * m_sampleRate);
+
+ qDebug() << "AMMod::openFileStream: " << m_fileName.toStdString().c_str()
+ << " fileSize: " << m_fileSize << "bytes"
+ << " length: " << m_recordLength << " seconds";
+
+ MsgReportFileSourceStreamData *report;
+ report = MsgReportFileSourceStreamData::create(m_sampleRate, m_recordLength);
+ getOutputMessageQueue()->push(report);
+}
+
+void NFMMod::seekFileStream(int seekPercentage)
+{
+ QMutexLocker mutexLocker(&m_settingsMutex);
+
+ if (m_ifstream.is_open())
+ {
+ int seekPoint = ((m_recordLength * seekPercentage) / 100) * m_sampleRate;
+ seekPoint *= sizeof(Real);
+ m_ifstream.clear();
+ m_ifstream.seekg(seekPoint, std::ios::beg);
+ }
+}
diff --git a/plugins/channeltx/modnfm/nfmmod.h b/plugins/channeltx/modnfm/nfmmod.h
new file mode 100644
index 000000000..88a70939f
--- /dev/null
+++ b/plugins/channeltx/modnfm/nfmmod.h
@@ -0,0 +1,300 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2016 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_CHANNELTX_MODNFM_NFMMOD_H_
+#define PLUGINS_CHANNELTX_MODNFM_NFMMOD_H_
+
+#include
+#include
+#include
+#include
+
+#include "dsp/basebandsamplesource.h"
+#include "dsp/nco.h"
+#include "dsp/interpolator.h"
+#include "dsp/lowpass.h"
+#include "dsp/movingaverage.h"
+#include "dsp/agc.h"
+#include "audio/audiofifo.h"
+#include "util/message.h"
+
+class NFMMod : public BasebandSampleSource {
+ Q_OBJECT
+
+public:
+ typedef enum
+ {
+ NFMModInputNone,
+ NFMModInputTone,
+ NFMModInputFile,
+ NFMModInputAudio
+ } NFMModInputAF;
+
+ class MsgConfigureFileSourceName : public Message
+ {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ const QString& getFileName() const { return m_fileName; }
+
+ static MsgConfigureFileSourceName* create(const QString& fileName)
+ {
+ return new MsgConfigureFileSourceName(fileName);
+ }
+
+ private:
+ QString m_fileName;
+
+ MsgConfigureFileSourceName(const QString& fileName) :
+ Message(),
+ m_fileName(fileName)
+ { }
+ };
+
+ class MsgConfigureFileSourceSeek : public Message
+ {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ int getPercentage() const { return m_seekPercentage; }
+
+ static MsgConfigureFileSourceSeek* create(int seekPercentage)
+ {
+ return new MsgConfigureFileSourceSeek(seekPercentage);
+ }
+
+ protected:
+ int m_seekPercentage; //!< percentage of seek position from the beginning 0..100
+
+ MsgConfigureFileSourceSeek(int seekPercentage) :
+ Message(),
+ m_seekPercentage(seekPercentage)
+ { }
+ };
+
+ class MsgConfigureFileSourceStreamTiming : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+
+ static MsgConfigureFileSourceStreamTiming* create()
+ {
+ return new MsgConfigureFileSourceStreamTiming();
+ }
+
+ private:
+
+ MsgConfigureFileSourceStreamTiming() :
+ Message()
+ { }
+ };
+
+ class MsgConfigureAFInput : public Message
+ {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ NFMModInputAF getAFInput() const { return m_afInput; }
+
+ static MsgConfigureAFInput* create(NFMModInputAF afInput)
+ {
+ return new MsgConfigureAFInput(afInput);
+ }
+
+ private:
+ NFMModInputAF m_afInput;
+
+ MsgConfigureAFInput(NFMModInputAF afInput) :
+ Message(),
+ m_afInput(afInput)
+ { }
+ };
+
+ class MsgReportFileSourceStreamTiming : public Message
+ {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ std::size_t getSamplesCount() const { return m_samplesCount; }
+
+ static MsgReportFileSourceStreamTiming* create(std::size_t samplesCount)
+ {
+ return new MsgReportFileSourceStreamTiming(samplesCount);
+ }
+
+ protected:
+ std::size_t m_samplesCount;
+
+ MsgReportFileSourceStreamTiming(std::size_t samplesCount) :
+ Message(),
+ m_samplesCount(samplesCount)
+ { }
+ };
+
+ class MsgReportFileSourceStreamData : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ int getSampleRate() const { return m_sampleRate; }
+ quint32 getRecordLength() const { return m_recordLength; }
+
+ static MsgReportFileSourceStreamData* create(int sampleRate,
+ quint32 recordLength)
+ {
+ return new MsgReportFileSourceStreamData(sampleRate, recordLength);
+ }
+
+ protected:
+ int m_sampleRate;
+ quint32 m_recordLength;
+
+ MsgReportFileSourceStreamData(int sampleRate,
+ quint32 recordLength) :
+ Message(),
+ m_sampleRate(sampleRate),
+ m_recordLength(recordLength)
+ { }
+ };
+
+ //=================================================================
+
+ NFMMod();
+ ~NFMMod();
+
+ void configure(MessageQueue* messageQueue, Real rfBandwidth, Real afBandwidth, float modFactor, int volumeFactor, bool audioMute, bool playLoop);
+
+ virtual void pull(Sample& sample);
+ virtual void start();
+ virtual void stop();
+ virtual bool handleMessage(const Message& cmd);
+
+ Real getMagSq() const { return m_magsq; }
+
+private:
+ class MsgConfigureNFMMod : public Message
+ {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ Real getRFBandwidth() const { return m_rfBandwidth; }
+ Real getAFBandwidth() const { return m_afBandwidth; }
+ float getModFactor() const { return m_modFactor; }
+ int getVolumeFactor() const { return m_volumeFactor; }
+ bool getAudioMute() const { return m_audioMute; }
+ bool getPlayLoop() const { return m_playLoop; }
+
+ static MsgConfigureNFMMod* create(Real rfBandwidth, Real afBandwidth, float modFactor, int volumeFactor, bool audioMute, bool playLoop)
+ {
+ return new MsgConfigureNFMMod(rfBandwidth, afBandwidth, modFactor, volumeFactor, audioMute, playLoop);
+ }
+
+ private:
+ Real m_rfBandwidth;
+ Real m_afBandwidth;
+ float m_modFactor;
+ int m_volumeFactor;
+ bool m_audioMute;
+ bool m_playLoop;
+
+ MsgConfigureNFMMod(Real rfBandwidth, Real afBandwidth, float modFactor, int volumeFactor, bool audioMute, bool playLoop) :
+ Message(),
+ m_rfBandwidth(rfBandwidth),
+ m_afBandwidth(afBandwidth),
+ m_modFactor(modFactor),
+ m_volumeFactor(volumeFactor),
+ m_audioMute(audioMute),
+ m_playLoop(playLoop)
+ { }
+ };
+
+ //=================================================================
+
+ struct AudioSample {
+ qint16 l;
+ qint16 r;
+ };
+ typedef std::vector AudioVector;
+
+ enum RateState {
+ RSInitialFill,
+ RSRunning
+ };
+
+ struct Config {
+ int m_outputSampleRate;
+ qint64 m_inputFrequencyOffset;
+ Real m_rfBandwidth;
+ Real m_afBandwidth;
+ float m_modFactor;
+ int m_volumeFactor;
+ quint32 m_audioSampleRate;
+ bool m_audioMute;
+ bool m_playLoop;
+
+ Config() :
+ m_outputSampleRate(-1),
+ m_inputFrequencyOffset(0),
+ m_rfBandwidth(-1),
+ m_afBandwidth(-1),
+ m_modFactor(0.2f),
+ m_volumeFactor(20),
+ m_audioSampleRate(0),
+ m_audioMute(false),
+ m_playLoop(false)
+ { }
+ };
+
+ //=================================================================
+
+ Config m_config;
+ Config m_running;
+
+ NCO m_carrierNco;
+ NCO m_toneNco;
+ Complex m_modSample;
+ Interpolator m_interpolator;
+ Real m_interpolatorDistance;
+ Real m_interpolatorDistanceRemain;
+ bool m_interpolatorConsumed;
+ Lowpass m_lowpass;
+
+ Real m_magsq;
+ MovingAverage m_movingAverage;
+ SimpleAGC m_volumeAGC;
+
+ //AudioVector m_audioBuffer;
+ //uint m_audioBufferFill;
+
+ AudioFifo m_audioFifo;
+ SampleVector m_sampleBuffer;
+ QMutex m_settingsMutex;
+
+ std::ifstream m_ifstream;
+ QString m_fileName;
+ quint64 m_fileSize; //!< raw file size (bytes)
+ quint32 m_recordLength; //!< record length in seconds computed from file size
+ int m_sampleRate;
+
+ NFMModInputAF m_afInput;
+
+ void apply();
+ void pullAF(Real& sample);
+ void openFileStream();
+ void seekFileStream(int seekPercentage);
+};
+
+
+#endif /* PLUGINS_CHANNELTX_MODNFM_NFMMOD_H_ */
diff --git a/plugins/channeltx/modnfm/nfmmodgui.cpp b/plugins/channeltx/modnfm/nfmmodgui.cpp
new file mode 100644
index 000000000..69b56b110
--- /dev/null
+++ b/plugins/channeltx/modnfm/nfmmodgui.cpp
@@ -0,0 +1,465 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2016 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 "device/devicesinkapi.h"
+#include "dsp/upchannelizer.h"
+
+#include "dsp/threadedbasebandsamplesource.h"
+#include "ui_nfmmodgui.h"
+#include "plugin/pluginapi.h"
+#include "util/simpleserializer.h"
+#include "util/db.h"
+#include "gui/basicchannelsettingswidget.h"
+#include "dsp/dspengine.h"
+#include "mainwindow.h"
+#include "nfmmodgui.h"
+
+const QString NFMModGUI::m_channelID = "sdrangel.channeltx.modnfm";
+
+const int NFMModGUI::m_rfBW[] = {
+ 3000, 4000, 5000, 6250, 8330, 10000, 12500, 15000, 20000, 25000, 40000
+};
+
+NFMModGUI* NFMModGUI::create(PluginAPI* pluginAPI, DeviceSinkAPI *deviceAPI)
+{
+ NFMModGUI* gui = new NFMModGUI(pluginAPI, deviceAPI);
+ return gui;
+}
+
+void NFMModGUI::destroy()
+{
+}
+
+void NFMModGUI::setName(const QString& name)
+{
+ setObjectName(name);
+}
+
+QString NFMModGUI::getName() const
+{
+ return objectName();
+}
+
+qint64 NFMModGUI::getCenterFrequency() const {
+ return m_channelMarker.getCenterFrequency();
+}
+
+void NFMModGUI::setCenterFrequency(qint64 centerFrequency)
+{
+ m_channelMarker.setCenterFrequency(centerFrequency);
+ applySettings();
+}
+
+void NFMModGUI::resetToDefaults()
+{
+ blockApplySettings(true);
+
+ ui->rfBW->setValue(6);
+ ui->afBW->setValue(3);
+ ui->modPercent->setValue(20);
+ ui->micVolume->setValue(50);
+ ui->deltaFrequency->setValue(0);
+
+ blockApplySettings(false);
+ applySettings();
+}
+
+QByteArray NFMModGUI::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->modPercent->value());
+ s.writeU32(5, m_channelMarker.getColor().rgb());
+ return s.final();
+}
+
+bool NFMModGUI::deserialize(const QByteArray& data)
+{
+ SimpleDeserializer d(data);
+
+ if(!d.isValid())
+ {
+ resetToDefaults();
+ return false;
+ }
+
+ if(d.getVersion() == 1)
+ {
+ QByteArray bytetmp;
+ quint32 u32tmp;
+ qint32 tmp;
+
+ blockApplySettings(true);
+ m_channelMarker.blockSignals(true);
+
+ 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->modPercent->setValue(tmp);
+
+ if(d.readU32(5, &u32tmp))
+ {
+ m_channelMarker.setColor(u32tmp);
+ }
+
+ blockApplySettings(false);
+ m_channelMarker.blockSignals(false);
+
+ applySettings();
+ return true;
+ }
+ else
+ {
+ resetToDefaults();
+ return false;
+ }
+}
+
+bool NFMModGUI::handleMessage(const Message& message)
+{
+ if (NFMMod::MsgReportFileSourceStreamData::match(message))
+ {
+ m_recordSampleRate = ((NFMMod::MsgReportFileSourceStreamData&)message).getSampleRate();
+ m_recordLength = ((NFMMod::MsgReportFileSourceStreamData&)message).getRecordLength();
+ m_samplesCount = 0;
+ updateWithStreamData();
+ return true;
+ }
+ else if (NFMMod::MsgReportFileSourceStreamTiming::match(message))
+ {
+ m_samplesCount = ((NFMMod::MsgReportFileSourceStreamTiming&)message).getSamplesCount();
+ updateWithStreamTime();
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+void NFMModGUI::viewChanged()
+{
+ applySettings();
+}
+
+void NFMModGUI::handleSourceMessages()
+{
+ Message* message;
+
+ while ((message = m_nfmMod->getOutputMessageQueue()->pop()) != 0)
+ {
+ if (handleMessage(*message))
+ {
+ delete message;
+ }
+ }
+}
+
+void NFMModGUI::on_deltaMinus_toggled(bool minus)
+{
+ int deltaFrequency = m_channelMarker.getCenterFrequency();
+ bool minusDelta = (deltaFrequency < 0);
+
+ if (minus ^ minusDelta) // sign change
+ {
+ m_channelMarker.setCenterFrequency(-deltaFrequency);
+ }
+}
+
+void NFMModGUI::on_deltaFrequency_changed(quint64 value)
+{
+ if (ui->deltaMinus->isChecked()) {
+ m_channelMarker.setCenterFrequency(-value);
+ } else {
+ m_channelMarker.setCenterFrequency(value);
+ }
+}
+
+void NFMModGUI::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 NFMModGUI::on_afBW_valueChanged(int value)
+{
+ ui->afBWText->setText(QString("%1 kHz").arg(value));
+ applySettings();
+}
+
+void NFMModGUI::on_modPercent_valueChanged(int value)
+{
+ ui->modPercentText->setText(QString("%1").arg(value));
+ applySettings();
+}
+
+void NFMModGUI::on_micVolume_valueChanged(int value)
+{
+ ui->micVolumeText->setText(QString("%1").arg(value));
+ applySettings();
+}
+
+void NFMModGUI::on_audioMute_toggled(bool checked)
+{
+ applySettings();
+}
+
+void NFMModGUI::on_playLoop_toggled(bool checked)
+{
+ applySettings();
+}
+
+void NFMModGUI::on_play_toggled(bool checked)
+{
+ ui->tone->setEnabled(!checked); // release other source inputs
+ ui->mic->setEnabled(!checked);
+ m_modAFInput = checked ? NFMMod::NFMModInputFile : NFMMod::NFMModInputNone;
+ NFMMod::MsgConfigureAFInput* message = NFMMod::MsgConfigureAFInput::create(m_modAFInput);
+ m_nfmMod->getInputMessageQueue()->push(message);
+ ui->navTimeSlider->setEnabled(!checked);
+ m_enableNavTime = !checked;
+}
+
+void NFMModGUI::on_tone_toggled(bool checked)
+{
+ ui->play->setEnabled(!checked); // release other source inputs
+ ui->mic->setEnabled(!checked);
+ m_modAFInput = checked ? NFMMod::NFMModInputTone : NFMMod::NFMModInputNone;
+ NFMMod::MsgConfigureAFInput* message = NFMMod::MsgConfigureAFInput::create(m_modAFInput);
+ m_nfmMod->getInputMessageQueue()->push(message);
+}
+
+void NFMModGUI::on_mic_toggled(bool checked)
+{
+ ui->play->setEnabled(!checked); // release other source inputs
+ ui->tone->setEnabled(!checked); // release other source inputs
+ m_modAFInput = checked ? NFMMod::NFMModInputAudio : NFMMod::NFMModInputNone;
+ NFMMod::MsgConfigureAFInput* message = NFMMod::MsgConfigureAFInput::create(m_modAFInput);
+ m_nfmMod->getInputMessageQueue()->push(message);
+}
+
+void NFMModGUI::on_navTimeSlider_valueChanged(int value)
+{
+ if (m_enableNavTime && ((value >= 0) && (value <= 100)))
+ {
+ int t_sec = (m_recordLength * value) / 100;
+ QTime t(0, 0, 0, 0);
+ t = t.addSecs(t_sec);
+
+ NFMMod::MsgConfigureFileSourceSeek* message = NFMMod::MsgConfigureFileSourceSeek::create(value);
+ m_nfmMod->getInputMessageQueue()->push(message);
+ }
+}
+
+void NFMModGUI::on_showFileDialog_clicked(bool checked)
+{
+ QString fileName = QFileDialog::getOpenFileName(this,
+ tr("Open raw audio file"), ".", tr("Raw audio Files (*.raw)"));
+
+ if (fileName != "")
+ {
+ m_fileName = fileName;
+ ui->recordFileText->setText(m_fileName);
+ ui->play->setEnabled(true);
+ configureFileName();
+ }
+}
+
+void NFMModGUI::configureFileName()
+{
+ qDebug() << "FileSourceGui::configureFileName: " << m_fileName.toStdString().c_str();
+ NFMMod::MsgConfigureFileSourceName* message = NFMMod::MsgConfigureFileSourceName::create(m_fileName);
+ m_nfmMod->getInputMessageQueue()->push(message);
+}
+
+void NFMModGUI::onWidgetRolled(QWidget* widget, bool rollDown)
+{
+}
+
+void NFMModGUI::onMenuDoubleClicked()
+{
+ if(!m_basicSettingsShown) {
+ m_basicSettingsShown = true;
+ BasicChannelSettingsWidget* bcsw = new BasicChannelSettingsWidget(&m_channelMarker, this);
+ bcsw->show();
+ }
+}
+
+NFMModGUI::NFMModGUI(PluginAPI* pluginAPI, DeviceSinkAPI *deviceAPI, QWidget* parent) :
+ RollupWidget(parent),
+ ui(new Ui::NFMModGUI),
+ m_pluginAPI(pluginAPI),
+ m_deviceAPI(deviceAPI),
+ m_channelMarker(this),
+ m_basicSettingsShown(false),
+ m_doApplySettings(true),
+ m_channelPowerDbAvg(20,0),
+ m_recordLength(0),
+ m_recordSampleRate(48000),
+ m_samplesCount(0),
+ m_tickCount(0),
+ m_enableNavTime(false),
+ m_modAFInput(NFMMod::NFMModInputNone)
+{
+ 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_nfmMod = new NFMMod();
+ m_channelizer = new UpChannelizer(m_nfmMod);
+ m_threadedChannelizer = new ThreadedBasebandSampleSource(m_channelizer, this);
+ //m_pluginAPI->addThreadedSink(m_threadedChannelizer);
+ m_deviceAPI->addThreadedSource(m_threadedChannelizer);
+
+ connect(&m_pluginAPI->getMainWindow()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick()));
+
+ ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::ReverseGold));
+
+ //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_deviceAPI->registerChannelInstance(m_channelID, this);
+ m_deviceAPI->addChannelMarker(&m_channelMarker);
+ m_deviceAPI->addRollupWidget(this);
+
+ ui->play->setEnabled(false);
+ ui->play->setChecked(false);
+ ui->tone->setChecked(false);
+ ui->mic->setChecked(false);
+
+ applySettings();
+
+ connect(m_nfmMod->getOutputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages()));
+}
+
+NFMModGUI::~NFMModGUI()
+{
+ m_deviceAPI->removeChannelInstance(this);
+ m_deviceAPI->removeThreadedSource(m_threadedChannelizer);
+ delete m_threadedChannelizer;
+ delete m_channelizer;
+ delete m_nfmMod;
+ //delete m_channelMarker;
+ delete ui;
+}
+
+void NFMModGUI::blockApplySettings(bool block)
+{
+ m_doApplySettings = !block;
+}
+
+void NFMModGUI::applySettings()
+{
+ if (m_doApplySettings)
+ {
+ setTitleColor(m_channelMarker.getColor());
+
+ m_channelizer->configure(m_channelizer->getInputMessageQueue(),
+ 48000,
+ m_channelMarker.getCenterFrequency());
+
+ ui->deltaFrequency->setValue(abs(m_channelMarker.getCenterFrequency()));
+ ui->deltaMinus->setChecked(m_channelMarker.getCenterFrequency() < 0);
+
+ m_nfmMod->configure(m_nfmMod->getInputMessageQueue(),
+ m_rfBW[ui->rfBW->value()],
+ ui->afBW->value() * 1000.0,
+ ui->modPercent->value() / 100.0f,
+ ui->micVolume->value(),
+ ui->audioMute->isChecked(),
+ ui->playLoop->isChecked());
+ }
+}
+
+void NFMModGUI::leaveEvent(QEvent*)
+{
+ blockApplySettings(true);
+ m_channelMarker.setHighlighted(false);
+ blockApplySettings(false);
+}
+
+void NFMModGUI::enterEvent(QEvent*)
+{
+ blockApplySettings(true);
+ m_channelMarker.setHighlighted(true);
+ blockApplySettings(false);
+}
+
+void NFMModGUI::tick()
+{
+ Real powDb = CalcDb::dbPower(m_nfmMod->getMagSq());
+ m_channelPowerDbAvg.feed(powDb);
+ ui->channelPower->setText(QString::number(m_channelPowerDbAvg.average(), 'f', 1));
+
+ if (((++m_tickCount & 0xf) == 0) && (m_modAFInput == NFMMod::NFMModInputFile))
+ {
+ NFMMod::MsgConfigureFileSourceStreamTiming* message = NFMMod::MsgConfigureFileSourceStreamTiming::create();
+ m_nfmMod->getInputMessageQueue()->push(message);
+ }
+}
+
+void NFMModGUI::updateWithStreamData()
+{
+ QTime recordLength(0, 0, 0, 0);
+ recordLength = recordLength.addSecs(m_recordLength);
+ QString s_time = recordLength.toString("hh:mm:ss");
+ ui->recordLengthText->setText(s_time);
+ updateWithStreamTime();
+}
+
+void NFMModGUI::updateWithStreamTime()
+{
+ int t_sec = 0;
+ int t_msec = 0;
+
+ if (m_recordSampleRate > 0)
+ {
+ t_msec = ((m_samplesCount * 1000) / m_recordSampleRate) % 1000;
+ t_sec = m_samplesCount / m_recordSampleRate;
+ }
+
+ QTime t(0, 0, 0, 0);
+ t = t.addSecs(t_sec);
+ t = t.addMSecs(t_msec);
+ QString s_timems = t.toString("hh:mm:ss.zzz");
+ QString s_time = t.toString("hh:mm:ss");
+ ui->relTimeText->setText(s_timems);
+
+ if (!m_enableNavTime)
+ {
+ float posRatio = (float) t_sec / (float) m_recordLength;
+ ui->navTimeSlider->setValue((int) (posRatio * 100.0));
+ }
+}
diff --git a/plugins/channeltx/modnfm/nfmmodgui.h b/plugins/channeltx/modnfm/nfmmodgui.h
new file mode 100644
index 000000000..bf74fb9d5
--- /dev/null
+++ b/plugins/channeltx/modnfm/nfmmodgui.h
@@ -0,0 +1,118 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2016 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_CHANNELTX_MODNFM_NFMMODGUI_H_
+#define PLUGINS_CHANNELTX_MODNFM_NFMMODGUI_H_
+
+#include "gui/rollupwidget.h"
+#include "plugin/plugingui.h"
+#include "dsp/channelmarker.h"
+#include "dsp/movingaverage.h"
+
+#include "nfmmod.h"
+
+class PluginAPI;
+class DeviceSinkAPI;
+
+class ThreadedBasebandSampleSource;
+class UpChannelizer;
+class NFMMod;
+
+namespace Ui {
+ class NFMModGUI;
+}
+
+class NFMModGUI : public RollupWidget, public PluginGUI {
+ Q_OBJECT
+
+public:
+ static NFMModGUI* create(PluginAPI* pluginAPI, DeviceSinkAPI *deviceAPI);
+ 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 bool handleMessage(const Message& message);
+
+ static const QString m_channelID;
+
+private slots:
+ void viewChanged();
+ void handleSourceMessages();
+
+ void on_deltaFrequency_changed(quint64 value);
+ void on_deltaMinus_toggled(bool minus);
+ void on_rfBW_valueChanged(int value);
+ void on_afBW_valueChanged(int value);
+ void on_modPercent_valueChanged(int value);
+ void on_micVolume_valueChanged(int value);
+ void on_audioMute_toggled(bool checked);
+ void on_tone_toggled(bool checked);
+ void on_mic_toggled(bool checked);
+ void on_play_toggled(bool checked);
+
+ void on_playLoop_toggled(bool checked);
+ void on_navTimeSlider_valueChanged(int value);
+ void on_showFileDialog_clicked(bool checked);
+
+ void onWidgetRolled(QWidget* widget, bool rollDown);
+ void onMenuDoubleClicked();
+
+ void configureFileName();
+ void tick();
+
+private:
+ Ui::NFMModGUI* ui;
+ PluginAPI* m_pluginAPI;
+ DeviceSinkAPI* m_deviceAPI;
+ ChannelMarker m_channelMarker;
+ bool m_basicSettingsShown;
+ bool m_doApplySettings;
+
+ ThreadedBasebandSampleSource* m_threadedChannelizer;
+ UpChannelizer* m_channelizer;
+ NFMMod* m_nfmMod;
+ MovingAverage m_channelPowerDbAvg;
+
+ QString m_fileName;
+ quint32 m_recordLength;
+ int m_recordSampleRate;
+ int m_samplesCount;
+ std::size_t m_tickCount;
+ bool m_enableNavTime;
+ NFMMod::NFMModInputAF m_modAFInput;
+
+ static const int m_rfBW[];
+
+ explicit NFMModGUI(PluginAPI* pluginAPI, DeviceSinkAPI *deviceAPI, QWidget* parent = NULL);
+ virtual ~NFMModGUI();
+
+ void blockApplySettings(bool block);
+ void applySettings();
+ void updateWithStreamData();
+ void updateWithStreamTime();
+
+ void leaveEvent(QEvent*);
+ void enterEvent(QEvent*);
+};
+
+#endif /* PLUGINS_CHANNELTX_MODNFM_NFMMODGUI_H_ */
diff --git a/plugins/channeltx/modnfm/nfmmodgui.ui b/plugins/channeltx/modnfm/nfmmodgui.ui
new file mode 100644
index 000000000..f8bb8ca53
--- /dev/null
+++ b/plugins/channeltx/modnfm/nfmmodgui.ui
@@ -0,0 +1,607 @@
+
+
+ NFMModGUI
+
+
+
+ 0
+ 0
+ 266
+ 190
+
+
+
+
+ Sans Serif
+ 9
+
+
+
+ Qt::StrongFocus
+
+
+ NFM Modulator
+
+
+
+
+ 10
+ 10
+ 251
+ 161
+
+
+
+ Settings
+
+
+
+ 3
+
+
+ 2
+
+ -
+
+
-
+
+
-
+
+
+ Frequency shift direction
+
+
+ ...
+
+
+
+ :/plus.png
+ :/minus.png
+
+
+
+ true
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 32
+ 16
+
+
+
+
+ Monospace
+ 12
+
+
+
+ SizeVerCursor
+
+
+ Qt::StrongFocus
+
+
+ Demod shift frequency from center in Hz
+
+
+
+ -
+
+
+ Hz
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
-
+
+
+ Channel power
+
+
+ Qt::RightToLeft
+
+
+ 0.0
+
+
+
+ -
+
+
+ dB
+
+
+
+ -
+
+
+ Mute/Unmute audio
+
+
+ ...
+
+
+
+ :/sound_on.png
+ :/sound_off.png:/sound_on.png
+
+
+ true
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+ RF BW
+
+
+
+ -
+
+
+ Demodulator (RF) bandwidth
+
+
+ 10
+
+
+ 1
+
+
+ 6
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+
+ 50
+ 0
+
+
+
+ 12.5kHz
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+
+
+ -
+
+
-
+
+
+ AF BW
+
+
+
+ -
+
+
+ Audio bandwidth
+
+
+ 1
+
+
+ 20
+
+
+ 1
+
+
+ 3
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+
+ 50
+ 0
+
+
+
+ 3 kHz
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+
+
+ -
+
+
-
+
+
+ Mod%
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+ Modulation percentage
+
+
+ 100
+
+
+ 1
+
+
+ 20
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+
+ 30
+ 0
+
+
+
+ 20
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
+
+ 24
+ 24
+
+
+
+ Audio input volume
+
+
+ 100
+
+
+ 1
+
+
+ 50
+
+
+
+ -
+
+
+
+ 30
+ 0
+
+
+
+ Audio input volume level
+
+
+
+
+
+ 50
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+
+
+ -
+
+
-
+
+
+ Tone modulation (1 kHz)
+
+
+ ...
+
+
+
+ :/carrier.png:/carrier.png
+
+
+
+ -
+
+
+ Audio input
+
+
+ ...
+
+
+
+ :/microphone.png:/microphone.png
+
+
+ true
+
+
+
+ -
+
+
+
+ 24
+ 24
+
+
+
+
+ 24
+ 24
+
+
+
+ Open record file (48 kHz 32 bit float LE mono)
+
+
+
+
+
+
+ :/preset-load.png:/preset-load.png
+
+
+
+ -
+
+
+ ...
+
+
+
+
+
+ -
+
+
-
+
+
+ Play record file in a loop
+
+
+ ...
+
+
+
+ :/playloop.png:/playloop.png
+
+
+
+ -
+
+
+ Record file play/pause
+
+
+ ...
+
+
+
+ :/play.png
+ :/pause.png
+ :/play.png
+ :/play.png:/play.png
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
+ false
+
+
+
+ 90
+ 0
+
+
+
+ Record time from start
+
+
+ 00:00:00.000
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
+ false
+
+
+
+ 60
+ 0
+
+
+
+ Total record time
+
+
+ 00:00:00
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+
+
+ -
+
+
-
+
+
+ Record file time navigator
+
+
+ 100
+
+
+ 1
+
+
+ Qt::Horizontal
+
+
+
+
+
+
+
+
+
+
+ RollupWidget
+ QWidget
+
+ 1
+
+
+ ValueDial
+ QWidget
+
+ 1
+
+
+ ButtonSwitch
+ QToolButton
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/channeltx/modnfm/nfmmodplugin.cpp b/plugins/channeltx/modnfm/nfmmodplugin.cpp
new file mode 100644
index 000000000..5178e7fee
--- /dev/null
+++ b/plugins/channeltx/modnfm/nfmmodplugin.cpp
@@ -0,0 +1,65 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2016 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 "plugin/pluginapi.h"
+
+#include "nfmmodgui.h"
+#include "nfmmodplugin.h"
+
+const PluginDescriptor NFMModPlugin::m_pluginDescriptor = {
+ QString("NFM Modulator"),
+ QString("2.4.0"),
+ QString("(c) Edouard Griffiths, F4EXB"),
+ QString("https://github.com/f4exb/sdrangel"),
+ true,
+ QString("https://github.com/f4exb/sdrangel")
+};
+
+NFMModPlugin::NFMModPlugin(QObject* parent) :
+ QObject(parent)
+{
+}
+
+const PluginDescriptor& NFMModPlugin::getPluginDescriptor() const
+{
+ return m_pluginDescriptor;
+}
+
+void NFMModPlugin::initPlugin(PluginAPI* pluginAPI)
+{
+ m_pluginAPI = pluginAPI;
+
+ // register AM modulator
+ m_pluginAPI->registerTxChannel(NFMModGUI::m_channelID, this);
+}
+
+PluginGUI* NFMModPlugin::createTxChannel(const QString& channelName, DeviceSinkAPI *deviceAPI)
+{
+ if(channelName == NFMModGUI::m_channelID)
+ {
+ NFMModGUI* gui = NFMModGUI::create(m_pluginAPI, deviceAPI);
+ return gui;
+ } else {
+ return 0;
+ }
+}
+
+void NFMModPlugin::createInstanceModNFM(DeviceSinkAPI *deviceAPI)
+{
+ NFMModGUI* gui = NFMModGUI::create(m_pluginAPI, deviceAPI);
+}
diff --git a/plugins/channeltx/modnfm/nfmmodplugin.h b/plugins/channeltx/modnfm/nfmmodplugin.h
new file mode 100644
index 000000000..79d8d9438
--- /dev/null
+++ b/plugins/channeltx/modnfm/nfmmodplugin.h
@@ -0,0 +1,47 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2016 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_NFMMODPLUGIN_H
+#define INCLUDE_NFMMODPLUGIN_H
+
+#include
+#include "plugin/plugininterface.h"
+
+class DeviceSinkAPI;
+
+class NFMModPlugin : public QObject, PluginInterface {
+ Q_OBJECT
+ Q_INTERFACES(PluginInterface)
+ Q_PLUGIN_METADATA(IID "sdrangel.channeltx.nfmmod")
+
+public:
+ explicit NFMModPlugin(QObject* parent = NULL);
+
+ const PluginDescriptor& getPluginDescriptor() const;
+ void initPlugin(PluginAPI* pluginAPI);
+
+ PluginGUI* createTxChannel(const QString& channelName, DeviceSinkAPI *deviceAPI);
+
+private:
+ static const PluginDescriptor m_pluginDescriptor;
+
+ PluginAPI* m_pluginAPI;
+
+private slots:
+ void createInstanceModNFM(DeviceSinkAPI *deviceAPI);
+};
+
+#endif // INCLUDE_NFMMODPLUGIN_H