diff --git a/plugins/channeltx/CMakeLists.txt b/plugins/channeltx/CMakeLists.txt
index c43165da7..f2ea8f308 100644
--- a/plugins/channeltx/CMakeLists.txt
+++ b/plugins/channeltx/CMakeLists.txt
@@ -2,3 +2,4 @@ project(mod)
add_subdirectory(modam)
add_subdirectory(modnfm)
+add_subdirectory(modssb)
diff --git a/plugins/channeltx/modam/ammod.cpp b/plugins/channeltx/modam/ammod.cpp
index 852b4fedf..ef555ca07 100644
--- a/plugins/channeltx/modam/ammod.cpp
+++ b/plugins/channeltx/modam/ammod.cpp
@@ -232,8 +232,6 @@ void AMMod::stop()
bool AMMod::handleMessage(const Message& cmd)
{
- qDebug() << "AMMod::handleMessage";
-
if (UpChannelizer::MsgChannelizerNotification::match(cmd))
{
UpChannelizer::MsgChannelizerNotification& notif = (UpChannelizer::MsgChannelizerNotification&) cmd;
diff --git a/plugins/channeltx/modssb/CMakeLists.txt b/plugins/channeltx/modssb/CMakeLists.txt
new file mode 100644
index 000000000..ed5e4f80c
--- /dev/null
+++ b/plugins/channeltx/modssb/CMakeLists.txt
@@ -0,0 +1,43 @@
+project(modssb)
+
+set(modssb_SOURCES
+ ssbmod.cpp
+ ssbmodgui.cpp
+ ssbmodplugin.cpp
+)
+
+set(modssb_HEADERS
+ ssbmod.h
+ ssbmodgui.h
+ ssbmodplugin.h
+)
+
+set(modssb_FORMS
+ ssbmodgui.ui
+)
+
+include_directories(
+ .
+ ${CMAKE_CURRENT_BINARY_DIR}
+)
+
+add_definitions(${QT_DEFINITIONS})
+add_definitions(-DQT_PLUGIN)
+add_definitions(-DQT_SHARED)
+
+qt5_wrap_ui(modssb_FORMS_HEADERS ${modssb_FORMS})
+
+add_library(modssb SHARED
+ ${modssb_SOURCES}
+ ${modssb_HEADERS_MOC}
+ ${modssb_FORMS_HEADERS}
+)
+
+target_link_libraries(modssb
+ ${QT_LIBRARIES}
+ sdrbase
+)
+
+qt5_use_modules(modssb Core Widgets)
+
+install(TARGETS modssb DESTINATION lib/plugins/channeltx)
\ No newline at end of file
diff --git a/plugins/channeltx/modssb/ssbmod.cpp b/plugins/channeltx/modssb/ssbmod.cpp
new file mode 100644
index 000000000..c21fa8d69
--- /dev/null
+++ b/plugins/channeltx/modssb/ssbmod.cpp
@@ -0,0 +1,392 @@
+///////////////////////////////////////////////////////////////////////////////////
+// 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 "ssbmod.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include "dsp/dspengine.h"
+#include "dsp/pidcontroller.h"
+
+MESSAGE_CLASS_DEFINITION(SSBMod::MsgConfigureSSBMod, Message)
+MESSAGE_CLASS_DEFINITION(SSBMod::MsgConfigureFileSourceName, Message)
+MESSAGE_CLASS_DEFINITION(SSBMod::MsgConfigureFileSourceSeek, Message)
+MESSAGE_CLASS_DEFINITION(SSBMod::MsgConfigureAFInput, Message)
+MESSAGE_CLASS_DEFINITION(SSBMod::MsgConfigureFileSourceStreamTiming, Message)
+MESSAGE_CLASS_DEFINITION(SSBMod::MsgReportFileSourceStreamData, Message)
+MESSAGE_CLASS_DEFINITION(SSBMod::MsgReportFileSourceStreamTiming, Message)
+
+const int SSBMod::m_levelNbSamples = 480; // every 10ms
+
+SSBMod::SSBMod() :
+ m_audioFifo(4, 48000),
+ m_settingsMutex(QMutex::Recursive),
+ m_fileSize(0),
+ m_recordLength(0),
+ m_sampleRate(48000),
+ m_afInput(SSBModInputNone),
+ m_levelCalcCount(0),
+ m_peakLevel(0.0f),
+ m_levelSum(0.0f)
+{
+ setObjectName("SSBMod");
+
+ m_config.m_outputSampleRate = 48000;
+ m_config.m_inputFrequencyOffset = 0;
+ m_config.m_rfBandwidth = 12500;
+ m_config.m_toneFrequency = 1000.0f;
+ 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);
+
+ // CW keyer
+ m_cwKeyer.setSampleRate(m_config.m_audioSampleRate);
+ m_cwKeyer.setWPM(13);
+ m_cwKeyer.setMode(CWKeyer::CWNone);
+}
+
+SSBMod::~SSBMod()
+{
+ DSPEngine::instance()->removeAudioSource(&m_audioFifo);
+}
+
+void SSBMod::configure(MessageQueue* messageQueue,
+ Real rfBandwidth,
+ float toneFrequency,
+ float volumeFactor,
+ bool audioMute,
+ bool playLoop)
+{
+ Message* cmd = MsgConfigureSSBMod::create(rfBandwidth, toneFrequency, volumeFactor, audioMute, playLoop);
+ messageQueue->push(cmd);
+}
+
+void SSBMod::pull(Sample& sample)
+{
+ Complex ci;
+
+ m_settingsMutex.lock();
+
+ if (m_interpolatorDistance > 1.0f) // decimate
+ {
+ modulateSample();
+
+ while (!m_interpolator.decimate(&m_interpolatorDistanceRemain, m_modSample, &ci))
+ {
+ modulateSample();
+ }
+ }
+ else
+ {
+ if (m_interpolator.interpolate(&m_interpolatorDistanceRemain, m_modSample, &ci))
+ {
+ modulateSample();
+ }
+ }
+
+ 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 SSBMod::modulateSample()
+{
+ Real t;
+
+ pullAF(t);
+ calculateLevel(t);
+
+ m_modSample.real(0.0f); // TOOO
+ m_modSample.imag(0.0f);
+}
+
+void SSBMod::pullAF(Real& sample)
+{
+ int16_t audioSample[2];
+
+ switch (m_afInput)
+ {
+ case SSBModInputTone:
+ sample = m_toneNco.next();
+ break;
+ case SSBModInputFile:
+ // 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));
+ sample *= m_running.m_volumeFactor;
+ }
+ }
+ else
+ {
+ sample = 0.0f;
+ }
+ break;
+ case SSBModInputAudio:
+ m_audioFifo.read(reinterpret_cast(audioSample), 1, 10);
+ sample = ((audioSample[0] + audioSample[1]) / 65536.0f) * m_running.m_volumeFactor;
+ break;
+ case SSBModInputCWTone:
+ if (m_cwKeyer.getSample())
+ {
+ sample = m_toneNco.next();
+ }
+ else
+ {
+ sample = 0.0f;
+ m_toneNco.setPhase(0);
+ }
+ break;
+ case SSBModInputNone:
+ default:
+ sample = 0.0f;
+ break;
+ }
+}
+
+void SSBMod::calculateLevel(Real& sample)
+{
+ if (m_levelCalcCount < m_levelNbSamples)
+ {
+ m_peakLevel = std::max(std::fabs(m_peakLevel), sample);
+ m_levelSum += sample * sample;
+ m_levelCalcCount++;
+ }
+ else
+ {
+ qreal rmsLevel = sqrt(m_levelSum / m_levelNbSamples);
+ //qDebug("NFMMod::calculateLevel: %f %f", rmsLevel, m_peakLevel);
+ emit levelChanged(rmsLevel, m_peakLevel, m_levelNbSamples);
+ m_peakLevel = 0.0f;
+ m_levelSum = 0.0f;
+ m_levelCalcCount = 0;
+ }
+}
+
+void SSBMod::start()
+{
+ qDebug() << "SSBMod::start: m_outputSampleRate: " << m_config.m_outputSampleRate
+ << " m_inputFrequencyOffset: " << m_config.m_inputFrequencyOffset;
+
+ m_audioFifo.clear();
+}
+
+void SSBMod::stop()
+{
+}
+
+bool SSBMod::handleMessage(const Message& cmd)
+{
+ 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() << "SSBMod::handleMessage: MsgChannelizerNotification:"
+ << " m_outputSampleRate: " << m_config.m_outputSampleRate
+ << " m_inputFrequencyOffset: " << m_config.m_inputFrequencyOffset;
+
+ return true;
+ }
+ else if (MsgConfigureSSBMod::match(cmd))
+ {
+ MsgConfigureSSBMod& cfg = (MsgConfigureSSBMod&) cmd;
+
+ m_config.m_rfBandwidth = cfg.getRFBandwidth();
+ m_config.m_toneFrequency = cfg.getToneFrequency();
+ m_config.m_volumeFactor = cfg.getVolumeFactor();
+ m_config.m_audioMute = cfg.getAudioMute();
+ m_config.m_playLoop = cfg.getPlayLoop();
+
+ apply();
+
+ qDebug() << "SSBMod::handleMessage: MsgConfigureSSBMod:"
+ << " m_rfBandwidth: " << m_config.m_rfBandwidth
+ << " m_toneFrequency: " << m_config.m_toneFrequency
+ << " 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 SSBMod::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_config.m_audioSampleRate != m_running.m_audioSampleRate))
+ {
+ 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_toneFrequency != m_running.m_toneFrequency) ||
+ (m_config.m_audioSampleRate != m_running.m_audioSampleRate))
+ {
+ m_settingsMutex.lock();
+ m_toneNco.setFreq(m_config.m_toneFrequency, m_config.m_audioSampleRate);
+ m_settingsMutex.unlock();
+ }
+
+ if (m_config.m_audioSampleRate != m_running.m_audioSampleRate)
+ {
+ m_cwKeyer.setSampleRate(m_config.m_audioSampleRate);
+ }
+
+ 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_toneFrequency = m_config.m_toneFrequency;
+ 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 SSBMod::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 SSBMod::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/modssb/ssbmod.h b/plugins/channeltx/modssb/ssbmod.h
new file mode 100644
index 000000000..787823a71
--- /dev/null
+++ b/plugins/channeltx/modssb/ssbmod.h
@@ -0,0 +1,320 @@
+///////////////////////////////////////////////////////////////////////////////////
+// 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_MODSSB_SSBMOD_H_
+#define PLUGINS_CHANNELTX_MODSSB_SSBMOD_H_
+
+#include
+#include
+#include
+#include
+
+#include "dsp/basebandsamplesource.h"
+#include "dsp/nco.h"
+#include "dsp/interpolator.h"
+#include "dsp/movingaverage.h"
+#include "dsp/agc.h"
+#include "dsp/cwkeyer.h"
+#include "audio/audiofifo.h"
+#include "util/message.h"
+
+class SSBMod : public BasebandSampleSource {
+ Q_OBJECT
+
+public:
+ typedef enum
+ {
+ SSBModInputNone,
+ SSBModInputTone,
+ SSBModInputFile,
+ SSBModInputAudio,
+ SSBModInputCWTone
+ } SSBModInputAF;
+
+ 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:
+ SSBModInputAF getAFInput() const { return m_afInput; }
+
+ static MsgConfigureAFInput* create(SSBModInputAF afInput)
+ {
+ return new MsgConfigureAFInput(afInput);
+ }
+
+ private:
+ SSBModInputAF m_afInput;
+
+ MsgConfigureAFInput(SSBModInputAF 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)
+ { }
+ };
+
+ //=================================================================
+
+ SSBMod();
+ ~SSBMod();
+
+ void configure(MessageQueue* messageQueue,
+ Real rfBandwidth,
+ float toneFrequency,
+ float 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; }
+
+ CWKeyer *getCWKeyer() { return &m_cwKeyer; }
+
+signals:
+ /**
+ * Level changed
+ * \param rmsLevel RMS level in range 0.0 - 1.0
+ * \param peakLevel Peak level in range 0.0 - 1.0
+ * \param numSamples Number of audio samples analyzed
+ */
+ void levelChanged(qreal rmsLevel, qreal peakLevel, int numSamples);
+
+
+private:
+ class MsgConfigureSSBMod : public Message
+ {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ Real getRFBandwidth() const { return m_rfBandwidth; }
+ float getToneFrequency() const { return m_toneFrequency; }
+ float getVolumeFactor() const { return m_volumeFactor; }
+ bool getAudioMute() const { return m_audioMute; }
+ bool getPlayLoop() const { return m_playLoop; }
+
+ static MsgConfigureSSBMod* create(Real rfBandwidth, float toneFreqeuncy, float volumeFactor, bool audioMute, bool playLoop)
+ {
+ return new MsgConfigureSSBMod(rfBandwidth, toneFreqeuncy, volumeFactor, audioMute, playLoop);
+ }
+
+ private:
+ Real m_rfBandwidth;
+ float m_toneFrequency;
+ float m_volumeFactor;
+ bool m_audioMute;
+ bool m_playLoop;
+
+ MsgConfigureSSBMod(Real rfBandwidth, float toneFrequency, float volumeFactor, bool audioMute, bool playLoop) :
+ Message(),
+ m_rfBandwidth(rfBandwidth),
+ m_toneFrequency(toneFrequency),
+ 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;
+ float m_toneFrequency;
+ float m_volumeFactor;
+ quint32 m_audioSampleRate;
+ bool m_audioMute;
+ bool m_playLoop;
+
+ Config() :
+ m_outputSampleRate(-1),
+ m_inputFrequencyOffset(0),
+ m_rfBandwidth(-1),
+ m_toneFrequency(100),
+ m_volumeFactor(1.0f),
+ 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;
+
+ 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;
+
+ SSBModInputAF m_afInput;
+ quint32 m_levelCalcCount;
+ Real m_peakLevel;
+ Real m_levelSum;
+ CWKeyer m_cwKeyer;
+
+ static const int m_levelNbSamples;
+
+ void apply();
+ void pullAF(Real& sample);
+ void calculateLevel(Real& sample);
+ void modulateSample();
+ void openFileStream();
+ void seekFileStream(int seekPercentage);
+};
+
+
+#endif /* PLUGINS_CHANNELTX_MODSSB_SSBMOD_H_ */
diff --git a/plugins/channeltx/modssb/ssbmodgui.cpp b/plugins/channeltx/modssb/ssbmodgui.cpp
new file mode 100644
index 000000000..e0c98d674
--- /dev/null
+++ b/plugins/channeltx/modssb/ssbmodgui.cpp
@@ -0,0 +1,498 @@
+///////////////////////////////////////////////////////////////////////////////////
+// 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 "ssbmodgui.h"
+
+#include "device/devicesinkapi.h"
+#include "dsp/upchannelizer.h"
+
+#include "dsp/threadedbasebandsamplesource.h"
+#include "ui_ssbmodgui.h"
+#include "plugin/pluginapi.h"
+#include "util/simpleserializer.h"
+#include "util/db.h"
+#include "gui/basicchannelsettingswidget.h"
+#include "dsp/dspengine.h"
+#include "mainwindow.h"
+
+const QString SSBModGUI::m_channelID = "sdrangel.channeltx.modssb";
+
+SSBModGUI* SSBModGUI::create(PluginAPI* pluginAPI, DeviceSinkAPI *deviceAPI)
+{
+ SSBModGUI* gui = new SSBModGUI(pluginAPI, deviceAPI);
+ return gui;
+}
+
+void SSBModGUI::destroy()
+{
+}
+
+void SSBModGUI::setName(const QString& name)
+{
+ setObjectName(name);
+}
+
+QString SSBModGUI::getName() const
+{
+ return objectName();
+}
+
+qint64 SSBModGUI::getCenterFrequency() const {
+ return m_channelMarker.getCenterFrequency();
+}
+
+void SSBModGUI::setCenterFrequency(qint64 centerFrequency)
+{
+ m_channelMarker.setCenterFrequency(centerFrequency);
+ applySettings();
+}
+
+void SSBModGUI::resetToDefaults()
+{
+ blockApplySettings(true);
+
+ ui->BW->setValue(30);
+ ui->lowCut->setValue(3);
+ ui->spanLog2->setValue(3);
+ ui->toneFrequency->setValue(100);
+ ui->deltaFrequency->setValue(0);
+ m_audioBinaural = false;
+ m_audioFlipChannels = false;
+ m_dsb = false;
+
+ blockApplySettings(false);
+ applySettings();
+}
+
+QByteArray SSBModGUI::serialize() const
+{
+ SimpleSerializer s(1);
+
+ s.writeS32(1, m_channelMarker.getCenterFrequency());
+ s.writeS32(2, ui->BW->value());
+ s.writeS32(3, ui->toneFrequency->value());
+ s.writeBlob(4, ui->spectrumGUI->serialize());
+ s.writeU32(5, m_channelMarker.getColor().rgb());
+ s.writeBlob(6, ui->cwKeyerGUI->serialize());
+ s.writeS32(7, ui->lowCut->value());
+ s.writeS32(8, ui->spanLog2->value());
+ s.writeBool(9, m_audioBinaural);
+ s.writeBool(10, m_audioFlipChannels);
+ s.writeBool(11, m_dsb);
+
+ return s.final();
+}
+
+bool SSBModGUI::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->BW->setValue(tmp);
+ d.readS32(3, &tmp, 100);
+ ui->toneFrequency->setValue(tmp);
+ d.readBlob(4, &bytetmp);
+ ui->spectrumGUI->deserialize(bytetmp);
+
+ if(d.readU32(5, &u32tmp))
+ {
+ m_channelMarker.setColor(u32tmp);
+ }
+
+ d.readBlob(6, &bytetmp);
+ ui->cwKeyerGUI->deserialize(bytetmp);
+
+ d.readS32(7, &tmp, 3);
+ ui->lowCut->setValue(tmp);
+ d.readS32(8, &tmp, 20);
+ ui->spanLog2->setValue(tmp);
+ setNewRate(tmp);
+ d.readBool(9, &m_audioBinaural);
+ ui->audioBinaural->setChecked(m_audioBinaural);
+ d.readBool(10, &m_audioFlipChannels);
+ ui->audioFlipChannels->setChecked(m_audioFlipChannels);
+ d.readBool(11, &m_dsb);
+ ui->dsb->setChecked(m_dsb);
+
+ blockApplySettings(false);
+ m_channelMarker.blockSignals(false);
+
+ applySettings();
+ return true;
+ }
+ else
+ {
+ resetToDefaults();
+ return false;
+ }
+}
+
+bool SSBModGUI::handleMessage(const Message& message)
+{
+ if (SSBMod::MsgReportFileSourceStreamData::match(message))
+ {
+ m_recordSampleRate = ((SSBMod::MsgReportFileSourceStreamData&)message).getSampleRate();
+ m_recordLength = ((SSBMod::MsgReportFileSourceStreamData&)message).getRecordLength();
+ m_samplesCount = 0;
+ updateWithStreamData();
+ return true;
+ }
+ else if (SSBMod::MsgReportFileSourceStreamTiming::match(message))
+ {
+ m_samplesCount = ((SSBMod::MsgReportFileSourceStreamTiming&)message).getSamplesCount();
+ updateWithStreamTime();
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+void SSBModGUI::viewChanged()
+{
+ applySettings();
+}
+
+void SSBModGUI::handleSourceMessages()
+{
+ Message* message;
+
+ while ((message = m_ssbMod->getOutputMessageQueue()->pop()) != 0)
+ {
+ if (handleMessage(*message))
+ {
+ delete message;
+ }
+ }
+}
+
+void SSBModGUI::on_deltaMinus_toggled(bool minus)
+{
+ int deltaFrequency = m_channelMarker.getCenterFrequency();
+ bool minusDelta = (deltaFrequency < 0);
+
+ if (minus ^ minusDelta) // sign change
+ {
+ m_channelMarker.setCenterFrequency(-deltaFrequency);
+ }
+}
+
+void SSBModGUI::on_deltaFrequency_changed(quint64 value)
+{
+ if (ui->deltaMinus->isChecked()) {
+ m_channelMarker.setCenterFrequency(-value);
+ } else {
+ m_channelMarker.setCenterFrequency(value);
+ }
+}
+
+void SSBModGUI::on_BW_valueChanged(int value)
+{
+ ui->BWText->setText(QString("%1 kHz").arg(value / 10.0, 0, 'f', 1));
+ m_channelMarker.setBandwidth(value * 100);
+ applySettings();
+}
+
+void SSBModGUI::on_toneFrequency_valueChanged(int value)
+{
+ ui->toneFrequencyText->setText(QString("%1k").arg(value / 100.0, 0, 'f', 2));
+ applySettings();
+}
+
+void SSBModGUI::on_volume_valueChanged(int value)
+{
+ ui->volumeText->setText(QString("%1").arg(value / 10.0, 0, 'f', 1));
+ applySettings();
+}
+
+void SSBModGUI::on_audioMute_toggled(bool checked)
+{
+ applySettings();
+}
+
+void SSBModGUI::on_playLoop_toggled(bool checked)
+{
+ applySettings();
+}
+
+void SSBModGUI::on_play_toggled(bool checked)
+{
+ ui->tone->setEnabled(!checked); // release other source inputs
+ ui->morseKeyer->setEnabled(!checked);
+ ui->mic->setEnabled(!checked);
+ m_modAFInput = checked ? SSBMod::SSBModInputFile : SSBMod::SSBModInputNone;
+ SSBMod::MsgConfigureAFInput* message = SSBMod::MsgConfigureAFInput::create(m_modAFInput);
+ m_ssbMod->getInputMessageQueue()->push(message);
+ ui->navTimeSlider->setEnabled(!checked);
+ m_enableNavTime = !checked;
+}
+
+void SSBModGUI::on_tone_toggled(bool checked)
+{
+ ui->play->setEnabled(!checked); // release other source inputs
+ ui->morseKeyer->setEnabled(!checked);
+ ui->mic->setEnabled(!checked);
+ m_modAFInput = checked ? SSBMod::SSBModInputTone : SSBMod::SSBModInputNone;
+ SSBMod::MsgConfigureAFInput* message = SSBMod::MsgConfigureAFInput::create(m_modAFInput);
+ m_ssbMod->getInputMessageQueue()->push(message);
+}
+
+void SSBModGUI::on_morseKeyer_toggled(bool checked)
+{
+ ui->play->setEnabled(!checked); // release other source inputs
+ ui->tone->setEnabled(!checked); // release other source inputs
+ ui->mic->setEnabled(!checked);
+ m_modAFInput = checked ? SSBMod::SSBModInputCWTone : SSBMod::SSBModInputNone;
+ SSBMod::MsgConfigureAFInput* message = SSBMod::MsgConfigureAFInput::create(m_modAFInput);
+ m_ssbMod->getInputMessageQueue()->push(message);
+}
+
+void SSBModGUI::on_mic_toggled(bool checked)
+{
+ ui->play->setEnabled(!checked); // release other source inputs
+ ui->morseKeyer->setEnabled(!checked);
+ ui->tone->setEnabled(!checked); // release other source inputs
+ m_modAFInput = checked ? SSBMod::SSBModInputAudio : SSBMod::SSBModInputNone;
+ SSBMod::MsgConfigureAFInput* message = SSBMod::MsgConfigureAFInput::create(m_modAFInput);
+ m_ssbMod->getInputMessageQueue()->push(message);
+}
+
+void SSBModGUI::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);
+
+ SSBMod::MsgConfigureFileSourceSeek* message = SSBMod::MsgConfigureFileSourceSeek::create(value);
+ m_ssbMod->getInputMessageQueue()->push(message);
+ }
+}
+
+void SSBModGUI::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 SSBModGUI::configureFileName()
+{
+ qDebug() << "FileSourceGui::configureFileName: " << m_fileName.toStdString().c_str();
+ SSBMod::MsgConfigureFileSourceName* message = SSBMod::MsgConfigureFileSourceName::create(m_fileName);
+ m_ssbMod->getInputMessageQueue()->push(message);
+}
+
+void SSBModGUI::onWidgetRolled(QWidget* widget, bool rollDown)
+{
+}
+
+void SSBModGUI::onMenuDoubleClicked()
+{
+ if(!m_basicSettingsShown) {
+ m_basicSettingsShown = true;
+ BasicChannelSettingsWidget* bcsw = new BasicChannelSettingsWidget(&m_channelMarker, this);
+ bcsw->show();
+ }
+}
+
+SSBModGUI::SSBModGUI(PluginAPI* pluginAPI, DeviceSinkAPI *deviceAPI, QWidget* parent) :
+ RollupWidget(parent),
+ ui(new Ui::SSBModGUI),
+ 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(SSBMod::SSBModInputNone)
+{
+ 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_ssbMod = new SSBMod();
+ m_channelizer = new UpChannelizer(m_ssbMod);
+ 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::green);
+ m_channelMarker.setBandwidth(5000);
+ 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->morseKeyer->setChecked(false);
+ ui->mic->setChecked(false);
+
+ ui->cwKeyerGUI->setBuddies(m_ssbMod->getInputMessageQueue(), m_ssbMod->getCWKeyer());
+
+ applySettings();
+
+ connect(m_ssbMod->getOutputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages()));
+ connect(m_ssbMod, SIGNAL(levelChanged(qreal, qreal, int)), ui->volumeMeter, SLOT(levelChanged(qreal, qreal, int)));
+}
+
+SSBModGUI::~SSBModGUI()
+{
+ m_deviceAPI->removeChannelInstance(this);
+ m_deviceAPI->removeThreadedSource(m_threadedChannelizer);
+ delete m_threadedChannelizer;
+ delete m_channelizer;
+ delete m_ssbMod;
+ //delete m_channelMarker;
+ delete ui;
+}
+
+void SSBModGUI::blockApplySettings(bool block)
+{
+ m_doApplySettings = !block;
+}
+
+void SSBModGUI::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_ssbMod->configure(m_ssbMod->getInputMessageQueue(),
+ ui->BW->value() * 100.0,
+ ui->toneFrequency->value() * 10.0f,
+ ui->volume->value() / 10.0f ,
+ ui->audioMute->isChecked(),
+ ui->playLoop->isChecked());
+ }
+}
+
+void SSBModGUI::leaveEvent(QEvent*)
+{
+ blockApplySettings(true);
+ m_channelMarker.setHighlighted(false);
+ blockApplySettings(false);
+}
+
+void SSBModGUI::enterEvent(QEvent*)
+{
+ blockApplySettings(true);
+ m_channelMarker.setHighlighted(true);
+ blockApplySettings(false);
+}
+
+void SSBModGUI::tick()
+{
+ Real powDb = CalcDb::dbPower(m_ssbMod->getMagSq());
+ m_channelPowerDbAvg.feed(powDb);
+ ui->channelPower->setText(QString::number(m_channelPowerDbAvg.average(), 'f', 1));
+
+ if (((++m_tickCount & 0xf) == 0) && (m_modAFInput == SSBMod::SSBModInputFile))
+ {
+ SSBMod::MsgConfigureFileSourceStreamTiming* message = SSBMod::MsgConfigureFileSourceStreamTiming::create();
+ m_ssbMod->getInputMessageQueue()->push(message);
+ }
+}
+
+void SSBModGUI::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 SSBModGUI::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/modssb/ssbmodgui.h b/plugins/channeltx/modssb/ssbmodgui.h
new file mode 100644
index 000000000..b87e2e99b
--- /dev/null
+++ b/plugins/channeltx/modssb/ssbmodgui.h
@@ -0,0 +1,121 @@
+///////////////////////////////////////////////////////////////////////////////////
+// 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_MODSSB_SSBMODGUI_H_
+#define PLUGINS_CHANNELTX_MODSSB_SSBMODGUI_H_
+
+#include "gui/rollupwidget.h"
+#include "plugin/plugingui.h"
+#include "dsp/channelmarker.h"
+#include "dsp/movingaverage.h"
+#include "ssbmod.h"
+
+class PluginAPI;
+class DeviceSinkAPI;
+
+class ThreadedBasebandSampleSource;
+class UpChannelizer;
+class SSBMod;
+
+namespace Ui {
+ class SSBModGUI;
+}
+
+class SSBModGUI : public RollupWidget, public PluginGUI {
+ Q_OBJECT
+
+public:
+ static SSBModGUI* 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_BW_valueChanged(int value);
+ void on_volume_valueChanged(int value);
+ void on_audioMute_toggled(bool checked);
+ void on_tone_toggled(bool checked);
+ void on_toneFrequency_valueChanged(int value);
+ void on_mic_toggled(bool checked);
+ void on_play_toggled(bool checked);
+ void on_morseKeyer_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::SSBModGUI* ui;
+ PluginAPI* m_pluginAPI;
+ DeviceSinkAPI* m_deviceAPI;
+ ChannelMarker m_channelMarker;
+ bool m_basicSettingsShown;
+ bool m_doApplySettings;
+
+ ThreadedBasebandSampleSource* m_threadedChannelizer;
+ UpChannelizer* m_channelizer;
+ SSBMod* m_ssbMod;
+ MovingAverage m_channelPowerDbAvg;
+
+ QString m_fileName;
+ quint32 m_recordLength;
+ int m_recordSampleRate;
+ int m_samplesCount;
+ std::size_t m_tickCount;
+ bool m_enableNavTime;
+ SSBMod::SSBModInputAF m_modAFInput;
+
+ bool m_audioBinaural;
+ bool m_audioFlipChannels;
+ bool m_dsb;
+
+ explicit SSBModGUI(PluginAPI* pluginAPI, DeviceSinkAPI *deviceAPI, QWidget* parent = NULL);
+ virtual ~SSBModGUI();
+
+ bool setNewRate(int spanLog2);
+
+ void blockApplySettings(bool block);
+ void applySettings();
+ void updateWithStreamData();
+ void updateWithStreamTime();
+
+ void leaveEvent(QEvent*);
+ void enterEvent(QEvent*);
+};
+
+#endif /* PLUGINS_CHANNELTX_MODSSB_SSBMODGUI_H_ */
diff --git a/plugins/channeltx/modssb/ssbmodgui.ui b/plugins/channeltx/modssb/ssbmodgui.ui
new file mode 100644
index 000000000..003e9a15c
--- /dev/null
+++ b/plugins/channeltx/modssb/ssbmodgui.ui
@@ -0,0 +1,851 @@
+
+
+ SSBModGUI
+
+
+
+ 0
+ 0
+ 370
+ 643
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 370
+ 0
+
+
+
+
+ Sans Serif
+ 9
+
+
+
+ SSB Modulator
+
+
+
+
+ 0
+ 0
+ 360
+ 331
+
+
+
+
+ 360
+ 0
+
+
+
+ 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
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
+ Toggle btw Mono and Binaural I/Q audio
+
+
+ ...
+
+
+
+ :/mono.png
+ :/stereo.png:/mono.png
+
+
+ true
+
+
+
+ -
+
+
+ Flip left/right audio channels
+
+
+ ...
+
+
+
+ :/flip_lr.png
+ :/flip_rl.png:/flip_lr.png
+
+
+ true
+
+
+
+ -
+
+
+ DSB/SSB toggle
+
+
+ ...
+
+
+
+ :/usb.png
+ :/dsb.png:/usb.png
+
+
+ true
+
+
+
+
+
+
+
+ -
+
+
-
+
+
+ Span
+
+
+
+ -
+
+
+ Spectrum display frequency span
+
+
+ 1
+
+
+ 5
+
+
+ 1
+
+
+ 3
+
+
+ 3
+
+
+ Qt::Horizontal
+
+
+ true
+
+
+ true
+
+
+
+ -
+
+
+ 6.0k
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+
+
+ -
+
+
-
+
+
+ BW
+
+
+
+ -
+
+
+ Lowpass filter cutoff frequency
+
+
+ -60
+
+
+ 60
+
+
+ 1
+
+
+ 30
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+
+ 50
+ 0
+
+
+
+ 3.0k
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+
+
+ -
+
+
-
+
+
+ Low cut
+
+
+
+ -
+
+
+ Highpass filter cutoff frequency (SSB)
+
+
+ -60
+
+
+ 60
+
+
+ 1
+
+
+ 3
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+
+ 50
+ 0
+
+
+
+ 0.3k
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+
+
+ -
+
+
-
+
+
+ Vol
+
+
+
+ -
+
+
+
+ 24
+ 24
+
+
+
+ Audio input gain
+
+
+ 100
+
+
+ 1
+
+
+ 10
+
+
+
+ -
+
+
+ Mute/Unmute audio
+
+
+
+
+
+
+ :/sound_on.png
+ :/sound_off.png:/sound_on.png
+
+
+ true
+
+
+
+ -
+
+
+
+ 25
+ 0
+
+
+
+ Audio input gain value
+
+
+
+
+
+ 1.0
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Level (% full range) top trace: average, bottom trace: instantaneous peak, tip: peak hold
+
+
+
+
+
+ -
+
+
-
+
+
+ Tone modulation (1 kHz)
+
+
+ ...
+
+
+
+ :/carrier.png:/carrier.png
+
+
+
+ -
+
+
+ Morse keyer at tone frequency
+
+
+ ...
+
+
+
+ :/morsekey.png:/morsekey.png
+
+
+
+ -
+
+
+
+ 24
+ 24
+
+
+
+ Tone frequency
+
+
+ 10
+
+
+ 250
+
+
+ 1
+
+
+ 100
+
+
+
+ -
+
+
+
+ 36
+ 0
+
+
+
+ Tone frequency (kHz)
+
+
+ 1.00k
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
+ Audio input
+
+
+ ...
+
+
+
+ :/microphone.png:/microphone.png
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
-
+
+
+
+
+ -
+
+
-
+
+
+ ...
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 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
+
+
+
+
+
+
+
+
+
+
+ 0
+ 340
+ 351
+ 284
+
+
+
+ Channel Spectrum
+
+
+
+ 2
+
+
+ 3
+
+ -
+
+
+
+ 200
+ 250
+
+
+
+
+ Monospace
+ 8
+
+
+
+
+ -
+
+
+
+
+
+
+
+ RollupWidget
+ QWidget
+
+ 1
+
+
+ ValueDial
+ QWidget
+
+ 1
+
+
+ GLSpectrum
+ QWidget
+
+ 1
+
+
+ GLSpectrumGUI
+ QWidget
+
+ 1
+
+
+ LevelMeterVU
+ QWidget
+
+ 1
+
+
+ ButtonSwitch
+ QToolButton
+
+
+
+ CWKeyerGUI
+ QWidget
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/channeltx/modssb/ssbmodplugin.cpp b/plugins/channeltx/modssb/ssbmodplugin.cpp
new file mode 100644
index 000000000..b635ed49d
--- /dev/null
+++ b/plugins/channeltx/modssb/ssbmodplugin.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 "ssbmodgui.h"
+#include "ssbmodplugin.h"
+
+const PluginDescriptor SSBModPlugin::m_pluginDescriptor = {
+ QString("SSB Modulator"),
+ QString("2.5.0"),
+ QString("(c) Edouard Griffiths, F4EXB"),
+ QString("https://github.com/f4exb/sdrangel"),
+ true,
+ QString("https://github.com/f4exb/sdrangel")
+};
+
+SSBModPlugin::SSBModPlugin(QObject* parent) :
+ QObject(parent)
+{
+}
+
+const PluginDescriptor& SSBModPlugin::getPluginDescriptor() const
+{
+ return m_pluginDescriptor;
+}
+
+void SSBModPlugin::initPlugin(PluginAPI* pluginAPI)
+{
+ m_pluginAPI = pluginAPI;
+
+ // register SSB modulator
+ m_pluginAPI->registerTxChannel(SSBModGUI::m_channelID, this);
+}
+
+PluginGUI* SSBModPlugin::createTxChannel(const QString& channelName, DeviceSinkAPI *deviceAPI)
+{
+ if(channelName == SSBModGUI::m_channelID)
+ {
+ SSBModGUI* gui = SSBModGUI::create(m_pluginAPI, deviceAPI);
+ return gui;
+ } else {
+ return 0;
+ }
+}
+
+void SSBModPlugin::createInstanceModSSB(DeviceSinkAPI *deviceAPI)
+{
+ SSBModGUI* gui = SSBModGUI::create(m_pluginAPI, deviceAPI);
+}
diff --git a/plugins/channeltx/modssb/ssbmodplugin.h b/plugins/channeltx/modssb/ssbmodplugin.h
new file mode 100644
index 000000000..a081e9d54
--- /dev/null
+++ b/plugins/channeltx/modssb/ssbmodplugin.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_SSBMODPLUGIN_H
+#define INCLUDE_SSBMODPLUGIN_H
+
+#include
+#include "plugin/plugininterface.h"
+
+class DeviceSinkAPI;
+
+class SSBModPlugin : public QObject, PluginInterface {
+ Q_OBJECT
+ Q_INTERFACES(PluginInterface)
+ Q_PLUGIN_METADATA(IID "sdrangel.channeltx.ssbmod")
+
+public:
+ explicit SSBModPlugin(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 createInstanceModSSB(DeviceSinkAPI *deviceAPI);
+};
+
+#endif // INCLUDE_SSBMODPLUGIN_H