From 989feb9108b71510cb6b1eee6b4d909ad77c0395 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 29 Mar 2016 03:17:59 +0200 Subject: [PATCH] FCD Pro+ plugin: non ALSA classes to handle FCD audio for Windows build --- fcdlib/fcdtraits.h | 3 + .../fcdproplus/fcdproplusinput.cpp | 3 +- .../samplesource/fcdproplus/fcdproplusinput.h | 3 +- .../fcdproplus/fcdproplusinputqt.cpp | 318 ++++++++++++++++++ .../fcdproplus/fcdproplusinputqt.h | 89 +++++ .../fcdproplus/fcdproplusreader.cpp | 145 ++++++++ .../fcdproplus/fcdproplusreader.h | 59 ++++ 7 files changed, 616 insertions(+), 4 deletions(-) create mode 100644 plugins/samplesource/fcdproplus/fcdproplusinputqt.cpp create mode 100644 plugins/samplesource/fcdproplus/fcdproplusinputqt.h create mode 100644 plugins/samplesource/fcdproplus/fcdproplusreader.cpp create mode 100644 plugins/samplesource/fcdproplus/fcdproplusreader.h diff --git a/fcdlib/fcdtraits.h b/fcdlib/fcdtraits.h index 82b710c69..74df0c2d5 100644 --- a/fcdlib/fcdtraits.h +++ b/fcdlib/fcdtraits.h @@ -23,6 +23,7 @@ struct fcd_traits static const uint16_t productId = 0x0; static const int sampleRate = 48000; static const int convBufSize = (1<<11); + static const int fcdBufSize = (1<<12); static const char *alsaDeviceName; static const char *interfaceIID; static const char *displayedName; @@ -37,6 +38,7 @@ struct fcd_traits static const uint16_t productId = 0xFB56; static const int sampleRate = 96000; static const int convBufSize = (1<<11); + static const int fcdBufSize = (1<<12); static const char *alsaDeviceName; static const char *interfaceIID; static const char *displayedName; @@ -51,6 +53,7 @@ struct fcd_traits static const uint16_t productId = 0xFB31; static const int sampleRate = 192000; static const int convBufSize = (1<<12); + static const int fcdBufSize = (1<<18); static const char *alsaDeviceName; static const char *interfaceIID; static const char *displayedName; diff --git a/plugins/samplesource/fcdproplus/fcdproplusinput.cpp b/plugins/samplesource/fcdproplus/fcdproplusinput.cpp index b0120919d..a8fd7acb2 100644 --- a/plugins/samplesource/fcdproplus/fcdproplusinput.cpp +++ b/plugins/samplesource/fcdproplus/fcdproplusinput.cpp @@ -1,6 +1,5 @@ /////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // -// written by Christian Daniel // +// 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 // diff --git a/plugins/samplesource/fcdproplus/fcdproplusinput.h b/plugins/samplesource/fcdproplus/fcdproplusinput.h index c806bfa92..f4af3db1e 100644 --- a/plugins/samplesource/fcdproplus/fcdproplusinput.h +++ b/plugins/samplesource/fcdproplus/fcdproplusinput.h @@ -1,6 +1,5 @@ /////////////////////////////////////////////////////////////////////////////////// -// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany // -// written by Christian Daniel // +// 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 // diff --git a/plugins/samplesource/fcdproplus/fcdproplusinputqt.cpp b/plugins/samplesource/fcdproplus/fcdproplusinputqt.cpp new file mode 100644 index 000000000..b388e5d9c --- /dev/null +++ b/plugins/samplesource/fcdproplus/fcdproplusinputqt.cpp @@ -0,0 +1,318 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 Edouard Griffiths, F4EXB // +// // +// This is a pure Qt (non ALSA) FCD sound card reader for Windows build // +// // +// 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 "dsp/dspcommands.h" +#include "dsp/dspengine.h" +#include "fcdproplusinputqt.h" + +#include "fcdproplusgui.h" +#include "fcdproplusreader.h" +#include "fcdtraits.h" +#include "fcdproplusconst.h" + +MESSAGE_CLASS_DEFINITION(FCDProPlusInput::MsgConfigureFCD, Message) + +FCDProPlusInput::FCDProPlusInput() : + m_dev(0), + m_settings(), + m_deviceDescription(fcd_traits::displayedName) +{ + m_FCDReader = new FCDProPlusReader(&m_sampleFifo); +} + +FCDProPlusInput::~FCDProPlusInput() +{ + stop(); + delete m_FCDReader; +} + +bool FCDProPlusInput::init(const Message& cmd) +{ + return false; +} + +bool FCDProPlusInput::start(int device) +{ + qDebug() << "FCDProPlusInput::start with device #" << device; + + QMutexLocker mutexLocker(&m_mutex); + + m_dev = fcdOpen(fcd_traits::vendorId, fcd_traits::productId, device); + + if (m_dev == 0) + { + qCritical("FCDProPlusInput::start: could not open FCD"); + return false; + } + + /* Apply settings before streaming to avoid bus contention; + * there is very little spare bandwidth on a full speed USB device. + * Failure is harmless if no device is found + * ... This is rubbish...*/ + + //applySettings(m_settings, true); + + if(!m_sampleFifo.setSize(96000*4)) + { + qCritical("Could not allocate SampleFifo"); + return false; + } + + m_FCDReader->startWork(); + + mutexLocker.unlock(); + applySettings(m_settings, true); + + qDebug("FCDProPlusInput::started"); + return true; +} + +void FCDProPlusInput::stop() +{ + QMutexLocker mutexLocker(&m_mutex); + + m_FCDReader->stopWork(); + + fcdClose(m_dev); + m_dev = 0; +} + +const QString& FCDProPlusInput::getDeviceDescription() const +{ + return m_deviceDescription; +} + +int FCDProPlusInput::getSampleRate() const +{ + return fcd_traits::sampleRate; +} + +quint64 FCDProPlusInput::getCenterFrequency() const +{ + return m_settings.m_centerFrequency; +} + +bool FCDProPlusInput::handleMessage(const Message& message) +{ + if(MsgConfigureFCD::match(message)) + { + qDebug() << "FCDProPlusInput::handleMessage: MsgConfigureFCD"; + MsgConfigureFCD& conf = (MsgConfigureFCD&) message; + applySettings(conf.getSettings(), false); + return true; + } + else + { + return false; + } +} + +void FCDProPlusInput::applySettings(const FCDProPlusSettings& settings, bool force) +{ + bool signalChange = false; + + if ((m_settings.m_centerFrequency != settings.m_centerFrequency) || force) + { + qDebug() << "FCDProPlusInput::applySettings: fc: " << settings.m_centerFrequency; + m_settings.m_centerFrequency = settings.m_centerFrequency; + + if (m_dev != 0) + { + set_center_freq((double) m_settings.m_centerFrequency); + } + + signalChange = true; + } + + if ((m_settings.m_lnaGain != settings.m_lnaGain) || force) + { + m_settings.m_lnaGain = settings.m_lnaGain; + + if (m_dev != 0) + { + set_lna_gain(settings.m_lnaGain); + } + } + + if ((m_settings.m_biasT != settings.m_biasT) || force) + { + m_settings.m_biasT = settings.m_biasT; + + if (m_dev != 0) + { + set_bias_t(settings.m_biasT); + } + } + + if ((m_settings.m_mixGain != settings.m_mixGain) || force) + { + m_settings.m_mixGain = settings.m_mixGain; + + if (m_dev != 0) + { + set_mixer_gain(settings.m_mixGain); + } + } + + if ((m_settings.m_ifGain != settings.m_ifGain) || force) + { + m_settings.m_ifGain = settings.m_ifGain; + + if (m_dev != 0) + { + set_if_gain(settings.m_ifGain); + } + } + + if ((m_settings.m_ifFilterIndex != settings.m_ifFilterIndex) || force) + { + m_settings.m_ifFilterIndex = settings.m_ifFilterIndex; + + if (m_dev != 0) + { + set_if_filter(settings.m_ifFilterIndex); + } + } + + if ((m_settings.m_rfFilterIndex != settings.m_rfFilterIndex) || force) + { + m_settings.m_rfFilterIndex = settings.m_rfFilterIndex; + + if (m_dev != 0) + { + set_rf_filter(settings.m_rfFilterIndex); + } + } + + if ((m_settings.m_LOppmTenths != settings.m_LOppmTenths) || force) + { + m_settings.m_LOppmTenths = settings.m_LOppmTenths; + + if (m_dev != 0) + { + set_lo_ppm(); + } + } + + if ((m_settings.m_dcBlock != settings.m_dcBlock) || force) + { + m_settings.m_dcBlock = settings.m_dcBlock; + DSPEngine::instance()->configureCorrections(m_settings.m_dcBlock, m_settings.m_iqImbalance); + } + + if ((m_settings.m_iqImbalance != settings.m_iqImbalance) || force) + { + m_settings.m_iqImbalance = settings.m_iqImbalance; + DSPEngine::instance()->configureCorrections(m_settings.m_dcBlock, m_settings.m_iqImbalance); + } + + if (signalChange) + { + DSPSignalNotification *notif = new DSPSignalNotification(fcd_traits::sampleRate, m_settings.m_centerFrequency); + DSPEngine::instance()->getInputMessageQueue()->push(notif); + } +} + +void FCDProPlusInput::set_center_freq(double freq) +{ + freq += freq*(((double) m_settings.m_LOppmTenths)/10000000.0); + + if (fcdAppSetFreq(m_dev, freq) == FCD_MODE_NONE) + { + qDebug("No FCD HID found for frquency change"); + } +} + +void FCDProPlusInput::set_bias_t(bool on) +{ + quint8 cmd = on ? 1 : 0; + + fcdAppSetParam(m_dev, FCDPROPLUS_HID_CMD_SET_BIAS_TEE, &cmd, 1); +} + +void FCDProPlusInput::set_lna_gain(bool on) +{ + quint8 cmd = on ? 1 : 0; + + fcdAppSetParam(m_dev, FCDPROPLUS_HID_CMD_SET_LNA_GAIN, &cmd, 1); +} + +void FCDProPlusInput::set_mixer_gain(bool on) +{ + quint8 cmd = on ? 1 : 0; + + fcdAppSetParam(m_dev, FCDPROPLUS_HID_CMD_SET_MIXER_GAIN, &cmd, 1); +} + +void FCDProPlusInput::set_if_gain(int gain) +{ + if (gain < 0) + { + return; + } + + quint8 cmd_value = gain; + + if (fcdAppSetParam(m_dev, FCDPROPLUS_HID_CMD_SET_IF_GAIN, &cmd_value, 1) != FCD_MODE_APP) + { + qWarning() << "FCDProPlusInput::set_if_gain: failed to set at " << cmd_value; + } +} + +void FCDProPlusInput::set_if_filter(int filterIndex) +{ + if ((filterIndex < 0) || (filterIndex >= FCDProPlusConstants::fcdproplus_if_filter_nb_values())) + { + return; + } + + quint8 cmd_value = FCDProPlusConstants::if_filters[filterIndex].value; + + if (fcdAppSetParam(m_dev, FCDPROPLUS_HID_CMD_SET_IF_FILTER, &cmd_value, 1) != FCD_MODE_APP) + { + qWarning() << "FCDProPlusInput::set_if_filter: failed to set at " << cmd_value; + } +} + + +void FCDProPlusInput::set_rf_filter(int filterIndex) +{ + if ((filterIndex < 0) || (filterIndex >= FCDProPlusConstants::fcdproplus_rf_filter_nb_values())) + { + return; + } + + quint8 cmd_value = FCDProPlusConstants::rf_filters[filterIndex].value; + + if (fcdAppSetParam(m_dev, FCDPROPLUS_HID_CMD_SET_RF_FILTER, &cmd_value, 1) != FCD_MODE_APP) + { + qWarning() << "FCDProPlusInput::set_rf_filter: failed to set at " << cmd_value; + } +} + +void FCDProPlusInput::set_lo_ppm() +{ + set_center_freq((double) m_settings.m_centerFrequency); +} + + + + diff --git a/plugins/samplesource/fcdproplus/fcdproplusinputqt.h b/plugins/samplesource/fcdproplus/fcdproplusinputqt.h new file mode 100644 index 000000000..4b3063f01 --- /dev/null +++ b/plugins/samplesource/fcdproplus/fcdproplusinputqt.h @@ -0,0 +1,89 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 Edouard Griffiths, F4EXB // +// // +// This is a pure Qt (non ALSA) FCD sound card reader for Windows build // +// // +// 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_FCDINPUT_H +#define INCLUDE_FCDINPUT_H + +#include "fcdproplussettings.h" +#include "dsp/samplesource.h" +#include "fcdhid.h" +#include +#include + +struct fcd_buffer { + void *start; + std::size_t length; +}; + +class FCDProPlusReader; + +class FCDProPlusInput : public SampleSource { +public: + class MsgConfigureFCD : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const FCDProPlusSettings& getSettings() const { return m_settings; } + + static MsgConfigureFCD* create(const FCDProPlusSettings& settings) + { + return new MsgConfigureFCD(settings); + } + + private: + FCDProPlusSettings m_settings; + + MsgConfigureFCD(const FCDProPlusSettings& settings) : + Message(), + m_settings(settings) + { } + }; + + FCDProPlusInput(); + virtual ~FCDProPlusInput(); + + virtual bool init(const Message& cmd); + virtual bool start(int device); + virtual void stop(); + + virtual const QString& getDeviceDescription() const; + virtual int getSampleRate() const; + virtual quint64 getCenterFrequency() const; + + virtual bool handleMessage(const Message& message); + + void set_center_freq(double freq); + void set_bias_t(bool on); + void set_lna_gain(bool on); + void set_mixer_gain(bool on); + void set_if_gain(int gain); + void set_rf_filter(int filterIndex); + void set_if_filter(int filterIndex); + void set_lo_ppm(); + +private: + void applySettings(const FCDProPlusSettings& settings, bool force); + + hid_device *m_dev; + QMutex m_mutex; + FCDProPlusSettings m_settings; + FCDProPlusReader* m_FCDReader; + QString m_deviceDescription; +}; + +#endif // INCLUDE_FCD_H diff --git a/plugins/samplesource/fcdproplus/fcdproplusreader.cpp b/plugins/samplesource/fcdproplus/fcdproplusreader.cpp new file mode 100644 index 000000000..1c37b4ab1 --- /dev/null +++ b/plugins/samplesource/fcdproplus/fcdproplusreader.cpp @@ -0,0 +1,145 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 +#include +#include "dsp/samplefifo.h" +#include "fcdproplusreader.h" +#include "fcdtraits.h" + +FCDProPlusReader::FCDProPlusReader(SampleFifo* sampleFifo, QObject* parent) : + QObject(parent), + m_fcdAudioInput(0), + m_fcdInput(0), + m_running(false), + m_convertBuffer(fcd_traits::convBufSize), + m_fcdBuffer(fcd_traits::fcdBufSize, 0), + m_sampleFifo(sampleFifo) +{ +} + +FCDProPlusReader::~FCDProPlusReader() +{ + if (m_fcdAudioInput) { + delete m_fcdAudioInput; + } +} + +void FCDProPlusReader::startWork() +{ + QList audioDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput); + + QList::iterator audioDeviceIt, fcdDeviceIt = audioDevices.end(); + + for (audioDeviceIt = audioDevices.begin(); audioDeviceIt != audioDevices.end(); ++audioDeviceIt) + { + QAudioFormat fcdAudioFormat = audioDeviceIt->preferredFormat(); + int sampleRate = fcdAudioFormat.sampleRate(); + int sampleBits = fcdAudioFormat.sampleSize(); + + if ((sampleRate == 192000) && (sampleBits == 16)) + { + qDebug() << "FCDProPlusReader::startWork: found: " << audioDeviceIt->deviceName() + << " sampleRate: " << fcdAudioFormat.sampleRate() + << " sampleBits: " << fcdAudioFormat.sampleSize(); + fcdDeviceIt = audioDeviceIt; + break; + } + } + + if (fcdDeviceIt == audioDevices.end()) + { + qCritical() << "FCDProPlusReader::startWork: FCD Pro+ sound card not found"; + return; + } + + openFcdAudio(*fcdDeviceIt); + + if (!m_fcdAudioInput) + { + qCritical() << "FCDProPlusReader::startWork: cannot open FCD Pro+ sound card"; + return; + } + + m_fcdAudioInput->stop(); + m_fcdInput = m_fcdAudioInput->start(); + + if (!m_fcdInput) + { + qCritical() << "FCDProPlusReader::startWork: cannot start FCD Pro+ sound card"; + return; + } + + connect(m_fcdInput, SIGNAL(readyRead()), this, SLOT(readFcdAudio())); + m_running = true; + + qDebug() << "FCDProPlusReader::startWork: started"; +} + +void FCDProPlusReader::stopWork() +{ + m_running = false; + disconnect(m_fcdInput, SIGNAL(readyRead()), this, SLOT(readFcdAudio())); + m_fcdAudioInput->stop(); + + qDebug() << "FCDProPlusReader::stopWork: stopped"; +} + +void FCDProPlusReader::openFcdAudio(const QAudioDeviceInfo& fcdAudioDeviceInfo) +{ + QAudioFormat fcdAudioFormat = fcdAudioDeviceInfo.preferredFormat(); + + qDebug() << "FCDProPlusReader::openFcdAudio: device: " << fcdAudioDeviceInfo.deviceName() + << " sampleRate: " << fcdAudioFormat.sampleRate() + << " sampleBits: " << fcdAudioFormat.sampleSize(); + + m_fcdAudioInput = new QAudioInput(fcdAudioDeviceInfo, fcdAudioFormat, this); + //m_fcdAudioInput->setBufferSize(1<<14); + //m_fcdAudioInput->setNotifyInterval(50); +} + +void FCDProPlusReader::readFcdAudio() +{ + if (!m_fcdAudioInput) { + return; + } + + int len = m_fcdAudioInput->bytesReady(); + +// qDebug() << "FCDProPlusReader::readFcdAudio:" +// << " buffer size: " << m_fcdAudioInput->bufferSize() +// << " interval: " << m_fcdAudioInput->notifyInterval() +// << " len: " << len; + + if (len > fcd_traits::fcdBufSize) { + len = fcd_traits::fcdBufSize; + } + + int readLen = m_fcdInput->read(m_fcdBuffer.data(), len); + + if (readLen > 0) { + m_sampleFifo->write((const quint8*) m_fcdBuffer.constData(), (uint) readLen); + } +} + diff --git a/plugins/samplesource/fcdproplus/fcdproplusreader.h b/plugins/samplesource/fcdproplus/fcdproplusreader.h new file mode 100644 index 000000000..5a69dab82 --- /dev/null +++ b/plugins/samplesource/fcdproplus/fcdproplusreader.h @@ -0,0 +1,59 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_FCDPROPLUSREADER_H +#define INCLUDE_FCDPROPLUSREADER_H + +#include +#include +#include +#include + +#include "dsp/samplefifo.h" +#include "dsp/inthalfbandfilter.h" + +class QAudioInput; +class QIODevice; +class QAudioDeviceInfo; + +class FCDProPlusReader : public QObject { + Q_OBJECT + +public: + FCDProPlusReader(SampleFifo* sampleFifo, QObject* parent = NULL); + ~FCDProPlusReader(); + + void startWork(); + void stopWork(); + +private: + QAudioInput *m_fcdAudioInput; + QIODevice *m_fcdInput; + + QMutex m_startWaitMutex; + QWaitCondition m_startWaiter; + bool m_running; + + SampleVector m_convertBuffer; + QByteArray m_fcdBuffer; + SampleFifo* m_sampleFifo; + + void openFcdAudio(const QAudioDeviceInfo& fcdAudioDeviceInfo); + +private slots: + void readFcdAudio(); +}; +#endif // INCLUDE_FCDPROPLUSREADER_H