diff --git a/plugins/channeltx/modwfm/wfmmod.cpp b/plugins/channeltx/modwfm/wfmmod.cpp index 8038f67d1..ca33b73ff 100644 --- a/plugins/channeltx/modwfm/wfmmod.cpp +++ b/plugins/channeltx/modwfm/wfmmod.cpp @@ -1,480 +1,539 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2016 Edouard Griffiths, F4EXB // -// // -// This program is free software; you can redistribute it and/or modify // -// it under the terms of the GNU General Public License as published by // -// the Free Software Foundation as version 3 of the License, or // -// // -// This program is distributed in the hope that it will be useful, // -// but WITHOUT ANY WARRANTY; without even the implied warranty of // -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // -// GNU General Public License V3 for more details. // -// // -// You should have received a copy of the GNU General Public License // -// along with this program. If not, see . // -/////////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include -#include -#include -#include -#include "dsp/dspengine.h" -#include "dsp/pidcontroller.h" -#include "wfmmod.h" - -MESSAGE_CLASS_DEFINITION(WFMMod::MsgConfigureWFMMod, Message) -MESSAGE_CLASS_DEFINITION(WFMMod::MsgConfigureFileSourceName, Message) -MESSAGE_CLASS_DEFINITION(WFMMod::MsgConfigureFileSourceSeek, Message) -MESSAGE_CLASS_DEFINITION(WFMMod::MsgConfigureAFInput, Message) -MESSAGE_CLASS_DEFINITION(WFMMod::MsgConfigureFileSourceStreamTiming, Message) -MESSAGE_CLASS_DEFINITION(WFMMod::MsgReportFileSourceStreamData, Message) -MESSAGE_CLASS_DEFINITION(WFMMod::MsgReportFileSourceStreamTiming, Message) - -const int WFMMod::m_levelNbSamples = 480; // every 10ms -const int WFMMod::m_rfFilterFFTLength = 1024; - -WFMMod::WFMMod() : - m_modPhasor(0.0f), - m_movingAverage(40, 0), - m_volumeAGC(40, 0), - m_audioFifo(4800), - m_settingsMutex(QMutex::Recursive), - m_fileSize(0), - m_recordLength(0), - m_sampleRate(48000), - m_afInput(WFMModInputNone), - m_levelCalcCount(0), - m_peakLevel(0.0f), - m_levelSum(0.0f) -{ - setObjectName("WFMod"); - - m_config.m_outputSampleRate = 384000; - m_config.m_inputFrequencyOffset = 0; - m_config.m_rfBandwidth = 125000; - m_config.m_afBandwidth = 8000; - m_config.m_fmDeviation = 50000.0f; - m_config.m_toneFrequency = 1000.0f; - m_config.m_audioSampleRate = DSPEngine::instance()->getAudioSampleRate(); - - m_rfFilter = new fftfilt(-62500.0 / 384000.0, 62500.0 / 384000.0, m_rfFilterFFTLength); - m_rfFilterBuffer = new Complex[m_rfFilterFFTLength]; - memset(m_rfFilterBuffer, 0, sizeof(Complex)*(m_rfFilterFFTLength)); - m_rfFilterBufferIndex = 0; - - apply(); - - - m_audioBuffer.resize(1<<14); - m_audioBufferFill = 0; - - m_movingAverage.resize(16, 0); - m_volumeAGC.resize(4096, 0.003, 0); - m_magsq = 0.0; - - m_toneNco.setFreq(1000.0, m_config.m_audioSampleRate); - m_toneNcoRF.setFreq(1000.0, m_config.m_outputSampleRate); - DSPEngine::instance()->addAudioSource(&m_audioFifo); - - // CW keyer - m_cwKeyer.setSampleRate(m_config.m_outputSampleRate); - m_cwSmoother.setNbFadeSamples(m_config.m_outputSampleRate / 250); // 4 ms - m_cwKeyer.setWPM(13); - m_cwKeyer.setMode(CWKeyer::CWNone); - m_cwKeyer.reset(); -} - -WFMMod::~WFMMod() -{ - delete m_rfFilter; - delete[] m_rfFilterBuffer; - DSPEngine::instance()->removeAudioSource(&m_audioFifo); -} - -void WFMMod::configure(MessageQueue* messageQueue, - Real rfBandwidth, - Real afBandwidth, - float fmDeviation, - float toneFrequency, - float volumeFactor, - bool channelMute, - bool playLoop) -{ - Message* cmd = MsgConfigureWFMMod::create(rfBandwidth, afBandwidth, fmDeviation, toneFrequency, volumeFactor, channelMute, playLoop); - messageQueue->push(cmd); -} - -void WFMMod::pull(Sample& sample) -{ - if (m_running.m_channelMute) - { - sample.m_real = 0.0f; - sample.m_imag = 0.0f; - return; - } - - Complex ci, ri; - fftfilt::cmplx *rf; - int rf_out; - - m_settingsMutex.lock(); - - if ((m_afInput == WFMModInputFile) || (m_afInput == WFMModInputAudio)) - { - if (m_interpolator.interpolate(&m_interpolatorDistanceRemain, m_modSample, &ri)) - { - pullAF(m_modSample); - calculateLevel(m_modSample.real()); - m_audioBufferFill++; - } - - m_interpolatorDistanceRemain += m_interpolatorDistance; - } - else - { - pullAF(ri); - } - - m_modPhasor += (m_running.m_fmDeviation / (float) m_running.m_outputSampleRate) * ri.real() * M_PI * 2.0f; - ci.real(cos(m_modPhasor) * 29204.0f); // -1 dB - ci.imag(sin(m_modPhasor) * 29204.0f); - - // RF filtering - rf_out = m_rfFilter->runFilt(ci, &rf); - - if (rf_out > 0) - { - memcpy((void *) m_rfFilterBuffer, (const void *) rf, rf_out*sizeof(Complex)); - m_rfFilterBufferIndex = 0; - - } - - ci = m_rfFilterBuffer[m_rfFilterBufferIndex] * m_carrierNco.nextIQ(); // shift to carrier frequency - m_rfFilterBufferIndex++; - - m_settingsMutex.unlock(); - - Real magsq = ci.real() * ci.real() + ci.imag() * ci.imag(); - magsq /= (1<<30); - m_movingAverage.feed(magsq); - m_magsq = m_movingAverage.average(); - - sample.m_real = (FixReal) ci.real(); - sample.m_imag = (FixReal) ci.imag(); -} - -void WFMMod::pullAudio(int nbSamples) -{ - unsigned int nbSamplesAudio = nbSamples * ((Real) m_config.m_audioSampleRate / (Real) m_config.m_basebandSampleRate); - - if (nbSamplesAudio > m_audioBuffer.size()) - { - m_audioBuffer.resize(nbSamplesAudio); - } - - m_audioFifo.read(reinterpret_cast(&m_audioBuffer[0]), nbSamplesAudio, 10); - m_audioBufferFill = 0; -} - -void WFMMod::pullAF(Complex& sample) -{ - switch (m_afInput) - { - case WFMModInputTone: - sample.real(m_toneNcoRF.next() * m_running.m_volumeFactor); - sample.imag(0.0f); - break; - case WFMModInputFile: - // sox f4exb_call.wav --encoding float --endian little f4exb_call.raw - // ffplay -f f32le -ar 48k -ac 1 f4exb_call.raw - if (m_ifstream.is_open()) - { - if (m_ifstream.eof()) - { - if (m_running.m_playLoop) - { - m_ifstream.clear(); - m_ifstream.seekg(0, std::ios::beg); - } - } - - if (m_ifstream.eof()) - { - sample.real(0.0f); - sample.imag(0.0f); - } - else - { - Real s; - m_ifstream.read(reinterpret_cast(&s), sizeof(Real)); - sample.real(s * m_running.m_volumeFactor); - sample.imag(0.0f); - } - } - else - { - sample.real(0.0f); - sample.imag(0.0f); - } - break; - case WFMModInputAudio: - { - sample.real(((m_audioBuffer[m_audioBufferFill].l + m_audioBuffer[m_audioBufferFill].r) / 65536.0f) * m_running.m_volumeFactor); - sample.imag(0.0f); - } - break; - case WFMModInputCWTone: - Real fadeFactor; - - if (m_cwKeyer.getSample()) - { - m_cwSmoother.getFadeSample(true, fadeFactor); - sample.real(m_toneNcoRF.next() * m_running.m_volumeFactor * fadeFactor); - sample.imag(0.0f); - } - else - { - if (m_cwSmoother.getFadeSample(false, fadeFactor)) - { - sample.real(m_toneNcoRF.next() * m_running.m_volumeFactor * fadeFactor); - sample.imag(0.0f); - } - else - { - sample.real(0.0f); - sample.imag(0.0f); - m_toneNcoRF.setPhase(0); - } - } - break; - case WFMModInputNone: - default: - sample.real(0.0f); - sample.imag(0.0f); - break; - } -} - -void WFMMod::calculateLevel(const Real& sample) -{ - if (m_levelCalcCount < m_levelNbSamples) - { - m_peakLevel = std::max(std::fabs(m_peakLevel), sample); - m_levelSum += sample * sample; - m_levelCalcCount++; - } - else - { - qreal rmsLevel = sqrt(m_levelSum / m_levelNbSamples); - //qDebug("WFMMod::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 WFMMod::start() -{ - qDebug() << "WFMMod::start: m_outputSampleRate: " << m_config.m_outputSampleRate - << " m_inputFrequencyOffset: " << m_config.m_inputFrequencyOffset; - - m_audioFifo.clear(); -} - -void WFMMod::stop() -{ -} - -bool WFMMod::handleMessage(const Message& cmd) -{ - if (UpChannelizer::MsgChannelizerNotification::match(cmd)) - { - UpChannelizer::MsgChannelizerNotification& notif = (UpChannelizer::MsgChannelizerNotification&) cmd; - - m_config.m_basebandSampleRate = notif.getBasebandSampleRate(); - m_config.m_outputSampleRate = notif.getSampleRate(); - m_config.m_inputFrequencyOffset = notif.getFrequencyOffset(); - - apply(); - - qDebug() << "WFMMod::handleMessage: MsgChannelizerNotification:" - << " m_basebandSampleRate: " << m_config.m_basebandSampleRate - << " m_outputSampleRate: " << m_config.m_outputSampleRate - << " m_inputFrequencyOffset: " << m_config.m_inputFrequencyOffset; - - return true; - } - else if (MsgConfigureWFMMod::match(cmd)) - { - MsgConfigureWFMMod& cfg = (MsgConfigureWFMMod&) cmd; - - m_config.m_rfBandwidth = cfg.getRFBandwidth(); - m_config.m_afBandwidth = cfg.getAFBandwidth(); - m_config.m_fmDeviation = cfg.getFMDeviation(); - m_config.m_toneFrequency = cfg.getToneFrequency(); - m_config.m_volumeFactor = cfg.getVolumeFactor(); - m_config.m_channelMute = cfg.getChannelMute(); - m_config.m_playLoop = cfg.getPlayLoop(); - - apply(); - - qDebug() << "WFMMod::handleMessage: MsgConfigureWFMMod:" - << " m_rfBandwidth: " << m_config.m_rfBandwidth - << " m_afBandwidth: " << m_config.m_afBandwidth - << " m_fmDeviation: " << m_config.m_fmDeviation - << " m_toneFrequency: " << m_config.m_toneFrequency - << " m_volumeFactor: " << m_config.m_volumeFactor - << " m_channelMute: " << m_config.m_channelMute - << " m_playLoop: " << m_config.m_playLoop; - - return true; - } - else if (MsgConfigureFileSourceName::match(cmd)) - { - MsgConfigureFileSourceName& conf = (MsgConfigureFileSourceName&) cmd; - m_fileName = conf.getFileName(); - openFileStream(); - return true; - } - else if (MsgConfigureFileSourceSeek::match(cmd)) - { - MsgConfigureFileSourceSeek& conf = (MsgConfigureFileSourceSeek&) cmd; - int seekPercentage = conf.getPercentage(); - seekFileStream(seekPercentage); - - return true; - } - else if (MsgConfigureAFInput::match(cmd)) - { - MsgConfigureAFInput& conf = (MsgConfigureAFInput&) cmd; - m_afInput = conf.getAFInput(); - - return true; - } - else if (MsgConfigureFileSourceStreamTiming::match(cmd)) - { - std::size_t samplesCount; - - if (m_ifstream.eof()) { - samplesCount = m_fileSize / sizeof(Real); - } else { - samplesCount = m_ifstream.tellg() / sizeof(Real); - } - - MsgReportFileSourceStreamTiming *report; - report = MsgReportFileSourceStreamTiming::create(samplesCount); - getMessageQueueToGUI()->push(report); - - return true; - } - else - { - return false; - } -} - -void WFMMod::apply() -{ - - if ((m_config.m_inputFrequencyOffset != m_running.m_inputFrequencyOffset) || - (m_config.m_outputSampleRate != m_running.m_outputSampleRate)) - { - m_settingsMutex.lock(); - m_carrierNco.setFreq(m_config.m_inputFrequencyOffset, m_config.m_outputSampleRate); - m_settingsMutex.unlock(); - } - - if((m_config.m_outputSampleRate != m_running.m_outputSampleRate) || - (m_config.m_audioSampleRate != m_running.m_audioSampleRate) || - (m_config.m_afBandwidth != m_running.m_afBandwidth)) - { - m_settingsMutex.lock(); - m_interpolatorDistanceRemain = 0; - m_interpolatorConsumed = false; - m_interpolatorDistance = (Real) m_config.m_audioSampleRate / (Real) m_config.m_outputSampleRate; - m_interpolator.create(48, m_config.m_audioSampleRate, m_config.m_rfBandwidth / 2.2, 3.0); - m_settingsMutex.unlock(); - } - - if ((m_config.m_rfBandwidth != m_running.m_rfBandwidth) || - (m_config.m_outputSampleRate != m_running.m_outputSampleRate)) - { - m_settingsMutex.lock(); - Real lowCut = -(m_config.m_rfBandwidth / 2.0) / m_config.m_outputSampleRate; - Real hiCut = (m_config.m_rfBandwidth / 2.0) / m_config.m_outputSampleRate; - m_rfFilter->create_filter(lowCut, hiCut); - m_settingsMutex.unlock(); - } - - if ((m_config.m_toneFrequency != m_running.m_toneFrequency) || - (m_config.m_audioSampleRate != m_running.m_audioSampleRate)) - { - m_settingsMutex.lock(); - m_toneNco.setFreq(m_config.m_toneFrequency, m_config.m_audioSampleRate); - m_settingsMutex.unlock(); - } - - if ((m_config.m_toneFrequency != m_running.m_toneFrequency) || - (m_config.m_outputSampleRate != m_running.m_outputSampleRate)) - { - m_settingsMutex.lock(); - m_toneNcoRF.setFreq(m_config.m_toneFrequency, m_config.m_outputSampleRate); - m_settingsMutex.unlock(); - } - - if (m_config.m_outputSampleRate != m_running.m_outputSampleRate) - { - m_cwKeyer.setSampleRate(m_config.m_outputSampleRate); - m_cwSmoother.setNbFadeSamples(m_config.m_outputSampleRate / 250); // 4 ms - m_cwKeyer.reset(); - } - - m_running.m_outputSampleRate = m_config.m_outputSampleRate; - m_running.m_inputFrequencyOffset = m_config.m_inputFrequencyOffset; - m_running.m_rfBandwidth = m_config.m_rfBandwidth; - m_running.m_afBandwidth = m_config.m_afBandwidth; - m_running.m_fmDeviation = m_config.m_fmDeviation; - m_running.m_volumeFactor = m_config.m_volumeFactor; - m_running.m_audioSampleRate = m_config.m_audioSampleRate; - m_running.m_channelMute = m_config.m_channelMute; - m_running.m_playLoop = m_config.m_playLoop; -} - -void WFMMod::openFileStream() -{ - if (m_ifstream.is_open()) { - m_ifstream.close(); - } - - m_ifstream.open(m_fileName.toStdString().c_str(), std::ios::binary | std::ios::ate); - m_fileSize = m_ifstream.tellg(); - m_ifstream.seekg(0,std::ios_base::beg); - - m_sampleRate = 48000; // fixed rate - m_recordLength = m_fileSize / (sizeof(Real) * m_sampleRate); - - qDebug() << "WFMMod::openFileStream: " << m_fileName.toStdString().c_str() - << " fileSize: " << m_fileSize << "bytes" - << " length: " << m_recordLength << " seconds"; - - MsgReportFileSourceStreamData *report; - report = MsgReportFileSourceStreamData::create(m_sampleRate, m_recordLength); - getMessageQueueToGUI()->push(report); -} - -void WFMMod::seekFileStream(int seekPercentage) -{ - QMutexLocker mutexLocker(&m_settingsMutex); - - if (m_ifstream.is_open()) - { - int seekPoint = ((m_recordLength * seekPercentage) / 100) * m_sampleRate; - seekPoint *= sizeof(Real); - m_ifstream.clear(); - m_ifstream.seekg(seekPoint, std::ios::beg); - } -} +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include "dsp/dspengine.h" +#include "dsp/pidcontroller.h" +#include "wfmmod.h" + +MESSAGE_CLASS_DEFINITION(WFMMod::MsgConfigureWFMMod, Message) +MESSAGE_CLASS_DEFINITION(WFMMod::MsgConfigureWFMModPrivate, Message) +MESSAGE_CLASS_DEFINITION(WFMMod::MsgConfigureFileSourceName, Message) +MESSAGE_CLASS_DEFINITION(WFMMod::MsgConfigureFileSourceSeek, Message) +MESSAGE_CLASS_DEFINITION(WFMMod::MsgConfigureAFInput, Message) +MESSAGE_CLASS_DEFINITION(WFMMod::MsgConfigureFileSourceStreamTiming, Message) +MESSAGE_CLASS_DEFINITION(WFMMod::MsgReportFileSourceStreamData, Message) +MESSAGE_CLASS_DEFINITION(WFMMod::MsgReportFileSourceStreamTiming, Message) + +const int WFMMod::m_levelNbSamples = 480; // every 10ms +const int WFMMod::m_rfFilterFFTLength = 1024; + +WFMMod::WFMMod() : + m_modPhasor(0.0f), + m_movingAverage(40, 0), + m_volumeAGC(40, 0), + m_audioFifo(4800), + m_settingsMutex(QMutex::Recursive), + m_fileSize(0), + m_recordLength(0), + m_sampleRate(48000), + m_afInput(WFMModInputNone), + m_levelCalcCount(0), + m_peakLevel(0.0f), + m_levelSum(0.0f) +{ + setObjectName("WFMod"); + + m_config.m_outputSampleRate = 384000; + m_config.m_inputFrequencyOffset = 0; + m_config.m_rfBandwidth = 125000; + m_config.m_afBandwidth = 8000; + m_config.m_fmDeviation = 50000.0f; + m_config.m_toneFrequency = 1000.0f; + m_config.m_audioSampleRate = DSPEngine::instance()->getAudioSampleRate(); + + m_rfFilter = new fftfilt(-62500.0 / 384000.0, 62500.0 / 384000.0, m_rfFilterFFTLength); + m_rfFilterBuffer = new Complex[m_rfFilterFFTLength]; + memset(m_rfFilterBuffer, 0, sizeof(Complex)*(m_rfFilterFFTLength)); + m_rfFilterBufferIndex = 0; + + apply(); + + + m_audioBuffer.resize(1<<14); + m_audioBufferFill = 0; + + m_movingAverage.resize(16, 0); + m_volumeAGC.resize(4096, 0.003, 0); + m_magsq = 0.0; + + m_toneNco.setFreq(1000.0, m_config.m_audioSampleRate); + m_toneNcoRF.setFreq(1000.0, m_config.m_outputSampleRate); + DSPEngine::instance()->addAudioSource(&m_audioFifo); + + // CW keyer + m_cwKeyer.setSampleRate(m_config.m_outputSampleRate); + m_cwSmoother.setNbFadeSamples(m_config.m_outputSampleRate / 250); // 4 ms + m_cwKeyer.setWPM(13); + m_cwKeyer.setMode(CWKeyer::CWNone); + m_cwKeyer.reset(); +} + +WFMMod::~WFMMod() +{ + delete m_rfFilter; + delete[] m_rfFilterBuffer; + DSPEngine::instance()->removeAudioSource(&m_audioFifo); +} + +void WFMMod::configure(MessageQueue* messageQueue, + Real rfBandwidth, + Real afBandwidth, + float fmDeviation, + float toneFrequency, + float volumeFactor, + bool channelMute, + bool playLoop) +{ + Message* cmd = MsgConfigureWFMModPrivate::create(rfBandwidth, afBandwidth, fmDeviation, toneFrequency, volumeFactor, channelMute, playLoop); + messageQueue->push(cmd); +} + +void WFMMod::pull(Sample& sample) +{ + if (m_running.m_channelMute) + { + sample.m_real = 0.0f; + sample.m_imag = 0.0f; + return; + } + + Complex ci, ri; + fftfilt::cmplx *rf; + int rf_out; + + m_settingsMutex.lock(); + + if ((m_afInput == WFMModInputFile) || (m_afInput == WFMModInputAudio)) + { + if (m_interpolator.interpolate(&m_interpolatorDistanceRemain, m_modSample, &ri)) + { + pullAF(m_modSample); + calculateLevel(m_modSample.real()); + m_audioBufferFill++; + } + + m_interpolatorDistanceRemain += m_interpolatorDistance; + } + else + { + pullAF(ri); + } + + m_modPhasor += (m_running.m_fmDeviation / (float) m_running.m_outputSampleRate) * ri.real() * M_PI * 2.0f; + ci.real(cos(m_modPhasor) * 29204.0f); // -1 dB + ci.imag(sin(m_modPhasor) * 29204.0f); + + // RF filtering + rf_out = m_rfFilter->runFilt(ci, &rf); + + if (rf_out > 0) + { + memcpy((void *) m_rfFilterBuffer, (const void *) rf, rf_out*sizeof(Complex)); + m_rfFilterBufferIndex = 0; + + } + + ci = m_rfFilterBuffer[m_rfFilterBufferIndex] * m_carrierNco.nextIQ(); // shift to carrier frequency + m_rfFilterBufferIndex++; + + m_settingsMutex.unlock(); + + Real magsq = ci.real() * ci.real() + ci.imag() * ci.imag(); + magsq /= (1<<30); + m_movingAverage.feed(magsq); + m_magsq = m_movingAverage.average(); + + sample.m_real = (FixReal) ci.real(); + sample.m_imag = (FixReal) ci.imag(); +} + +void WFMMod::pullAudio(int nbSamples) +{ + unsigned int nbSamplesAudio = nbSamples * ((Real) m_config.m_audioSampleRate / (Real) m_config.m_basebandSampleRate); + + if (nbSamplesAudio > m_audioBuffer.size()) + { + m_audioBuffer.resize(nbSamplesAudio); + } + + m_audioFifo.read(reinterpret_cast(&m_audioBuffer[0]), nbSamplesAudio, 10); + m_audioBufferFill = 0; +} + +void WFMMod::pullAF(Complex& sample) +{ + switch (m_afInput) + { + case WFMModInputTone: + sample.real(m_toneNcoRF.next() * m_running.m_volumeFactor); + sample.imag(0.0f); + break; + case WFMModInputFile: + // sox f4exb_call.wav --encoding float --endian little f4exb_call.raw + // ffplay -f f32le -ar 48k -ac 1 f4exb_call.raw + if (m_ifstream.is_open()) + { + if (m_ifstream.eof()) + { + if (m_running.m_playLoop) + { + m_ifstream.clear(); + m_ifstream.seekg(0, std::ios::beg); + } + } + + if (m_ifstream.eof()) + { + sample.real(0.0f); + sample.imag(0.0f); + } + else + { + Real s; + m_ifstream.read(reinterpret_cast(&s), sizeof(Real)); + sample.real(s * m_running.m_volumeFactor); + sample.imag(0.0f); + } + } + else + { + sample.real(0.0f); + sample.imag(0.0f); + } + break; + case WFMModInputAudio: + { + sample.real(((m_audioBuffer[m_audioBufferFill].l + m_audioBuffer[m_audioBufferFill].r) / 65536.0f) * m_running.m_volumeFactor); + sample.imag(0.0f); + } + break; + case WFMModInputCWTone: + Real fadeFactor; + + if (m_cwKeyer.getSample()) + { + m_cwSmoother.getFadeSample(true, fadeFactor); + sample.real(m_toneNcoRF.next() * m_running.m_volumeFactor * fadeFactor); + sample.imag(0.0f); + } + else + { + if (m_cwSmoother.getFadeSample(false, fadeFactor)) + { + sample.real(m_toneNcoRF.next() * m_running.m_volumeFactor * fadeFactor); + sample.imag(0.0f); + } + else + { + sample.real(0.0f); + sample.imag(0.0f); + m_toneNcoRF.setPhase(0); + } + } + break; + case WFMModInputNone: + default: + sample.real(0.0f); + sample.imag(0.0f); + break; + } +} + +void WFMMod::calculateLevel(const Real& sample) +{ + if (m_levelCalcCount < m_levelNbSamples) + { + m_peakLevel = std::max(std::fabs(m_peakLevel), sample); + m_levelSum += sample * sample; + m_levelCalcCount++; + } + else + { + qreal rmsLevel = sqrt(m_levelSum / m_levelNbSamples); + //qDebug("WFMMod::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 WFMMod::start() +{ + qDebug() << "WFMMod::start: m_outputSampleRate: " << m_config.m_outputSampleRate + << " m_inputFrequencyOffset: " << m_config.m_inputFrequencyOffset; + + m_audioFifo.clear(); +} + +void WFMMod::stop() +{ +} + +bool WFMMod::handleMessage(const Message& cmd) +{ + if (UpChannelizer::MsgChannelizerNotification::match(cmd)) + { + UpChannelizer::MsgChannelizerNotification& notif = (UpChannelizer::MsgChannelizerNotification&) cmd; + + m_config.m_basebandSampleRate = notif.getBasebandSampleRate(); + m_config.m_outputSampleRate = notif.getSampleRate(); + m_config.m_inputFrequencyOffset = notif.getFrequencyOffset(); + + apply(); + + qDebug() << "WFMMod::handleMessage: MsgChannelizerNotification:" + << " m_basebandSampleRate: " << m_config.m_basebandSampleRate + << " m_outputSampleRate: " << m_config.m_outputSampleRate + << " m_inputFrequencyOffset: " << m_config.m_inputFrequencyOffset; + + return true; + } + else if (MsgConfigureWFMModPrivate::match(cmd)) + { + MsgConfigureWFMModPrivate& cfg = (MsgConfigureWFMModPrivate&) cmd; + + m_config.m_rfBandwidth = cfg.getRFBandwidth(); + m_config.m_afBandwidth = cfg.getAFBandwidth(); + m_config.m_fmDeviation = cfg.getFMDeviation(); + m_config.m_toneFrequency = cfg.getToneFrequency(); + m_config.m_volumeFactor = cfg.getVolumeFactor(); + m_config.m_channelMute = cfg.getChannelMute(); + m_config.m_playLoop = cfg.getPlayLoop(); + + apply(); + + qDebug() << "WFMMod::handleMessage: MsgConfigureWFMMod:" + << " m_rfBandwidth: " << m_config.m_rfBandwidth + << " m_afBandwidth: " << m_config.m_afBandwidth + << " m_fmDeviation: " << m_config.m_fmDeviation + << " m_toneFrequency: " << m_config.m_toneFrequency + << " m_volumeFactor: " << m_config.m_volumeFactor + << " m_channelMute: " << m_config.m_channelMute + << " m_playLoop: " << m_config.m_playLoop; + + return true; + } + else if (MsgConfigureFileSourceName::match(cmd)) + { + MsgConfigureFileSourceName& conf = (MsgConfigureFileSourceName&) cmd; + m_fileName = conf.getFileName(); + openFileStream(); + return true; + } + else if (MsgConfigureFileSourceSeek::match(cmd)) + { + MsgConfigureFileSourceSeek& conf = (MsgConfigureFileSourceSeek&) cmd; + int seekPercentage = conf.getPercentage(); + seekFileStream(seekPercentage); + + return true; + } + else if (MsgConfigureAFInput::match(cmd)) + { + MsgConfigureAFInput& conf = (MsgConfigureAFInput&) cmd; + m_afInput = conf.getAFInput(); + + return true; + } + else if (MsgConfigureFileSourceStreamTiming::match(cmd)) + { + std::size_t samplesCount; + + if (m_ifstream.eof()) { + samplesCount = m_fileSize / sizeof(Real); + } else { + samplesCount = m_ifstream.tellg() / sizeof(Real); + } + + MsgReportFileSourceStreamTiming *report; + report = MsgReportFileSourceStreamTiming::create(samplesCount); + getMessageQueueToGUI()->push(report); + + return true; + } + else + { + return false; + } +} + +void WFMMod::apply() +{ + + if ((m_config.m_inputFrequencyOffset != m_running.m_inputFrequencyOffset) || + (m_config.m_outputSampleRate != m_running.m_outputSampleRate)) + { + m_settingsMutex.lock(); + m_carrierNco.setFreq(m_config.m_inputFrequencyOffset, m_config.m_outputSampleRate); + m_settingsMutex.unlock(); + } + + if((m_config.m_outputSampleRate != m_running.m_outputSampleRate) || + (m_config.m_audioSampleRate != m_running.m_audioSampleRate) || + (m_config.m_afBandwidth != m_running.m_afBandwidth)) + { + m_settingsMutex.lock(); + m_interpolatorDistanceRemain = 0; + m_interpolatorConsumed = false; + m_interpolatorDistance = (Real) m_config.m_audioSampleRate / (Real) m_config.m_outputSampleRate; + m_interpolator.create(48, m_config.m_audioSampleRate, m_config.m_rfBandwidth / 2.2, 3.0); + m_settingsMutex.unlock(); + } + + if ((m_config.m_rfBandwidth != m_running.m_rfBandwidth) || + (m_config.m_outputSampleRate != m_running.m_outputSampleRate)) + { + m_settingsMutex.lock(); + Real lowCut = -(m_config.m_rfBandwidth / 2.0) / m_config.m_outputSampleRate; + Real hiCut = (m_config.m_rfBandwidth / 2.0) / m_config.m_outputSampleRate; + m_rfFilter->create_filter(lowCut, hiCut); + m_settingsMutex.unlock(); + } + + if ((m_config.m_toneFrequency != m_running.m_toneFrequency) || + (m_config.m_audioSampleRate != m_running.m_audioSampleRate)) + { + m_settingsMutex.lock(); + m_toneNco.setFreq(m_config.m_toneFrequency, m_config.m_audioSampleRate); + m_settingsMutex.unlock(); + } + + if ((m_config.m_toneFrequency != m_running.m_toneFrequency) || + (m_config.m_outputSampleRate != m_running.m_outputSampleRate)) + { + m_settingsMutex.lock(); + m_toneNcoRF.setFreq(m_config.m_toneFrequency, m_config.m_outputSampleRate); + m_settingsMutex.unlock(); + } + + if (m_config.m_outputSampleRate != m_running.m_outputSampleRate) + { + m_cwKeyer.setSampleRate(m_config.m_outputSampleRate); + m_cwSmoother.setNbFadeSamples(m_config.m_outputSampleRate / 250); // 4 ms + m_cwKeyer.reset(); + } + + m_running.m_outputSampleRate = m_config.m_outputSampleRate; + m_running.m_inputFrequencyOffset = m_config.m_inputFrequencyOffset; + m_running.m_rfBandwidth = m_config.m_rfBandwidth; + m_running.m_afBandwidth = m_config.m_afBandwidth; + m_running.m_fmDeviation = m_config.m_fmDeviation; + m_running.m_volumeFactor = m_config.m_volumeFactor; + m_running.m_audioSampleRate = m_config.m_audioSampleRate; + m_running.m_channelMute = m_config.m_channelMute; + m_running.m_playLoop = m_config.m_playLoop; +} + +void WFMMod::openFileStream() +{ + if (m_ifstream.is_open()) { + m_ifstream.close(); + } + + m_ifstream.open(m_fileName.toStdString().c_str(), std::ios::binary | std::ios::ate); + m_fileSize = m_ifstream.tellg(); + m_ifstream.seekg(0,std::ios_base::beg); + + m_sampleRate = 48000; // fixed rate + m_recordLength = m_fileSize / (sizeof(Real) * m_sampleRate); + + qDebug() << "WFMMod::openFileStream: " << m_fileName.toStdString().c_str() + << " fileSize: " << m_fileSize << "bytes" + << " length: " << m_recordLength << " seconds"; + + MsgReportFileSourceStreamData *report; + report = MsgReportFileSourceStreamData::create(m_sampleRate, m_recordLength); + getMessageQueueToGUI()->push(report); +} + +void WFMMod::seekFileStream(int seekPercentage) +{ + QMutexLocker mutexLocker(&m_settingsMutex); + + if (m_ifstream.is_open()) + { + int seekPoint = ((m_recordLength * seekPercentage) / 100) * m_sampleRate; + seekPoint *= sizeof(Real); + m_ifstream.clear(); + m_ifstream.seekg(seekPoint, std::ios::beg); + } +} + +void WFMMod::applySettings(const WFMModSettings& settings, bool force) +{ + if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || + (settings.m_outputSampleRate != m_settings.m_outputSampleRate)) + { + m_settingsMutex.lock(); + m_carrierNco.setFreq(settings.m_inputFrequencyOffset, settings.m_outputSampleRate); + m_settingsMutex.unlock(); + } + + if((settings.m_outputSampleRate != m_settings.m_outputSampleRate) || + (settings.m_audioSampleRate != m_settings.m_audioSampleRate) || + (settings.m_afBandwidth != m_settings.m_afBandwidth) || force) + { + m_settingsMutex.lock(); + m_interpolatorDistanceRemain = 0; + m_interpolatorConsumed = false; + m_interpolatorDistance = (Real) settings.m_audioSampleRate / (Real) settings.m_outputSampleRate; + m_interpolator.create(48, settings.m_audioSampleRate, settings.m_rfBandwidth / 2.2, 3.0); + m_settingsMutex.unlock(); + } + + if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || + (settings.m_outputSampleRate != m_settings.m_outputSampleRate) || force) + { + m_settingsMutex.lock(); + Real lowCut = -(settings.m_rfBandwidth / 2.0) / settings.m_outputSampleRate; + Real hiCut = (settings.m_rfBandwidth / 2.0) / settings.m_outputSampleRate; + m_rfFilter->create_filter(lowCut, hiCut); + m_settingsMutex.unlock(); + } + + if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || + (settings.m_audioSampleRate != m_settings.m_audioSampleRate) || force) + { + m_settingsMutex.lock(); + m_toneNco.setFreq(settings.m_toneFrequency, settings.m_audioSampleRate); + m_settingsMutex.unlock(); + } + + if ((settings.m_toneFrequency != m_settings.m_toneFrequency) || + (settings.m_outputSampleRate != m_settings.m_outputSampleRate) || force) + { + m_settingsMutex.lock(); + m_toneNcoRF.setFreq(settings.m_toneFrequency, settings.m_outputSampleRate); + m_settingsMutex.unlock(); + } + + if ((settings.m_outputSampleRate != m_settings.m_outputSampleRate) || force) + { + m_cwKeyer.setSampleRate(settings.m_outputSampleRate); + m_cwSmoother.setNbFadeSamples(settings.m_outputSampleRate / 250); // 4 ms + m_cwKeyer.reset(); + } + + m_settings = settings; +} diff --git a/plugins/channeltx/modwfm/wfmmod.h b/plugins/channeltx/modwfm/wfmmod.h index d217048cf..e919baab5 100644 --- a/plugins/channeltx/modwfm/wfmmod.h +++ b/plugins/channeltx/modwfm/wfmmod.h @@ -1,355 +1,382 @@ -/////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2016 Edouard Griffiths, F4EXB // -// // -// This program is free software; you can redistribute it and/or modify // -// it under the terms of the GNU General Public License as published by // -// the Free Software Foundation as version 3 of the License, or // -// // -// This program is distributed in the hope that it will be useful, // -// but WITHOUT ANY WARRANTY; without even the implied warranty of // -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // -// GNU General Public License V3 for more details. // -// // -// You should have received a copy of the GNU General Public License // -// along with this program. If not, see . // -/////////////////////////////////////////////////////////////////////////////////// - -#ifndef PLUGINS_CHANNELTX_MODWFM_WFMMOD_H_ -#define PLUGINS_CHANNELTX_MODWFM_WFMMOD_H_ - -#include -#include -#include -#include - -#include "dsp/basebandsamplesource.h" -#include "dsp/nco.h" -#include "dsp/ncof.h" -#include "dsp/interpolator.h" -#include "dsp/fftfilt.h" -#include "dsp/movingaverage.h" -#include "dsp/agc.h" -#include "dsp/cwkeyer.h" -#include "audio/audiofifo.h" -#include "util/message.h" - -class WFMMod : public BasebandSampleSource { - Q_OBJECT - -public: - typedef enum - { - WFMModInputNone, - WFMModInputTone, - WFMModInputFile, - WFMModInputAudio, - WFMModInputCWTone - } WFMModInputAF; - - class MsgConfigureFileSourceName : public Message - { - MESSAGE_CLASS_DECLARATION - - public: - const QString& getFileName() const { return m_fileName; } - - static MsgConfigureFileSourceName* create(const QString& fileName) - { - return new MsgConfigureFileSourceName(fileName); - } - - private: - QString m_fileName; - - MsgConfigureFileSourceName(const QString& fileName) : - Message(), - m_fileName(fileName) - { } - }; - - class MsgConfigureFileSourceSeek : public Message - { - MESSAGE_CLASS_DECLARATION - - public: - int getPercentage() const { return m_seekPercentage; } - - static MsgConfigureFileSourceSeek* create(int seekPercentage) - { - return new MsgConfigureFileSourceSeek(seekPercentage); - } - - protected: - int m_seekPercentage; //!< percentage of seek position from the beginning 0..100 - - MsgConfigureFileSourceSeek(int seekPercentage) : - Message(), - m_seekPercentage(seekPercentage) - { } - }; - - class MsgConfigureFileSourceStreamTiming : public Message { - MESSAGE_CLASS_DECLARATION - - public: - - static MsgConfigureFileSourceStreamTiming* create() - { - return new MsgConfigureFileSourceStreamTiming(); - } - - private: - - MsgConfigureFileSourceStreamTiming() : - Message() - { } - }; - - class MsgConfigureAFInput : public Message - { - MESSAGE_CLASS_DECLARATION - - public: - WFMModInputAF getAFInput() const { return m_afInput; } - - static MsgConfigureAFInput* create(WFMModInputAF afInput) - { - return new MsgConfigureAFInput(afInput); - } - - private: - WFMModInputAF m_afInput; - - MsgConfigureAFInput(WFMModInputAF afInput) : - Message(), - m_afInput(afInput) - { } - }; - - class MsgReportFileSourceStreamTiming : public Message - { - MESSAGE_CLASS_DECLARATION - - public: - std::size_t getSamplesCount() const { return m_samplesCount; } - - static MsgReportFileSourceStreamTiming* create(std::size_t samplesCount) - { - return new MsgReportFileSourceStreamTiming(samplesCount); - } - - protected: - std::size_t m_samplesCount; - - MsgReportFileSourceStreamTiming(std::size_t samplesCount) : - Message(), - m_samplesCount(samplesCount) - { } - }; - - class MsgReportFileSourceStreamData : public Message { - MESSAGE_CLASS_DECLARATION - - public: - int getSampleRate() const { return m_sampleRate; } - quint32 getRecordLength() const { return m_recordLength; } - - static MsgReportFileSourceStreamData* create(int sampleRate, - quint32 recordLength) - { - return new MsgReportFileSourceStreamData(sampleRate, recordLength); - } - - protected: - int m_sampleRate; - quint32 m_recordLength; - - MsgReportFileSourceStreamData(int sampleRate, - quint32 recordLength) : - Message(), - m_sampleRate(sampleRate), - m_recordLength(recordLength) - { } - }; - - //================================================================= - - WFMMod(); - ~WFMMod(); - - void configure(MessageQueue* messageQueue, - Real rfBandwidth, - Real afBandwidth, - float fmDeviation, - float toneFrequency, - float volumeFactor, - bool audioMute, - bool playLoop); - - virtual void pull(Sample& sample); - virtual void pullAudio(int nbSamples); - virtual void start(); - virtual void stop(); - virtual bool handleMessage(const Message& cmd); - - double getMagSq() const { return m_magsq; } - - CWKeyer *getCWKeyer() { return &m_cwKeyer; } - -signals: - /** - * Level changed - * \param rmsLevel RMS level in range 0.0 - 1.0 - * \param peakLevel Peak level in range 0.0 - 1.0 - * \param numSamples Number of audio samples analyzed - */ - void levelChanged(qreal rmsLevel, qreal peakLevel, int numSamples); - - -private: - class MsgConfigureWFMMod : public Message - { - MESSAGE_CLASS_DECLARATION - - public: - Real getRFBandwidth() const { return m_rfBandwidth; } - Real getAFBandwidth() const { return m_afBandwidth; } - float getFMDeviation() const { return m_fmDeviation; } - float getToneFrequency() const { return m_toneFrequency; } - float getVolumeFactor() const { return m_volumeFactor; } - bool getChannelMute() const { return m_channelMute; } - bool getPlayLoop() const { return m_playLoop; } - - static MsgConfigureWFMMod* create(Real rfBandwidth, - Real afBandwidth, - float fmDeviation, - float toneFrequency, - float volumeFactor, - bool channelMute, - bool playLoop) - { - return new MsgConfigureWFMMod(rfBandwidth, - afBandwidth, - fmDeviation, - toneFrequency, - volumeFactor, - channelMute, - playLoop); - } - - private: - Real m_rfBandwidth; - Real m_afBandwidth; - float m_fmDeviation; - float m_toneFrequency; - float m_volumeFactor; - bool m_channelMute; - bool m_playLoop; - - MsgConfigureWFMMod(Real rfBandwidth, - Real afBandwidth, - float fmDeviation, - float toneFrequency, - float volumeFactor, - bool channelMute, - bool playLoop) : - Message(), - m_rfBandwidth(rfBandwidth), - m_afBandwidth(afBandwidth), - m_fmDeviation(fmDeviation), - m_toneFrequency(toneFrequency), - m_volumeFactor(volumeFactor), - m_channelMute(channelMute), - m_playLoop(playLoop) - { } - }; - - //================================================================= - - enum RateState { - RSInitialFill, - RSRunning - }; - - struct Config { - int m_basebandSampleRate; - int m_outputSampleRate; - qint64 m_inputFrequencyOffset; - Real m_rfBandwidth; - Real m_afBandwidth; - float m_fmDeviation; - float m_toneFrequency; - float m_volumeFactor; - quint32 m_audioSampleRate; - bool m_channelMute; - bool m_playLoop; - - Config() : - m_basebandSampleRate(0), - m_outputSampleRate(-1), - m_inputFrequencyOffset(0), - m_rfBandwidth(-1), - m_afBandwidth(-1), - m_fmDeviation(5000.0f), - m_toneFrequency(1000.0f), - m_volumeFactor(1.0f), - m_audioSampleRate(0), - m_channelMute(false), - m_playLoop(false) - { } - }; - - //================================================================= - - Config m_config; - Config m_running; - - NCO m_carrierNco; - NCOF m_toneNco; - NCOF m_toneNcoRF; - float m_modPhasor; //!< baseband modulator phasor - Complex m_modSample; - Interpolator m_interpolator; - Real m_interpolatorDistance; - Real m_interpolatorDistanceRemain; - bool m_interpolatorConsumed; - - fftfilt* m_rfFilter; - static const int m_rfFilterFFTLength; - fftfilt::cmplx *m_rfFilterBuffer; - int m_rfFilterBufferIndex; - - double m_magsq; - MovingAverage m_movingAverage; - SimpleAGC m_volumeAGC; - - AudioVector m_audioBuffer; - uint m_audioBufferFill; - - AudioFifo m_audioFifo; - SampleVector m_sampleBuffer; - QMutex m_settingsMutex; - - std::ifstream m_ifstream; - QString m_fileName; - quint64 m_fileSize; //!< raw file size (bytes) - quint32 m_recordLength; //!< record length in seconds computed from file size - int m_sampleRate; - - WFMModInputAF m_afInput; - quint32 m_levelCalcCount; - Real m_peakLevel; - Real m_levelSum; - CWKeyer m_cwKeyer; - CWSmoother m_cwSmoother; - static const int m_levelNbSamples; - - void apply(); - void pullAF(Complex& sample); - void calculateLevel(const Real& sample); - void openFileStream(); - void seekFileStream(int seekPercentage); -}; - - -#endif /* PLUGINS_CHANNELTX_MODWFM_WFMMOD_H_ */ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_CHANNELTX_MODWFM_WFMMOD_H_ +#define PLUGINS_CHANNELTX_MODWFM_WFMMOD_H_ + +#include +#include +#include +#include + +#include "dsp/basebandsamplesource.h" +#include "dsp/nco.h" +#include "dsp/ncof.h" +#include "dsp/interpolator.h" +#include "dsp/fftfilt.h" +#include "dsp/movingaverage.h" +#include "dsp/agc.h" +#include "dsp/cwkeyer.h" +#include "audio/audiofifo.h" +#include "util/message.h" + +#include "wfmmodsettings.h" + +class WFMMod : public BasebandSampleSource { + Q_OBJECT + +public: + typedef enum + { + WFMModInputNone, + WFMModInputTone, + WFMModInputFile, + WFMModInputAudio, + WFMModInputCWTone + } WFMModInputAF; + + class MsgConfigureWFMMod : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const WFMModSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureWFMMod* create(const WFMModSettings& settings, bool force) + { + return new MsgConfigureWFMMod(settings, force); + } + + private: + WFMModSettings m_settings; + bool m_force; + + MsgConfigureWFMMod(const WFMModSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgConfigureFileSourceName : public Message + { + MESSAGE_CLASS_DECLARATION + + public: + const QString& getFileName() const { return m_fileName; } + + static MsgConfigureFileSourceName* create(const QString& fileName) + { + return new MsgConfigureFileSourceName(fileName); + } + + private: + QString m_fileName; + + MsgConfigureFileSourceName(const QString& fileName) : + Message(), + m_fileName(fileName) + { } + }; + + class MsgConfigureFileSourceSeek : public Message + { + MESSAGE_CLASS_DECLARATION + + public: + int getPercentage() const { return m_seekPercentage; } + + static MsgConfigureFileSourceSeek* create(int seekPercentage) + { + return new MsgConfigureFileSourceSeek(seekPercentage); + } + + protected: + int m_seekPercentage; //!< percentage of seek position from the beginning 0..100 + + MsgConfigureFileSourceSeek(int seekPercentage) : + Message(), + m_seekPercentage(seekPercentage) + { } + }; + + class MsgConfigureFileSourceStreamTiming : public Message { + MESSAGE_CLASS_DECLARATION + + public: + + static MsgConfigureFileSourceStreamTiming* create() + { + return new MsgConfigureFileSourceStreamTiming(); + } + + private: + + MsgConfigureFileSourceStreamTiming() : + Message() + { } + }; + + class MsgConfigureAFInput : public Message + { + MESSAGE_CLASS_DECLARATION + + public: + WFMModInputAF getAFInput() const { return m_afInput; } + + static MsgConfigureAFInput* create(WFMModInputAF afInput) + { + return new MsgConfigureAFInput(afInput); + } + + private: + WFMModInputAF m_afInput; + + MsgConfigureAFInput(WFMModInputAF afInput) : + Message(), + m_afInput(afInput) + { } + }; + + class MsgReportFileSourceStreamTiming : public Message + { + MESSAGE_CLASS_DECLARATION + + public: + std::size_t getSamplesCount() const { return m_samplesCount; } + + static MsgReportFileSourceStreamTiming* create(std::size_t samplesCount) + { + return new MsgReportFileSourceStreamTiming(samplesCount); + } + + protected: + std::size_t m_samplesCount; + + MsgReportFileSourceStreamTiming(std::size_t samplesCount) : + Message(), + m_samplesCount(samplesCount) + { } + }; + + class MsgReportFileSourceStreamData : public Message { + MESSAGE_CLASS_DECLARATION + + public: + int getSampleRate() const { return m_sampleRate; } + quint32 getRecordLength() const { return m_recordLength; } + + static MsgReportFileSourceStreamData* create(int sampleRate, + quint32 recordLength) + { + return new MsgReportFileSourceStreamData(sampleRate, recordLength); + } + + protected: + int m_sampleRate; + quint32 m_recordLength; + + MsgReportFileSourceStreamData(int sampleRate, + quint32 recordLength) : + Message(), + m_sampleRate(sampleRate), + m_recordLength(recordLength) + { } + }; + + //================================================================= + + WFMMod(); + ~WFMMod(); + + void configure(MessageQueue* messageQueue, + Real rfBandwidth, + Real afBandwidth, + float fmDeviation, + float toneFrequency, + float volumeFactor, + bool audioMute, + bool playLoop); + + virtual void pull(Sample& sample); + virtual void pullAudio(int nbSamples); + virtual void start(); + virtual void stop(); + virtual bool handleMessage(const Message& cmd); + + double getMagSq() const { return m_magsq; } + + CWKeyer *getCWKeyer() { return &m_cwKeyer; } + +signals: + /** + * Level changed + * \param rmsLevel RMS level in range 0.0 - 1.0 + * \param peakLevel Peak level in range 0.0 - 1.0 + * \param numSamples Number of audio samples analyzed + */ + void levelChanged(qreal rmsLevel, qreal peakLevel, int numSamples); + + +private: + class MsgConfigureWFMModPrivate : public Message + { + MESSAGE_CLASS_DECLARATION + + public: + Real getRFBandwidth() const { return m_rfBandwidth; } + Real getAFBandwidth() const { return m_afBandwidth; } + float getFMDeviation() const { return m_fmDeviation; } + float getToneFrequency() const { return m_toneFrequency; } + float getVolumeFactor() const { return m_volumeFactor; } + bool getChannelMute() const { return m_channelMute; } + bool getPlayLoop() const { return m_playLoop; } + + static MsgConfigureWFMModPrivate* create(Real rfBandwidth, + Real afBandwidth, + float fmDeviation, + float toneFrequency, + float volumeFactor, + bool channelMute, + bool playLoop) + { + return new MsgConfigureWFMModPrivate(rfBandwidth, + afBandwidth, + fmDeviation, + toneFrequency, + volumeFactor, + channelMute, + playLoop); + } + + private: + Real m_rfBandwidth; + Real m_afBandwidth; + float m_fmDeviation; + float m_toneFrequency; + float m_volumeFactor; + bool m_channelMute; + bool m_playLoop; + + MsgConfigureWFMModPrivate(Real rfBandwidth, + Real afBandwidth, + float fmDeviation, + float toneFrequency, + float volumeFactor, + bool channelMute, + bool playLoop) : + Message(), + m_rfBandwidth(rfBandwidth), + m_afBandwidth(afBandwidth), + m_fmDeviation(fmDeviation), + m_toneFrequency(toneFrequency), + m_volumeFactor(volumeFactor), + m_channelMute(channelMute), + m_playLoop(playLoop) + { } + }; + + //================================================================= + + enum RateState { + RSInitialFill, + RSRunning + }; + + struct Config { + int m_basebandSampleRate; + int m_outputSampleRate; + qint64 m_inputFrequencyOffset; + Real m_rfBandwidth; + Real m_afBandwidth; + float m_fmDeviation; + float m_toneFrequency; + float m_volumeFactor; + quint32 m_audioSampleRate; + bool m_channelMute; + bool m_playLoop; + + Config() : + m_basebandSampleRate(0), + m_outputSampleRate(-1), + m_inputFrequencyOffset(0), + m_rfBandwidth(-1), + m_afBandwidth(-1), + m_fmDeviation(5000.0f), + m_toneFrequency(1000.0f), + m_volumeFactor(1.0f), + m_audioSampleRate(0), + m_channelMute(false), + m_playLoop(false) + { } + }; + + //================================================================= + + Config m_config; + Config m_running; + WFMModSettings m_settings; + + NCO m_carrierNco; + NCOF m_toneNco; + NCOF m_toneNcoRF; + float m_modPhasor; //!< baseband modulator phasor + Complex m_modSample; + Interpolator m_interpolator; + Real m_interpolatorDistance; + Real m_interpolatorDistanceRemain; + bool m_interpolatorConsumed; + + fftfilt* m_rfFilter; + static const int m_rfFilterFFTLength; + fftfilt::cmplx *m_rfFilterBuffer; + int m_rfFilterBufferIndex; + + double m_magsq; + MovingAverage m_movingAverage; + SimpleAGC m_volumeAGC; + + AudioVector m_audioBuffer; + uint m_audioBufferFill; + + AudioFifo m_audioFifo; + SampleVector m_sampleBuffer; + QMutex m_settingsMutex; + + std::ifstream m_ifstream; + QString m_fileName; + quint64 m_fileSize; //!< raw file size (bytes) + quint32 m_recordLength; //!< record length in seconds computed from file size + int m_sampleRate; + + WFMModInputAF m_afInput; + quint32 m_levelCalcCount; + Real m_peakLevel; + Real m_levelSum; + CWKeyer m_cwKeyer; + CWSmoother m_cwSmoother; + static const int m_levelNbSamples; + + void apply(); + void applySettings(const WFMModSettings& settings, bool force = false); + void pullAF(Complex& sample); + void calculateLevel(const Real& sample); + void openFileStream(); + void seekFileStream(int seekPercentage); +}; + + +#endif /* PLUGINS_CHANNELTX_MODWFM_WFMMOD_H_ */