diff --git a/plugins/feature/denoiser/denoiser.cpp b/plugins/feature/denoiser/denoiser.cpp index 04705d838..03a6d8a28 100644 --- a/plugins/feature/denoiser/denoiser.cpp +++ b/plugins/feature/denoiser/denoiser.cpp @@ -86,7 +86,11 @@ Denoiser::~Denoiser() &Denoiser::networkManagerFinished ); delete m_networkManager; - stop(); + + if (m_running) + { + stop(); + } } void Denoiser::start() @@ -284,7 +288,6 @@ void Denoiser::applySettings(const DenoiserSettings& settings, const QListgetInputMessageQueue()->push(msg); } - if (settings.m_useReverseAPI) { bool fullUpdate = (settingsKeys.contains("useReverseAPI") && settings.m_useReverseAPI) || diff --git a/plugins/feature/denoiser/denoisergui.cpp b/plugins/feature/denoiser/denoisergui.cpp index 4eaf27da9..58e398a3b 100644 --- a/plugins/feature/denoiser/denoisergui.cpp +++ b/plugins/feature/denoiser/denoisergui.cpp @@ -22,6 +22,9 @@ #include "gui/basicfeaturesettingsdialog.h" #include "gui/dialpopup.h" #include "gui/dialogpositioner.h" +#include "gui/crightclickenabler.h" +#include "gui/audioselectdialog.h" +#include "dsp/dspengine.h" #include "util/db.h" #include "maincore.h" @@ -157,6 +160,9 @@ DenoiserGUI::DenoiserGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Featu connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &))); connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); + CRightClickEnabler *audioMuteRightClickEnabler = new CRightClickEnabler(ui->audioMute); + connect(audioMuteRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(audioSelect(const QPoint &))); + connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus())); m_statusTimer.start(1000); @@ -341,6 +347,23 @@ void DenoiserGUI::on_showFileDialog_clicked(bool checked) } } +void DenoiserGUI::audioSelect(const QPoint& p) +{ + qDebug("DenoiserGUI::audioSelect"); + AudioSelectDialog audioSelect(DSPEngine::instance()->getAudioDeviceManager(), m_settings.m_audioDeviceName); + audioSelect.move(p); + new DialogPositioner(&audioSelect, false); + audioSelect.exec(); + + if (audioSelect.m_selected) + { + m_settings.m_audioDeviceName = audioSelect.m_audioDeviceName; + m_settingsKeys.append("audioDeviceName"); + applySettings(); + } +} + + void DenoiserGUI::tick() { m_channelPowerAvg(m_denoiser->getMagSqAvg()); diff --git a/plugins/feature/denoiser/denoisergui.h b/plugins/feature/denoiser/denoisergui.h index 25580d1e7..980a55464 100644 --- a/plugins/feature/denoiser/denoisergui.h +++ b/plugins/feature/denoiser/denoisergui.h @@ -91,6 +91,7 @@ private slots: void on_channelApply_clicked(); void on_record_toggled(bool checked); void on_showFileDialog_clicked(bool checked); + void audioSelect(const QPoint& p); void updateStatus(); void tick(); }; diff --git a/plugins/feature/denoiser/denoisersettings.cpp b/plugins/feature/denoiser/denoisersettings.cpp index 8e2c520f0..701cfdd8b 100644 --- a/plugins/feature/denoiser/denoisersettings.cpp +++ b/plugins/feature/denoiser/denoisersettings.cpp @@ -17,6 +17,7 @@ #include "util/simpleserializer.h" #include "settings/serializable.h" +#include "audio/audiodevicemanager.h" #include "denoisersettings.h" @@ -43,6 +44,7 @@ void DenoiserSettings::resetToDefaults() m_denoiserType = DenoiserType::DenoiserType_RNnoise; m_title = "Denoiser"; m_audioMute = false; + m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName; m_rgbColor = 0xffd700; // gold m_useReverseAPI = false; m_reverseAPIAddress = "localhost"; @@ -61,6 +63,7 @@ QByteArray DenoiserSettings::serialize() const s.writeS32(1, static_cast(m_denoiserType)); s.writeBool(14, m_audioMute); + s.writeString(15, m_audioDeviceName); s.writeString(2, m_title); s.writeU32(3, m_rgbColor); s.writeBool(4, m_useReverseAPI); @@ -99,6 +102,7 @@ bool DenoiserSettings::deserialize(const QByteArray& data) d.readS32(1, &itmp, 1); m_denoiserType = static_cast(itmp); d.readBool(14, &m_audioMute, false); + d.readString(15, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName); d.readString(2, &m_title, "Denoiser"); d.readU32(3, &m_rgbColor, 0xffd700); // gold d.readBool(4, &m_useReverseAPI, false); @@ -143,6 +147,9 @@ void DenoiserSettings::applySettings(const QStringList& settingsKeys, const Deno if (settingsKeys.contains("audioMute")) { m_audioMute = settings.m_audioMute; } + if (settingsKeys.contains("audioDeviceName")) { + m_audioDeviceName = settings.m_audioDeviceName; + } if (settingsKeys.contains("title")) { m_title = settings.m_title; } @@ -185,6 +192,9 @@ QString DenoiserSettings::getDebugString(const QStringList& settingsKeys, bool f if (settingsKeys.contains("audioMute") || force) { debugString += QString("Audio Mute: %1 ").arg(m_audioMute ? "true" : "false"); } + if (settingsKeys.contains("audioDeviceName") || force) { + debugString += QString("Audio Device Name: %1 ").arg(m_audioDeviceName); + } if (settingsKeys.contains("title") || force) { debugString += QString("Title: %1 ").arg(m_title); } diff --git a/plugins/feature/denoiser/denoisersettings.h b/plugins/feature/denoiser/denoisersettings.h index 22f95b513..01e57b6a6 100644 --- a/plugins/feature/denoiser/denoisersettings.h +++ b/plugins/feature/denoiser/denoisersettings.h @@ -33,6 +33,7 @@ struct DenoiserSettings DenoiserType m_denoiserType; bool m_audioMute; + QString m_audioDeviceName; QString m_title; quint32 m_rgbColor; bool m_useReverseAPI; diff --git a/plugins/feature/denoiser/denoiserworker.cpp b/plugins/feature/denoiser/denoiserworker.cpp index f60d4901e..96040cced 100644 --- a/plugins/feature/denoiser/denoiserworker.cpp +++ b/plugins/feature/denoiser/denoiserworker.cpp @@ -16,6 +16,8 @@ /////////////////////////////////////////////////////////////////////////////////// #include "dsp/wavfilerecord.h" +#include "audio/audiodevicemanager.h" +#include "dsp/dspengine.h" #include "denoiserworker.h" @@ -31,13 +33,20 @@ DenoiserWorker::DenoiserWorker(QObject *parent) : m_sampleBufferSize(0), m_channelPowerAvg(), m_wavFileRecord(nullptr), + m_recordSilenceNbSamples(0), + m_recordSilenceCount(0), m_nbBytes(0) { + m_audioBuffer.resize(4800); + m_audioBufferFill = 0; + m_audioFifo.setSize(4800 * 4); + DSPEngine::instance()->getAudioDeviceManager()->addAudioSink(getAudioFifo(), getInputMessageQueue()); } DenoiserWorker::~DenoiserWorker() { m_inputMessageQueue.clear(); + DSPEngine::instance()->getAudioDeviceManager()->removeAudioSink(getAudioFifo()); } void DenoiserWorker::reset() @@ -106,20 +115,7 @@ void DenoiserWorker::feedPart( for (int is = 0; is < countSamples; is++) { const Sample& sample = m_sampleBuffer[is]; - - if ((sample.m_real == 0) && (sample.m_imag == 0)) - { - if (m_wavFileRecord->isRecording()) { - m_wavFileRecord->stopRecording(); - } - } - else - { - if (!m_wavFileRecord->isRecording()) { - m_wavFileRecord->startRecording(); - } - writeSampleToFile(sample); - } + writeSampleToFile(sample); } } } @@ -204,16 +200,87 @@ bool DenoiserWorker::handleMessage(const Message& cmd) void DenoiserWorker::applySettings(const DenoiserSettings& settings, const QStringList& settingsKeys, bool force) { QMutexLocker mutexLocker(&m_mutex); - Q_UNUSED(settingsKeys) - Q_UNUSED(force) - m_settings = settings; + if (settingsKeys.contains("fileRecordName") || force) + { + if (m_wavFileRecord) + { + QStringList dotBreakout = settings.m_fileRecordName.split(QLatin1Char('.')); + + if (dotBreakout.size() > 1) + { + QString extension = dotBreakout.last(); + + if (extension != "wav") { + dotBreakout.last() = "wav"; + } + } + else + { + dotBreakout.append("wav"); + } + + QString newFileRecordName = dotBreakout.join(QLatin1Char('.')); + QString fileBase; + FileRecordInterface::guessTypeFromFileName(newFileRecordName, fileBase); + qDebug("DemodAnalyzerWorker::applySettings: newFileRecordName: %s fileBase: %s", qPrintable(newFileRecordName), qPrintable(fileBase)); + m_wavFileRecord->setFileName(fileBase); + } + } + + if (settingsKeys.contains("recordToFile") || force) + { + if (m_wavFileRecord) + { + if (settings.m_recordToFile) + { + if (!m_wavFileRecord->isRecording()) { + m_wavFileRecord->startRecording(); + } + } + else + { + if (m_wavFileRecord->isRecording()) { + m_wavFileRecord->stopRecording(); + } + } + + m_recordSilenceCount = 0; + } + } + + if ((settingsKeys.contains("audioDeviceName") && (settings.m_audioDeviceName != m_settings.m_audioDeviceName)) || force) + { + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + int audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_audioDeviceName); + audioDeviceManager->removeAudioSink(getAudioFifo()); + audioDeviceManager->addAudioSink(getAudioFifo(), getInputMessageQueue(), audioDeviceIndex); + unsigned int audioSampleRate = audioDeviceManager->getOutputSampleRate(audioDeviceIndex); + qDebug() << "DenoiserWorker::applySettings: audio device name:" << settings.m_audioDeviceName + << " index:" << audioDeviceIndex << " sample rate:" << audioSampleRate; + // TODO: handle sample rate change + } + + if (force) { + m_settings = settings; + } else { + m_settings.applySettings(settingsKeys, settings); + } } void DenoiserWorker::applySampleRate(int sampleRate) { QMutexLocker mutexLocker(&m_mutex); m_sinkSampleRate = sampleRate; + + if (m_wavFileRecord) + { + if (m_wavFileRecord->isRecording()) { + m_wavFileRecord->stopRecording(); + } + + m_wavFileRecord->setSampleRate(m_sinkSampleRate); + } } void DenoiserWorker::handleData() @@ -243,3 +310,69 @@ void DenoiserWorker::handleData() m_dataFifo->readCommit((unsigned int) count); } } + +void DenoiserWorker::processSample( + DataFifo::DataType dataType, + const QByteArray::const_iterator& begin, + int i +) +{ + switch(dataType) + { + case DataFifo::DataTypeI16: { + int16_t *s = (int16_t*) begin; + double re = s[i] / (double) std::numeric_limits::max(); + m_magsq = re*re; + m_channelPowerAvg(m_magsq); + + m_sampleBuffer[i].setReal(re * SDR_RX_SCALEF); + m_sampleBuffer[i].setImag(0); + + m_audioBuffer[m_audioBufferFill].l = s[i]; + m_audioBuffer[m_audioBufferFill].r = s[i]; + ++m_audioBufferFill; + + if (m_audioBufferFill >= m_audioBuffer.size()) + { + std::size_t res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); + + if (res != m_audioBufferFill) + { + qDebug("DenoiserWorker::processSample: %lu/%lu audio samples written", res, m_audioBufferFill); + m_audioFifo.clear(); + } + + m_audioBufferFill = 0; + } + } + break; + case DataFifo::DataTypeCI16: { + int16_t *s = (int16_t*) begin; + double re = s[2*i] / (double) std::numeric_limits::max(); + double im = s[2*i+1] / (double) std::numeric_limits::max(); + m_magsq = re*re + im*im; + m_channelPowerAvg(m_magsq); + + m_sampleBuffer[i].setReal(re * SDR_RX_SCALEF); + m_sampleBuffer[i].setImag(im * SDR_RX_SCALEF); + + m_audioBuffer[m_audioBufferFill].l = s[2*i]; + m_audioBuffer[m_audioBufferFill].r = s[2*i+1]; + ++m_audioBufferFill; + + if (m_audioBufferFill >= m_audioBuffer.size()) + { + std::size_t res = m_audioFifo.write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); + + if (res != m_audioBufferFill) + { + qDebug("DenoiserWorker::processSample: %lu/%lu audio samples written", res, m_audioBufferFill); + m_audioFifo.clear(); + } + + m_audioBufferFill = 0; + } + } + break; + } +} diff --git a/plugins/feature/denoiser/denoiserworker.h b/plugins/feature/denoiser/denoiserworker.h index 2a4baf0cc..4c912ff26 100644 --- a/plugins/feature/denoiser/denoiserworker.h +++ b/plugins/feature/denoiser/denoiserworker.h @@ -18,14 +18,16 @@ #define INCLUDE_FEATURE_DENOISER_DENOISERWORKER_H_ #include -#include +#include #include +#include #include "util/movingaverage.h" #include "util/message.h" #include "util/messagequeue.h" #include "dsp/dsptypes.h" #include "dsp/datafifo.h" +#include "audio/audiofifo.h" #include "denoisersettings.h" @@ -86,7 +88,7 @@ public: double getMagSq() const { return m_magsq; } double getMagSqAvg() const { return (double) m_channelPowerAvg; } -private: + private: DataFifo *m_dataFifo; int m_sinkSampleRate; MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication @@ -97,9 +99,15 @@ private: int m_sampleBufferSize; MovingAverageUtil m_channelPowerAvg; WavFileRecord* m_wavFileRecord; + int m_recordSilenceNbSamples; + int m_recordSilenceCount; int m_nbBytes; + AudioVector m_audioBuffer; + AudioFifo m_audioFifo; + std::size_t m_audioBufferFill; QRecursiveMutex m_mutex; + AudioFifo *getAudioFifo() { return &m_audioFifo; } void feedPart( const QByteArray::const_iterator& begin, const QByteArray::const_iterator& end, @@ -109,37 +117,11 @@ private: bool handleMessage(const Message& cmd); void writeSampleToFile(const Sample& sample); - inline void processSample( + void processSample( DataFifo::DataType dataType, const QByteArray::const_iterator& begin, int i - ) - { - switch(dataType) - { - case DataFifo::DataTypeI16: { - int16_t *s = (int16_t*) begin; - double re = s[i] / (double) std::numeric_limits::max(); - m_magsq = re*re; - m_channelPowerAvg(m_magsq); - - m_sampleBuffer[i].setReal(re * SDR_RX_SCALEF); - m_sampleBuffer[i].setImag(0); - } - break; - case DataFifo::DataTypeCI16: { - int16_t *s = (int16_t*) begin; - double re = s[2*i] / (double) std::numeric_limits::max(); - double im = s[2*i+1] / (double) std::numeric_limits::max(); - m_magsq = re*re + im*im; - m_channelPowerAvg(m_magsq); - - m_sampleBuffer[i].setReal(re * SDR_RX_SCALEF); - m_sampleBuffer[i].setImag(im * SDR_RX_SCALEF); - } - break; - } - } + ); private slots: