diff --git a/plugins/channelrx/CMakeLists.txt b/plugins/channelrx/CMakeLists.txt index ff9814290..796249a08 100644 --- a/plugins/channelrx/CMakeLists.txt +++ b/plugins/channelrx/CMakeLists.txt @@ -30,6 +30,11 @@ if(CM256CC_FOUND) add_subdirectory(remotesink) endif(CM256CC_FOUND) +find_package(Codec2) +if (CODEC2_FOUND) + add_subdirectory(demodfreedv) +endif(CODEC2_FOUND) + if (BUILD_DEBIAN) add_subdirectory(demoddsd) add_subdirectory(remotesink) diff --git a/plugins/channelrx/demodfreedv/CMakeLists.txt b/plugins/channelrx/demodfreedv/CMakeLists.txt new file mode 100644 index 000000000..4b41d26ce --- /dev/null +++ b/plugins/channelrx/demodfreedv/CMakeLists.txt @@ -0,0 +1,49 @@ +project(demodfreedv) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +set(freedv_SOURCES + freedvdemod.cpp + freedvdemodgui.cpp + freedvdemodsettings.cpp + freedvplugin.cpp +) + +set(freedv_HEADERS + freedvdemod.h + freedvdemodgui.h + freedvdemodsettings.h + freedvplugin.h +) + +set(freedv_FORMS + freedvdemodgui.ui +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +qt5_wrap_ui(freedv_FORMS_HEADERS ${freedv_FORMS}) + +add_library(demodfreedv SHARED + ${freedv_SOURCES} + ${freedv_HEADERS_MOC} + ${freedv_FORMS_HEADERS} +) + +target_link_libraries(demodfreedv + ${QT_LIBRARIES} + sdrbase + sdrgui +) + +target_link_libraries(demodfreedv Qt5::Core Qt5::Widgets) + +install(TARGETS demodfreedv DESTINATION lib/plugins/channelrx) diff --git a/plugins/channelrx/demodfreedv/freedvdemod.cpp b/plugins/channelrx/demodfreedv/freedvdemod.cpp new file mode 100644 index 000000000..1129d5b84 --- /dev/null +++ b/plugins/channelrx/demodfreedv/freedvdemod.cpp @@ -0,0 +1,898 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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 "SWGChannelSettings.h" +#include "SWGSSBDemodSettings.h" +#include "SWGChannelReport.h" +#include "SWGSSBDemodReport.h" + +#include "audio/audiooutput.h" +#include "dsp/dspengine.h" +#include "dsp/downchannelizer.h" +#include "dsp/threadedbasebandsamplesink.h" +#include "dsp/dspcommands.h" +#include "device/devicesourceapi.h" +#include "util/db.h" + +#include "freedvdemod.h" + +MESSAGE_CLASS_DEFINITION(FreeDVDemod::MsgConfigureFreeDVDemod, Message) +MESSAGE_CLASS_DEFINITION(FreeDVDemod::MsgConfigureFreeDVDemodPrivate, Message) +MESSAGE_CLASS_DEFINITION(FreeDVDemod::MsgConfigureChannelizer, Message) + +const QString FreeDVDemod::m_channelIdURI = "sdrangel.channel.freedvdemod"; +const QString FreeDVDemod::m_channelId = "FreeDVDemod"; + +FreeDVDemod::FreeDVDemod(DeviceSourceAPI *deviceAPI) : + ChannelSinkAPI(m_channelIdURI), + m_deviceAPI(deviceAPI), + m_audioBinaual(false), + m_audioFlipChannels(false), + m_dsb(false), + m_audioMute(false), + m_agc(12000, agcTarget, 1e-2), + m_agcActive(false), + m_agcClamping(false), + m_agcNbSamples(12000), + m_agcPowerThreshold(1e-2), + m_agcThresholdGate(0), + m_squelchDelayLine(2*48000), + m_audioActive(false), + m_sampleSink(0), + m_audioFifo(24000), + m_settingsMutex(QMutex::Recursive) +{ + setObjectName(m_channelId); + + m_Bandwidth = 5000; + m_LowCutoff = 300; + m_volume = 2.0; + m_spanLog2 = 3; + m_inputSampleRate = 48000; + m_inputFrequencyOffset = 0; + + DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(&m_audioFifo, getInputMessageQueue()); + m_audioSampleRate = DSPEngine::instance()->getAudioDeviceManager()->getOutputSampleRate(); + + m_audioBuffer.resize(1<<14); + m_audioBufferFill = 0; + m_undersampleCount = 0; + m_sum = 0; + + m_usb = true; + m_magsq = 0.0f; + m_magsqSum = 0.0f; + m_magsqPeak = 0.0f; + m_magsqCount = 0; + + m_agc.setClampMax(SDR_RX_SCALED/100.0); + m_agc.setClamping(m_agcClamping); + + SSBFilter = new fftfilt(m_LowCutoff / m_audioSampleRate, m_Bandwidth / m_audioSampleRate, ssbFftLen); + DSBFilter = new fftfilt((2.0f * m_Bandwidth) / m_audioSampleRate, 2 * ssbFftLen); + + applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); + applySettings(m_settings, true); + + m_channelizer = new DownChannelizer(this); + m_threadedChannelizer = new ThreadedBasebandSampleSink(m_channelizer, this); + m_deviceAPI->addThreadedSink(m_threadedChannelizer); + m_deviceAPI->addChannelAPI(this); + + m_networkManager = new QNetworkAccessManager(); + connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); +} + +FreeDVDemod::~FreeDVDemod() +{ + disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*))); + delete m_networkManager; + DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(&m_audioFifo); + + m_deviceAPI->removeChannelAPI(this); + m_deviceAPI->removeThreadedSink(m_threadedChannelizer); + delete m_threadedChannelizer; + delete m_channelizer; + delete SSBFilter; + delete DSBFilter; +} + +void FreeDVDemod::configure(MessageQueue* messageQueue, + Real Bandwidth, + Real LowCutoff, + Real volume, + int spanLog2, + bool audioBinaural, + bool audioFlipChannel, + bool dsb, + bool audioMute, + bool agc, + bool agcClamping, + int agcTimeLog2, + int agcPowerThreshold, + int agcThresholdGate) +{ + Message* cmd = MsgConfigureFreeDVDemodPrivate::create( + Bandwidth, + LowCutoff, + volume, + spanLog2, + audioBinaural, + audioFlipChannel, + dsb, + audioMute, + agc, + agcClamping, + agcTimeLog2, + agcPowerThreshold, + agcThresholdGate); + messageQueue->push(cmd); +} + +void FreeDVDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly) +{ + (void) positiveOnly; + Complex ci; + fftfilt::cmplx *sideband; + int n_out; + + m_settingsMutex.lock(); + + int decim = 1<<(m_spanLog2 - 1); + unsigned char decim_mask = decim - 1; // counter LSB bit mask for decimation by 2^(m_scaleLog2 - 1) + + for(SampleVector::const_iterator it = begin; it < end; ++it) + { + Complex c(it->real(), it->imag()); + c *= m_nco.nextIQ(); + + if(m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci)) + { + if (m_dsb) + { + n_out = DSBFilter->runDSB(ci, &sideband); + } + else + { + n_out = SSBFilter->runSSB(ci, &sideband, m_usb); + } + + m_interpolatorDistanceRemain += m_interpolatorDistance; + } + else + { + n_out = 0; + } + + for (int i = 0; i < n_out; i++) + { + // Downsample by 2^(m_scaleLog2 - 1) for SSB band spectrum display + // smart decimation with bit gain using float arithmetic (23 bits significand) + + m_sum += sideband[i]; + + if (!(m_undersampleCount++ & decim_mask)) + { + Real avgr = m_sum.real() / decim; + Real avgi = m_sum.imag() / decim; + m_magsq = (avgr * avgr + avgi * avgi) / (SDR_RX_SCALED*SDR_RX_SCALED); + + m_magsqSum += m_magsq; + + if (m_magsq > m_magsqPeak) + { + m_magsqPeak = m_magsq; + } + + m_magsqCount++; + + if (!m_dsb & !m_usb) + { // invert spectrum for LSB + m_sampleBuffer.push_back(Sample(avgi, avgr)); + } + else + { + m_sampleBuffer.push_back(Sample(avgr, avgi)); + } + + m_sum.real(0.0); + m_sum.imag(0.0); + } + + float agcVal = m_agcActive ? m_agc.feedAndGetValue(sideband[i]) : 10.0; // 10.0 for 3276.8, 1.0 for 327.68 + fftfilt::cmplx& delayedSample = m_squelchDelayLine.readBack(m_agc.getStepDownDelay()); + m_audioActive = delayedSample.real() != 0.0; + m_squelchDelayLine.write(sideband[i]*agcVal); + + if (m_audioMute) + { + m_audioBuffer[m_audioBufferFill].r = 0; + m_audioBuffer[m_audioBufferFill].l = 0; + } + else + { + fftfilt::cmplx z = delayedSample * m_agc.getStepValue(); + + if (m_audioBinaual) + { + if (m_audioFlipChannels) + { + m_audioBuffer[m_audioBufferFill].r = (qint16)(z.imag() * m_volume); + m_audioBuffer[m_audioBufferFill].l = (qint16)(z.real() * m_volume); + } + else + { + m_audioBuffer[m_audioBufferFill].r = (qint16)(z.real() * m_volume); + m_audioBuffer[m_audioBufferFill].l = (qint16)(z.imag() * m_volume); + } + } + else + { + Real demod = (z.real() + z.imag()) * 0.7; + qint16 sample = (qint16)(demod * m_volume); + m_audioBuffer[m_audioBufferFill].l = sample; + m_audioBuffer[m_audioBufferFill].r = sample; + } + } + + ++m_audioBufferFill; + + if (m_audioBufferFill >= m_audioBuffer.size()) + { + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); + + if (res != m_audioBufferFill) + { + qDebug("FreeDVDemod::feed: %u/%u samples written", res, m_audioBufferFill); + } + + m_audioBufferFill = 0; + } + } + } + + uint res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); + + if (res != m_audioBufferFill) + { + qDebug("FreeDVDemod::feed: %u/%u tail samples written", res, m_audioBufferFill); + } + + m_audioBufferFill = 0; + + if (m_sampleSink != 0) + { + m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), !m_dsb); + } + + m_sampleBuffer.clear(); + + m_settingsMutex.unlock(); +} + +void FreeDVDemod::start() +{ + applyChannelSettings(m_inputSampleRate, m_inputFrequencyOffset, true); +} + +void FreeDVDemod::stop() +{ +} + +bool FreeDVDemod::handleMessage(const Message& cmd) +{ + if (DownChannelizer::MsgChannelizerNotification::match(cmd)) + { + DownChannelizer::MsgChannelizerNotification& notif = (DownChannelizer::MsgChannelizerNotification&) cmd; + qDebug("FreeDVDemod::handleMessage: MsgChannelizerNotification: m_sampleRate"); + + applyChannelSettings(notif.getSampleRate(), notif.getFrequencyOffset()); + + return true; + } + else if (MsgConfigureChannelizer::match(cmd)) + { + MsgConfigureChannelizer& cfg = (MsgConfigureChannelizer&) cmd; + qDebug() << "FreeDVDemod::handleMessage: MsgConfigureChannelizer: sampleRate: " << cfg.getSampleRate() + << " centerFrequency: " << cfg.getCenterFrequency(); + + m_channelizer->configure(m_channelizer->getInputMessageQueue(), + cfg.getSampleRate(), + cfg.getCenterFrequency()); + + return true; + } + else if (MsgConfigureFreeDVDemod::match(cmd)) + { + MsgConfigureFreeDVDemod& cfg = (MsgConfigureFreeDVDemod&) cmd; + qDebug("FreeDVDemod::handleMessage: MsgConfigureFreeDVDemod"); + + applySettings(cfg.getSettings(), cfg.getForce()); + + return true; + } + else if (BasebandSampleSink::MsgThreadedSink::match(cmd)) + { + BasebandSampleSink::MsgThreadedSink& cfg = (BasebandSampleSink::MsgThreadedSink&) cmd; + const QThread *thread = cfg.getThread(); + qDebug("FreeDVDemod::handleMessage: BasebandSampleSink::MsgThreadedSink: %p", thread); + return true; + } + else if (DSPConfigureAudio::match(cmd)) + { + DSPConfigureAudio& cfg = (DSPConfigureAudio&) cmd; + uint32_t sampleRate = cfg.getSampleRate(); + + qDebug() << "FreeDVDemod::handleMessage: DSPConfigureAudio:" + << " sampleRate: " << sampleRate; + + if (sampleRate != m_audioSampleRate) { + applyAudioSampleRate(sampleRate); + } + + return true; + } + else if (DSPSignalNotification::match(cmd)) + { + return true; + } + else + { + if(m_sampleSink != 0) + { + return m_sampleSink->handleMessage(cmd); + } + else + { + return false; + } + } +} + +void FreeDVDemod::applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force) +{ + qDebug() << "FreeDVDemod::applyChannelSettings:" + << " inputSampleRate: " << inputSampleRate + << " inputFrequencyOffset: " << inputFrequencyOffset; + + if ((m_inputFrequencyOffset != inputFrequencyOffset) || + (m_inputSampleRate != inputSampleRate) || force) + { + m_nco.setFreq(-inputFrequencyOffset, inputSampleRate); + } + + if ((m_inputSampleRate != inputSampleRate) || force) + { + m_settingsMutex.lock(); + m_interpolator.create(16, inputSampleRate, m_Bandwidth * 1.5f, 2.0f); + m_interpolatorDistanceRemain = 0; + m_interpolatorDistance = (Real) inputSampleRate / (Real) m_audioSampleRate; + m_settingsMutex.unlock(); + } + + m_inputSampleRate = inputSampleRate; + m_inputFrequencyOffset = inputFrequencyOffset; +} + +void FreeDVDemod::applyAudioSampleRate(int sampleRate) +{ + qDebug("FreeDVDemod::applyAudioSampleRate: %d", sampleRate); + + MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( + sampleRate, m_settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(channelConfigMsg); + + m_settingsMutex.lock(); + + m_interpolator.create(16, m_inputSampleRate, m_Bandwidth * 1.5f, 2.0f); + m_interpolatorDistanceRemain = 0; + m_interpolatorDistance = (Real) m_inputSampleRate / (Real) sampleRate; + + SSBFilter->create_filter(m_LowCutoff / (float) sampleRate, m_Bandwidth / (float) sampleRate); + DSBFilter->create_dsb_filter((2.0f * m_Bandwidth) / (float) sampleRate); + + int agcNbSamples = (sampleRate / 1000) * (1<push(cfg); + } +} + +void FreeDVDemod::applySettings(const FreeDVDemodSettings& settings, bool force) +{ + qDebug() << "FreeDVDemod::applySettings:" + << " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset + << " m_rfBandwidth: " << settings.m_rfBandwidth + << " m_lowCutoff: " << settings.m_lowCutoff + << " m_volume: " << settings.m_volume + << " m_spanLog2: " << settings.m_spanLog2 + << " m_audioBinaual: " << settings.m_audioBinaural + << " m_audioFlipChannels: " << settings.m_audioFlipChannels + << " m_dsb: " << settings.m_dsb + << " m_audioMute: " << settings.m_audioMute + << " m_agcActive: " << settings.m_agc + << " m_agcClamping: " << settings.m_agcClamping + << " m_agcTimeLog2: " << settings.m_agcTimeLog2 + << " agcPowerThreshold: " << settings.m_agcPowerThreshold + << " agcThresholdGate: " << settings.m_agcThresholdGate + << " m_audioDeviceName: " << settings.m_audioDeviceName + << " force: " << force; + + QList reverseAPIKeys; + + if((m_settings.m_inputFrequencyOffset != settings.m_inputFrequencyOffset) || force) { + reverseAPIKeys.append("inputFrequencyOffset"); + } + if((m_settings.m_rfBandwidth != settings.m_rfBandwidth) || force) { + reverseAPIKeys.append("rfBandwidth"); + } + if((m_settings.m_lowCutoff != settings.m_lowCutoff) || force) { + reverseAPIKeys.append("lowCutoff"); + } + + if((m_settings.m_rfBandwidth != settings.m_rfBandwidth) || + (m_settings.m_lowCutoff != settings.m_lowCutoff) || force) + { + float band, lowCutoff; + + band = settings.m_rfBandwidth; + lowCutoff = settings.m_lowCutoff; + + if (band < 0) { + band = -band; + lowCutoff = -lowCutoff; + m_usb = false; + } else { + m_usb = true; + } + + if (band < 100.0f) + { + band = 100.0f; + lowCutoff = 0; + } + + m_Bandwidth = band; + m_LowCutoff = lowCutoff; + + m_settingsMutex.lock(); + m_interpolator.create(16, m_inputSampleRate, m_Bandwidth * 1.5f, 2.0f); + m_interpolatorDistanceRemain = 0; + m_interpolatorDistance = (Real) m_inputSampleRate / (Real) m_audioSampleRate; + SSBFilter->create_filter(m_LowCutoff / (float) m_audioSampleRate, m_Bandwidth / (float) m_audioSampleRate); + DSBFilter->create_dsb_filter((2.0f * m_Bandwidth) / (float) m_audioSampleRate); + m_settingsMutex.unlock(); + } + + if ((m_settings.m_volume != settings.m_volume) || force) + { + reverseAPIKeys.append("volume"); + m_volume = settings.m_volume; + m_volume /= 4.0; // for 3276.8 + } + + if ((m_settings.m_agcTimeLog2 != settings.m_agcTimeLog2) || force) { + reverseAPIKeys.append("agcTimeLog2"); + } + if ((m_settings.m_agcPowerThreshold != settings.m_agcPowerThreshold) || force) { + reverseAPIKeys.append("agcPowerThreshold"); + } + if ((m_settings.m_agcThresholdGate != settings.m_agcThresholdGate) || force) { + reverseAPIKeys.append("agcThresholdGate"); + } + if ((m_settings.m_agcClamping != settings.m_agcClamping) || force) { + reverseAPIKeys.append("agcClamping"); + } + + if ((m_settings.m_agcTimeLog2 != settings.m_agcTimeLog2) || + (m_settings.m_agcPowerThreshold != settings.m_agcPowerThreshold) || + (m_settings.m_agcThresholdGate != settings.m_agcThresholdGate) || + (m_settings.m_agcClamping != settings.m_agcClamping) || force) + { + int agcNbSamples = (m_audioSampleRate / 1000) * (1<getAudioDeviceManager(); + int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName); + audioDeviceManager->addAudioSink(&m_audioFifo, getInputMessageQueue(), audioDeviceIndex); + uint32_t audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex); + + if (m_audioSampleRate != audioSampleRate) { + applyAudioSampleRate(audioSampleRate); + } + } + + if ((m_settings.m_spanLog2 != settings.m_spanLog2) || force) { + reverseAPIKeys.append("spanLog2"); + } + if ((m_settings.m_audioBinaural != settings.m_audioBinaural) || force) { + reverseAPIKeys.append("audioBinaural"); + } + if ((m_settings.m_audioFlipChannels != settings.m_audioFlipChannels) || force) { + reverseAPIKeys.append("audioFlipChannels"); + } + if ((m_settings.m_dsb != settings.m_dsb) || force) { + reverseAPIKeys.append("dsb"); + } + if ((m_settings.m_audioMute != settings.m_audioMute) || force) { + reverseAPIKeys.append("audioMute"); + } + if ((m_settings.m_agc != settings.m_agc) || force) { + reverseAPIKeys.append("agc"); + } + + m_spanLog2 = settings.m_spanLog2; + m_audioBinaual = settings.m_audioBinaural; + m_audioFlipChannels = settings.m_audioFlipChannels; + m_dsb = settings.m_dsb; + m_audioMute = settings.m_audioMute; + m_agcActive = settings.m_agc; + + if (settings.m_useReverseAPI) + { + bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || + (m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) || + (m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) || + (m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex) || + (m_settings.m_reverseAPIChannelIndex != settings.m_reverseAPIChannelIndex); + webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force); + } + + m_settings = settings; +} + +QByteArray FreeDVDemod::serialize() const +{ + return m_settings.serialize(); +} + +bool FreeDVDemod::deserialize(const QByteArray& data) +{ + if (m_settings.deserialize(data)) + { + MsgConfigureFreeDVDemod *msg = MsgConfigureFreeDVDemod::create(m_settings, true); + m_inputMessageQueue.push(msg); + return true; + } + else + { + m_settings.resetToDefaults(); + MsgConfigureFreeDVDemod *msg = MsgConfigureFreeDVDemod::create(m_settings, true); + m_inputMessageQueue.push(msg); + return false; + } +} + +int FreeDVDemod::webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setSsbDemodSettings(new SWGSDRangel::SWGSSBDemodSettings()); + response.getSsbDemodSettings()->init(); + webapiFormatChannelSettings(response, m_settings); + return 200; +} + +int FreeDVDemod::webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + FreeDVDemodSettings settings = m_settings; + bool frequencyOffsetChanged = false; + + if (channelSettingsKeys.contains("inputFrequencyOffset")) + { + settings.m_inputFrequencyOffset = response.getSsbDemodSettings()->getInputFrequencyOffset(); + frequencyOffsetChanged = true; + } + if (channelSettingsKeys.contains("rfBandwidth")) { + settings.m_rfBandwidth = response.getSsbDemodSettings()->getRfBandwidth(); + } + if (channelSettingsKeys.contains("lowCutoff")) { + settings.m_lowCutoff = response.getSsbDemodSettings()->getLowCutoff(); + } + if (channelSettingsKeys.contains("volume")) { + settings.m_volume = response.getSsbDemodSettings()->getVolume(); + } + if (channelSettingsKeys.contains("spanLog2")) { + settings.m_spanLog2 = response.getSsbDemodSettings()->getSpanLog2(); + } + if (channelSettingsKeys.contains("audioBinaural")) { + settings.m_audioBinaural = response.getSsbDemodSettings()->getAudioBinaural() != 0; + } + if (channelSettingsKeys.contains("audioFlipChannels")) { + settings.m_audioFlipChannels = response.getSsbDemodSettings()->getAudioFlipChannels() != 0; + } + if (channelSettingsKeys.contains("dsb")) { + settings.m_dsb = response.getSsbDemodSettings()->getDsb() != 0; + } + if (channelSettingsKeys.contains("audioMute")) { + settings.m_audioMute = response.getSsbDemodSettings()->getAudioMute() != 0; + } + if (channelSettingsKeys.contains("agc")) { + settings.m_agc = response.getSsbDemodSettings()->getAgc() != 0; + } + if (channelSettingsKeys.contains("agcClamping")) { + settings.m_agcClamping = response.getSsbDemodSettings()->getAgcClamping() != 0; + } + if (channelSettingsKeys.contains("agcTimeLog2")) { + settings.m_agcTimeLog2 = response.getSsbDemodSettings()->getAgcTimeLog2(); + } + if (channelSettingsKeys.contains("agcPowerThreshold")) { + settings.m_agcPowerThreshold = response.getSsbDemodSettings()->getAgcPowerThreshold(); + } + if (channelSettingsKeys.contains("agcThresholdGate")) { + settings.m_agcThresholdGate = response.getSsbDemodSettings()->getAgcThresholdGate(); + } + if (channelSettingsKeys.contains("rgbColor")) { + settings.m_rgbColor = response.getSsbDemodSettings()->getRgbColor(); + } + if (channelSettingsKeys.contains("title")) { + settings.m_title = *response.getSsbDemodSettings()->getTitle(); + } + if (channelSettingsKeys.contains("audioDeviceName")) { + settings.m_audioDeviceName = *response.getSsbDemodSettings()->getAudioDeviceName(); + } + + if (frequencyOffsetChanged) + { + MsgConfigureChannelizer* channelConfigMsg = MsgConfigureChannelizer::create( + m_audioSampleRate, settings.m_inputFrequencyOffset); + m_inputMessageQueue.push(channelConfigMsg); + } + + MsgConfigureFreeDVDemod *msg = MsgConfigureFreeDVDemod::create(settings, force); + m_inputMessageQueue.push(msg); + + qDebug("FreeDVDemod::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue); + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureFreeDVDemod *msgToGUI = MsgConfigureFreeDVDemod::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatChannelSettings(response, settings); + + return 200; +} + +int FreeDVDemod::webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setSsbDemodReport(new SWGSDRangel::SWGSSBDemodReport()); + response.getSsbDemodReport()->init(); + webapiFormatChannelReport(response); + return 200; +} + +void FreeDVDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const FreeDVDemodSettings& settings) +{ + response.getSsbDemodSettings()->setAudioMute(settings.m_audioMute ? 1 : 0); + response.getSsbDemodSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + response.getSsbDemodSettings()->setRfBandwidth(settings.m_rfBandwidth); + response.getSsbDemodSettings()->setLowCutoff(settings.m_lowCutoff); + response.getSsbDemodSettings()->setVolume(settings.m_volume); + response.getSsbDemodSettings()->setSpanLog2(settings.m_spanLog2); + response.getSsbDemodSettings()->setAudioBinaural(settings.m_audioBinaural ? 1 : 0); + response.getSsbDemodSettings()->setAudioFlipChannels(settings.m_audioFlipChannels ? 1 : 0); + response.getSsbDemodSettings()->setDsb(settings.m_dsb ? 1 : 0); + response.getSsbDemodSettings()->setAudioMute(settings.m_audioMute ? 1 : 0); + response.getSsbDemodSettings()->setAgc(settings.m_agc ? 1 : 0); + response.getSsbDemodSettings()->setAgcClamping(settings.m_agcClamping ? 1 : 0); + response.getSsbDemodSettings()->setAgcTimeLog2(settings.m_agcTimeLog2); + response.getSsbDemodSettings()->setAgcPowerThreshold(settings.m_agcPowerThreshold); + response.getSsbDemodSettings()->setAgcThresholdGate(settings.m_agcThresholdGate); + response.getSsbDemodSettings()->setRgbColor(settings.m_rgbColor); + + if (response.getSsbDemodSettings()->getTitle()) { + *response.getSsbDemodSettings()->getTitle() = settings.m_title; + } else { + response.getSsbDemodSettings()->setTitle(new QString(settings.m_title)); + } + + if (response.getSsbDemodSettings()->getAudioDeviceName()) { + *response.getSsbDemodSettings()->getAudioDeviceName() = settings.m_audioDeviceName; + } else { + response.getSsbDemodSettings()->setAudioDeviceName(new QString(settings.m_audioDeviceName)); + } +} + +void FreeDVDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response) +{ + double magsqAvg, magsqPeak; + int nbMagsqSamples; + getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples); + + response.getSsbDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg)); + response.getSsbDemodReport()->setSquelch(m_audioActive ? 1 : 0); + response.getSsbDemodReport()->setAudioSampleRate(m_audioSampleRate); + response.getSsbDemodReport()->setChannelSampleRate(m_inputSampleRate); +} + +void FreeDVDemod::webapiReverseSendSettings(QList& channelSettingsKeys, const FreeDVDemodSettings& settings, bool force) +{ + SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings(); + swgChannelSettings->setTx(0); + swgChannelSettings->setChannelType(new QString("SSBDemod")); + swgChannelSettings->setSsbDemodSettings(new SWGSDRangel::SWGSSBDemodSettings()); + SWGSDRangel::SWGSSBDemodSettings *swgSSBDemodSettings = swgChannelSettings->getSsbDemodSettings(); + + // transfer data that has been modified. When force is on transfer all data except reverse API data + + if (channelSettingsKeys.contains("inputFrequencyOffset") || force) { + swgSSBDemodSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset); + } + if (channelSettingsKeys.contains("rfBandwidth") || force) { + swgSSBDemodSettings->setRfBandwidth(settings.m_rfBandwidth); + } + if (channelSettingsKeys.contains("lowCutoff") || force) { + swgSSBDemodSettings->setLowCutoff(settings.m_lowCutoff); + } + if (channelSettingsKeys.contains("volume") || force) { + swgSSBDemodSettings->setVolume(settings.m_volume); + } + if (channelSettingsKeys.contains("spanLog2") || force) { + swgSSBDemodSettings->setSpanLog2(settings.m_spanLog2); + } + if (channelSettingsKeys.contains("audioBinaural") || force) { + swgSSBDemodSettings->setAudioBinaural(settings.m_audioBinaural ? 1 : 0); + } + if (channelSettingsKeys.contains("audioFlipChannels") || force) { + swgSSBDemodSettings->setAudioFlipChannels(settings.m_audioFlipChannels ? 1 : 0); + } + if (channelSettingsKeys.contains("dsb") || force) { + swgSSBDemodSettings->setDsb(settings.m_dsb ? 1 : 0); + } + if (channelSettingsKeys.contains("audioMute") || force) { + swgSSBDemodSettings->setAudioMute(settings.m_audioMute ? 1 : 0); + } + if (channelSettingsKeys.contains("agc") || force) { + swgSSBDemodSettings->setAgc(settings.m_agc ? 1 : 0); + } + if (channelSettingsKeys.contains("agcClamping") || force) { + swgSSBDemodSettings->setAgcClamping(settings.m_agcClamping ? 1 : 0); + } + if (channelSettingsKeys.contains("agcTimeLog2") || force) { + swgSSBDemodSettings->setAgcTimeLog2(settings.m_agcTimeLog2); + } + if (channelSettingsKeys.contains("agcPowerThreshold") || force) { + swgSSBDemodSettings->setAgcPowerThreshold(settings.m_agcPowerThreshold); + } + if (channelSettingsKeys.contains("agcThresholdGate") || force) { + swgSSBDemodSettings->setAgcThresholdGate(settings.m_agcThresholdGate); + } + if (channelSettingsKeys.contains("rgbColor") || force) { + swgSSBDemodSettings->setRgbColor(settings.m_rgbColor); + } + if (channelSettingsKeys.contains("title") || force) { + swgSSBDemodSettings->setTitle(new QString(settings.m_title)); + } + if (channelSettingsKeys.contains("audioDeviceName") || force) { + swgSSBDemodSettings->setAudioDeviceName(new QString(settings.m_audioDeviceName)); + } + + QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings") + .arg(settings.m_reverseAPIAddress) + .arg(settings.m_reverseAPIPort) + .arg(settings.m_reverseAPIDeviceIndex) + .arg(settings.m_reverseAPIChannelIndex); + m_networkRequest.setUrl(QUrl(channelSettingsURL)); + m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QBuffer *buffer=new QBuffer(); + buffer->open((QBuffer::ReadWrite)); + buffer->write(swgChannelSettings->asJson().toUtf8()); + buffer->seek(0); + + // Always use PATCH to avoid passing reverse API settings + m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer); + + delete swgChannelSettings; +} + +void FreeDVDemod::networkManagerFinished(QNetworkReply *reply) +{ + QNetworkReply::NetworkError replyError = reply->error(); + + if (replyError) + { + qWarning() << "FreeDVDemod::networkManagerFinished:" + << " error(" << (int) replyError + << "): " << replyError + << ": " << reply->errorString(); + return; + } + + QString answer = reply->readAll(); + answer.chop(1); // remove last \n + qDebug("FreeDVDemod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str()); +} diff --git a/plugins/channelrx/demodfreedv/freedvdemod.h b/plugins/channelrx/demodfreedv/freedvdemod.h new file mode 100644 index 000000000..4eec60f21 --- /dev/null +++ b/plugins/channelrx/demodfreedv/freedvdemod.h @@ -0,0 +1,333 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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_FREEDVDEMOD_H +#define INCLUDE_FREEDVDEMOD_H + +#include + +#include +#include + +#include "dsp/basebandsamplesink.h" +#include "channel/channelsinkapi.h" +#include "dsp/ncof.h" +#include "dsp/interpolator.h" +#include "dsp/fftfilt.h" +#include "dsp/agc.h" +#include "audio/audiofifo.h" +#include "util/message.h" +#include "util/doublebufferfifo.h" + +#include "freedvdemodsettings.h" + +#define ssbFftLen 1024 +#define agcTarget 3276.8 // -10 dB amplitude => -20 dB power: center of normal signal + +class QNetworkAccessManager; +class QNetworkReply; +class DeviceSourceAPI; +class ThreadedBasebandSampleSink; +class DownChannelizer; + +class FreeDVDemod : public BasebandSampleSink, public ChannelSinkAPI { + Q_OBJECT +public: + class MsgConfigureFreeDVDemod : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const FreeDVDemodSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureFreeDVDemod* create(const FreeDVDemodSettings& settings, bool force) + { + return new MsgConfigureFreeDVDemod(settings, force); + } + + private: + FreeDVDemodSettings m_settings; + bool m_force; + + MsgConfigureFreeDVDemod(const FreeDVDemodSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgConfigureChannelizer : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getSampleRate() const { return m_sampleRate; } + int getCenterFrequency() const { return m_centerFrequency; } + + static MsgConfigureChannelizer* create(int sampleRate, int centerFrequency) + { + return new MsgConfigureChannelizer(sampleRate, centerFrequency); + } + + private: + int m_sampleRate; + int m_centerFrequency; + + MsgConfigureChannelizer(int sampleRate, int centerFrequency) : + Message(), + m_sampleRate(sampleRate), + m_centerFrequency(centerFrequency) + { } + }; + + FreeDVDemod(DeviceSourceAPI *deviceAPI); + virtual ~FreeDVDemod(); + virtual void destroy() { delete this; } + void setSampleSink(BasebandSampleSink* sampleSink) { m_sampleSink = sampleSink; } + + void configure(MessageQueue* messageQueue, + Real Bandwidth, + Real LowCutoff, + Real volume, + int spanLog2, + bool audioBinaural, + bool audioFlipChannels, + bool dsb, + bool audioMute, + bool agc, + bool agcClamping, + int agcTimeLog2, + int agcPowerThreshold, + int agcThresholdGate); + + virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool positiveOnly); + virtual void start(); + virtual void stop(); + virtual bool handleMessage(const Message& cmd); + + virtual void getIdentifier(QString& id) { id = objectName(); } + virtual void getTitle(QString& title) { title = m_settings.m_title; } + virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; } + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + uint32_t getAudioSampleRate() const { return m_audioSampleRate; } + double getMagSq() const { return m_magsq; } + bool getAudioActive() const { return m_audioActive; } + + void getMagSqLevels(double& avg, double& peak, int& nbSamples) + { + if (m_magsqCount > 0) + { + m_magsq = m_magsqSum / m_magsqCount; + m_magSqLevelStore.m_magsq = m_magsq; + m_magSqLevelStore.m_magsqPeak = m_magsqPeak; + } + + avg = m_magSqLevelStore.m_magsq; + peak = m_magSqLevelStore.m_magsqPeak; + nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount; + + m_magsqSum = 0.0f; + m_magsqPeak = 0.0f; + m_magsqCount = 0; + } + + virtual int webapiSettingsGet( + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& channelSettingsKeys, + SWGSDRangel::SWGChannelSettings& response, + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGChannelReport& response, + QString& errorMessage); + + static const QString m_channelIdURI; + static const QString m_channelId; + +private: + struct MagSqLevelsStore + { + MagSqLevelsStore() : + m_magsq(1e-12), + m_magsqPeak(1e-12) + {} + double m_magsq; + double m_magsqPeak; + }; + + class MsgConfigureFreeDVDemodPrivate : public Message { + MESSAGE_CLASS_DECLARATION + + public: + Real getBandwidth() const { return m_Bandwidth; } + Real getLoCutoff() const { return m_LowCutoff; } + Real getVolume() const { return m_volume; } + int getSpanLog2() const { return m_spanLog2; } + bool getAudioBinaural() const { return m_audioBinaural; } + bool getAudioFlipChannels() const { return m_audioFlipChannels; } + bool getDSB() const { return m_dsb; } + bool getAudioMute() const { return m_audioMute; } + bool getAGC() const { return m_agc; } + bool getAGCClamping() const { return m_agcClamping; } + int getAGCTimeLog2() const { return m_agcTimeLog2; } + int getAGCPowerThershold() const { return m_agcPowerThreshold; } + int getAGCThersholdGate() const { return m_agcThresholdGate; } + + static MsgConfigureFreeDVDemodPrivate* create(Real Bandwidth, + Real LowCutoff, + Real volume, + int spanLog2, + bool audioBinaural, + bool audioFlipChannels, + bool dsb, + bool audioMute, + bool agc, + bool agcClamping, + int agcTimeLog2, + int agcPowerThreshold, + int agcThresholdGate) + { + return new MsgConfigureFreeDVDemodPrivate( + Bandwidth, + LowCutoff, + volume, + spanLog2, + audioBinaural, + audioFlipChannels, + dsb, + audioMute, + agc, + agcClamping, + agcTimeLog2, + agcPowerThreshold, + agcThresholdGate); + } + + private: + Real m_Bandwidth; + Real m_LowCutoff; + Real m_volume; + int m_spanLog2; + bool m_audioBinaural; + bool m_audioFlipChannels; + bool m_dsb; + bool m_audioMute; + bool m_agc; + bool m_agcClamping; + int m_agcTimeLog2; + int m_agcPowerThreshold; + int m_agcThresholdGate; + + MsgConfigureFreeDVDemodPrivate(Real Bandwidth, + Real LowCutoff, + Real volume, + int spanLog2, + bool audioBinaural, + bool audioFlipChannels, + bool dsb, + bool audioMute, + bool agc, + bool agcClamping, + int agcTimeLog2, + int agcPowerThreshold, + int agcThresholdGate) : + Message(), + m_Bandwidth(Bandwidth), + m_LowCutoff(LowCutoff), + m_volume(volume), + m_spanLog2(spanLog2), + m_audioBinaural(audioBinaural), + m_audioFlipChannels(audioFlipChannels), + m_dsb(dsb), + m_audioMute(audioMute), + m_agc(agc), + m_agcClamping(agcClamping), + m_agcTimeLog2(agcTimeLog2), + m_agcPowerThreshold(agcPowerThreshold), + m_agcThresholdGate(agcThresholdGate) + { } + }; + + DeviceSourceAPI *m_deviceAPI; + ThreadedBasebandSampleSink* m_threadedChannelizer; + DownChannelizer* m_channelizer; + FreeDVDemodSettings m_settings; + + Real m_Bandwidth; + Real m_LowCutoff; + Real m_volume; + int m_spanLog2; + fftfilt::cmplx m_sum; + int m_undersampleCount; + int m_inputSampleRate; + int m_inputFrequencyOffset; + bool m_audioBinaual; + bool m_audioFlipChannels; + bool m_usb; + bool m_dsb; + bool m_audioMute; + double m_magsq; + double m_magsqSum; + double m_magsqPeak; + int m_magsqCount; + MagSqLevelsStore m_magSqLevelStore; + MagAGC m_agc; + bool m_agcActive; + bool m_agcClamping; + int m_agcNbSamples; //!< number of audio (48 kHz) samples for AGC averaging + double m_agcPowerThreshold; //!< AGC power threshold (linear) + int m_agcThresholdGate; //!< Gate length in number of samples befor threshold triggers + DoubleBufferFIFO m_squelchDelayLine; + bool m_audioActive; //!< True if an audio signal is produced (no AGC or AGC and above threshold) + + NCOF m_nco; + Interpolator m_interpolator; + Real m_interpolatorDistance; + Real m_interpolatorDistanceRemain; + fftfilt* SSBFilter; + fftfilt* DSBFilter; + + BasebandSampleSink* m_sampleSink; + SampleVector m_sampleBuffer; + + AudioVector m_audioBuffer; + uint m_audioBufferFill; + AudioFifo m_audioFifo; + quint32 m_audioSampleRate; + + QNetworkAccessManager *m_networkManager; + QNetworkRequest m_networkRequest; + + QMutex m_settingsMutex; + + void applyChannelSettings(int inputSampleRate, int inputFrequencyOffset, bool force = false); + void applySettings(const FreeDVDemodSettings& settings, bool force = false); + void applyAudioSampleRate(int sampleRate); + void webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const FreeDVDemodSettings& settings); + void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response); + void webapiReverseSendSettings(QList& channelSettingsKeys, const FreeDVDemodSettings& settings, bool force); + +private slots: + void networkManagerFinished(QNetworkReply *reply); +}; + +#endif // INCLUDE_FREEDVDEMOD_H diff --git a/plugins/channelrx/demodfreedv/freedvdemodgui.cpp b/plugins/channelrx/demodfreedv/freedvdemodgui.cpp new file mode 100644 index 000000000..7e2c731ef --- /dev/null +++ b/plugins/channelrx/demodfreedv/freedvdemodgui.cpp @@ -0,0 +1,638 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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 "freedvdemodgui.h" + +#include "device/devicesourceapi.h" +#include "device/deviceuiset.h" + +#include "ui_freedvdemodgui.h" +#include "dsp/spectrumvis.h" +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "gui/glspectrum.h" +#include "gui/basicchannelsettingsdialog.h" +#include "plugin/pluginapi.h" +#include "util/simpleserializer.h" +#include "util/db.h" +#include "gui/crightclickenabler.h" +#include "gui/audioselectdialog.h" +#include "mainwindow.h" +#include "freedvdemod.h" + +FreeDVDemodGUI* FreeDVDemodGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) +{ + FreeDVDemodGUI* gui = new FreeDVDemodGUI(pluginAPI, deviceUISet, rxChannel); + return gui; +} + +void FreeDVDemodGUI::destroy() +{ + delete this; +} + +void FreeDVDemodGUI::setName(const QString& name) +{ + setObjectName(name); +} + +QString FreeDVDemodGUI::getName() const +{ + return objectName(); +} + +qint64 FreeDVDemodGUI::getCenterFrequency() const +{ + return m_channelMarker.getCenterFrequency(); +} + +void FreeDVDemodGUI::setCenterFrequency(qint64 centerFrequency) +{ + m_channelMarker.setCenterFrequency(centerFrequency); + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); + applySettings(); +} + +void FreeDVDemodGUI::resetToDefaults() +{ + m_settings.resetToDefaults(); +} + +QByteArray FreeDVDemodGUI::serialize() const +{ + return m_settings.serialize(); +} + +bool FreeDVDemodGUI::deserialize(const QByteArray& data) +{ + if(m_settings.deserialize(data)) + { + displaySettings(); + applySettings(true); // will have true + return true; + } + else + { + m_settings.resetToDefaults(); + displaySettings(); + applySettings(true); // will have true + return false; + } +} + +bool FreeDVDemodGUI::handleMessage(const Message& message) +{ + if (FreeDVDemod::MsgConfigureFreeDVDemod::match(message)) + { + qDebug("FreeDVDemodGUI::handleMessage: FreeDVDemodGUI::MsgConfigureFreeDVDemod"); + const FreeDVDemod::MsgConfigureFreeDVDemod& cfg = (FreeDVDemod::MsgConfigureFreeDVDemod&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else if (DSPConfigureAudio::match(message)) + { + qDebug("FreeDVDemodGUI::handleMessage: DSPConfigureAudio: %d", m_freeDVDemod->getAudioSampleRate()); + applyBandwidths(5 - ui->spanLog2->value()); // will update spectrum details with new sample rate + return true; + } + else + { + return false; + } +} + +void FreeDVDemodGUI::handleInputMessages() +{ + Message* message; + + while ((message = getInputMessageQueue()->pop()) != 0) + { + if (handleMessage(*message)) + { + delete message; + } + } +} + +void FreeDVDemodGUI::channelMarkerChangedByCursor() +{ + ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); + applySettings(); +} + +void FreeDVDemodGUI::channelMarkerHighlightedByCursor() +{ + setHighlighted(m_channelMarker.getHighlighted()); +} + +void FreeDVDemodGUI::on_audioBinaural_toggled(bool binaural) +{ + m_audioBinaural = binaural; + m_settings.m_audioBinaural = binaural; + applySettings(); +} + +void FreeDVDemodGUI::on_audioFlipChannels_toggled(bool flip) +{ + m_audioFlipChannels = flip; + m_settings.m_audioFlipChannels = flip; + applySettings(); +} + +void FreeDVDemodGUI::on_dsb_toggled(bool dsb) +{ + ui->flipSidebands->setEnabled(!dsb); + applyBandwidths(5 - ui->spanLog2->value()); +} + +void FreeDVDemodGUI::on_deltaFrequency_changed(qint64 value) +{ + m_channelMarker.setCenterFrequency(value); + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); + applySettings(); +} + +void FreeDVDemodGUI::on_BW_valueChanged(int value) +{ + (void) value; + applyBandwidths(5 - ui->spanLog2->value()); +} + +void FreeDVDemodGUI::on_lowCut_valueChanged(int value) +{ + (void) value; + applyBandwidths(5 - ui->spanLog2->value()); +} + +void FreeDVDemodGUI::on_volume_valueChanged(int value) +{ + ui->volumeText->setText(QString("%1").arg(value / 10.0, 0, 'f', 1)); + m_settings.m_volume = value / 10.0; + applySettings(); +} + +void FreeDVDemodGUI::on_agc_toggled(bool checked) +{ + m_settings.m_agc = checked; + applySettings(); +} + +void FreeDVDemodGUI::on_agcClamping_toggled(bool checked) +{ + m_settings.m_agcClamping = checked; + applySettings(); +} + +void FreeDVDemodGUI::on_agcTimeLog2_valueChanged(int value) +{ + QString s = QString::number((1<agcTimeText->setText(s); + m_settings.m_agcTimeLog2 = value; + applySettings(); +} + +void FreeDVDemodGUI::on_agcPowerThreshold_valueChanged(int value) +{ + displayAGCPowerThreshold(value); + m_settings.m_agcPowerThreshold = value; + applySettings(); +} + +void FreeDVDemodGUI::on_agcThresholdGate_valueChanged(int value) +{ + QString s = QString::number(value, 'f', 0); + ui->agcThresholdGateText->setText(s); + m_settings.m_agcThresholdGate = value; + applySettings(); +} + +void FreeDVDemodGUI::on_audioMute_toggled(bool checked) +{ + m_audioMute = checked; + m_settings.m_audioMute = checked; + applySettings(); +} + +void FreeDVDemodGUI::on_spanLog2_valueChanged(int value) +{ + if ((value < 0) || (value > 4)) { + return; + } + + applyBandwidths(5 - ui->spanLog2->value()); +} + +void FreeDVDemodGUI::on_flipSidebands_clicked(bool checked) +{ + (void) checked; + int bwValue = ui->BW->value(); + int lcValue = ui->lowCut->value(); + ui->BW->setValue(-bwValue); + ui->lowCut->setValue(-lcValue); +} + +void FreeDVDemodGUI::onMenuDialogCalled(const QPoint &p) +{ + BasicChannelSettingsDialog dialog(&m_channelMarker, this); + dialog.setUseReverseAPI(m_settings.m_useReverseAPI); + dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress); + dialog.setReverseAPIPort(m_settings.m_reverseAPIPort); + dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex); + dialog.setReverseAPIChannelIndex(m_settings.m_reverseAPIChannelIndex); + + dialog.move(p); + dialog.exec(); + + m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency(); + m_settings.m_rgbColor = m_channelMarker.getColor().rgb(); + m_settings.m_title = m_channelMarker.getTitle(); + m_settings.m_useReverseAPI = dialog.useReverseAPI(); + m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress(); + m_settings.m_reverseAPIPort = dialog.getReverseAPIPort(); + m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex(); + m_settings.m_reverseAPIChannelIndex = dialog.getReverseAPIChannelIndex(); + + setWindowTitle(m_settings.m_title); + setTitleColor(m_settings.m_rgbColor); + + applySettings(); +} + +void FreeDVDemodGUI::onWidgetRolled(QWidget* widget, bool rollDown) +{ + (void) widget; + (void) rollDown; +} + +FreeDVDemodGUI::FreeDVDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) : + RollupWidget(parent), + ui(new Ui::FreeDVDemodGUI), + m_pluginAPI(pluginAPI), + m_deviceUISet(deviceUISet), + m_channelMarker(this), + m_doApplySettings(true), + m_spectrumRate(6000), + m_audioBinaural(false), + m_audioFlipChannels(false), + m_audioMute(false), + m_squelchOpen(false) +{ + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose, true); + connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool))); + connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); + + m_spectrumVis = new SpectrumVis(SDR_RX_SCALEF, ui->glSpectrum); + m_freeDVDemod = (FreeDVDemod*) rxChannel; + m_freeDVDemod->setSampleSink(m_spectrumVis); + m_freeDVDemod->setMessageQueueToGUI(getInputMessageQueue()); + + resetToDefaults(); + + ui->glSpectrum->setCenterFrequency(m_spectrumRate/2); + ui->glSpectrum->setSampleRate(m_spectrumRate); + ui->glSpectrum->setDisplayWaterfall(true); + ui->glSpectrum->setDisplayMaxHold(true); + ui->glSpectrum->setSsbSpectrum(true); + ui->glSpectrum->connectTimer(MainWindow::getInstance()->getMasterTimer()); + + connect(&MainWindow::getInstance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); + + CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->audioMute); + connect(audioMuteRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(audioSelect())); + + ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03))); + ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold)); + ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999); + ui->channelPowerMeter->setColorTheme(LevelMeterSignalDB::ColorGreenAndBlue); + + m_channelMarker.setVisible(true); // activate signal on the last setting only + + m_settings.setChannelMarker(&m_channelMarker); + m_settings.setSpectrumGUI(ui->spectrumGUI); + + m_deviceUISet->registerRxChannelInstance(FreeDVDemod::m_channelIdURI, this); + m_deviceUISet->addChannelMarker(&m_channelMarker); + m_deviceUISet->addRollupWidget(this); + + connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor())); + connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor())); + connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + + ui->spectrumGUI->setBuddies(m_spectrumVis->getInputMessageQueue(), m_spectrumVis, ui->glSpectrum); + + m_iconDSBUSB.addPixmap(QPixmap("://dsb.png"), QIcon::Normal, QIcon::On); + m_iconDSBUSB.addPixmap(QPixmap("://usb.png"), QIcon::Normal, QIcon::Off); + m_iconDSBLSB.addPixmap(QPixmap("://dsb.png"), QIcon::Normal, QIcon::On); + m_iconDSBLSB.addPixmap(QPixmap("://lsb.png"), QIcon::Normal, QIcon::Off); + + displaySettings(); + applyBandwidths(5 - ui->spanLog2->value(), true); // does applySettings(true) +} + +FreeDVDemodGUI::~FreeDVDemodGUI() +{ + m_deviceUISet->removeRxChannelInstance(this); + delete m_freeDVDemod; // TODO: check this: when the GUI closes it has to delete the demodulator + delete m_spectrumVis; + delete ui; +} + +bool FreeDVDemodGUI::blockApplySettings(bool block) +{ + bool ret = !m_doApplySettings; + m_doApplySettings = !block; + return ret; +} + +void FreeDVDemodGUI::applySettings(bool force) +{ + if (m_doApplySettings) + { + FreeDVDemod::MsgConfigureChannelizer* channelConfigMsg = FreeDVDemod::MsgConfigureChannelizer::create( + m_freeDVDemod->getAudioSampleRate(), m_channelMarker.getCenterFrequency()); + m_freeDVDemod->getInputMessageQueue()->push(channelConfigMsg); + + FreeDVDemod::MsgConfigureFreeDVDemod* message = FreeDVDemod::MsgConfigureFreeDVDemod::create( m_settings, force); + m_freeDVDemod->getInputMessageQueue()->push(message); + } +} + +void FreeDVDemodGUI::applyBandwidths(int spanLog2, bool force) +{ + bool dsb = ui->dsb->isChecked(); + //int spanLog2 = ui->spanLog2->value(); + m_spectrumRate = m_freeDVDemod->getAudioSampleRate() / (1<BW->value(); + int lw = ui->lowCut->value(); + int bwMax = m_freeDVDemod->getAudioSampleRate() / (100*(1<BW->setTickInterval(tickInterval); + ui->lowCut->setTickInterval(tickInterval); + + bw = bw < -bwMax ? -bwMax : bw > bwMax ? bwMax : bw; + + if (bw < 0) { + lw = lw < bw+1 ? bw+1 : lw < 0 ? lw : 0; + } else if (bw > 0) { + lw = lw > bw-1 ? bw-1 : lw < 0 ? 0 : lw; + } else { + lw = 0; + } + + if (dsb) + { + bw = bw < 0 ? -bw : bw; + lw = 0; + } + + QString spanStr = QString::number(bwMax/10.0, 'f', 1); + QString bwStr = QString::number(bw/10.0, 'f', 1); + QString lwStr = QString::number(lw/10.0, 'f', 1); + + if (dsb) + { + ui->BWText->setText(tr("%1%2k").arg(QChar(0xB1, 0x00)).arg(bwStr)); + ui->spanText->setText(tr("%1%2k").arg(QChar(0xB1, 0x00)).arg(spanStr)); + ui->scaleMinus->setText("0"); + ui->scaleCenter->setText(""); + ui->scalePlus->setText(tr("%1").arg(QChar(0xB1, 0x00))); + ui->lsbLabel->setText(""); + ui->usbLabel->setText(""); + ui->glSpectrum->setCenterFrequency(0); + ui->glSpectrum->setSampleRate(2*m_spectrumRate); + ui->glSpectrum->setSsbSpectrum(false); + ui->glSpectrum->setLsbDisplay(false); + } + else + { + ui->BWText->setText(tr("%1k").arg(bwStr)); + ui->spanText->setText(tr("%1k").arg(spanStr)); + ui->scaleMinus->setText("-"); + ui->scaleCenter->setText("0"); + ui->scalePlus->setText("+"); + ui->lsbLabel->setText("LSB"); + ui->usbLabel->setText("USB"); + ui->glSpectrum->setCenterFrequency(m_spectrumRate/2); + ui->glSpectrum->setSampleRate(m_spectrumRate); + ui->glSpectrum->setSsbSpectrum(true); + ui->glSpectrum->setLsbDisplay(bw < 0); + } + + ui->lowCutText->setText(tr("%1k").arg(lwStr)); + + + ui->BW->blockSignals(true); + ui->lowCut->blockSignals(true); + + ui->BW->setMaximum(bwMax); + ui->BW->setMinimum(dsb ? 0 : -bwMax); + ui->BW->setValue(bw); + + ui->lowCut->setMaximum(dsb ? 0 : bwMax); + ui->lowCut->setMinimum(dsb ? 0 : -bwMax); + ui->lowCut->setValue(lw); + + ui->lowCut->blockSignals(false); + ui->BW->blockSignals(false); + + ui->channelPowerMeter->setRange(FreeDVDemodSettings::m_minPowerThresholdDB, 0); + + m_settings.m_dsb = dsb; + m_settings.m_spanLog2 = spanLog2; + m_settings.m_rfBandwidth = bw * 100; + m_settings.m_lowCutoff = lw * 100; + + applySettings(force); + + bool wasBlocked = blockApplySettings(true); + m_channelMarker.setBandwidth(bw * 200); + m_channelMarker.setSidebands(dsb ? ChannelMarker::dsb : bw < 0 ? ChannelMarker::lsb : ChannelMarker::usb); + ui->dsb->setIcon(bw < 0 ? m_iconDSBLSB: m_iconDSBUSB); + if (!dsb) { m_channelMarker.setLowCutoff(lw * 100); } + blockApplySettings(wasBlocked); +} + +void FreeDVDemodGUI::displaySettings() +{ + m_channelMarker.blockSignals(true); + m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset); + m_channelMarker.setBandwidth(m_settings.m_rfBandwidth * 2); + m_channelMarker.setTitle(m_settings.m_title); + m_channelMarker.setLowCutoff(m_settings.m_lowCutoff); + + ui->flipSidebands->setEnabled(!m_settings.m_dsb); + + if (m_settings.m_dsb) { + m_channelMarker.setSidebands(ChannelMarker::dsb); + } else { + if (m_settings.m_rfBandwidth < 0) { + m_channelMarker.setSidebands(ChannelMarker::lsb); + ui->dsb->setIcon(m_iconDSBLSB); + } else { + m_channelMarker.setSidebands(ChannelMarker::usb); + ui->dsb->setIcon(m_iconDSBUSB); + } + } + + m_channelMarker.blockSignals(false); + m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only + + setTitleColor(m_settings.m_rgbColor); + setWindowTitle(m_channelMarker.getTitle()); + + blockApplySettings(true); + + ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); + + ui->agc->setChecked(m_settings.m_agc); + ui->agcClamping->setChecked(m_settings.m_agcClamping); + ui->audioBinaural->setChecked(m_settings.m_audioBinaural); + ui->audioFlipChannels->setChecked(m_settings.m_audioFlipChannels); + ui->audioMute->setChecked(m_settings.m_audioMute); + ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency()); + + // Prevent uncontrolled triggering of applyBandwidths + ui->spanLog2->blockSignals(true); + ui->dsb->blockSignals(true); + ui->BW->blockSignals(true); + + ui->dsb->setChecked(m_settings.m_dsb); + ui->spanLog2->setValue(5 - m_settings.m_spanLog2); + + ui->BW->setValue(m_settings.m_rfBandwidth / 100.0); + QString s = QString::number(m_settings.m_rfBandwidth/1000.0, 'f', 1); + + if (m_settings.m_dsb) + { + ui->BWText->setText(tr("%1%2k").arg(QChar(0xB1, 0x00)).arg(s)); + } + else + { + ui->BWText->setText(tr("%1k").arg(s)); + } + + ui->spanLog2->blockSignals(false); + ui->dsb->blockSignals(false); + ui->BW->blockSignals(false); + + // The only one of the four signals triggering applyBandwidths will trigger it once only with all other values + // set correctly and therefore validate the settings and apply them to dependent widgets + ui->lowCut->setValue(m_settings.m_lowCutoff / 100.0); + ui->lowCutText->setText(tr("%1k").arg(m_settings.m_lowCutoff / 1000.0)); + + ui->volume->setValue(m_settings.m_volume * 10.0); + ui->volumeText->setText(QString("%1").arg(m_settings.m_volume, 0, 'f', 1)); + + ui->agcTimeLog2->setValue(m_settings.m_agcTimeLog2); + s = QString::number((1<agcTimeLog2->value()), 'f', 0); + ui->agcTimeText->setText(s); + + ui->agcPowerThreshold->setValue(m_settings.m_agcPowerThreshold); + displayAGCPowerThreshold(ui->agcPowerThreshold->value()); + + ui->agcThresholdGate->setValue(m_settings.m_agcThresholdGate); + s = QString::number(ui->agcThresholdGate->value(), 'f', 0); + ui->agcThresholdGateText->setText(s); + + blockApplySettings(false); +} + +void FreeDVDemodGUI::displayAGCPowerThreshold(int value) +{ + if (value == FreeDVDemodSettings::m_minPowerThresholdDB) + { + ui->agcPowerThresholdText->setText("---"); + } + else + { + QString s = QString::number(value, 'f', 0); + ui->agcPowerThresholdText->setText(s); + } +} + +void FreeDVDemodGUI::leaveEvent(QEvent*) +{ + m_channelMarker.setHighlighted(false); +} + +void FreeDVDemodGUI::enterEvent(QEvent*) +{ + m_channelMarker.setHighlighted(true); +} + +void FreeDVDemodGUI::audioSelect() +{ + qDebug("FreeDVDemodGUI::audioSelect"); + AudioSelectDialog audioSelect(DSPEngine::instance()->getAudioDeviceManager(), m_settings.m_audioDeviceName); + audioSelect.exec(); + + if (audioSelect.m_selected) + { + m_settings.m_audioDeviceName = audioSelect.m_audioDeviceName; + applySettings(); + } +} + +void FreeDVDemodGUI::tick() +{ + double magsqAvg, magsqPeak; + int nbMagsqSamples; + m_freeDVDemod->getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples); + double powDbAvg = CalcDb::dbPower(magsqAvg); + double powDbPeak = CalcDb::dbPower(magsqPeak); + + ui->channelPowerMeter->levelChanged( + (FreeDVDemodSettings::m_mminPowerThresholdDBf + powDbAvg) / FreeDVDemodSettings::m_mminPowerThresholdDBf, + (FreeDVDemodSettings::m_mminPowerThresholdDBf + powDbPeak) / FreeDVDemodSettings::m_mminPowerThresholdDBf, + nbMagsqSamples); + + if (m_tickCount % 4 == 0) { + ui->channelPower->setText(tr("%1 dB").arg(powDbAvg, 0, 'f', 1)); + } + + bool squelchOpen = m_freeDVDemod->getAudioActive(); + + if (squelchOpen != m_squelchOpen) + { + if (squelchOpen) { + ui->audioMute->setStyleSheet("QToolButton { background-color : green; }"); + } else { + ui->audioMute->setStyleSheet("QToolButton { background:rgb(79,79,79); }"); + } + + m_squelchOpen = squelchOpen; + } + + m_tickCount++; +} diff --git a/plugins/channelrx/demodfreedv/freedvdemodgui.h b/plugins/channelrx/demodfreedv/freedvdemodgui.h new file mode 100644 index 000000000..fd2dc0828 --- /dev/null +++ b/plugins/channelrx/demodfreedv/freedvdemodgui.h @@ -0,0 +1,120 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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_FREEDVDEMODGUI_H +#define INCLUDE_FREEDVDEMODGUI_H + +#include + +#include "plugin/plugininstancegui.h" +#include "gui/rollupwidget.h" +#include "dsp/channelmarker.h" +#include "dsp/movingaverage.h" +#include "util/messagequeue.h" +#include "freedvdemodsettings.h" + +class PluginAPI; +class DeviceUISet; + +class AudioFifo; +class FreeDVDemod; +class SpectrumVis; +class BasebandSampleSink; + +namespace Ui { + class FreeDVDemodGUI; +} + +class FreeDVDemodGUI : public RollupWidget, public PluginInstanceGUI { + Q_OBJECT + +public: + static FreeDVDemodGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel); + virtual 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 MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + virtual bool handleMessage(const Message& message); + +public slots: + void channelMarkerChangedByCursor(); + void channelMarkerHighlightedByCursor(); + +private: + Ui::FreeDVDemodGUI* ui; + PluginAPI* m_pluginAPI; + DeviceUISet* m_deviceUISet; + ChannelMarker m_channelMarker; + FreeDVDemodSettings m_settings; + bool m_doApplySettings; + int m_spectrumRate; + bool m_audioBinaural; + bool m_audioFlipChannels; + bool m_audioMute; + bool m_squelchOpen; + uint32_t m_tickCount; + + FreeDVDemod* m_freeDVDemod; + SpectrumVis* m_spectrumVis; + MessageQueue m_inputMessageQueue; + + QIcon m_iconDSBUSB; + QIcon m_iconDSBLSB; + + explicit FreeDVDemodGUI(PluginAPI* pluginAPI, DeviceUISet* deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0); + virtual ~FreeDVDemodGUI(); + + bool blockApplySettings(bool block); + void applySettings(bool force = false); + void applyBandwidths(int spanLog2, bool force = false); + void displaySettings(); + + void displayAGCPowerThreshold(int value); + + void leaveEvent(QEvent*); + void enterEvent(QEvent*); + +private slots: + void on_deltaFrequency_changed(qint64 value); + void on_audioBinaural_toggled(bool binaural); + void on_audioFlipChannels_toggled(bool flip); + void on_dsb_toggled(bool dsb); + void on_BW_valueChanged(int value); + void on_lowCut_valueChanged(int value); + void on_volume_valueChanged(int value); + void on_agc_toggled(bool checked); + void on_agcClamping_toggled(bool checked); + void on_agcTimeLog2_valueChanged(int value); + void on_agcPowerThreshold_valueChanged(int value); + void on_agcThresholdGate_valueChanged(int value); + void on_audioMute_toggled(bool checked); + void on_spanLog2_valueChanged(int value); + void on_flipSidebands_clicked(bool checked); + void onWidgetRolled(QWidget* widget, bool rollDown); + void onMenuDialogCalled(const QPoint& p); + void handleInputMessages(); + void audioSelect(); + void tick(); +}; + +#endif // INCLUDE_FREEDVDEMODGUI_H diff --git a/plugins/channelrx/demodfreedv/freedvdemodgui.ui b/plugins/channelrx/demodfreedv/freedvdemodgui.ui new file mode 100644 index 000000000..f28c54b12 --- /dev/null +++ b/plugins/channelrx/demodfreedv/freedvdemodgui.ui @@ -0,0 +1,999 @@ + + + FreeDVDemodGUI + + + + 0 + 0 + 412 + 190 + + + + + 0 + 0 + + + + + 412 + 0 + + + + + Liberation Sans + 9 + + + + SSB Demodulator + + + + + 0 + 0 + 410 + 171 + + + + + 410 + 0 + + + + Settings + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + 2 + + + + + + + + 16 + 0 + + + + Df + + + + + + + + 0 + 0 + + + + + 125 + 16 + + + + + Liberation Mono + 12 + + + + PointingHandCursor + + + Qt::StrongFocus + + + Demod shift frequency from center in Hz + + + + + + + Hz + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 60 + 0 + + + + Channel power + + + Qt::RightToLeft + + + -100.0 dB + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 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 + + + + + + + Qt::Vertical + + + + + + + Flip sideband in SSB mode (LSB->USB or USB->LSB) + + + + + + + :/flip_sidebands.png:/flip_sidebands.png + + + + + + + DSB/SSB toggle + + + ... + + + + :/usb.png + :/dsb.png:/usb.png + + + true + + + + + + + + + + + + + dB + + + + + + + + 0 + 0 + + + + + 0 + 24 + + + + + Liberation Mono + 8 + + + + + + + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + Span + + + + + + + Demod frequency span + + + 0 + + + 4 + + + 1 + + + 2 + + + 2 + + + Qt::Horizontal + + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + 6.0k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + Low cut + + + + + + + + 16777215 + 16 + + + + Highpass filter cutoff frequency (SSB) + + + -60 + + + 60 + + + 1 + + + 3 + + + Qt::Horizontal + + + false + + + QSlider::NoTicks + + + 5 + + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + 0.3k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + Hi cut + + + + + + + + 16777215 + 16 + + + + Lowpass filter cutoff frequency + + + -60 + + + 60 + + + 1 + + + 30 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 5 + + + + + + + + 50 + 0 + + + + + 50 + 16777215 + + + + 3.0k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + 50 + 0 + + + + + 50 + 10 + + + + + 8 + + + + f: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 10 + 10 + + + + + 8 + + + + - + + + + + + + + 8 + + + + LSB + + + Qt::AlignCenter + + + + + + + + 12 + 10 + + + + + 8 + + + + 0 + + + Qt::AlignCenter + + + + + + + + 8 + + + + USB + + + Qt::AlignCenter + + + + + + + + 10 + 10 + + + + + 8 + + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 50 + 0 + + + + + 50 + 10 + + + + + + + + + + + + + Qt::Horizontal + + + + + + + + + Vol + + + + + + + + 24 + 24 + + + + + 24 + 24 + + + + 1 + + + + + + + + 30 + 16777215 + + + + 2.0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Vertical + + + + + + + Toggle AGC + + + AGC + + + true + + + + + + + Toggle AGC clamping to maximum allowable signal + + + CL + + + true + + + + + + + + 24 + 24 + + + + AGC time constant (ms in log2 steps) + + + 4 + + + 11 + + + 1 + + + 7 + + + + + + + + 35 + 0 + + + + AGC time constant (ms) + + + 0000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 24 + 24 + + + + Power threshold (dB) + + + -120 + + + 0 + + + 1 + + + -40 + + + + + + + + 26 + 0 + + + + Power threshold (dB) + + + -000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 24 + 24 + + + + Power threshold gate (ms) + + + 20 + + + 1 + + + 4 + + + + + + + + 16 + 0 + + + + Power threshold gate (ms) + + + 00 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Mute/Unmute audio + + + + + + + :/sound_on.png + :/sound_off.png:/sound_on.png + + + true + + + + + + + + + + + 10 + 170 + 218 + 284 + + + + Channel Spectrum + + + + 2 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 200 + 250 + + + + + Liberation Mono + 8 + + + + + + + + + + + + + RollupWidget + QWidget +
gui/rollupwidget.h
+ 1 +
+ + LevelMeterSignalDB + QWidget +
gui/levelmeter.h
+ 1 +
+ + GLSpectrum + QWidget +
gui/glspectrum.h
+ 1 +
+ + GLSpectrumGUI + QWidget +
gui/glspectrumgui.h
+ 1 +
+ + ValueDialZ + QWidget +
gui/valuedialz.h
+ 1 +
+ + ButtonSwitch + QToolButton +
gui/buttonswitch.h
+
+ + TickedSlider + QSlider +
gui/tickedslider.h
+
+
+ + + + +
diff --git a/plugins/channelrx/demodfreedv/freedvdemodsettings.cpp b/plugins/channelrx/demodfreedv/freedvdemodsettings.cpp new file mode 100644 index 000000000..c56522742 --- /dev/null +++ b/plugins/channelrx/demodfreedv/freedvdemodsettings.cpp @@ -0,0 +1,216 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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/dspengine.h" +#include "util/simpleserializer.h" +#include "settings/serializable.h" +#include "freedvdemodsettings.h" + +#ifdef SDR_RX_SAMPLE_24BIT +const int FreeDVDemodSettings::m_minPowerThresholdDB = -120; +const float FreeDVDemodSettings::m_mminPowerThresholdDBf = 120.0f; +#else +const int FreeDVDemodSettings::m_minPowerThresholdDB = -100; +const float FreeDVDemodSettings::m_mminPowerThresholdDBf = 100.0f; +#endif + +FreeDVDemodSettings::FreeDVDemodSettings() : + m_channelMarker(0), + m_spectrumGUI(0) +{ + resetToDefaults(); +} + +void FreeDVDemodSettings::resetToDefaults() +{ + m_audioBinaural = false; + m_audioFlipChannels = false; + m_dsb = false; + m_audioMute = false; + m_agc = false; + m_agcClamping = false; + m_agcPowerThreshold = -40; + m_agcThresholdGate = 4; + m_agcTimeLog2 = 7; + m_rfBandwidth = 3000; + m_lowCutoff = 300; + m_volume = 3.0; + m_spanLog2 = 3; + m_inputFrequencyOffset = 0; + m_rgbColor = QColor(0, 255, 204).rgb(); + m_title = "FreeDV Demodulator"; + m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; + m_freeDVMode = FreeDVMode2400A; + m_useReverseAPI = false; + m_reverseAPIAddress = "127.0.0.1"; + m_reverseAPIPort = 8888; + m_reverseAPIDeviceIndex = 0; + m_reverseAPIChannelIndex = 0; +} + +QByteArray FreeDVDemodSettings::serialize() const +{ + SimpleSerializer s(1); + s.writeS32(1, m_inputFrequencyOffset); + s.writeS32(2, m_rfBandwidth / 100.0); + s.writeS32(3, m_volume * 10.0); + + if (m_spectrumGUI) { + s.writeBlob(4, m_spectrumGUI->serialize()); + } + + s.writeU32(5, m_rgbColor); + s.writeS32(6, m_lowCutoff / 100.0); + s.writeS32(7, m_spanLog2); + s.writeBool(8, m_audioBinaural); + s.writeBool(9, m_audioFlipChannels); + s.writeBool(10, m_dsb); + s.writeBool(11, m_agc); + s.writeS32(12, m_agcTimeLog2); + s.writeS32(13, m_agcPowerThreshold); + s.writeS32(14, m_agcThresholdGate); + s.writeBool(15, m_agcClamping); + s.writeString(16, m_title); + s.writeString(17, m_audioDeviceName); + s.writeBool(18, m_useReverseAPI); + s.writeString(19, m_reverseAPIAddress); + s.writeU32(20, m_reverseAPIPort); + s.writeU32(21, m_reverseAPIDeviceIndex); + s.writeU32(22, m_reverseAPIChannelIndex); + s.writeS32(23, (int) m_freeDVMode); + + return s.final(); +} + +bool FreeDVDemodSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if(!d.isValid()) + { + resetToDefaults(); + return false; + } + + if(d.getVersion() == 1) + { + QByteArray bytetmp; + qint32 tmp; + uint32_t utmp; + QString strtmp; + + d.readS32(1, &m_inputFrequencyOffset, 0); + d.readS32(2, &tmp, 30); + m_rfBandwidth = tmp * 100.0; + d.readS32(3, &tmp, 30); + m_volume = tmp / 10.0; + + if (m_spectrumGUI) { + d.readBlob(4, &bytetmp); + m_spectrumGUI->deserialize(bytetmp); + } + + d.readU32(5, &m_rgbColor); + d.readS32(6, &tmp, 30); + m_lowCutoff = tmp * 100.0; + d.readS32(7, &m_spanLog2, 3); + d.readBool(8, &m_audioBinaural, false); + d.readBool(9, &m_audioFlipChannels, false); + d.readBool(10, &m_dsb, false); + d.readBool(11, &m_agc, false); + d.readS32(12, &m_agcTimeLog2, 7); + d.readS32(13, &m_agcPowerThreshold, -40); + d.readS32(14, &m_agcThresholdGate, 4); + d.readBool(15, &m_agcClamping, false); + d.readString(16, &m_title, "SSB Demodulator"); + d.readString(17, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName); + d.readBool(18, &m_useReverseAPI, false); + d.readString(19, &m_reverseAPIAddress, "127.0.0.1"); + d.readU32(20, &utmp, 0); + + if ((utmp > 1023) && (utmp < 65535)) { + m_reverseAPIPort = utmp; + } else { + m_reverseAPIPort = 8888; + } + + d.readU32(21, &utmp, 0); + m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp; + d.readU32(22, &utmp, 0); + m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp; + + d.readS32(23, &tmp, 0); + if ((tmp < 0) || (tmp > (int) FreeDVMode::FreeDVMode700D)) { + m_freeDVMode = FreeDVMode::FreeDVMode2400A; + } else { + m_freeDVMode = (FreeDVMode) tmp; + } + + return true; + } + else + { + resetToDefaults(); + return false; + } +} + +int FreeDVDemodSettings::getHiCutoff(FreeDVMode freeDVMode) +{ + switch(freeDVMode) + { + case FreeDVMode800XA: // C4FM NB + return 2400.0; + break; + case FreeDVMode700D: // OFDM + case FreeDVMode1600: // OFDM + return 2200.0; + break; + case FreeDVMode2400A: // C4FM WB + default: + return 6000.0; + break; + } +} + +int FreeDVDemodSettings::getLowCutoff(FreeDVMode freeDVMode) +{ + switch(freeDVMode) + { + case FreeDVMode800XA: // C4FM NB + return 400.0; + break; + case FreeDVMode700D: // OFDM + case FreeDVMode1600: // OFDM + return 800.0; + break; + case FreeDVMode2400A: // C4FM WB + default: + return 0.0; + break; + } +} + +int FreeDVDemodSettings::getModSampleRate(FreeDVMode freeDVMode) +{ + if (freeDVMode == FreeDVMode2400A) { + return 48000; + } else { + return 8000; + } +} diff --git a/plugins/channelrx/demodfreedv/freedvdemodsettings.h b/plugins/channelrx/demodfreedv/freedvdemodsettings.h new file mode 100644 index 000000000..f05ca1b36 --- /dev/null +++ b/plugins/channelrx/demodfreedv/freedvdemodsettings.h @@ -0,0 +1,79 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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_CHANNELRX_DEMODFREEDV_FREEDVDEMODSETTINGS_H_ +#define PLUGINS_CHANNELRX_DEMODFREEDV_FREEDVDEMODSETTINGS_H_ + +#include +#include +#include + +class Serializable; + +struct FreeDVDemodSettings +{ + typedef enum + { + FreeDVMode2400A, + FreeDVMode1600, + FreeDVMode800XA, + FreeDVMode700D + } FreeDVMode; + + qint32 m_inputFrequencyOffset; + Real m_rfBandwidth; + Real m_lowCutoff; + Real m_volume; + int m_spanLog2; + bool m_audioBinaural; + bool m_audioFlipChannels; + bool m_dsb; + bool m_audioMute; + bool m_agc; + bool m_agcClamping; + int m_agcTimeLog2; + int m_agcPowerThreshold; + int m_agcThresholdGate; + quint32 m_rgbColor; + QString m_title; + QString m_audioDeviceName; + FreeDVMode m_freeDVMode; + bool m_useReverseAPI; + QString m_reverseAPIAddress; + uint16_t m_reverseAPIPort; + uint16_t m_reverseAPIDeviceIndex; + uint16_t m_reverseAPIChannelIndex; + + Serializable *m_channelMarker; + Serializable *m_spectrumGUI; + + FreeDVDemodSettings(); + void resetToDefaults(); + void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; } + void setSpectrumGUI(Serializable *spectrumGUI) { m_spectrumGUI = spectrumGUI; } + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + + static const int m_minPowerThresholdDB; + static const float m_mminPowerThresholdDBf; + + static int getHiCutoff(FreeDVMode freeDVMode); + static int getLowCutoff(FreeDVMode freeDVMode); + static int getModSampleRate(FreeDVMode freeDVMode); +}; + + +#endif /* PLUGINS_CHANNELRX_DEMODFREEDV_FREEDVDEMODSETTINGS_H_ */ diff --git a/plugins/channelrx/demodfreedv/freedvplugin.cpp b/plugins/channelrx/demodfreedv/freedvplugin.cpp new file mode 100644 index 000000000..1c0c009aa --- /dev/null +++ b/plugins/channelrx/demodfreedv/freedvplugin.cpp @@ -0,0 +1,78 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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 "device/devicesourceapi.h" +#include "plugin/pluginapi.h" +#ifndef SERVER_MODE +#include "freedvdemodgui.h" +#endif +#include "freedvdemod.h" +#include "freedvplugin.h" + +const PluginDescriptor FreeDVPlugin::m_pluginDescriptor = { + QString("FreeDV Demodulator"), + QString("4.5.0"), + QString("(c) Edouard Griffiths, F4EXB"), + QString("https://github.com/f4exb/sdrangel"), + true, + QString("https://github.com/f4exb/sdrangel") +}; + +FreeDVPlugin::FreeDVPlugin(QObject* parent) : + QObject(parent), + m_pluginAPI(0) +{ +} + +const PluginDescriptor& FreeDVPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void FreeDVPlugin::initPlugin(PluginAPI* pluginAPI) +{ + m_pluginAPI = pluginAPI; + + // register demodulator + m_pluginAPI->registerRxChannel(FreeDVDemod::m_channelIdURI, FreeDVDemod::m_channelId, this); +} + +#ifdef SERVER_MODE +PluginInstanceGUI* FreeDVPlugin::createRxChannelGUI( + DeviceUISet *deviceUISet __attribute__((unused)), + BasebandSampleSink *rxChannel __attribute__((unused))) +{ + return 0; +} +#else +PluginInstanceGUI* FreeDVPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) +{ + return FreeDVDemodGUI::create(m_pluginAPI, deviceUISet, rxChannel); +} +#endif + +BasebandSampleSink* FreeDVPlugin::createRxChannelBS(DeviceSourceAPI *deviceAPI) +{ + return new FreeDVDemod(deviceAPI); +} + +ChannelSinkAPI* FreeDVPlugin::createRxChannelCS(DeviceSourceAPI *deviceAPI) +{ + return new FreeDVDemod(deviceAPI); +} + diff --git a/plugins/channelrx/demodfreedv/freedvplugin.h b/plugins/channelrx/demodfreedv/freedvplugin.h new file mode 100644 index 000000000..78375d750 --- /dev/null +++ b/plugins/channelrx/demodfreedv/freedvplugin.h @@ -0,0 +1,47 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2019 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_FREEDVPLUGIN_H +#define INCLUDE_FREEDVPLUGIN_H + +#include +#include "plugin/plugininterface.h" + +class DeviceUISet; +class BasebandSampleSink; + +class FreeDVPlugin : public QObject, PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID "sdrangel.channel.freedvdemod") + +public: + explicit FreeDVPlugin(QObject* parent = NULL); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + virtual PluginInstanceGUI* createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel); + virtual BasebandSampleSink* createRxChannelBS(DeviceSourceAPI *deviceAPI); + virtual ChannelSinkAPI* createRxChannelCS(DeviceSourceAPI *deviceAPI); + +private: + static const PluginDescriptor m_pluginDescriptor; + + PluginAPI* m_pluginAPI; +}; + +#endif // INCLUDE_FREEDVPLUGIN_H diff --git a/plugins/channelrx/demodssb/ssbdemodgui.cpp b/plugins/channelrx/demodssb/ssbdemodgui.cpp index ca4702b0b..94fc7e5f4 100644 --- a/plugins/channelrx/demodssb/ssbdemodgui.cpp +++ b/plugins/channelrx/demodssb/ssbdemodgui.cpp @@ -1,6 +1,5 @@ #include -#include "ssbdemodgui.h" #include "ssbdemodgui.h" #include