diff --git a/plugins/channeltx/CMakeLists.txt b/plugins/channeltx/CMakeLists.txt index 89f92144f..06e040138 100644 --- a/plugins/channeltx/CMakeLists.txt +++ b/plugins/channeltx/CMakeLists.txt @@ -1,6 +1,7 @@ project(mod) add_subdirectory(modam) +add_subdirectory(modatv) add_subdirectory(modnfm) add_subdirectory(modssb) add_subdirectory(modwfm) diff --git a/plugins/channeltx/modatv/CMakeLists.txt b/plugins/channeltx/modatv/CMakeLists.txt new file mode 100644 index 000000000..3f98fd4dc --- /dev/null +++ b/plugins/channeltx/modatv/CMakeLists.txt @@ -0,0 +1,43 @@ +project(modatv) + +set(modatv_SOURCES + atvmod.cpp + atvmodgui.cpp + atvmodplugin.cpp +) + +set(modatv_HEADERS + atvmod.h + atvmodgui.h + atvmodplugin.h +) + +set(modatv_FORMS + atvmodgui.ui +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} +) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +qt5_wrap_ui(modatv_FORMS_HEADERS ${modatv_FORMS}) + +add_library(modatv SHARED + ${modatv_SOURCES} + ${modatv_HEADERS_MOC} + ${modatv_FORMS_HEADERS} +) + +target_link_libraries(modatv + ${QT_LIBRARIES} + sdrbase +) + +qt5_use_modules(modatv Core Widgets) + +install(TARGETS modatv DESTINATION lib/plugins/channeltx) \ No newline at end of file diff --git a/plugins/channeltx/modatv/atvmod.cpp b/plugins/channeltx/modatv/atvmod.cpp new file mode 100644 index 000000000..d88b217f6 --- /dev/null +++ b/plugins/channeltx/modatv/atvmod.cpp @@ -0,0 +1,305 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "dsp/upchannelizer.h" +#include "atvmod.h" + +MESSAGE_CLASS_DEFINITION(ATVMod::MsgConfigureATVMod, Message) + +const float ATVMod::m_blackLevel = 0.3f; +const float ATVMod::m_spanLevel = 0.7f; +const int ATVMod::m_levelNbSamples = 10000; // every 10ms + +ATVMod::ATVMod() : + m_evenImage(true), + m_tvSampleRate(1000000), + m_settingsMutex(QMutex::Recursive), + m_horizontalCount(0), + m_lineCount(0) +{ + setObjectName("ATVMod"); + + m_config.m_outputSampleRate = 1000000; + m_config.m_inputFrequencyOffset = 0; + m_config.m_rfBandwidth = 1000000; + m_config.m_atvModInput = ATVModInputBarChart; + m_config.m_atvStd = ATVStdPAL625; + + applyStandard(); + + m_interpolatorDistanceRemain = 0.0f; + m_interpolatorDistance = 1.0f; + + apply(true); + + m_movingAverage.resize(16, 0); +} + +ATVMod::~ATVMod() +{ +} + +void ATVMod::configure(MessageQueue* messageQueue, + Real rfBandwidth, + ATVStd atvStd, + ATVModInput atvModInput, + Real uniformLevel, + bool channelMute) +{ + Message* cmd = MsgConfigureATVMod::create(rfBandwidth, atvStd, atvModInput, uniformLevel); + messageQueue->push(cmd); +} + +void ATVMod::pullAudio(int nbSamples) +{ +} + +void ATVMod::pull(Sample& sample) +{ + Complex ci; + + m_settingsMutex.lock(); + + if (m_tvSampleRate == m_running.m_outputSampleRate) // no interpolation nor decimation + { + modulateSample(); + pullFinalize(m_modSample, sample); + } + else + { + 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; + pullFinalize(ci, sample); + } +} + +void ATVMod::pullFinalize(Complex& ci, Sample& sample) +{ + 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); + + sample.m_real = (FixReal) ci.real(); + sample.m_imag = (FixReal) ci.imag(); +} + +void ATVMod::modulateSample() +{ + Real t; + + pullVideo(t); + calculateLevel(t); + + // TODO For now do AM 95% + m_modSample.real((t*0.95 + 1.0f) * 16384.0f); // modulate and scale zero frequency carrier + m_modSample.imag(0.0f); +} + +void ATVMod::pullVideo(Real& sample) +{ + if ((m_lineCount < 21) || (m_lineCount > 621) || ((m_lineCount > 309) && (m_lineCount < 335))) + { + pullVSyncLine(m_horizontalCount - (m_pointsPerSync + m_pointsPerBP), sample); + } + else + { + pullImageLine(sample); + } + + if (m_horizontalCount < m_nbHorizPoints) + { + m_horizontalCount++; + } + else + { + if (m_lineCount < m_nbLines) + { + m_lineCount++; + } + else + { + m_lineCount = 0; + } + + m_horizontalCount = 0; + } +} + +void ATVMod::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 = std::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 ATVMod::start() +{ + qDebug() << "ATVMod::start: m_outputSampleRate: " << m_config.m_outputSampleRate + << " m_inputFrequencyOffset: " << m_config.m_inputFrequencyOffset; +} + +void ATVMod::stop() +{ +} + +bool ATVMod::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() << "ATVMod::handleMessage: MsgChannelizerNotification:" + << " m_outputSampleRate: " << m_config.m_outputSampleRate + << " m_inputFrequencyOffset: " << m_config.m_inputFrequencyOffset; + + return true; + } + else if (MsgConfigureATVMod::match(cmd)) + { + MsgConfigureATVMod& cfg = (MsgConfigureATVMod&) cmd; + + m_config.m_rfBandwidth = cfg.getRFBandwidth(); + m_config.m_atvModInput = cfg.getATVModInput(); + m_config.m_atvStd = cfg.getATVStd(); + m_config.m_uniformLevel = cfg.getUniformLevel(); + + apply(); + + qDebug() << "ATVMod::handleMessage: MsgConfigureATVMod:" + << " m_rfBandwidth: " << m_config.m_rfBandwidth + << " m_atvStd: " << (int) m_config.m_atvStd + << " m_atvModInput: " << (int) m_config.m_atvModInput + << " m_uniformLevel: " << m_config.m_uniformLevel; + + return true; + } + else + { + return false; + } +} + +void ATVMod::apply(bool force) +{ + if ((m_config.m_outputSampleRate != m_running.m_outputSampleRate) || + (m_config.m_atvStd != m_running.m_atvStd) || + (m_config.m_rfBandwidth != m_running.m_rfBandwidth) || force) + { + int rateMultiple = getSampleRateMultiple(m_config.m_atvStd); + m_tvSampleRate = (m_config.m_outputSampleRate / rateMultiple) * rateMultiple; + + m_settingsMutex.lock(); + + if (m_tvSampleRate > 0) + { + m_interpolatorDistanceRemain = 0; + m_interpolatorDistance = (Real) m_tvSampleRate / (Real) m_config.m_outputSampleRate; + m_interpolator.create(48, m_tvSampleRate, m_config.m_rfBandwidth / 2.2, 3.0); + } + else + { + m_tvSampleRate = m_config.m_outputSampleRate; + } + + applyStandard(); // set all timings + m_settingsMutex.unlock(); + } + + 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(); + } + + 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_atvModInput = m_config.m_atvModInput; + m_running.m_atvStd = m_config.m_atvStd; + m_running.m_uniformLevel = m_config.m_uniformLevel; +} + +int ATVMod::getSampleRateMultiple(ATVStd std) +{ + switch(std) + { + case ATVStdPAL625: + default: + return 1000000; + } +} + +void ATVMod::applyStandard() +{ + int rateMultiple = getSampleRateMultiple(m_config.m_atvStd); + m_pointsPerTU = m_tvSampleRate / rateMultiple; + + switch(m_config.m_atvStd) + { + case ATVStdPAL625: + default: + m_pointsPerSync = 5 * m_pointsPerTU; // would be 4.7 us actually + m_pointsPerBP = 5 * m_pointsPerTU; // would be 4.7 us actually + m_pointsPerFP = 2 * m_pointsPerTU; // would be 1.5 us actually + m_pointsPerFSync = 3 * m_pointsPerTU; // would be 2.3 us actually + m_pointsPerLine = (64 - 12) * m_pointsPerTU; // what is left in a 64 us line + m_pointsPerBar = 10 * m_pointsPerTU; // set a bar length to 10 us (~5 bars per line) + m_nbLines = 625; + m_interlaced = true; + m_nbHorizPoints = 64 * m_pointsPerTU; + } +} diff --git a/plugins/channeltx/modatv/atvmod.h b/plugins/channeltx/modatv/atvmod.h new file mode 100644 index 000000000..ad99612ed --- /dev/null +++ b/plugins/channeltx/modatv/atvmod.h @@ -0,0 +1,301 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 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_MODATV_ATVMOD_H_ +#define PLUGINS_CHANNELTX_MODATV_ATVMOD_H_ + +#include +#include + +#include + +#include "dsp/basebandsamplesource.h" +#include "dsp/nco.h" +#include "dsp/interpolator.h" +#include "dsp/movingaverage.h" +#include "util/message.h" + +class ATVMod : public BasebandSampleSource { + Q_OBJECT + +public: + typedef enum + { + ATVStdPAL625 + } ATVStd; + + typedef enum + { + ATVModInputUniform, + ATVModInputBarChart, + ATVModInputGradient + } ATVModInput; + + ATVMod(); + ~ATVMod(); + + void configure(MessageQueue* messageQueue, + Real rfBandwidth, + ATVStd atvStd, + ATVModInput atvModInput, + Real uniformLevel, + bool channelMute); + + virtual void pull(Sample& sample); + virtual void pullAudio(int nbSamples); // this is used for video signal actually + virtual void start(); + virtual void stop(); + virtual bool handleMessage(const Message& cmd); + + Real getMagSq() const { return m_movingAverage.average(); } + + static int getSampleRateMultiple(ATVStd std); + +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 MsgConfigureATVMod : public Message + { + MESSAGE_CLASS_DECLARATION + + public: + Real getRFBandwidth() const { return m_rfBandwidth; } + ATVStd getATVStd() const { return m_atvStd; } + ATVModInput getATVModInput() const { return m_atvModInput; } + Real getUniformLevel() const { return m_uniformLevel; } + + static MsgConfigureATVMod* create( + Real rfBandwidth, + ATVStd atvStd, + ATVModInput atvModInput, + Real uniformLevel) + { + return new MsgConfigureATVMod(rfBandwidth, atvStd, atvModInput, uniformLevel); + } + + private: + Real m_rfBandwidth; + ATVStd m_atvStd; + ATVModInput m_atvModInput; + Real m_uniformLevel; + + MsgConfigureATVMod( + Real rfBandwidth, + ATVStd atvStd, + ATVModInput atvModInput, + Real uniformLevel) : + Message(), + m_rfBandwidth(rfBandwidth), + m_atvStd(atvStd), + m_atvModInput(atvModInput), + m_uniformLevel(uniformLevel) + { } + }; + + struct Config + { + int m_outputSampleRate; //!< sample rate from channelizer + qint64 m_inputFrequencyOffset; //!< offset from baseband center frequency + Real m_rfBandwidth; //!< Bandwidth of modulated signal + ATVStd m_atvStd; //!< Standard + ATVModInput m_atvModInput; //!< Input source type + Real m_uniformLevel; //!< Percentage between black and white for uniform screen display + + Config() : + m_outputSampleRate(-1), + m_inputFrequencyOffset(0), + m_rfBandwidth(0), + m_atvStd(ATVStdPAL625), + m_atvModInput(ATVModInputBarChart), + m_uniformLevel(0.5f) + { } + }; + + Config m_config; + Config m_running; + + NCO m_carrierNco; + Complex m_modSample; + Interpolator m_interpolator; + Real m_interpolatorDistance; + Real m_interpolatorDistanceRemain; + int m_tvSampleRate; //!< sample rate for generating signal + uint32_t m_pointsPerSync; //!< number of line points for the horizontal sync + uint32_t m_pointsPerBP; //!< number of line points for the back porch + uint32_t m_pointsPerLine; //!< number of line points for the image line + uint32_t m_pointsPerFP; //!< number of line points for the front porch + uint32_t m_pointsPerFSync; //!< number of line points for the field first sync + uint32_t m_pointsPerBar; //!< number of line points for a bar of the bar chart + uint32_t m_pointsPerTU; //!< number of line points per time unit + uint32_t m_nbLines; //!< number of lines per complete frame + uint32_t m_nbHorizPoints; //!< number of line points per horizontal line + bool m_interlaced; //!< true if image is interlaced (2 half frames per frame) + bool m_evenImage; + QMutex m_settingsMutex; + int m_horizontalCount; //!< current point index on line + int m_lineCount; //!< current line index in frame + + MovingAverage m_movingAverage; + quint32 m_levelCalcCount; + Real m_peakLevel; + Real m_levelSum; + + static const float m_blackLevel; + static const float m_spanLevel; + static const int m_levelNbSamples; + + void apply(bool force = false); + void pullFinalize(Complex& ci, Sample& sample); + void pullVideo(Real& sample); + void calculateLevel(Real& sample); + void modulateSample(); + void applyStandard(); + + inline void pullImageLine(Real& sample) + { + if (m_horizontalCount < m_pointsPerSync) // sync pulse + { + sample = 0.0f; // ultra-black + } + else if (m_horizontalCount < m_pointsPerSync + m_pointsPerBP) // back porch + { + sample = 0.3f; // black + } + else if (m_horizontalCount < m_pointsPerSync + m_pointsPerBP + m_pointsPerLine) + { + int pointIndex = m_horizontalCount - (m_pointsPerSync + m_pointsPerBP); + + switch(m_running.m_atvModInput) + { + case ATVModInputBarChart: + sample = (pointIndex / m_pointsPerBar) * (m_spanLevel/5.0f) + m_blackLevel; + break; + case ATVModInputGradient: + sample = (pointIndex / m_pointsPerLine) * m_spanLevel + m_blackLevel; + break; + case ATVModInputUniform: + default: + sample = m_spanLevel * m_running.m_uniformLevel + m_blackLevel; + } + } + else // front porch + { + sample = m_blackLevel; // black + } + } + + inline void pullVSyncLine(int pointIndex, Real& sample) + { + switch (m_lineCount) + { + case 0: // __|__| + case 1: + case 313: + case 314: + { + int halfIndex = m_horizontalCount % (m_nbHorizPoints/2); + + if (halfIndex < (m_nbHorizPoints/2) - m_pointsPerSync) // ultra-black + { + sample = 0.0f; + } + else // black + { + sample = m_blackLevel; + } + } + break; + case 2: // __||XX + if (m_horizontalCount < (m_nbHorizPoints/2) - m_pointsPerSync) + { + sample = 0.0f; + } + else if (m_horizontalCount < (m_nbHorizPoints/2)) + { + sample = m_blackLevel; + } + else if (m_horizontalCount < (m_nbHorizPoints/2) + m_pointsPerFSync) + { + sample = 0.0f; + } + else + { + sample = m_blackLevel; + } + break; + case 3: // |XX|XX + case 4: + case 310: + case 311: + case 315: + case 316: + case 622: + case 623: + case 624: + { + int halfIndex = m_horizontalCount % (m_nbHorizPoints/2); + + if (halfIndex < m_pointsPerFSync) // ultra-black + { + sample = 0.0f; + } + else // black + { + sample = m_blackLevel; + } + } + break; + case 312: // |XX__| + if (m_horizontalCount < m_pointsPerFSync) + { + sample = 0.0f; + } + else if (m_horizontalCount < (m_nbHorizPoints/2)) + { + sample = m_blackLevel; + } + else if (m_horizontalCount < m_nbHorizPoints - m_pointsPerSync) + { + sample = 0.0f; + } + else + { + sample = m_blackLevel; + } + break; + default: // black images + if (m_horizontalCount < m_pointsPerSync) + { + sample = 0.0f; + } + else + { + sample = m_blackLevel; + } + break; + } + } +}; + + +#endif /* PLUGINS_CHANNELTX_MODATV_ATVMOD_H_ */ diff --git a/plugins/channeltx/modatv/atvmodgui.cpp b/plugins/channeltx/modatv/atvmodgui.cpp new file mode 100644 index 000000000..33d1e601c --- /dev/null +++ b/plugins/channeltx/modatv/atvmodgui.cpp @@ -0,0 +1,339 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 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_atvmodgui.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 "atvmodgui.h" + +const QString ATVModGUI::m_channelID = "sdrangel.channeltx.modatv"; + +ATVModGUI* ATVModGUI::create(PluginAPI* pluginAPI, DeviceSinkAPI *deviceAPI) +{ + ATVModGUI* gui = new ATVModGUI(pluginAPI, deviceAPI); + return gui; +} + +void ATVModGUI::destroy() +{ +} + +void ATVModGUI::setName(const QString& name) +{ + setObjectName(name); +} + +QString ATVModGUI::getName() const +{ + return objectName(); +} + +qint64 ATVModGUI::getCenterFrequency() const { + return m_channelMarker.getCenterFrequency(); +} + +void ATVModGUI::setCenterFrequency(qint64 centerFrequency) +{ + m_channelMarker.setCenterFrequency(centerFrequency); + applySettings(); +} + +void ATVModGUI::resetToDefaults() +{ + blockApplySettings(true); + + ui->rfBW->setValue(12); + ui->uniformLevel->setValue(35); + ui->volume->setValue(10); + ui->standard->setCurrentIndex(0); + ui->inputSelect->setCurrentIndex(0); + ui->deltaFrequency->setValue(0); + + blockApplySettings(false); + applySettings(); +} + +QByteArray ATVModGUI::serialize() const +{ + SimpleSerializer s(1); + + s.writeS32(1, m_channelMarker.getCenterFrequency()); + s.writeS32(2, ui->rfBW->value()); + s.writeS32(3, ui->uniformLevel->value()); + s.writeS32(4, ui->standard->currentIndex()); + s.writeS32(5, ui->inputSelect->currentIndex()); + s.writeU32(6, m_channelMarker.getColor().rgb()); + s.writeS32(7, ui->volume->value()); + + return s.final(); +} + +bool ATVModGUI::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, 100); + ui->uniformLevel->setValue(tmp); + d.readS32(4, &tmp, 0); + ui->standard->setCurrentIndex(tmp); + d.readS32(5, &tmp, 0); + ui->inputSelect->setCurrentIndex(tmp); + + if(d.readU32(6, &u32tmp)) + { + m_channelMarker.setColor(u32tmp); + } + + d.readS32(7, &tmp, 10); + ui->volume->setValue(tmp); + + blockApplySettings(false); + m_channelMarker.blockSignals(false); + + applySettings(); + return true; + } + else + { + resetToDefaults(); + return false; + } +} + +bool ATVModGUI::handleMessage(const Message& message) +{ + return false; +} + +void ATVModGUI::viewChanged() +{ + applySettings(); +} + +void ATVModGUI::handleSourceMessages() +{ + Message* message; + + while ((message = m_atvMod->getOutputMessageQueue()->pop()) != 0) + { + if (handleMessage(*message)) + { + delete message; + } + } +} + +void ATVModGUI::on_deltaMinus_toggled(bool minus) +{ + int deltaFrequency = m_channelMarker.getCenterFrequency(); + bool minusDelta = (deltaFrequency < 0); + + if (minus ^ minusDelta) // sign change + { + m_channelMarker.setCenterFrequency(-deltaFrequency); + } +} + +void ATVModGUI::on_deltaFrequency_changed(quint64 value) +{ + if (ui->deltaMinus->isChecked()) { + m_channelMarker.setCenterFrequency(-value); + } else { + m_channelMarker.setCenterFrequency(value); + } +} + +void ATVModGUI::on_rfBW_valueChanged(int value) +{ + ui->rfBWText->setText(QString("%1 MHz").arg(value / 10.0, 0, 'f', 1)); + m_channelMarker.setBandwidth(value * 100000); + applySettings(); +} + +void ATVModGUI::on_uniformLevel_valueChanged(int value) +{ + ui->uniformLevelText->setText(QString("%1").arg(value)); + applySettings(); +} + +void ATVModGUI::on_inputSelect_currentIndexChanged(int index) +{ + applySettings(); +} + +void ATVModGUI::on_volume_valueChanged(int value) +{ + ui->volumeText->setText(QString("%1").arg(value / 10.0, 0, 'f', 1)); + applySettings(); +} + +void ATVModGUI::on_channelMute_toggled(bool checked) +{ + applySettings(); +} + +void ATVModGUI::onWidgetRolled(QWidget* widget, bool rollDown) +{ +} + +void ATVModGUI::onMenuDoubleClicked() +{ + if(!m_basicSettingsShown) { + m_basicSettingsShown = true; + BasicChannelSettingsWidget* bcsw = new BasicChannelSettingsWidget(&m_channelMarker, this); + bcsw->show(); + } +} + +ATVModGUI::ATVModGUI(PluginAPI* pluginAPI, DeviceSinkAPI *deviceAPI, QWidget* parent) : + RollupWidget(parent), + ui(new Ui::ATVModGUI), + 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) +{ + 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_atvMod = new ATVMod(); + m_channelizer = new UpChannelizer(m_atvMod); + 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::white); + 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); + + resetToDefaults(); + + connect(m_atvMod->getOutputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages())); + connect(m_atvMod, SIGNAL(levelChanged(qreal, qreal, int)), ui->volumeMeter, SLOT(levelChanged(qreal, qreal, int))); +} + +ATVModGUI::~ATVModGUI() +{ + m_deviceAPI->removeChannelInstance(this); + m_deviceAPI->removeThreadedSource(m_threadedChannelizer); + delete m_threadedChannelizer; + delete m_channelizer; + delete m_atvMod; + //delete m_channelMarker; + delete ui; +} + +void ATVModGUI::blockApplySettings(bool block) +{ + m_doApplySettings = !block; +} + +void ATVModGUI::applySettings() +{ + if (m_doApplySettings) + { + setTitleColor(m_channelMarker.getColor()); + + m_channelizer->configure(m_channelizer->getInputMessageQueue(), + m_channelizer->getOutputSampleRate(), + m_channelMarker.getCenterFrequency()); + + ui->deltaFrequency->setValue(abs(m_channelMarker.getCenterFrequency())); + ui->deltaMinus->setChecked(m_channelMarker.getCenterFrequency() < 0); + + m_atvMod->configure(m_atvMod->getInputMessageQueue(), + ui->rfBW->value() * 100000.0f, + (ATVMod::ATVStd) ui->standard->currentIndex(), + (ATVMod::ATVModInput) ui->inputSelect->currentIndex(), + ui->uniformLevel->value() / 100.0f, + ui->channelMute->isChecked()); + } +} + +void ATVModGUI::leaveEvent(QEvent*) +{ + blockApplySettings(true); + m_channelMarker.setHighlighted(false); + blockApplySettings(false); +} + +void ATVModGUI::enterEvent(QEvent*) +{ + blockApplySettings(true); + m_channelMarker.setHighlighted(true); + blockApplySettings(false); +} + +void ATVModGUI::tick() +{ + Real powDb = CalcDb::dbPower(m_atvMod->getMagSq()); + m_channelPowerDbAvg.feed(powDb); + ui->channelPower->setText(QString::number(m_channelPowerDbAvg.average(), 'f', 1)); +} diff --git a/plugins/channeltx/modatv/atvmodgui.h b/plugins/channeltx/modatv/atvmodgui.h new file mode 100644 index 000000000..decac1d90 --- /dev/null +++ b/plugins/channeltx/modatv/atvmodgui.h @@ -0,0 +1,104 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 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_MODTV_ATVMODGUI_H_ +#define PLUGINS_CHANNELTX_MODTV_ATVMODGUI_H_ + +#include "gui/rollupwidget.h" +#include "plugin/plugingui.h" +#include "dsp/channelmarker.h" +#include "dsp/movingaverage.h" +#include "atvmod.h" + +class PluginAPI; +class DeviceSinkAPI; + +class ThreadedBasebandSampleSource; +class UpChannelizer; +class ATVMod; + +namespace Ui { + class ATVModGUI; +} + +class ATVModGUI : public RollupWidget, public PluginGUI { + Q_OBJECT + +public: + static ATVModGUI* 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_uniformLevel_valueChanged(int value); + void on_inputSelect_currentIndexChanged(int index); + void on_volume_valueChanged(int value); + void on_channelMute_toggled(bool checked); + + void onWidgetRolled(QWidget* widget, bool rollDown); + void onMenuDoubleClicked(); + + void tick(); + +private: + Ui::ATVModGUI* ui; + PluginAPI* m_pluginAPI; + DeviceSinkAPI* m_deviceAPI; + ChannelMarker m_channelMarker; + bool m_basicSettingsShown; + bool m_doApplySettings; + + ThreadedBasebandSampleSource* m_threadedChannelizer; + UpChannelizer* m_channelizer; + ATVMod* m_atvMod; + MovingAverage m_channelPowerDbAvg; + + QString m_fileName; + quint32 m_recordLength; + int m_recordSampleRate; + int m_samplesCount; + std::size_t m_tickCount; + bool m_enableNavTime; + + explicit ATVModGUI(PluginAPI* pluginAPI, DeviceSinkAPI *deviceAPI, QWidget* parent = NULL); + virtual ~ATVModGUI(); + + void blockApplySettings(bool block); + void applySettings(); + + void leaveEvent(QEvent*); + void enterEvent(QEvent*); +}; + +#endif /* PLUGINS_CHANNELTX_MODAM_AMMODGUI_H_ */ diff --git a/plugins/channeltx/modatv/atvmodgui.ui b/plugins/channeltx/modatv/atvmodgui.ui new file mode 100644 index 000000000..7158a959d --- /dev/null +++ b/plugins/channeltx/modatv/atvmodgui.ui @@ -0,0 +1,635 @@ + + + ATVModGUI + + + + 0 + 0 + 342 + 363 + + + + + 0 + 0 + + + + + 0 + 0 + + + + + Sans Serif + 9 + + + + Qt::StrongFocus + + + ATV Modulator + + + + + 10 + 10 + 320 + 341 + + + + + 280 + 0 + + + + Settings + + + + 3 + + + 2 + + + 2 + + + 2 + + + 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 channel + + + ... + + + + :/txon.png + :/txoff.png:/txon.png + + + true + + + + + + + + + + + + + + PAL625L + + + + + + + + RFBW + + + + + + + Demodulator (RF) bandwidth + + + 1 + + + 100 + + + 1 + + + 10 + + + Qt::Horizontal + + + + + + + + 50 + 0 + + + + 1.0 MHz + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Qt::Horizontal + + + + + + + + + Vol + + + + + + + + 24 + 24 + + + + Audio input gain + + + 20 + + + 1 + + + 10 + + + + + + + + 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 + + + + + + + + + + + + Uniform + + + + + H Bars + + + + + Gradient + + + + + + + + + 24 + 24 + + + + Tone frequency + + + 0 + + + 100 + + + 1 + + + 50 + + + + + + + + 24 + 0 + + + + Tone frequency (kHz) + + + 50 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Vertical + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Horizontal + + + + + + + + + ... + + + + + + + + + + + + 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 +
gui/rollupwidget.h
+ 1 +
+ + ValueDial + QWidget +
gui/valuedial.h
+ 1 +
+ + LevelMeterVU + QWidget +
gui/levelmeter.h
+ 1 +
+ + ButtonSwitch + QToolButton +
gui/buttonswitch.h
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/channeltx/modatv/atvmodplugin.cpp b/plugins/channeltx/modatv/atvmodplugin.cpp new file mode 100644 index 000000000..ad477a535 --- /dev/null +++ b/plugins/channeltx/modatv/atvmodplugin.cpp @@ -0,0 +1,68 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 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 "atvmodgui.h" +#include "atvmodplugin.h" + +const PluginDescriptor ATVModPlugin::m_pluginDescriptor = { + QString("ATV Modulator"), + QString("3.3.0"), + QString("(c) Edouard Griffiths, F4EXB"), + QString("https://github.com/f4exb/sdrangel"), + true, + QString("https://github.com/f4exb/sdrangel") +}; + +ATVModPlugin::ATVModPlugin(QObject* parent) : + QObject(parent) +{ +} + +const PluginDescriptor& ATVModPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void ATVModPlugin::initPlugin(PluginAPI* pluginAPI) +{ + m_pluginAPI = pluginAPI; + + // register ATV modulator + m_pluginAPI->registerTxChannel(ATVModGUI::m_channelID, this); +} + +PluginGUI* ATVModPlugin::createTxChannel(const QString& channelName, DeviceSinkAPI *deviceAPI) +{ + if(channelName == ATVModGUI::m_channelID) + { + ATVModGUI* gui = ATVModGUI::create(m_pluginAPI, deviceAPI); + return gui; + } else { + return 0; + } +} + +void ATVModPlugin::createInstanceModATV(DeviceSinkAPI *deviceAPI) +{ + ATVModGUI* gui = ATVModGUI::create(m_pluginAPI, deviceAPI); +} + + + diff --git a/plugins/channeltx/modatv/atvmodplugin.h b/plugins/channeltx/modatv/atvmodplugin.h new file mode 100644 index 000000000..7e3cb51ba --- /dev/null +++ b/plugins/channeltx/modatv/atvmodplugin.h @@ -0,0 +1,47 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2017 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_MODATV_ATVMODPLUGIN_H_ +#define PLUGINS_CHANNELTX_MODATV_ATVMODPLUGIN_H_ + +#include +#include "plugin/plugininterface.h" + +class DeviceSinkAPI; + +class ATVModPlugin : public QObject, PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID "sdrangel.channeltx.atvmod") + +public: + explicit ATVModPlugin(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 createInstanceModATV(DeviceSinkAPI *deviceAPI); +}; + +#endif /* PLUGINS_CHANNELTX_MODATV_ATVMODPLUGIN_H_ */ diff --git a/plugins/channeltx/modatv/modatv.pro b/plugins/channeltx/modatv/modatv.pro new file mode 100644 index 000000000..17ee296b4 --- /dev/null +++ b/plugins/channeltx/modatv/modatv.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 = modatv + +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 += atvmod.cpp\ + atvmodgui.cpp\ + atvmodplugin.cpp + +HEADERS += atvmod.h\ + atvmodgui.h\ + atvmodplugin.h + +FORMS += atvmodgui.ui + +LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase + +RESOURCES = ../../../sdrbase/resources/res.qrc