kopia lustrzana https://github.com/f4exb/sdrangel
				
				
				
			AMBE feature: creation and changes to DSD demod
							rodzic
							
								
									ee65186057
								
							
						
					
					
						commit
						1d72798d42
					
				| 
						 | 
				
			
			@ -39,6 +39,7 @@
 | 
			
		|||
#include "dsp/dspcommands.h"
 | 
			
		||||
#include "device/deviceapi.h"
 | 
			
		||||
#include "feature/feature.h"
 | 
			
		||||
#include "feature/featureset.h"
 | 
			
		||||
#include "settings/serializable.h"
 | 
			
		||||
#include "util/db.h"
 | 
			
		||||
#include "maincore.h"
 | 
			
		||||
| 
						 | 
				
			
			@ -46,6 +47,8 @@
 | 
			
		|||
#include "dsddemod.h"
 | 
			
		||||
 | 
			
		||||
MESSAGE_CLASS_DEFINITION(DSDDemod::MsgConfigureDSDDemod, Message)
 | 
			
		||||
MESSAGE_CLASS_DEFINITION(DSDDemod::MsgQueryAvailableAMBEFeatures, Message)
 | 
			
		||||
MESSAGE_CLASS_DEFINITION(DSDDemod::MsgReportAvailableAMBEFeatures, Message)
 | 
			
		||||
 | 
			
		||||
const char* const DSDDemod::m_channelIdURI = "sdrangel.channel.dsddemod";
 | 
			
		||||
const char* const DSDDemod::m_channelId = "DSDDemod";
 | 
			
		||||
| 
						 | 
				
			
			@ -82,6 +85,20 @@ DSDDemod::DSDDemod(DeviceAPI *deviceAPI) :
 | 
			
		|||
        this,
 | 
			
		||||
        &DSDDemod::handleIndexInDeviceSetChanged
 | 
			
		||||
    );
 | 
			
		||||
    QObject::connect(
 | 
			
		||||
        MainCore::instance(),
 | 
			
		||||
        &MainCore::featureAdded,
 | 
			
		||||
        this,
 | 
			
		||||
        &DSDDemod::handleFeatureAdded
 | 
			
		||||
    );
 | 
			
		||||
    QObject::connect(
 | 
			
		||||
        MainCore::instance(),
 | 
			
		||||
        &MainCore::featureRemoved,
 | 
			
		||||
        this,
 | 
			
		||||
        &DSDDemod::handleFeatureRemoved
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    scanAvailableAMBEFeatures();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DSDDemod::~DSDDemod()
 | 
			
		||||
| 
						 | 
				
			
			@ -176,6 +193,11 @@ bool DSDDemod::handleMessage(const Message& cmd)
 | 
			
		|||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    else if (MsgQueryAvailableAMBEFeatures::match(cmd))
 | 
			
		||||
    {
 | 
			
		||||
        notifyUpdateAMBEFeatures();
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		return false;
 | 
			
		||||
| 
						 | 
				
			
			@ -219,6 +241,8 @@ void DSDDemod::applySettings(const DSDDemodSettings& settings, bool force)
 | 
			
		|||
            << " m_traceStroke: " << settings.m_traceStroke
 | 
			
		||||
            << " m_traceDecay: " << settings.m_traceDecay
 | 
			
		||||
            << " m_streamIndex: " << settings.m_streamIndex
 | 
			
		||||
            << " m_ambeFeatureIndex: " << settings.m_ambeFeatureIndex
 | 
			
		||||
            << " m_connectAMBE: " << settings.m_connectAMBE
 | 
			
		||||
            << " force: " << force;
 | 
			
		||||
 | 
			
		||||
    QList<QString> reverseAPIKeys;
 | 
			
		||||
| 
						 | 
				
			
			@ -280,6 +304,30 @@ void DSDDemod::applySettings(const DSDDemodSettings& settings, bool force)
 | 
			
		|||
    if ((settings.m_audioDeviceName != m_settings.m_audioDeviceName) || force) {
 | 
			
		||||
        reverseAPIKeys.append("audioDeviceName");
 | 
			
		||||
    }
 | 
			
		||||
    if ((settings.m_ambeFeatureIndex != m_settings.m_ambeFeatureIndex) || force) {
 | 
			
		||||
        reverseAPIKeys.append("ambeFeatureIndex");
 | 
			
		||||
    }
 | 
			
		||||
    if ((settings.m_connectAMBE != m_settings.m_connectAMBE) || force) {
 | 
			
		||||
        reverseAPIKeys.append("connectAMBE");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ((m_settings.m_connectAMBE != settings.m_connectAMBE)
 | 
			
		||||
    ||  (m_settings.m_ambeFeatureIndex != settings.m_ambeFeatureIndex) || force)
 | 
			
		||||
    {
 | 
			
		||||
        if (settings.m_connectAMBE)
 | 
			
		||||
        {
 | 
			
		||||
            for (const auto& feature : m_availableAMBEFeatures)
 | 
			
		||||
            {
 | 
			
		||||
                if (feature.m_featureIndex == settings.m_ambeFeatureIndex) {
 | 
			
		||||
                    m_basebandSink->setAMBEFeatureMessageQueue(feature.m_feature->getInputMessageQueue());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            m_basebandSink->setAMBEFeatureMessageQueue(nullptr);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (m_settings.m_streamIndex != settings.m_streamIndex)
 | 
			
		||||
    {
 | 
			
		||||
| 
						 | 
				
			
			@ -339,6 +387,32 @@ bool DSDDemod::deserialize(const QByteArray& data)
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DSDDemod::scanAvailableAMBEFeatures()
 | 
			
		||||
{
 | 
			
		||||
    MainCore *mainCore = MainCore::instance();
 | 
			
		||||
    int nbFeatures = mainCore->getFeatureeSets()[0]->getNumberOfFeatures();
 | 
			
		||||
    m_availableAMBEFeatures.clear();
 | 
			
		||||
 | 
			
		||||
    for (int i = 0; i < nbFeatures; i++)
 | 
			
		||||
    {
 | 
			
		||||
        Feature *feature = mainCore->getFeatureeSets()[0]->getFeatureAt(i);
 | 
			
		||||
 | 
			
		||||
        if (feature->getURI() == "sdrangel.feature.ambe") {
 | 
			
		||||
            m_availableAMBEFeatures[feature] = DSDDemodSettings::AvailableAMBEFeature{i, feature};
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DSDDemod::notifyUpdateAMBEFeatures()
 | 
			
		||||
{
 | 
			
		||||
    if (getMessageQueueToGUI())
 | 
			
		||||
    {
 | 
			
		||||
        MsgReportAvailableAMBEFeatures *msg = MsgReportAvailableAMBEFeatures::create();
 | 
			
		||||
        msg->getFeatures() = m_availableAMBEFeatures.values();
 | 
			
		||||
        getMessageQueueToGUI()->push(msg);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DSDDemod::sendSampleRateToDemodAnalyzer()
 | 
			
		||||
{
 | 
			
		||||
    QList<ObjectPipe*> pipes;
 | 
			
		||||
| 
						 | 
				
			
			@ -794,3 +868,40 @@ void DSDDemod::handleIndexInDeviceSetChanged(int index)
 | 
			
		|||
    m_basebandSink->setFifoLabel(fifoLabel);
 | 
			
		||||
    m_basebandSink->setAudioFifoLabel(fifoLabel);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DSDDemod::handleFeatureAdded(int featureSetIndex, Feature *feature)
 | 
			
		||||
{
 | 
			
		||||
    if (featureSetIndex != 0) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ((feature->getURI() == "sdrangel.feature.ambe") && !m_availableAMBEFeatures.contains(feature))
 | 
			
		||||
    {
 | 
			
		||||
        m_availableAMBEFeatures[feature] = DSDDemodSettings::AvailableAMBEFeature{feature->getIndexInFeatureSet(), feature};
 | 
			
		||||
 | 
			
		||||
        if (m_settings.m_connectAMBE && (m_settings.m_ambeFeatureIndex == feature->getIndexInFeatureSet())) {
 | 
			
		||||
            m_basebandSink->setAMBEFeatureMessageQueue(feature->getInputMessageQueue());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        notifyUpdateAMBEFeatures();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DSDDemod::handleFeatureRemoved(int featureSetIndex, Feature *feature)
 | 
			
		||||
{
 | 
			
		||||
    if (featureSetIndex != 0) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (m_availableAMBEFeatures.contains(feature))
 | 
			
		||||
    {
 | 
			
		||||
        if (m_settings.m_ambeFeatureIndex == m_availableAMBEFeatures[feature].m_featureIndex)
 | 
			
		||||
        {
 | 
			
		||||
            m_settings.m_connectAMBE = false;
 | 
			
		||||
            m_basebandSink->setAMBEFeatureMessageQueue(nullptr);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        m_availableAMBEFeatures.remove(feature);
 | 
			
		||||
        notifyUpdateAMBEFeatures();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -62,6 +62,38 @@ public:
 | 
			
		|||
        { }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    class MsgQueryAvailableAMBEFeatures : public Message {
 | 
			
		||||
        MESSAGE_CLASS_DECLARATION
 | 
			
		||||
 | 
			
		||||
    public:
 | 
			
		||||
        static MsgQueryAvailableAMBEFeatures* create() {
 | 
			
		||||
            return new MsgQueryAvailableAMBEFeatures();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    protected:
 | 
			
		||||
        MsgQueryAvailableAMBEFeatures() :
 | 
			
		||||
            Message()
 | 
			
		||||
        { }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    class MsgReportAvailableAMBEFeatures : public Message {
 | 
			
		||||
        MESSAGE_CLASS_DECLARATION
 | 
			
		||||
 | 
			
		||||
    public:
 | 
			
		||||
        QList<DSDDemodSettings::AvailableAMBEFeature>& getFeatures() { return m_availableFeatures; }
 | 
			
		||||
 | 
			
		||||
        static MsgReportAvailableAMBEFeatures* create() {
 | 
			
		||||
            return new MsgReportAvailableAMBEFeatures();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
        QList<DSDDemodSettings::AvailableAMBEFeature> m_availableFeatures;
 | 
			
		||||
 | 
			
		||||
        MsgReportAvailableAMBEFeatures() :
 | 
			
		||||
            Message()
 | 
			
		||||
        {}
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    DSDDemod(DeviceAPI *deviceAPI);
 | 
			
		||||
	virtual ~DSDDemod();
 | 
			
		||||
	virtual void destroy() { delete this; }
 | 
			
		||||
| 
						 | 
				
			
			@ -140,6 +172,7 @@ private:
 | 
			
		|||
    DSDDemodBaseband *m_basebandSink;
 | 
			
		||||
	DSDDemodSettings m_settings;
 | 
			
		||||
    int m_basebandSampleRate; //!< stored from device message used when starting baseband sink
 | 
			
		||||
    QHash<Feature*, DSDDemodSettings::AvailableAMBEFeature> m_availableAMBEFeatures;
 | 
			
		||||
 | 
			
		||||
    QNetworkAccessManager *m_networkManager;
 | 
			
		||||
    QNetworkRequest m_networkRequest;
 | 
			
		||||
| 
						 | 
				
			
			@ -148,6 +181,8 @@ private:
 | 
			
		|||
 | 
			
		||||
	virtual bool handleMessage(const Message& cmd);
 | 
			
		||||
    void applySettings(const DSDDemodSettings& settings, bool force = false);
 | 
			
		||||
    void scanAvailableAMBEFeatures();
 | 
			
		||||
    void notifyUpdateAMBEFeatures();
 | 
			
		||||
    void sendSampleRateToDemodAnalyzer();
 | 
			
		||||
    void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response);
 | 
			
		||||
    void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const DSDDemodSettings& settings, bool force);
 | 
			
		||||
| 
						 | 
				
			
			@ -167,6 +202,8 @@ private:
 | 
			
		|||
private slots:
 | 
			
		||||
    void networkManagerFinished(QNetworkReply *reply);
 | 
			
		||||
    void handleIndexInDeviceSetChanged(int index);
 | 
			
		||||
    void handleFeatureAdded(int featureSetIndex, Feature *feature);
 | 
			
		||||
    void handleFeatureRemoved(int featureSetIndex, Feature *feature);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // INCLUDE_DSDDEMOD_H
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -75,6 +75,7 @@ public:
 | 
			
		|||
    void setChannel(ChannelAPI *channel);
 | 
			
		||||
    void setFifoLabel(const QString& label) { m_sampleFifo.setLabel(label); }
 | 
			
		||||
    void setAudioFifoLabel(const QString& label) { m_sink.setAudioFifoLabel(label); }
 | 
			
		||||
    void setAMBEFeatureMessageQueue(MessageQueue *ambeFeatureMessageQueue) { m_sink.setAmbeFeatureMessageQueue(ambeFeatureMessageQueue); }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    SampleSinkFifo m_sampleFifo;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -113,6 +113,13 @@ bool DSDDemodGUI::handleMessage(const Message& message)
 | 
			
		|||
        updateAbsoluteCenterFrequency();
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    else if (DSDDemod::MsgReportAvailableAMBEFeatures::match(message))
 | 
			
		||||
    {
 | 
			
		||||
        DSDDemod::MsgReportAvailableAMBEFeatures& report = (DSDDemod::MsgReportAvailableAMBEFeatures&) message;
 | 
			
		||||
        m_availableAMBEFeatures = report.getFeatures();
 | 
			
		||||
        updateAMBEFeaturesList();
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        return false;
 | 
			
		||||
| 
						 | 
				
			
			@ -263,6 +270,19 @@ void DSDDemodGUI::on_symbolPLLLock_toggled(bool checked)
 | 
			
		|||
    applySettings();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DSDDemodGUI::on_ambeSupport_clicked(bool checked)
 | 
			
		||||
{
 | 
			
		||||
    m_settings.m_connectAMBE = checked;
 | 
			
		||||
    m_settings.m_ambeFeatureIndex = m_availableAMBEFeatures[ui->ambeFeatures->currentIndex()].m_featureIndex;
 | 
			
		||||
    applySettings();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DSDDemodGUI::on_ambeFeatures_currentIndexChanged(int index)
 | 
			
		||||
{
 | 
			
		||||
    m_settings.m_ambeFeatureIndex = m_availableAMBEFeatures[index].m_featureIndex;
 | 
			
		||||
    applySettings();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DSDDemodGUI::onWidgetRolled(QWidget* widget, bool rollDown)
 | 
			
		||||
{
 | 
			
		||||
    (void) widget;
 | 
			
		||||
| 
						 | 
				
			
			@ -490,6 +510,17 @@ void DSDDemodGUI::displaySettings()
 | 
			
		|||
    ui->traceDecayText->setText(QString("%1").arg(m_settings.m_traceDecay));
 | 
			
		||||
    m_scopeVisXY->setDecay(m_settings.m_traceDecay);
 | 
			
		||||
 | 
			
		||||
    ui->ambeSupport->setChecked(m_settings.m_connectAMBE);
 | 
			
		||||
 | 
			
		||||
    for (int i = 0; i < ui->ambeFeatures->count(); i++)
 | 
			
		||||
    {
 | 
			
		||||
        if (ui->ambeFeatures->itemData(i).toInt() == m_settings.m_ambeFeatureIndex)
 | 
			
		||||
        {
 | 
			
		||||
            ui->ambeFeatures->setCurrentIndex(i);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    updateIndexLabel();
 | 
			
		||||
 | 
			
		||||
    getRollupContents()->restoreState(m_rollupState);
 | 
			
		||||
| 
						 | 
				
			
			@ -497,6 +528,32 @@ void DSDDemodGUI::displaySettings()
 | 
			
		|||
    blockApplySettings(false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DSDDemodGUI::updateAMBEFeaturesList()
 | 
			
		||||
{
 | 
			
		||||
    ui->ambeFeatures->blockSignals(true);
 | 
			
		||||
    ui->ambeSupport->blockSignals(true);
 | 
			
		||||
    ui->ambeFeatures->clear();
 | 
			
		||||
    bool unsetAMBE = true;
 | 
			
		||||
 | 
			
		||||
    for (int i = 0; i < m_availableAMBEFeatures.count(); i++)
 | 
			
		||||
    {
 | 
			
		||||
        ui->ambeFeatures->addItem(tr("F:%1").arg(m_availableAMBEFeatures[i].m_featureIndex), m_availableAMBEFeatures[i].m_featureIndex);
 | 
			
		||||
 | 
			
		||||
        if (m_settings.m_ambeFeatureIndex == m_availableAMBEFeatures[i].m_featureIndex)
 | 
			
		||||
        {
 | 
			
		||||
            unsetAMBE = false;
 | 
			
		||||
            ui->ambeFeatures->setCurrentIndex(i);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (unsetAMBE) {
 | 
			
		||||
        ui->ambeSupport->setChecked(false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ui->ambeSupport->blockSignals(false);
 | 
			
		||||
    ui->ambeFeatures->blockSignals(false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DSDDemodGUI::applySettings(bool force)
 | 
			
		||||
{
 | 
			
		||||
	if (m_doApplySettings)
 | 
			
		||||
| 
						 | 
				
			
			@ -658,6 +715,8 @@ void DSDDemodGUI::makeUIConnections()
 | 
			
		|||
    QObject::connect(ui->audioMute, &QToolButton::toggled, this, &DSDDemodGUI::on_audioMute_toggled);
 | 
			
		||||
    QObject::connect(ui->symbolPLLLock, &QToolButton::toggled, this, &DSDDemodGUI::on_symbolPLLLock_toggled);
 | 
			
		||||
    QObject::connect(ui->viewStatusLog, &QPushButton::clicked, this, &DSDDemodGUI::on_viewStatusLog_clicked);
 | 
			
		||||
    QObject::connect(ui->ambeSupport, &QCheckBox::clicked, this, &DSDDemodGUI::on_ambeSupport_clicked);
 | 
			
		||||
    QObject::connect(ui->ambeFeatures, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &DSDDemodGUI::on_ambeFeatures_currentIndexChanged);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DSDDemodGUI::updateAbsoluteCenterFrequency()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -90,6 +90,7 @@ private:
 | 
			
		|||
    qint64 m_deviceCenterFrequency;
 | 
			
		||||
    int m_basebandSampleRate;
 | 
			
		||||
	bool m_doApplySettings;
 | 
			
		||||
    QList<DSDDemodSettings::AvailableAMBEFeature> m_availableAMBEFeatures;
 | 
			
		||||
 | 
			
		||||
    ScopeVisXY* m_scopeVisXY;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -117,6 +118,7 @@ private:
 | 
			
		|||
	void blockApplySettings(bool block);
 | 
			
		||||
	void applySettings(bool force = false);
 | 
			
		||||
    void displaySettings();
 | 
			
		||||
    void updateAMBEFeaturesList();
 | 
			
		||||
	void updateMyPosition();
 | 
			
		||||
	bool handleMessage(const Message& message);
 | 
			
		||||
    void makeUIConnections();
 | 
			
		||||
| 
						 | 
				
			
			@ -145,6 +147,8 @@ private slots:
 | 
			
		|||
    void on_highPassFilter_toggled(bool checked);
 | 
			
		||||
    void on_audioMute_toggled(bool checked);
 | 
			
		||||
    void on_symbolPLLLock_toggled(bool checked);
 | 
			
		||||
    void on_ambeSupport_clicked(bool checked);
 | 
			
		||||
    void on_ambeFeatures_currentIndexChanged(int index);
 | 
			
		||||
    void onWidgetRolled(QWidget* widget, bool rollDown);
 | 
			
		||||
    void onMenuDialogCalled(const QPoint& p);
 | 
			
		||||
    void on_viewStatusLog_clicked();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1231,6 +1231,29 @@
 | 
			
		|||
        <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
 | 
			
		||||
       </property>
 | 
			
		||||
      </widget>
 | 
			
		||||
      <widget class="QCheckBox" name="ambeSupport">
 | 
			
		||||
       <property name="geometry">
 | 
			
		||||
        <rect>
 | 
			
		||||
         <x>10</x>
 | 
			
		||||
         <y>170</y>
 | 
			
		||||
         <width>71</width>
 | 
			
		||||
         <height>23</height>
 | 
			
		||||
        </rect>
 | 
			
		||||
       </property>
 | 
			
		||||
       <property name="text">
 | 
			
		||||
        <string>AMBE</string>
 | 
			
		||||
       </property>
 | 
			
		||||
      </widget>
 | 
			
		||||
      <widget class="QComboBox" name="ambeFeatures">
 | 
			
		||||
       <property name="geometry">
 | 
			
		||||
        <rect>
 | 
			
		||||
         <x>80</x>
 | 
			
		||||
         <y>170</y>
 | 
			
		||||
         <width>50</width>
 | 
			
		||||
         <height>20</height>
 | 
			
		||||
        </rect>
 | 
			
		||||
       </property>
 | 
			
		||||
      </widget>
 | 
			
		||||
     </widget>
 | 
			
		||||
    </item>
 | 
			
		||||
   </layout>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,7 +30,7 @@
 | 
			
		|||
const PluginDescriptor DSDDemodPlugin::m_pluginDescriptor = {
 | 
			
		||||
    DSDDemod::m_channelId,
 | 
			
		||||
	QStringLiteral("DSD Demodulator"),
 | 
			
		||||
    QStringLiteral("7.0.0"),
 | 
			
		||||
    QStringLiteral("7.2.0"),
 | 
			
		||||
	QStringLiteral("(c) Edouard Griffiths, F4EXB"),
 | 
			
		||||
	QStringLiteral("https://github.com/f4exb/sdrangel"),
 | 
			
		||||
	true,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -61,6 +61,8 @@ void DSDDemodSettings::resetToDefaults()
 | 
			
		|||
    m_reverseAPIChannelIndex = 0;
 | 
			
		||||
    m_workspaceIndex = 0;
 | 
			
		||||
    m_hidden = false;
 | 
			
		||||
    m_ambeFeatureIndex = -1;
 | 
			
		||||
    m_connectAMBE = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QByteArray DSDDemodSettings::serialize() const
 | 
			
		||||
| 
						 | 
				
			
			@ -107,6 +109,8 @@ QByteArray DSDDemodSettings::serialize() const
 | 
			
		|||
    s.writeS32(32, m_workspaceIndex);
 | 
			
		||||
    s.writeBlob(33, m_geometryBytes);
 | 
			
		||||
    s.writeBool(34, m_hidden);
 | 
			
		||||
    s.writeS32(35, m_ambeFeatureIndex);
 | 
			
		||||
    s.writeBool(36, m_connectAMBE);
 | 
			
		||||
 | 
			
		||||
    return s.final();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -190,6 +194,8 @@ bool DSDDemodSettings::deserialize(const QByteArray& data)
 | 
			
		|||
        d.readS32(32, &m_workspaceIndex, 0);
 | 
			
		||||
        d.readBlob(33, &m_geometryBytes);
 | 
			
		||||
        d.readBool(34, &m_hidden, false);
 | 
			
		||||
        d.readS32(35, &m_ambeFeatureIndex, -1);
 | 
			
		||||
        d.readBool(36, &m_connectAMBE, false);
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,9 +21,20 @@
 | 
			
		|||
#include <QByteArray>
 | 
			
		||||
 | 
			
		||||
class Serializable;
 | 
			
		||||
class Feature;
 | 
			
		||||
 | 
			
		||||
struct DSDDemodSettings
 | 
			
		||||
{
 | 
			
		||||
    struct AvailableAMBEFeature
 | 
			
		||||
    {
 | 
			
		||||
        int m_featureIndex;
 | 
			
		||||
        Feature *m_feature;
 | 
			
		||||
 | 
			
		||||
        AvailableAMBEFeature() = default;
 | 
			
		||||
        AvailableAMBEFeature(const AvailableAMBEFeature&) = default;
 | 
			
		||||
        AvailableAMBEFeature& operator=(const AvailableAMBEFeature&) = default;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    qint64 m_inputFrequencyOffset;
 | 
			
		||||
    Real  m_rfBandwidth;
 | 
			
		||||
    Real  m_fmDeviation;
 | 
			
		||||
| 
						 | 
				
			
			@ -55,6 +66,8 @@ struct DSDDemodSettings
 | 
			
		|||
    int m_workspaceIndex;
 | 
			
		||||
    QByteArray m_geometryBytes;
 | 
			
		||||
    bool m_hidden;
 | 
			
		||||
    int m_ambeFeatureIndex;
 | 
			
		||||
    bool m_connectAMBE;
 | 
			
		||||
 | 
			
		||||
    Serializable *m_channelMarker;
 | 
			
		||||
    Serializable *m_rollupState;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -34,6 +34,7 @@
 | 
			
		|||
#include "dsp/dspengine.h"
 | 
			
		||||
#include "dsp/basebandsamplesink.h"
 | 
			
		||||
#include "dsp/datafifo.h"
 | 
			
		||||
#include "dsp/dspcommands.h"
 | 
			
		||||
#include "audio/audiooutputdevice.h"
 | 
			
		||||
#include "util/db.h"
 | 
			
		||||
#include "util/messagequeue.h"
 | 
			
		||||
| 
						 | 
				
			
			@ -44,6 +45,7 @@
 | 
			
		|||
DSDDemodSink::DSDDemodSink() :
 | 
			
		||||
    m_channelSampleRate(48000),
 | 
			
		||||
    m_channelFrequencyOffset(0),
 | 
			
		||||
    m_ambeFeatureMessageQueue(nullptr),
 | 
			
		||||
    m_audioSampleRate(48000),
 | 
			
		||||
    m_interpolatorDistance(0.0f),
 | 
			
		||||
    m_interpolatorDistanceRemain(0.0f),
 | 
			
		||||
| 
						 | 
				
			
			@ -229,20 +231,32 @@ void DSDDemodSink::feed(const SampleVector::const_iterator& begin, const SampleV
 | 
			
		|||
                m_scopeSampleBuffer.push_back(s);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (DSPEngine::instance()->hasDVSerialSupport())
 | 
			
		||||
            // if (DSPEngine::instance()->hasDVSerialSupport())
 | 
			
		||||
            if (m_ambeFeatureMessageQueue)
 | 
			
		||||
            {
 | 
			
		||||
                if ((m_settings.m_slot1On) && m_dsdDecoder.mbeDVReady1())
 | 
			
		||||
                {
 | 
			
		||||
                    if (!m_settings.m_audioMute)
 | 
			
		||||
                    {
 | 
			
		||||
                        DSPEngine::instance()->pushMbeFrame(
 | 
			
		||||
                        m_ambeFeatureMessageQueue->push(
 | 
			
		||||
                            new DSPPushMbeFrame(
 | 
			
		||||
                                m_dsdDecoder.getMbeDVFrame1(),
 | 
			
		||||
                                m_dsdDecoder.getMbeRateIndex(),
 | 
			
		||||
                                m_settings.m_volume * 10.0,
 | 
			
		||||
                                m_settings.m_tdmaStereo ? 1 : 3, // left or both channels
 | 
			
		||||
                                m_settings.m_highPassFilter,
 | 
			
		||||
                                m_audioSampleRate/8000, // upsample from native 8k
 | 
			
		||||
                                &m_audioFifo1);
 | 
			
		||||
                                &m_audioFifo1
 | 
			
		||||
                            )
 | 
			
		||||
                        );
 | 
			
		||||
                        // DSPEngine::instance()->pushMbeFrame(
 | 
			
		||||
                        //         m_dsdDecoder.getMbeDVFrame1(),
 | 
			
		||||
                        //         m_dsdDecoder.getMbeRateIndex(),
 | 
			
		||||
                        //         m_settings.m_volume * 10.0,
 | 
			
		||||
                        //         m_settings.m_tdmaStereo ? 1 : 3, // left or both channels
 | 
			
		||||
                        //         m_settings.m_highPassFilter,
 | 
			
		||||
                        //         m_audioSampleRate/8000, // upsample from native 8k
 | 
			
		||||
                        //         &m_audioFifo1);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    m_dsdDecoder.resetMbeDV1();
 | 
			
		||||
| 
						 | 
				
			
			@ -252,14 +266,25 @@ void DSDDemodSink::feed(const SampleVector::const_iterator& begin, const SampleV
 | 
			
		|||
                {
 | 
			
		||||
                    if (!m_settings.m_audioMute)
 | 
			
		||||
                    {
 | 
			
		||||
                        DSPEngine::instance()->pushMbeFrame(
 | 
			
		||||
                        m_ambeFeatureMessageQueue->push(
 | 
			
		||||
                            new DSPPushMbeFrame(
 | 
			
		||||
                                m_dsdDecoder.getMbeDVFrame2(),
 | 
			
		||||
                                m_dsdDecoder.getMbeRateIndex(),
 | 
			
		||||
                                m_settings.m_volume * 10.0,
 | 
			
		||||
                                m_settings.m_tdmaStereo ? 2 : 3, // right or both channels
 | 
			
		||||
                                m_settings.m_highPassFilter,
 | 
			
		||||
                                m_audioSampleRate/8000, // upsample from native 8k
 | 
			
		||||
                                &m_audioFifo2);
 | 
			
		||||
                                &m_audioFifo2
 | 
			
		||||
                            )
 | 
			
		||||
                        );
 | 
			
		||||
                        // DSPEngine::instance()->pushMbeFrame(
 | 
			
		||||
                        //         m_dsdDecoder.getMbeDVFrame2(),
 | 
			
		||||
                        //         m_dsdDecoder.getMbeRateIndex(),
 | 
			
		||||
                        //         m_settings.m_volume * 10.0,
 | 
			
		||||
                        //         m_settings.m_tdmaStereo ? 2 : 3, // right or both channels
 | 
			
		||||
                        //         m_settings.m_highPassFilter,
 | 
			
		||||
                        //         m_audioSampleRate/8000, // upsample from native 8k
 | 
			
		||||
                        //         &m_audioFifo2);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    m_dsdDecoder.resetMbeDV2();
 | 
			
		||||
| 
						 | 
				
			
			@ -270,7 +295,8 @@ void DSDDemodSink::feed(const SampleVector::const_iterator& begin, const SampleV
 | 
			
		|||
        }
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!DSPEngine::instance()->hasDVSerialSupport())
 | 
			
		||||
    if (!m_ambeFeatureMessageQueue)
 | 
			
		||||
	// if (!DSPEngine::instance()->hasDVSerialSupport())
 | 
			
		||||
	{
 | 
			
		||||
	    if (m_settings.m_slot1On)
 | 
			
		||||
	    {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -83,6 +83,7 @@ public:
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    const char *updateAndGetStatusText();
 | 
			
		||||
    void setAmbeFeatureMessageQueue(MessageQueue *queue) { m_ambeFeatureMessageQueue = queue; }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    struct MagSqLevelsStore
 | 
			
		||||
| 
						 | 
				
			
			@ -114,6 +115,7 @@ private:
 | 
			
		|||
	int m_channelFrequencyOffset;
 | 
			
		||||
	DSDDemodSettings m_settings;
 | 
			
		||||
    ChannelAPI *m_channel;
 | 
			
		||||
    MessageQueue *m_ambeFeatureMessageQueue;
 | 
			
		||||
    int m_audioSampleRate;
 | 
			
		||||
    QVector<qint16> m_demodBuffer;
 | 
			
		||||
    int m_demodBufferFill;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,3 +39,7 @@ endif()
 | 
			
		|||
if (ENABLE_LIMESUITE AND LIMESUITE_FOUND)
 | 
			
		||||
    add_subdirectory(limerfe)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
if (LIBSERIALDV_FOUND)
 | 
			
		||||
    add_subdirectory(ambe)
 | 
			
		||||
endif()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,65 @@
 | 
			
		|||
project(ambe)
 | 
			
		||||
 | 
			
		||||
set(ambe_SOURCES
 | 
			
		||||
	ambe.cpp
 | 
			
		||||
    ambesettings.cpp
 | 
			
		||||
    ambeplugin.cpp
 | 
			
		||||
    ambeengine.cpp
 | 
			
		||||
    ambeworker.cpp
 | 
			
		||||
    # ambewebapiadapter.cpp
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
set(ambe_HEADERS
 | 
			
		||||
	ambe.h
 | 
			
		||||
    ambesettings.h
 | 
			
		||||
    ambeplugin.h
 | 
			
		||||
    ambeengine.h
 | 
			
		||||
    ambeworker.h
 | 
			
		||||
    # ambewebapiadapter.h
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
include_directories(
 | 
			
		||||
    ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
 | 
			
		||||
    ${LIBSERIALDV_INCLUDE_DIR}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
if(NOT SERVER_MODE)
 | 
			
		||||
    set(ambe_SOURCES
 | 
			
		||||
        ${ambe_SOURCES}
 | 
			
		||||
        ambegui.cpp
 | 
			
		||||
        ambegui.ui
 | 
			
		||||
    )
 | 
			
		||||
    set(ambe_HEADERS
 | 
			
		||||
        ${ambe_HEADERS}
 | 
			
		||||
	    ambegui.h
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    set(TARGET_NAME featureambe)
 | 
			
		||||
    set(TARGET_LIB "Qt5::Widgets")
 | 
			
		||||
    set(TARGET_LIB_GUI "sdrgui")
 | 
			
		||||
    set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
 | 
			
		||||
else()
 | 
			
		||||
    set(TARGET_NAME featureambesrv)
 | 
			
		||||
    set(TARGET_LIB "")
 | 
			
		||||
    set(TARGET_LIB_GUI "")
 | 
			
		||||
    set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
add_library(${TARGET_NAME} SHARED
 | 
			
		||||
	${ambe_SOURCES}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
target_link_libraries(${TARGET_NAME}
 | 
			
		||||
    Qt5::Core
 | 
			
		||||
    ${TARGET_LIB}
 | 
			
		||||
	sdrbase
 | 
			
		||||
	${TARGET_LIB_GUI}
 | 
			
		||||
    ${LIBSERIALDV_LIBRARY}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})
 | 
			
		||||
 | 
			
		||||
# Install debug symbols
 | 
			
		||||
if (WIN32)
 | 
			
		||||
    install(FILES $<TARGET_PDB_FILE:${TARGET_NAME}> CONFIGURATIONS Debug RelWithDebInfo DESTINATION ${INSTALL_FOLDER} )
 | 
			
		||||
endif()
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,170 @@
 | 
			
		|||
///////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
// Copyright (C) 2022 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                  //
 | 
			
		||||
// (at your option) any later version.                                           //
 | 
			
		||||
//                                                                               //
 | 
			
		||||
// 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 <http://www.gnu.org/licenses/>.          //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
#include <QDebug>
 | 
			
		||||
#include <QNetworkAccessManager>
 | 
			
		||||
#include <QNetworkReply>
 | 
			
		||||
#include <QBuffer>
 | 
			
		||||
 | 
			
		||||
#include "SWGFeatureSettings.h"
 | 
			
		||||
#include "SWGFeatureReport.h"
 | 
			
		||||
#include "SWGFeatureActions.h"
 | 
			
		||||
 | 
			
		||||
#include "settings/serializable.h"
 | 
			
		||||
#include "util/simpleserializer.h"
 | 
			
		||||
#include "dsp/dspcommands.h"
 | 
			
		||||
 | 
			
		||||
#include "ambe.h"
 | 
			
		||||
 | 
			
		||||
MESSAGE_CLASS_DEFINITION(AMBE::MsgConfigureAMBE, Message)
 | 
			
		||||
 | 
			
		||||
const char* const AMBE::m_featureIdURI = "sdrangel.feature.ambe";
 | 
			
		||||
const char* const AMBE::m_featureId = "AMBE";
 | 
			
		||||
 | 
			
		||||
AMBE::AMBE(WebAPIAdapterInterface *webAPIAdapterInterface) :
 | 
			
		||||
    Feature(m_featureIdURI, webAPIAdapterInterface)
 | 
			
		||||
{
 | 
			
		||||
    setObjectName(m_featureId);
 | 
			
		||||
    m_state = StIdle;
 | 
			
		||||
    m_errorMessage = "AMBE error";
 | 
			
		||||
    m_networkManager = new QNetworkAccessManager();
 | 
			
		||||
    QObject::connect(
 | 
			
		||||
        m_networkManager,
 | 
			
		||||
        &QNetworkAccessManager::finished,
 | 
			
		||||
        this,
 | 
			
		||||
        &AMBE::networkManagerFinished
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AMBE::~AMBE()
 | 
			
		||||
{
 | 
			
		||||
    QObject::disconnect(
 | 
			
		||||
        m_networkManager,
 | 
			
		||||
        &QNetworkAccessManager::finished,
 | 
			
		||||
        this,
 | 
			
		||||
        &AMBE::networkManagerFinished
 | 
			
		||||
    );
 | 
			
		||||
    delete m_networkManager;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBE::start()
 | 
			
		||||
{
 | 
			
		||||
	qDebug("AMBE::start");
 | 
			
		||||
    m_state = StRunning;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBE::stop()
 | 
			
		||||
{
 | 
			
		||||
    qDebug("AMBE::stop");
 | 
			
		||||
    m_state = StIdle;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBE::applySettings(const AMBESettings& settings, bool force)
 | 
			
		||||
{
 | 
			
		||||
    (void) force;
 | 
			
		||||
    m_settings = settings;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool AMBE::handleMessage(const Message& cmd)
 | 
			
		||||
{
 | 
			
		||||
	if (MsgConfigureAMBE::match(cmd))
 | 
			
		||||
	{
 | 
			
		||||
        MsgConfigureAMBE& cfg = (MsgConfigureAMBE&) cmd;
 | 
			
		||||
        qDebug() << "AMBE::handleMessage: MsgConfigureAMBE";
 | 
			
		||||
        applySettings(cfg.getSettings(), cfg.getForce());
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
    else if (DSPPushMbeFrame::match(cmd))
 | 
			
		||||
    {
 | 
			
		||||
        DSPPushMbeFrame& cfg = (DSPPushMbeFrame&) cmd;
 | 
			
		||||
        m_ambeEngine.pushMbeFrame(
 | 
			
		||||
            cfg.getMbeFrame(),
 | 
			
		||||
            cfg.getMbeRateIndex(),
 | 
			
		||||
            cfg.getMbeVolumeIndex(),
 | 
			
		||||
            cfg.getChannels(),
 | 
			
		||||
            cfg.getUseHP(),
 | 
			
		||||
            cfg.getUpsampling(),
 | 
			
		||||
            cfg.getAudioFifo()
 | 
			
		||||
        );
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QByteArray AMBE::serialize() const
 | 
			
		||||
{
 | 
			
		||||
    SimpleSerializer s(1);
 | 
			
		||||
    s.writeBlob(1, m_settings.serialize());
 | 
			
		||||
    return s.final();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool AMBE::deserialize(const QByteArray& data)
 | 
			
		||||
{
 | 
			
		||||
    SimpleDeserializer d(data);
 | 
			
		||||
 | 
			
		||||
    if (!d.isValid())
 | 
			
		||||
    {
 | 
			
		||||
        m_settings.resetToDefaults();
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (d.getVersion() == 1)
 | 
			
		||||
    {
 | 
			
		||||
        QByteArray bytetmp;
 | 
			
		||||
        d.readBlob(1, &bytetmp);
 | 
			
		||||
 | 
			
		||||
        if (m_settings.deserialize(bytetmp))
 | 
			
		||||
        {
 | 
			
		||||
            MsgConfigureAMBE *msg = MsgConfigureAMBE::create(m_settings, true);
 | 
			
		||||
            m_inputMessageQueue.push(msg);
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            m_settings.resetToDefaults();
 | 
			
		||||
            MsgConfigureAMBE *msg = MsgConfigureAMBE::create(m_settings, true);
 | 
			
		||||
            m_inputMessageQueue.push(msg);
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBE::networkManagerFinished(QNetworkReply *reply)
 | 
			
		||||
{
 | 
			
		||||
    QNetworkReply::NetworkError replyError = reply->error();
 | 
			
		||||
 | 
			
		||||
    if (replyError)
 | 
			
		||||
    {
 | 
			
		||||
        qWarning() << "AMBE::networkManagerFinished:"
 | 
			
		||||
                << " error(" << (int) replyError
 | 
			
		||||
                << "): " << replyError
 | 
			
		||||
                << ": " << reply->errorString();
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        QString answer = reply->readAll();
 | 
			
		||||
        answer.chop(1); // remove last \n
 | 
			
		||||
        qDebug("AMBE::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    reply->deleteLater();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,92 @@
 | 
			
		|||
///////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
// Copyright (C) 2022 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                  //
 | 
			
		||||
// (at your option) any later version.                                           //
 | 
			
		||||
//                                                                               //
 | 
			
		||||
// 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 <http://www.gnu.org/licenses/>.          //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
#ifndef INCLUDE_FEATURE_AMBE_H_
 | 
			
		||||
#define INCLUDE_FEATURE_AMBE_H_
 | 
			
		||||
 | 
			
		||||
#include <QNetworkRequest>
 | 
			
		||||
#include <QSet>
 | 
			
		||||
 | 
			
		||||
#include "feature/feature.h"
 | 
			
		||||
#include "util/message.h"
 | 
			
		||||
 | 
			
		||||
#include "ambeengine.h"
 | 
			
		||||
#include "ambesettings.h"
 | 
			
		||||
 | 
			
		||||
class WebAPIAdapterInterface;
 | 
			
		||||
class QNetworkAccessManager;
 | 
			
		||||
class QNetworkReply;
 | 
			
		||||
 | 
			
		||||
class AMBE : public Feature
 | 
			
		||||
{
 | 
			
		||||
	Q_OBJECT
 | 
			
		||||
public:
 | 
			
		||||
    class MsgConfigureAMBE : public Message {
 | 
			
		||||
        MESSAGE_CLASS_DECLARATION
 | 
			
		||||
 | 
			
		||||
    public:
 | 
			
		||||
        const AMBESettings& getSettings() const { return m_settings; }
 | 
			
		||||
        bool getForce() const { return m_force; }
 | 
			
		||||
 | 
			
		||||
        static MsgConfigureAMBE* create(const AMBESettings& settings, bool force) {
 | 
			
		||||
            return new MsgConfigureAMBE(settings, force);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
        AMBESettings m_settings;
 | 
			
		||||
        bool m_force;
 | 
			
		||||
 | 
			
		||||
        MsgConfigureAMBE(const AMBESettings& settings, bool force) :
 | 
			
		||||
            Message(),
 | 
			
		||||
            m_settings(settings),
 | 
			
		||||
            m_force(force)
 | 
			
		||||
        { }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    AMBE(WebAPIAdapterInterface *webAPIAdapterInterface);
 | 
			
		||||
    virtual ~AMBE();
 | 
			
		||||
    virtual void destroy() { delete this; }
 | 
			
		||||
    virtual bool handleMessage(const Message& cmd);
 | 
			
		||||
 | 
			
		||||
    virtual void getIdentifier(QString& id) const { id = objectName(); }
 | 
			
		||||
    virtual QString getIdentifier() const { return objectName(); }
 | 
			
		||||
    virtual void getTitle(QString& title) const { title = m_settings.m_title; }
 | 
			
		||||
 | 
			
		||||
    virtual QByteArray serialize() const;
 | 
			
		||||
    virtual bool deserialize(const QByteArray& data);
 | 
			
		||||
 | 
			
		||||
    AMBEEngine *getAMBEEngine() { return &m_ambeEngine; }
 | 
			
		||||
 | 
			
		||||
    static const char* const m_featureIdURI;
 | 
			
		||||
    static const char* const m_featureId;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    AMBESettings m_settings;
 | 
			
		||||
    AMBEEngine m_ambeEngine;
 | 
			
		||||
 | 
			
		||||
    QNetworkAccessManager *m_networkManager;
 | 
			
		||||
    QNetworkRequest m_networkRequest;
 | 
			
		||||
 | 
			
		||||
    void start();
 | 
			
		||||
    void stop();
 | 
			
		||||
    void applySettings(const AMBESettings& settings, bool force = false);
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
    void networkManagerFinished(QNetworkReply *reply);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,402 @@
 | 
			
		|||
///////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
// Copyright (C) 2019 F4EXB                                                      //
 | 
			
		||||
// written by Edouard Griffiths                                                  //
 | 
			
		||||
//                                                                               //
 | 
			
		||||
// 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                  //
 | 
			
		||||
// (at your option) any later version.                                           //
 | 
			
		||||
//                                                                               //
 | 
			
		||||
// 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 <http://www.gnu.org/licenses/>.          //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
#ifndef __APPLE__
 | 
			
		||||
#include <fcntl.h>
 | 
			
		||||
#include <sys/stat.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef _MSC_VER
 | 
			
		||||
#include <dirent.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <libgen.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#if !defined(_WIN32) && !defined(__APPLE__)
 | 
			
		||||
#include <termios.h>
 | 
			
		||||
#include <sys/ioctl.h>
 | 
			
		||||
#include <linux/serial.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include <chrono>
 | 
			
		||||
#include <thread>
 | 
			
		||||
 | 
			
		||||
#include <QThread>
 | 
			
		||||
#include <QBuffer>
 | 
			
		||||
#include <QDataStream>
 | 
			
		||||
 | 
			
		||||
#include "ambeworker.h"
 | 
			
		||||
#include "ambeengine.h"
 | 
			
		||||
 | 
			
		||||
AMBEEngine::AMBEEngine()
 | 
			
		||||
{}
 | 
			
		||||
 | 
			
		||||
AMBEEngine::~AMBEEngine()
 | 
			
		||||
{
 | 
			
		||||
    qDebug("AMBEEngine::~AMBEEngine: %lu controllers", m_controllers.size());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#if defined(_WIN32)
 | 
			
		||||
void AMBEEngine::getComList()
 | 
			
		||||
{
 | 
			
		||||
    m_comList.clear();
 | 
			
		||||
    m_comList8250.clear();
 | 
			
		||||
    char comCStr[16];
 | 
			
		||||
 | 
			
		||||
    // Arbitrarily set the list to the 20 first COM ports
 | 
			
		||||
    for (int i = 1; i <= 20; i++)
 | 
			
		||||
    {
 | 
			
		||||
        sprintf(comCStr, "COM%d", i);
 | 
			
		||||
        m_comList.push_back(std::string(comCStr));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Do not activate serial support at all for windows
 | 
			
		||||
void AMBEEngine::scan(std::vector<QString>& ambeDevices)
 | 
			
		||||
{
 | 
			
		||||
    (void) ambeDevices;
 | 
			
		||||
}
 | 
			
		||||
#elif defined(__APPLE__)
 | 
			
		||||
void AMBEEngine::getComList()
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
void AMBEEngine::scan(std::vector<QString>& ambeDevices)
 | 
			
		||||
{
 | 
			
		||||
    (void) ambeDevices;
 | 
			
		||||
}
 | 
			
		||||
#else
 | 
			
		||||
void AMBEEngine::getComList()
 | 
			
		||||
{
 | 
			
		||||
    int n;
 | 
			
		||||
    struct dirent **namelist;
 | 
			
		||||
    m_comList.clear();
 | 
			
		||||
    m_comList8250.clear();
 | 
			
		||||
    const char* sysdir = "/sys/class/tty/";
 | 
			
		||||
 | 
			
		||||
    // Scan through /sys/class/tty - it contains all tty-devices in the system
 | 
			
		||||
    n = scandir(sysdir, &namelist, NULL, alphasort);
 | 
			
		||||
    if (n < 0)
 | 
			
		||||
    {
 | 
			
		||||
        perror("scandir");
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        while (n--)
 | 
			
		||||
        {
 | 
			
		||||
            if (strcmp(namelist[n]->d_name, "..") && strcmp(namelist[n]->d_name, "."))
 | 
			
		||||
            {
 | 
			
		||||
                // Construct full absolute file path
 | 
			
		||||
                std::string devicedir = sysdir;
 | 
			
		||||
                devicedir += namelist[n]->d_name;
 | 
			
		||||
                // Register the device
 | 
			
		||||
                register_comport(m_comList, m_comList8250, devicedir);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            free(namelist[n]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        free(namelist);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Only non-serial8250 has been added to comList without any further testing
 | 
			
		||||
    // serial8250-devices must be probe to check for validity
 | 
			
		||||
    probe_serial8250_comports(m_comList, m_comList8250);
 | 
			
		||||
}
 | 
			
		||||
#endif // not Windows nor Apple
 | 
			
		||||
 | 
			
		||||
#if !defined(_WIN32) && !defined(__APPLE__)
 | 
			
		||||
void AMBEEngine::register_comport(
 | 
			
		||||
    std::vector<std::string>& comList,
 | 
			
		||||
    std::vector<std::string>& comList8250,
 | 
			
		||||
    const std::string& dir)
 | 
			
		||||
{
 | 
			
		||||
    // Get the driver the device is using
 | 
			
		||||
    std::string driver = get_driver(dir);
 | 
			
		||||
 | 
			
		||||
    // Skip devices without a driver
 | 
			
		||||
    if (driver.size() > 0)
 | 
			
		||||
    {
 | 
			
		||||
        //std::cerr << "register_comport: dir: "<< dir << " driver: " << driver << std::endl;
 | 
			
		||||
        std::string devfile = std::string("/dev/") + basename((char *) dir.c_str());
 | 
			
		||||
 | 
			
		||||
        // Put serial8250-devices in a seperate list
 | 
			
		||||
        if (driver == "serial8250") {
 | 
			
		||||
            comList8250.push_back(devfile);
 | 
			
		||||
        } else {
 | 
			
		||||
            comList.push_back(devfile);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEEngine::probe_serial8250_comports(
 | 
			
		||||
    std::vector<std::string>& comList,
 | 
			
		||||
    std::vector<std::string> comList8250)
 | 
			
		||||
{
 | 
			
		||||
    struct serial_struct serinfo;
 | 
			
		||||
    std::vector<std::string>::iterator it = comList8250.begin();
 | 
			
		||||
 | 
			
		||||
    // Iterate over all serial8250-devices
 | 
			
		||||
    while (it != comList8250.end())
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        // Try to open the device
 | 
			
		||||
        int fd = open((*it).c_str(), O_RDWR | O_NONBLOCK | O_NOCTTY);
 | 
			
		||||
 | 
			
		||||
        if (fd >= 0)
 | 
			
		||||
        {
 | 
			
		||||
            // Get serial_info
 | 
			
		||||
            if (ioctl(fd, TIOCGSERIAL, &serinfo) == 0)
 | 
			
		||||
            {
 | 
			
		||||
                // If device type is no PORT_UNKNOWN we accept the port
 | 
			
		||||
                if (serinfo.type != PORT_UNKNOWN) {
 | 
			
		||||
                    comList.push_back(*it);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            close(fd);
 | 
			
		||||
        }
 | 
			
		||||
        it++;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::string AMBEEngine::get_driver(const std::string& tty)
 | 
			
		||||
{
 | 
			
		||||
    struct stat st;
 | 
			
		||||
    std::string devicedir = tty;
 | 
			
		||||
    // Append '/device' to the tty-path
 | 
			
		||||
    devicedir += "/device";
 | 
			
		||||
 | 
			
		||||
    // Stat the devicedir and handle it if it is a symlink
 | 
			
		||||
    if (lstat(devicedir.c_str(), &st) == 0 && S_ISLNK(st.st_mode))
 | 
			
		||||
    {
 | 
			
		||||
        char buffer[1024];
 | 
			
		||||
        memset(buffer, 0, sizeof(buffer));
 | 
			
		||||
        // Append '/driver' and return basename of the target
 | 
			
		||||
        devicedir += "/driver";
 | 
			
		||||
 | 
			
		||||
        if (readlink(devicedir.c_str(), buffer, sizeof(buffer)) > 0) {
 | 
			
		||||
            return basename(buffer);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return "";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEEngine::scan(std::vector<QString>& ambeDevices)
 | 
			
		||||
{
 | 
			
		||||
    getComList();
 | 
			
		||||
    std::vector<std::string>::const_iterator it = m_comList.begin();
 | 
			
		||||
    ambeDevices.clear();
 | 
			
		||||
 | 
			
		||||
    while (it != m_comList.end())
 | 
			
		||||
    {
 | 
			
		||||
        AMBEWorker *worker = new AMBEWorker();
 | 
			
		||||
        qDebug("AMBEEngine::scan: com: %s", it->c_str());
 | 
			
		||||
 | 
			
		||||
        if (worker->open(*it))
 | 
			
		||||
        {
 | 
			
		||||
            ambeDevices.push_back(QString(it->c_str()));
 | 
			
		||||
            worker->close();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        delete worker;
 | 
			
		||||
        ++it;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
#endif // not Windows nor Apple
 | 
			
		||||
 | 
			
		||||
bool AMBEEngine::registerController(const std::string& deviceRef)
 | 
			
		||||
{
 | 
			
		||||
    AMBEWorker *worker = new AMBEWorker();
 | 
			
		||||
 | 
			
		||||
    if (worker->open(deviceRef))
 | 
			
		||||
    {
 | 
			
		||||
        qDebug("AMBEEngine::registerController: device: %s", deviceRef.c_str());
 | 
			
		||||
        m_controllers.push_back(AMBEController());
 | 
			
		||||
        m_controllers.back().worker = worker;
 | 
			
		||||
        m_controllers.back().thread = new QThread();
 | 
			
		||||
        m_controllers.back().device = deviceRef;
 | 
			
		||||
 | 
			
		||||
        m_controllers.back().worker->moveToThread(m_controllers.back().thread);
 | 
			
		||||
        connect(m_controllers.back().worker, SIGNAL(finished()), m_controllers.back().thread, SLOT(quit()));
 | 
			
		||||
        connect(m_controllers.back().worker, SIGNAL(finished()), m_controllers.back().worker, SLOT(deleteLater()));
 | 
			
		||||
        connect(m_controllers.back().thread, SIGNAL(finished()), m_controllers.back().thread, SLOT(deleteLater()));
 | 
			
		||||
        connect(&m_controllers.back().worker->m_inputMessageQueue, SIGNAL(messageEnqueued()), m_controllers.back().worker, SLOT(handleInputMessages()));
 | 
			
		||||
        std::this_thread::sleep_for(std::chrono::seconds(1));
 | 
			
		||||
        m_controllers.back().thread->start();
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    qWarning("AMBEEngine::registerController: failed to register device: %s", deviceRef.c_str());
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEEngine::releaseController(const std::string& deviceRef)
 | 
			
		||||
{
 | 
			
		||||
    std::vector<AMBEController>::iterator it = m_controllers.begin();
 | 
			
		||||
 | 
			
		||||
    while (it != m_controllers.end())
 | 
			
		||||
    {
 | 
			
		||||
        if (it->device == deviceRef)
 | 
			
		||||
        {
 | 
			
		||||
            disconnect(&it->worker->m_inputMessageQueue, SIGNAL(messageEnqueued()), it->worker, SLOT(handleInputMessages()));
 | 
			
		||||
            it->worker->stop();
 | 
			
		||||
            it->thread->wait(100);
 | 
			
		||||
            it->worker->m_inputMessageQueue.clear();
 | 
			
		||||
            it->worker->close();
 | 
			
		||||
            qDebug() << "AMBEEngine::releaseController: closed device at: " << it->device.c_str();
 | 
			
		||||
            m_controllers.erase(it);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ++it;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEEngine::releaseAll()
 | 
			
		||||
{
 | 
			
		||||
    std::vector<AMBEController>::iterator it = m_controllers.begin();
 | 
			
		||||
 | 
			
		||||
    while (it != m_controllers.end())
 | 
			
		||||
    {
 | 
			
		||||
        disconnect(&it->worker->m_inputMessageQueue, SIGNAL(messageEnqueued()), it->worker, SLOT(handleInputMessages()));
 | 
			
		||||
        it->worker->stop();
 | 
			
		||||
        it->thread->wait(100);
 | 
			
		||||
        it->worker->m_inputMessageQueue.clear();
 | 
			
		||||
        it->worker->close();
 | 
			
		||||
        qDebug() << "AMBEEngine::release: closed device at: " << it->device.c_str();
 | 
			
		||||
        ++it;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    m_controllers.clear();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEEngine::getDeviceRefs(std::vector<QString>& deviceNames)
 | 
			
		||||
{
 | 
			
		||||
    std::vector<AMBEController>::const_iterator it = m_controllers.begin();
 | 
			
		||||
 | 
			
		||||
    while (it != m_controllers.end())
 | 
			
		||||
    {
 | 
			
		||||
        deviceNames.push_back(QString(it->device.c_str()));
 | 
			
		||||
        ++it;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEEngine::pushMbeFrame(
 | 
			
		||||
        const unsigned char *mbeFrame,
 | 
			
		||||
        int mbeRateIndex,
 | 
			
		||||
        int mbeVolumeIndex,
 | 
			
		||||
        unsigned char channels,
 | 
			
		||||
        bool useLP,
 | 
			
		||||
        int upsampling,
 | 
			
		||||
        AudioFifo *audioFifo)
 | 
			
		||||
{
 | 
			
		||||
    std::vector<AMBEController>::iterator it = m_controllers.begin();
 | 
			
		||||
    std::vector<AMBEController>::iterator itAvail = m_controllers.end();
 | 
			
		||||
    bool done = false;
 | 
			
		||||
    QMutexLocker locker(&m_mutex);
 | 
			
		||||
 | 
			
		||||
    while (it != m_controllers.end())
 | 
			
		||||
    {
 | 
			
		||||
        if (it->worker->hasFifo(audioFifo))
 | 
			
		||||
        {
 | 
			
		||||
            it->worker->pushMbeFrame(mbeFrame, mbeRateIndex, mbeVolumeIndex, channels, useLP, upsampling, audioFifo);
 | 
			
		||||
            done = true;
 | 
			
		||||
        }
 | 
			
		||||
        else if (it->worker->isAvailable())
 | 
			
		||||
        {
 | 
			
		||||
            itAvail = it;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ++it;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!done)
 | 
			
		||||
    {
 | 
			
		||||
        if (itAvail != m_controllers.end())
 | 
			
		||||
        {
 | 
			
		||||
            int wNum = itAvail - m_controllers.begin();
 | 
			
		||||
 | 
			
		||||
            qDebug("AMBEEngine::pushMbeFrame: push %p on empty queue %d", audioFifo, wNum);
 | 
			
		||||
            itAvail->worker->pushMbeFrame(mbeFrame, mbeRateIndex, mbeVolumeIndex, channels, useLP, upsampling, audioFifo);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            qDebug("AMBEEngine::pushMbeFrame: no DV device available. MBE frame dropped");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QByteArray AMBEEngine::serialize() const
 | 
			
		||||
{
 | 
			
		||||
    qDebug("AMBEEngine::serialize");
 | 
			
		||||
    QStringList qDeviceList;
 | 
			
		||||
    std::vector<AMBEController>::const_iterator it = m_controllers.begin();
 | 
			
		||||
 | 
			
		||||
    while (it != m_controllers.end())
 | 
			
		||||
    {
 | 
			
		||||
        qDebug("AMBEEngine::serialize: %s", it->device.c_str());
 | 
			
		||||
        qDeviceList << QString(it->device.c_str());
 | 
			
		||||
        ++it;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    QByteArray data;
 | 
			
		||||
    QDataStream *stream = new QDataStream(&data, QIODevice::WriteOnly);
 | 
			
		||||
    (*stream) << qDeviceList;
 | 
			
		||||
    delete stream;
 | 
			
		||||
 | 
			
		||||
    return data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool AMBEEngine::deserialize(const QByteArray& data)
 | 
			
		||||
{
 | 
			
		||||
    if (data.size() <= 0)
 | 
			
		||||
    {
 | 
			
		||||
        qDebug("AMBEEngine::deserialize: invalid or no data");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    QStringList qDeviceList;
 | 
			
		||||
    QDataStream *stream = new QDataStream(data);
 | 
			
		||||
    (*stream) >> qDeviceList;
 | 
			
		||||
    delete stream;
 | 
			
		||||
 | 
			
		||||
    releaseAll();
 | 
			
		||||
 | 
			
		||||
    for (int i = 0; i < qDeviceList.size(); ++i)
 | 
			
		||||
    {
 | 
			
		||||
        qDebug(" AMBEEngine::deserialize: %s", qDeviceList.at(i).toStdString().c_str());
 | 
			
		||||
        registerController(qDeviceList.at(i).toStdString());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEEngine::formatTo(SWGSDRangel::SWGObject *swgObject) const
 | 
			
		||||
{
 | 
			
		||||
    (void) swgObject;
 | 
			
		||||
    // TODO
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEEngine::updateFrom(const QStringList& keys, const SWGSDRangel::SWGObject *swgObject)
 | 
			
		||||
{
 | 
			
		||||
    (void) keys;
 | 
			
		||||
    (void) swgObject;
 | 
			
		||||
    // TODO
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,93 @@
 | 
			
		|||
///////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
// Copyright (C) 2019 F4EXB                                                      //
 | 
			
		||||
// written by Edouard Griffiths                                                  //
 | 
			
		||||
//                                                                               //
 | 
			
		||||
// 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                  //
 | 
			
		||||
// (at your option) any later version.                                           //
 | 
			
		||||
//                                                                               //
 | 
			
		||||
// 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 <http://www.gnu.org/licenses/>.          //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
#ifndef SDRBASE_AMBE_AMBEENGINE_H_
 | 
			
		||||
#define SDRBASE_AMBE_AMBEENGINE_H_
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
#include <QObject>
 | 
			
		||||
#include <QMutex>
 | 
			
		||||
#include <QString>
 | 
			
		||||
#include <QByteArray>
 | 
			
		||||
 | 
			
		||||
#include "settings/serializable.h"
 | 
			
		||||
 | 
			
		||||
class QThread;
 | 
			
		||||
class AMBEWorker;
 | 
			
		||||
class AudioFifo;
 | 
			
		||||
 | 
			
		||||
class AMBEEngine : public QObject, public Serializable
 | 
			
		||||
{
 | 
			
		||||
    Q_OBJECT
 | 
			
		||||
public:
 | 
			
		||||
    AMBEEngine();
 | 
			
		||||
    ~AMBEEngine();
 | 
			
		||||
 | 
			
		||||
    void scan(std::vector<QString>& ambeDevices);
 | 
			
		||||
    void releaseAll();
 | 
			
		||||
 | 
			
		||||
    int getNbDevices() const { return m_controllers.size(); }   //!< number of devices used
 | 
			
		||||
    void getDeviceRefs(std::vector<QString>& devicesRefs);  //!< reference of the devices used (device path or url)
 | 
			
		||||
    bool registerController(const std::string& deviceRef);      //!< create a new controller for the device in reference
 | 
			
		||||
    void releaseController(const std::string& deviceRef);       //!< release controller resources for the device in reference
 | 
			
		||||
 | 
			
		||||
    void pushMbeFrame(
 | 
			
		||||
            const unsigned char *mbeFrame,
 | 
			
		||||
            int mbeRateIndex,
 | 
			
		||||
            int mbeVolumeIndex,
 | 
			
		||||
            unsigned char channels,
 | 
			
		||||
            bool useHP,
 | 
			
		||||
            int upsampling,
 | 
			
		||||
            AudioFifo *audioFifo);
 | 
			
		||||
 | 
			
		||||
    virtual QByteArray serialize() const;
 | 
			
		||||
    virtual bool deserialize(const QByteArray& data);
 | 
			
		||||
    virtual void formatTo(SWGSDRangel::SWGObject *swgObject) const; //!< Serialize to API
 | 
			
		||||
    virtual void updateFrom(const QStringList& keys, const SWGSDRangel::SWGObject *swgObject); //!< Deserialize from API
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    struct AMBEController
 | 
			
		||||
    {
 | 
			
		||||
        AMBEController() :
 | 
			
		||||
            thread(nullptr),
 | 
			
		||||
            worker(nullptr)
 | 
			
		||||
        {}
 | 
			
		||||
 | 
			
		||||
        QThread *thread;
 | 
			
		||||
        AMBEWorker *worker;
 | 
			
		||||
        std::string device;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
#ifndef _WIN32
 | 
			
		||||
    static std::string get_driver(const std::string& tty);
 | 
			
		||||
    static void register_comport(std::vector<std::string>& comList, std::vector<std::string>& comList8250, const std::string& dir);
 | 
			
		||||
    static void probe_serial8250_comports(std::vector<std::string>& comList, std::vector<std::string> comList8250);
 | 
			
		||||
#endif
 | 
			
		||||
    void getComList();
 | 
			
		||||
 | 
			
		||||
    std::vector<AMBEController> m_controllers;
 | 
			
		||||
    std::vector<std::string> m_comList;
 | 
			
		||||
    std::vector<std::string> m_comList8250;
 | 
			
		||||
    QMutex m_mutex;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#endif /* SDRBASE_AMBE_AMBEENGINE_H_ */
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,343 @@
 | 
			
		|||
///////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
// Copyright (C) 2022 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                  //
 | 
			
		||||
// (at your option) any later version.                                           //
 | 
			
		||||
//                                                                               //
 | 
			
		||||
// 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 <http://www.gnu.org/licenses/>.          //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
#include "feature/featureuiset.h"
 | 
			
		||||
#include "gui/basicfeaturesettingsdialog.h"
 | 
			
		||||
#include "gui/crightclickenabler.h"
 | 
			
		||||
 | 
			
		||||
#include "ui_ambegui.h"
 | 
			
		||||
#include "ambegui.h"
 | 
			
		||||
#include "ambe.h"
 | 
			
		||||
 | 
			
		||||
AMBEGUI* AMBEGUI::create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature)
 | 
			
		||||
{
 | 
			
		||||
	AMBEGUI* gui = new AMBEGUI(pluginAPI, featureUISet, feature);
 | 
			
		||||
	return gui;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEGUI::destroy()
 | 
			
		||||
{
 | 
			
		||||
	delete this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AMBEGUI::AMBEGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent) :
 | 
			
		||||
	FeatureGUI(parent),
 | 
			
		||||
	ui(new Ui::AMBEGUI),
 | 
			
		||||
	m_pluginAPI(pluginAPI),
 | 
			
		||||
    m_featureUISet(featureUISet),
 | 
			
		||||
	m_doApplySettings(true)
 | 
			
		||||
{
 | 
			
		||||
    m_feature = feature;
 | 
			
		||||
	setAttribute(Qt::WA_DeleteOnClose, true);
 | 
			
		||||
    m_helpURL = "plugins/feature/ambe/readme.md";
 | 
			
		||||
    RollupContents *rollupContents = getRollupContents();
 | 
			
		||||
	ui->setupUi(rollupContents);
 | 
			
		||||
    setSizePolicy(rollupContents->sizePolicy());
 | 
			
		||||
    rollupContents->arrangeRollups();
 | 
			
		||||
	connect(rollupContents, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
 | 
			
		||||
 | 
			
		||||
    m_ambe = reinterpret_cast<AMBE*>(feature);
 | 
			
		||||
    m_ambe->setMessageQueueToGUI(&m_inputMessageQueue);
 | 
			
		||||
 | 
			
		||||
    m_settings.setRollupState(&m_rollupState);
 | 
			
		||||
 | 
			
		||||
    connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
 | 
			
		||||
    connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
 | 
			
		||||
 | 
			
		||||
    populateSerialList();
 | 
			
		||||
    refreshInUseList();
 | 
			
		||||
    displaySettings();
 | 
			
		||||
    makeUIConnections();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AMBEGUI::~AMBEGUI()
 | 
			
		||||
{
 | 
			
		||||
	delete ui;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEGUI::resetToDefaults()
 | 
			
		||||
{
 | 
			
		||||
    m_settings.resetToDefaults();
 | 
			
		||||
    displaySettings();
 | 
			
		||||
	applySettings(true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QByteArray AMBEGUI::serialize() const
 | 
			
		||||
{
 | 
			
		||||
    return m_settings.serialize();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool AMBEGUI::deserialize(const QByteArray& data)
 | 
			
		||||
{
 | 
			
		||||
    if (m_settings.deserialize(data))
 | 
			
		||||
    {
 | 
			
		||||
        m_feature->setWorkspaceIndex(m_settings.m_workspaceIndex);
 | 
			
		||||
        displaySettings();
 | 
			
		||||
        refreshInUseList();
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        resetToDefaults();
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEGUI::setWorkspaceIndex(int index)
 | 
			
		||||
{
 | 
			
		||||
    m_settings.m_workspaceIndex = index;
 | 
			
		||||
    m_feature->setWorkspaceIndex(index);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEGUI::resizeEvent(QResizeEvent* size)
 | 
			
		||||
{
 | 
			
		||||
    int maxWidth = getRollupContents()->maximumWidth();
 | 
			
		||||
    int minHeight = getRollupContents()->minimumHeight() + getAdditionalHeight();
 | 
			
		||||
    resize(width() < maxWidth ? width() : maxWidth, minHeight);
 | 
			
		||||
    size->accept();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEGUI::onWidgetRolled(QWidget* widget, bool rollDown)
 | 
			
		||||
{
 | 
			
		||||
    (void) widget;
 | 
			
		||||
    (void) rollDown;
 | 
			
		||||
 | 
			
		||||
    getRollupContents()->saveState(m_rollupState);
 | 
			
		||||
    applySettings();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEGUI::onMenuDialogCalled(const QPoint &p)
 | 
			
		||||
{
 | 
			
		||||
    if (m_contextMenuType == ContextMenuChannelSettings)
 | 
			
		||||
    {
 | 
			
		||||
        BasicFeatureSettingsDialog dialog(this);
 | 
			
		||||
        dialog.setTitle(m_settings.m_title);
 | 
			
		||||
        dialog.setUseReverseAPI(m_settings.m_useReverseAPI);
 | 
			
		||||
        dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress);
 | 
			
		||||
        dialog.setReverseAPIPort(m_settings.m_reverseAPIPort);
 | 
			
		||||
        dialog.setReverseAPIFeatureSetIndex(m_settings.m_reverseAPIFeatureSetIndex);
 | 
			
		||||
        dialog.setReverseAPIFeatureIndex(m_settings.m_reverseAPIFeatureIndex);
 | 
			
		||||
        dialog.setDefaultTitle(m_displayedName);
 | 
			
		||||
 | 
			
		||||
        dialog.move(p);
 | 
			
		||||
        dialog.exec();
 | 
			
		||||
 | 
			
		||||
        m_settings.m_title = dialog.getTitle();
 | 
			
		||||
        m_settings.m_useReverseAPI = dialog.useReverseAPI();
 | 
			
		||||
        m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress();
 | 
			
		||||
        m_settings.m_reverseAPIPort = dialog.getReverseAPIPort();
 | 
			
		||||
        m_settings.m_reverseAPIFeatureSetIndex = dialog.getReverseAPIFeatureSetIndex();
 | 
			
		||||
        m_settings.m_reverseAPIFeatureIndex = dialog.getReverseAPIFeatureIndex();
 | 
			
		||||
 | 
			
		||||
        setTitle(m_settings.m_title);
 | 
			
		||||
        setTitleColor(m_settings.m_rgbColor);
 | 
			
		||||
 | 
			
		||||
        applySettings();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    resetContextMenuType();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEGUI::displaySettings()
 | 
			
		||||
{
 | 
			
		||||
    setTitleColor(m_settings.m_rgbColor);
 | 
			
		||||
    setWindowTitle(m_settings.m_title);
 | 
			
		||||
    setTitle(m_settings.m_title);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEGUI::applySettings(bool force)
 | 
			
		||||
{
 | 
			
		||||
	if (m_doApplySettings)
 | 
			
		||||
	{
 | 
			
		||||
	    AMBE::MsgConfigureAMBE* message = AMBE::MsgConfigureAMBE::create( m_settings, force);
 | 
			
		||||
	    m_ambe->getInputMessageQueue()->push(message);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool AMBEGUI::handleMessage(const Message& message)
 | 
			
		||||
{
 | 
			
		||||
    if (AMBE::MsgConfigureAMBE::match(message))
 | 
			
		||||
    {
 | 
			
		||||
        qDebug("AMBEGUI::handleMessage: AMBE::MsgConfigureAMBE");
 | 
			
		||||
        const AMBE::MsgConfigureAMBE& cfg = (AMBE::MsgConfigureAMBE&) message;
 | 
			
		||||
        m_settings = cfg.getSettings();
 | 
			
		||||
        displaySettings();
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEGUI::handleInputMessages()
 | 
			
		||||
{
 | 
			
		||||
    Message* message;
 | 
			
		||||
 | 
			
		||||
    while ((message = getInputMessageQueue()->pop()))
 | 
			
		||||
    {
 | 
			
		||||
        if (handleMessage(*message)) {
 | 
			
		||||
            delete message;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEGUI::populateSerialList()
 | 
			
		||||
{
 | 
			
		||||
    std::vector<QString> ambeSerialDevices;
 | 
			
		||||
    m_ambe->getAMBEEngine()->scan(ambeSerialDevices);
 | 
			
		||||
    ui->ambeSerialDevices->clear();
 | 
			
		||||
    std::vector<QString>::const_iterator it = ambeSerialDevices.begin();
 | 
			
		||||
 | 
			
		||||
    for (; it != ambeSerialDevices.end(); ++it) {
 | 
			
		||||
        ui->ambeSerialDevices->addItem(QString(*it));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEGUI::refreshInUseList()
 | 
			
		||||
{
 | 
			
		||||
    std::vector<QString> inUseDevices;
 | 
			
		||||
    m_ambe->getAMBEEngine()->getDeviceRefs(inUseDevices);
 | 
			
		||||
    ui->ambeDeviceRefs->clear();
 | 
			
		||||
    std::vector<QString>::const_iterator it = inUseDevices.begin();
 | 
			
		||||
 | 
			
		||||
    for (; it != inUseDevices.end(); ++it)
 | 
			
		||||
    {
 | 
			
		||||
        qDebug("AMBEGUI::refreshInUseList: %s", qPrintable(*it));
 | 
			
		||||
        ui->ambeDeviceRefs->addItem(*it);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
void AMBEGUI::on_importSerial_clicked()
 | 
			
		||||
{
 | 
			
		||||
    QListWidgetItem *serialItem = ui->ambeSerialDevices->currentItem();
 | 
			
		||||
 | 
			
		||||
    if (!serialItem)
 | 
			
		||||
    {
 | 
			
		||||
        ui->statusText->setText("No selection");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    QString serialName = serialItem->text();
 | 
			
		||||
    QList<QListWidgetItem*> foundItems = ui->ambeDeviceRefs->findItems(serialName, Qt::MatchFixedString|Qt::MatchCaseSensitive);
 | 
			
		||||
 | 
			
		||||
    if (foundItems.size() == 0)
 | 
			
		||||
    {
 | 
			
		||||
        if (m_ambe->getAMBEEngine()->registerController(serialName.toStdString()))
 | 
			
		||||
        {
 | 
			
		||||
            ui->ambeDeviceRefs->addItem(serialName);
 | 
			
		||||
            ui->statusText->setText(tr("%1 added").arg(serialName));
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            ui->statusText->setText(tr("Cannot open %1").arg(serialName));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        ui->statusText->setText("Device already in use");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEGUI::on_importAllSerial_clicked()
 | 
			
		||||
{
 | 
			
		||||
    int count = 0;
 | 
			
		||||
 | 
			
		||||
    for (int i = 0; i < ui->ambeSerialDevices->count(); i++)
 | 
			
		||||
    {
 | 
			
		||||
        const QListWidgetItem *serialItem = ui->ambeSerialDevices->item(i);
 | 
			
		||||
        QString serialName = serialItem->text();
 | 
			
		||||
        QList<QListWidgetItem*> foundItems = ui->ambeDeviceRefs->findItems(serialName, Qt::MatchFixedString|Qt::MatchCaseSensitive);
 | 
			
		||||
 | 
			
		||||
        if (foundItems.size() == 0)
 | 
			
		||||
        {
 | 
			
		||||
            if (m_ambe->getAMBEEngine()->registerController(serialName.toStdString()))
 | 
			
		||||
            {
 | 
			
		||||
                ui->ambeDeviceRefs->addItem(serialName);
 | 
			
		||||
                count++;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ui->statusText->setText(tr("%1 devices added").arg(count));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEGUI::on_removeAmbeDevice_clicked()
 | 
			
		||||
{
 | 
			
		||||
    QListWidgetItem *deviceItem = ui->ambeDeviceRefs->currentItem();
 | 
			
		||||
 | 
			
		||||
    if (!deviceItem)
 | 
			
		||||
    {
 | 
			
		||||
        ui->statusText->setText("No selection");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    QString deviceName = deviceItem->text();
 | 
			
		||||
    m_ambe->getAMBEEngine()->releaseController(deviceName.toStdString());
 | 
			
		||||
    ui->statusText->setText(tr("%1 removed").arg(deviceName));
 | 
			
		||||
    refreshInUseList();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEGUI::on_refreshAmbeList_clicked()
 | 
			
		||||
{
 | 
			
		||||
    refreshInUseList();
 | 
			
		||||
    ui->statusText->setText("In use refreshed");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEGUI::on_clearAmbeList_clicked()
 | 
			
		||||
{
 | 
			
		||||
    if (ui->ambeDeviceRefs->count() == 0)
 | 
			
		||||
    {
 | 
			
		||||
        ui->statusText->setText("No active items");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    m_ambe->getAMBEEngine()->releaseAll();
 | 
			
		||||
    ui->ambeDeviceRefs->clear();
 | 
			
		||||
    ui->statusText->setText("All items released");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEGUI::on_importAddress_clicked()
 | 
			
		||||
{
 | 
			
		||||
    QString addressAndPort = ui->ambeAddressText->text();
 | 
			
		||||
 | 
			
		||||
    QList<QListWidgetItem*> foundItems = ui->ambeDeviceRefs->findItems(addressAndPort, Qt::MatchFixedString|Qt::MatchCaseSensitive);
 | 
			
		||||
 | 
			
		||||
    if (foundItems.size() == 0)
 | 
			
		||||
    {
 | 
			
		||||
        if (m_ambe->getAMBEEngine()->registerController(addressAndPort.toStdString()))
 | 
			
		||||
        {
 | 
			
		||||
            ui->ambeDeviceRefs->addItem(addressAndPort);
 | 
			
		||||
            ui->statusText->setText(tr("%1 added").arg(addressAndPort));
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            ui->statusText->setText(tr("Cannot open %1").arg(addressAndPort));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        ui->statusText->setText("Address already in use");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEGUI::makeUIConnections()
 | 
			
		||||
{
 | 
			
		||||
    QObject::connect(ui->importSerial, &QPushButton::clicked, this, &AMBEGUI::on_importSerial_clicked);
 | 
			
		||||
    QObject::connect(ui->importAllSerial, &QPushButton::clicked, this, &AMBEGUI::on_importAllSerial_clicked);
 | 
			
		||||
    QObject::connect(ui->removeAmbeDevice, &QPushButton::clicked, this, &AMBEGUI::on_removeAmbeDevice_clicked);
 | 
			
		||||
    QObject::connect(ui->refreshAmbeList, &QPushButton::clicked, this, &AMBEGUI::on_refreshAmbeList_clicked);
 | 
			
		||||
    QObject::connect(ui->clearAmbeList, &QPushButton::clicked, this, &AMBEGUI::on_clearAmbeList_clicked);
 | 
			
		||||
    QObject::connect(ui->importAddress, &QPushButton::clicked, this, &AMBEGUI::on_importAddress_clicked);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,92 @@
 | 
			
		|||
///////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
// Copyright (C) 2022 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                  //
 | 
			
		||||
// (at your option) any later version.                                           //
 | 
			
		||||
//                                                                               //
 | 
			
		||||
// 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 <http://www.gnu.org/licenses/>.          //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
#ifndef INCLUDE_FEATURE_AMBEGUI_H_
 | 
			
		||||
#define INCLUDE_FEATURE_AMBEGUI_H_
 | 
			
		||||
 | 
			
		||||
#include "feature/featuregui.h"
 | 
			
		||||
#include "util/messagequeue.h"
 | 
			
		||||
#include "util/movingaverage.h"
 | 
			
		||||
#include "settings/rollupstate.h"
 | 
			
		||||
 | 
			
		||||
#include "ambesettings.h"
 | 
			
		||||
 | 
			
		||||
class PluginAPI;
 | 
			
		||||
class FeatureUISet;
 | 
			
		||||
class Feature;
 | 
			
		||||
class AMBE;
 | 
			
		||||
class DSPDeviceSourceEngine;
 | 
			
		||||
class DSPDeviceSinkEngine;
 | 
			
		||||
 | 
			
		||||
namespace Ui {
 | 
			
		||||
	class AMBEGUI;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class AMBEGUI : public FeatureGUI
 | 
			
		||||
{
 | 
			
		||||
    Q_OBJECT
 | 
			
		||||
public:
 | 
			
		||||
	static AMBEGUI* create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature);
 | 
			
		||||
	virtual void destroy();
 | 
			
		||||
 | 
			
		||||
	void resetToDefaults();
 | 
			
		||||
	QByteArray serialize() const;
 | 
			
		||||
	bool deserialize(const QByteArray& data);
 | 
			
		||||
	virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
 | 
			
		||||
    virtual void setWorkspaceIndex(int index);
 | 
			
		||||
    virtual int getWorkspaceIndex() const { return m_settings.m_workspaceIndex; }
 | 
			
		||||
    virtual void setGeometryBytes(const QByteArray& blob) { m_settings.m_geometryBytes = blob; }
 | 
			
		||||
    virtual QByteArray getGeometryBytes() const { return m_settings.m_geometryBytes; }
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
    void resizeEvent(QResizeEvent* size);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	Ui::AMBEGUI* ui;
 | 
			
		||||
    AMBE *m_ambe;
 | 
			
		||||
	PluginAPI* m_pluginAPI;
 | 
			
		||||
	FeatureUISet* m_featureUISet;
 | 
			
		||||
	AMBESettings m_settings;
 | 
			
		||||
	RollupState m_rollupState;
 | 
			
		||||
    bool m_doApplySettings;
 | 
			
		||||
	MessageQueue m_inputMessageQueue;
 | 
			
		||||
 | 
			
		||||
	explicit AMBEGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent = nullptr);
 | 
			
		||||
	virtual ~AMBEGUI();
 | 
			
		||||
 | 
			
		||||
    void populateSerialList();
 | 
			
		||||
    void refreshInUseList();
 | 
			
		||||
 | 
			
		||||
    void blockApplySettings(bool block) { m_doApplySettings = !block; }
 | 
			
		||||
    void applySettings(bool force = false);
 | 
			
		||||
	void displaySettings();
 | 
			
		||||
    bool handleMessage(const Message& message);
 | 
			
		||||
    void makeUIConnections();
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	void onMenuDialogCalled(const QPoint &p);
 | 
			
		||||
	void onWidgetRolled(QWidget* widget, bool rollDown);
 | 
			
		||||
    void handleInputMessages();
 | 
			
		||||
    void on_importSerial_clicked();
 | 
			
		||||
    void on_importAllSerial_clicked();
 | 
			
		||||
    void on_removeAmbeDevice_clicked();
 | 
			
		||||
    void on_refreshAmbeList_clicked();
 | 
			
		||||
    void on_clearAmbeList_clicked();
 | 
			
		||||
    void on_importAddress_clicked();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,285 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<ui version="4.0">
 | 
			
		||||
 <class>AMBEGUI</class>
 | 
			
		||||
 <widget class="RollupContents" name="AMBEGUI">
 | 
			
		||||
  <property name="geometry">
 | 
			
		||||
   <rect>
 | 
			
		||||
    <x>0</x>
 | 
			
		||||
    <y>0</y>
 | 
			
		||||
    <width>360</width>
 | 
			
		||||
    <height>452</height>
 | 
			
		||||
   </rect>
 | 
			
		||||
  </property>
 | 
			
		||||
  <property name="sizePolicy">
 | 
			
		||||
   <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
 | 
			
		||||
    <horstretch>0</horstretch>
 | 
			
		||||
    <verstretch>0</verstretch>
 | 
			
		||||
   </sizepolicy>
 | 
			
		||||
  </property>
 | 
			
		||||
  <property name="minimumSize">
 | 
			
		||||
   <size>
 | 
			
		||||
    <width>360</width>
 | 
			
		||||
    <height>452</height>
 | 
			
		||||
   </size>
 | 
			
		||||
  </property>
 | 
			
		||||
  <property name="maximumSize">
 | 
			
		||||
   <size>
 | 
			
		||||
    <width>560</width>
 | 
			
		||||
    <height>452</height>
 | 
			
		||||
   </size>
 | 
			
		||||
  </property>
 | 
			
		||||
  <property name="font">
 | 
			
		||||
   <font>
 | 
			
		||||
    <family>Liberation Sans</family>
 | 
			
		||||
    <pointsize>9</pointsize>
 | 
			
		||||
   </font>
 | 
			
		||||
  </property>
 | 
			
		||||
  <property name="windowTitle">
 | 
			
		||||
   <string>AMBE Decoder Controller</string>
 | 
			
		||||
  </property>
 | 
			
		||||
  <widget class="QWidget" name="settingsContainer" native="true">
 | 
			
		||||
   <property name="geometry">
 | 
			
		||||
    <rect>
 | 
			
		||||
     <x>0</x>
 | 
			
		||||
     <y>0</y>
 | 
			
		||||
     <width>358</width>
 | 
			
		||||
     <height>450</height>
 | 
			
		||||
    </rect>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="windowTitle">
 | 
			
		||||
    <string>Settings</string>
 | 
			
		||||
   </property>
 | 
			
		||||
   <layout class="QVBoxLayout" name="verticalLayout">
 | 
			
		||||
    <property name="spacing">
 | 
			
		||||
     <number>3</number>
 | 
			
		||||
    </property>
 | 
			
		||||
    <property name="leftMargin">
 | 
			
		||||
     <number>2</number>
 | 
			
		||||
    </property>
 | 
			
		||||
    <property name="topMargin">
 | 
			
		||||
     <number>2</number>
 | 
			
		||||
    </property>
 | 
			
		||||
    <property name="rightMargin">
 | 
			
		||||
     <number>2</number>
 | 
			
		||||
    </property>
 | 
			
		||||
    <property name="bottomMargin">
 | 
			
		||||
     <number>2</number>
 | 
			
		||||
    </property>
 | 
			
		||||
    <item>
 | 
			
		||||
     <layout class="QHBoxLayout" name="ipTextLayout">
 | 
			
		||||
      <item>
 | 
			
		||||
       <widget class="QLabel" name="ambeServerLabel">
 | 
			
		||||
        <property name="text">
 | 
			
		||||
         <string>AMBE server IP and port or direct input</string>
 | 
			
		||||
        </property>
 | 
			
		||||
       </widget>
 | 
			
		||||
      </item>
 | 
			
		||||
     </layout>
 | 
			
		||||
    </item>
 | 
			
		||||
    <item>
 | 
			
		||||
     <layout class="QHBoxLayout" name="ipLayout">
 | 
			
		||||
      <item>
 | 
			
		||||
       <widget class="QLineEdit" name="ambeAddressText">
 | 
			
		||||
        <property name="toolTip">
 | 
			
		||||
         <string>AMBE server address as ip:port or direct input</string>
 | 
			
		||||
        </property>
 | 
			
		||||
        <property name="inputMask">
 | 
			
		||||
         <string/>
 | 
			
		||||
        </property>
 | 
			
		||||
       </widget>
 | 
			
		||||
      </item>
 | 
			
		||||
     </layout>
 | 
			
		||||
    </item>
 | 
			
		||||
    <item>
 | 
			
		||||
     <layout class="QHBoxLayout" name="inUseLayout">
 | 
			
		||||
      <item>
 | 
			
		||||
       <widget class="QLabel" name="ambeSerialLabel">
 | 
			
		||||
        <property name="text">
 | 
			
		||||
         <string>In use</string>
 | 
			
		||||
        </property>
 | 
			
		||||
       </widget>
 | 
			
		||||
      </item>
 | 
			
		||||
      <item>
 | 
			
		||||
       <widget class="QPushButton" name="importAddress">
 | 
			
		||||
        <property name="toolTip">
 | 
			
		||||
         <string>Use server</string>
 | 
			
		||||
        </property>
 | 
			
		||||
        <property name="text">
 | 
			
		||||
         <string/>
 | 
			
		||||
        </property>
 | 
			
		||||
        <property name="icon">
 | 
			
		||||
         <iconset resource="../../../sdrgui/resources/res.qrc">
 | 
			
		||||
          <normaloff>:/arrow_down.png</normaloff>:/arrow_down.png</iconset>
 | 
			
		||||
        </property>
 | 
			
		||||
       </widget>
 | 
			
		||||
      </item>
 | 
			
		||||
      <item>
 | 
			
		||||
       <widget class="QPushButton" name="removeAmbeDevice">
 | 
			
		||||
        <property name="toolTip">
 | 
			
		||||
         <string>Release all devices and servers</string>
 | 
			
		||||
        </property>
 | 
			
		||||
        <property name="text">
 | 
			
		||||
         <string/>
 | 
			
		||||
        </property>
 | 
			
		||||
        <property name="icon">
 | 
			
		||||
         <iconset resource="../../../sdrgui/resources/res.qrc">
 | 
			
		||||
          <normaloff>:/bin.png</normaloff>:/bin.png</iconset>
 | 
			
		||||
        </property>
 | 
			
		||||
       </widget>
 | 
			
		||||
      </item>
 | 
			
		||||
      <item>
 | 
			
		||||
       <widget class="QPushButton" name="refreshAmbeList">
 | 
			
		||||
        <property name="toolTip">
 | 
			
		||||
         <string>Refresh list of devices and servers in use</string>
 | 
			
		||||
        </property>
 | 
			
		||||
        <property name="text">
 | 
			
		||||
         <string/>
 | 
			
		||||
        </property>
 | 
			
		||||
        <property name="icon">
 | 
			
		||||
         <iconset resource="../../../sdrgui/resources/res.qrc">
 | 
			
		||||
          <normaloff>:/recycle.png</normaloff>:/recycle.png</iconset>
 | 
			
		||||
        </property>
 | 
			
		||||
       </widget>
 | 
			
		||||
      </item>
 | 
			
		||||
      <item>
 | 
			
		||||
       <widget class="QPushButton" name="clearAmbeList">
 | 
			
		||||
        <property name="toolTip">
 | 
			
		||||
         <string>Remove all devices</string>
 | 
			
		||||
        </property>
 | 
			
		||||
        <property name="text">
 | 
			
		||||
         <string/>
 | 
			
		||||
        </property>
 | 
			
		||||
        <property name="icon">
 | 
			
		||||
         <iconset resource="../../../sdrgui/resources/res.qrc">
 | 
			
		||||
          <normaloff>:/sweep.png</normaloff>:/sweep.png</iconset>
 | 
			
		||||
        </property>
 | 
			
		||||
       </widget>
 | 
			
		||||
      </item>
 | 
			
		||||
      <item>
 | 
			
		||||
       <spacer name="horizontalSpacer_2">
 | 
			
		||||
        <property name="orientation">
 | 
			
		||||
         <enum>Qt::Horizontal</enum>
 | 
			
		||||
        </property>
 | 
			
		||||
        <property name="sizeHint" stdset="0">
 | 
			
		||||
         <size>
 | 
			
		||||
          <width>40</width>
 | 
			
		||||
          <height>20</height>
 | 
			
		||||
         </size>
 | 
			
		||||
        </property>
 | 
			
		||||
       </spacer>
 | 
			
		||||
      </item>
 | 
			
		||||
     </layout>
 | 
			
		||||
    </item>
 | 
			
		||||
    <item>
 | 
			
		||||
     <layout class="QHBoxLayout" name="localDeviceLayout">
 | 
			
		||||
      <item>
 | 
			
		||||
       <widget class="QListWidget" name="ambeDeviceRefs">
 | 
			
		||||
        <property name="maximumSize">
 | 
			
		||||
         <size>
 | 
			
		||||
          <width>16777215</width>
 | 
			
		||||
          <height>140</height>
 | 
			
		||||
         </size>
 | 
			
		||||
        </property>
 | 
			
		||||
        <property name="toolTip">
 | 
			
		||||
         <string>List of devices/servers in use</string>
 | 
			
		||||
        </property>
 | 
			
		||||
       </widget>
 | 
			
		||||
      </item>
 | 
			
		||||
     </layout>
 | 
			
		||||
    </item>
 | 
			
		||||
    <item>
 | 
			
		||||
     <layout class="QHBoxLayout" name="serialLayout">
 | 
			
		||||
      <item>
 | 
			
		||||
       <widget class="QLabel" name="ambeSerialLabel_2">
 | 
			
		||||
        <property name="text">
 | 
			
		||||
         <string>Serial devices</string>
 | 
			
		||||
        </property>
 | 
			
		||||
       </widget>
 | 
			
		||||
      </item>
 | 
			
		||||
      <item>
 | 
			
		||||
       <widget class="QPushButton" name="importSerial">
 | 
			
		||||
        <property name="toolTip">
 | 
			
		||||
         <string>Use serial device</string>
 | 
			
		||||
        </property>
 | 
			
		||||
        <property name="text">
 | 
			
		||||
         <string/>
 | 
			
		||||
        </property>
 | 
			
		||||
        <property name="icon">
 | 
			
		||||
         <iconset resource="../../../sdrgui/resources/res.qrc">
 | 
			
		||||
          <normaloff>:/arrow_up.png</normaloff>:/arrow_up.png</iconset>
 | 
			
		||||
        </property>
 | 
			
		||||
       </widget>
 | 
			
		||||
      </item>
 | 
			
		||||
      <item>
 | 
			
		||||
       <widget class="QPushButton" name="importAllSerial">
 | 
			
		||||
        <property name="toolTip">
 | 
			
		||||
         <string>Use all serial devices</string>
 | 
			
		||||
        </property>
 | 
			
		||||
        <property name="text">
 | 
			
		||||
         <string/>
 | 
			
		||||
        </property>
 | 
			
		||||
        <property name="icon">
 | 
			
		||||
         <iconset resource="../../../sdrgui/resources/res.qrc">
 | 
			
		||||
          <normaloff>:/double_arrow_up.png</normaloff>:/double_arrow_up.png</iconset>
 | 
			
		||||
        </property>
 | 
			
		||||
       </widget>
 | 
			
		||||
      </item>
 | 
			
		||||
      <item>
 | 
			
		||||
       <spacer name="horizontalSpacer">
 | 
			
		||||
        <property name="orientation">
 | 
			
		||||
         <enum>Qt::Horizontal</enum>
 | 
			
		||||
        </property>
 | 
			
		||||
        <property name="sizeHint" stdset="0">
 | 
			
		||||
         <size>
 | 
			
		||||
          <width>40</width>
 | 
			
		||||
          <height>20</height>
 | 
			
		||||
         </size>
 | 
			
		||||
        </property>
 | 
			
		||||
       </spacer>
 | 
			
		||||
      </item>
 | 
			
		||||
     </layout>
 | 
			
		||||
    </item>
 | 
			
		||||
    <item>
 | 
			
		||||
     <layout class="QHBoxLayout" name="serialLayout_2">
 | 
			
		||||
      <item>
 | 
			
		||||
       <widget class="QListWidget" name="ambeSerialDevices">
 | 
			
		||||
        <property name="maximumSize">
 | 
			
		||||
         <size>
 | 
			
		||||
          <width>16777215</width>
 | 
			
		||||
          <height>140</height>
 | 
			
		||||
         </size>
 | 
			
		||||
        </property>
 | 
			
		||||
        <property name="toolTip">
 | 
			
		||||
         <string>List of available AMBE serial devices available</string>
 | 
			
		||||
        </property>
 | 
			
		||||
       </widget>
 | 
			
		||||
      </item>
 | 
			
		||||
     </layout>
 | 
			
		||||
    </item>
 | 
			
		||||
    <item>
 | 
			
		||||
     <layout class="QHBoxLayout" name="statusLayout">
 | 
			
		||||
      <item>
 | 
			
		||||
       <widget class="QLabel" name="statusText">
 | 
			
		||||
        <property name="text">
 | 
			
		||||
         <string>...</string>
 | 
			
		||||
        </property>
 | 
			
		||||
       </widget>
 | 
			
		||||
      </item>
 | 
			
		||||
     </layout>
 | 
			
		||||
    </item>
 | 
			
		||||
   </layout>
 | 
			
		||||
  </widget>
 | 
			
		||||
 </widget>
 | 
			
		||||
 <customwidgets>
 | 
			
		||||
  <customwidget>
 | 
			
		||||
   <class>RollupContents</class>
 | 
			
		||||
   <extends>QWidget</extends>
 | 
			
		||||
   <header>gui/rollupcontents.h</header>
 | 
			
		||||
   <container>1</container>
 | 
			
		||||
  </customwidget>
 | 
			
		||||
 </customwidgets>
 | 
			
		||||
 <resources>
 | 
			
		||||
  <include location="../../../sdrgui/resources/res.qrc"/>
 | 
			
		||||
 </resources>
 | 
			
		||||
 <connections/>
 | 
			
		||||
</ui>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,80 @@
 | 
			
		|||
///////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
// Copyright (C) 2022 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                  //
 | 
			
		||||
// (at your option) any later version.                                           //
 | 
			
		||||
//                                                                               //
 | 
			
		||||
// 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 <http://www.gnu.org/licenses/>.          //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#include <QtPlugin>
 | 
			
		||||
#include "plugin/pluginapi.h"
 | 
			
		||||
 | 
			
		||||
#ifndef SERVER_MODE
 | 
			
		||||
#include "ambegui.h"
 | 
			
		||||
#endif
 | 
			
		||||
#include "ambe.h"
 | 
			
		||||
#include "ambeplugin.h"
 | 
			
		||||
// #include "simplepttwebapiadapter.h"
 | 
			
		||||
 | 
			
		||||
const PluginDescriptor AMBEPlugin::m_pluginDescriptor = {
 | 
			
		||||
    AMBE::m_featureId,
 | 
			
		||||
	QStringLiteral("AMBE Controller"),
 | 
			
		||||
    QStringLiteral("7.2.0"),
 | 
			
		||||
	QStringLiteral("(c) Edouard Griffiths, F4EXB"),
 | 
			
		||||
	QStringLiteral("https://github.com/f4exb/sdrangel"),
 | 
			
		||||
	true,
 | 
			
		||||
	QStringLiteral("https://github.com/f4exb/sdrangel")
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
AMBEPlugin::AMBEPlugin(QObject* parent) :
 | 
			
		||||
	QObject(parent),
 | 
			
		||||
	m_pluginAPI(nullptr)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const PluginDescriptor& AMBEPlugin::getPluginDescriptor() const
 | 
			
		||||
{
 | 
			
		||||
	return m_pluginDescriptor;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEPlugin::initPlugin(PluginAPI* pluginAPI)
 | 
			
		||||
{
 | 
			
		||||
	m_pluginAPI = pluginAPI;
 | 
			
		||||
 | 
			
		||||
	// register Simple PTT feature
 | 
			
		||||
	m_pluginAPI->registerFeature(AMBE::m_featureIdURI, AMBE::m_featureId, this);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#ifdef SERVER_MODE
 | 
			
		||||
FeatureGUI* AMBEPlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const
 | 
			
		||||
{
 | 
			
		||||
	(void) featureUISet;
 | 
			
		||||
	(void) feature;
 | 
			
		||||
    return nullptr;
 | 
			
		||||
}
 | 
			
		||||
#else
 | 
			
		||||
FeatureGUI* AMBEPlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const
 | 
			
		||||
{
 | 
			
		||||
	return AMBEGUI::create(m_pluginAPI, featureUISet, feature);
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
Feature* AMBEPlugin::createFeature(WebAPIAdapterInterface* webAPIAdapterInterface) const
 | 
			
		||||
{
 | 
			
		||||
    return new AMBE(webAPIAdapterInterface);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
FeatureWebAPIAdapter* AMBEPlugin::createFeatureWebAPIAdapter() const
 | 
			
		||||
{
 | 
			
		||||
	return nullptr; // TODO new SimplePTTWebAPIAdapter();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,48 @@
 | 
			
		|||
///////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
// Copyright (C) 2022 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                  //
 | 
			
		||||
// (at your option) any later version.                                           //
 | 
			
		||||
//                                                                               //
 | 
			
		||||
// 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 <http://www.gnu.org/licenses/>.          //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
#ifndef INCLUDE_FEATURE_AMBEPLUGIN_H
 | 
			
		||||
#define INCLUDE_FEATURE_AMBEPLUGIN_H
 | 
			
		||||
 | 
			
		||||
#include <QObject>
 | 
			
		||||
#include "plugin/plugininterface.h"
 | 
			
		||||
 | 
			
		||||
class FeatureGUI;
 | 
			
		||||
class WebAPIAdapterInterface;
 | 
			
		||||
 | 
			
		||||
class AMBEPlugin : public QObject, PluginInterface {
 | 
			
		||||
	Q_OBJECT
 | 
			
		||||
	Q_INTERFACES(PluginInterface)
 | 
			
		||||
	Q_PLUGIN_METADATA(IID "sdrangel.feature.ambe")
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	explicit AMBEPlugin(QObject* parent = nullptr);
 | 
			
		||||
 | 
			
		||||
	const PluginDescriptor& getPluginDescriptor() const;
 | 
			
		||||
	void initPlugin(PluginAPI* pluginAPI);
 | 
			
		||||
 | 
			
		||||
	virtual FeatureGUI* createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const;
 | 
			
		||||
	virtual Feature* createFeature(WebAPIAdapterInterface *webAPIAdapterInterface) const;
 | 
			
		||||
	virtual FeatureWebAPIAdapter* createFeatureWebAPIAdapter() const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	static const PluginDescriptor m_pluginDescriptor;
 | 
			
		||||
 | 
			
		||||
	PluginAPI* m_pluginAPI;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // INCLUDE_FEATURE_AMBEPLUGIN_H
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,114 @@
 | 
			
		|||
///////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
// Copyright (C) 2022 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                  //
 | 
			
		||||
// (at your option) any later version.                                           //
 | 
			
		||||
//                                                                               //
 | 
			
		||||
// 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 <http://www.gnu.org/licenses/>.          //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
#include <QColor>
 | 
			
		||||
 | 
			
		||||
#include "util/simpleserializer.h"
 | 
			
		||||
#include "settings/serializable.h"
 | 
			
		||||
 | 
			
		||||
#include "ambesettings.h"
 | 
			
		||||
 | 
			
		||||
AMBESettings::AMBESettings() :
 | 
			
		||||
    m_rollupState(nullptr)
 | 
			
		||||
{
 | 
			
		||||
    resetToDefaults();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBESettings::resetToDefaults()
 | 
			
		||||
{
 | 
			
		||||
    m_title = "AMBE Controller";
 | 
			
		||||
    m_rgbColor = QColor(255, 0, 0).rgb();
 | 
			
		||||
    m_useReverseAPI = false;
 | 
			
		||||
    m_reverseAPIAddress = "127.0.0.1";
 | 
			
		||||
    m_reverseAPIPort = 8888;
 | 
			
		||||
    m_reverseAPIFeatureSetIndex = 0;
 | 
			
		||||
    m_reverseAPIFeatureIndex = 0;
 | 
			
		||||
    m_workspaceIndex = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QByteArray AMBESettings::serialize() const
 | 
			
		||||
{
 | 
			
		||||
    SimpleSerializer s(1);
 | 
			
		||||
 | 
			
		||||
    s.writeString(1, m_title);
 | 
			
		||||
    s.writeU32(2, m_rgbColor);
 | 
			
		||||
    s.writeBool(7, m_useReverseAPI);
 | 
			
		||||
    s.writeString(8, m_reverseAPIAddress);
 | 
			
		||||
    s.writeU32(9, m_reverseAPIPort);
 | 
			
		||||
    s.writeU32(10, m_reverseAPIFeatureSetIndex);
 | 
			
		||||
    s.writeU32(11, m_reverseAPIFeatureIndex);
 | 
			
		||||
 | 
			
		||||
    if (m_rollupState) {
 | 
			
		||||
        s.writeBlob(12, m_rollupState->serialize());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    s.writeS32(18, m_workspaceIndex);
 | 
			
		||||
    s.writeBlob(19, m_geometryBytes);
 | 
			
		||||
 | 
			
		||||
    return s.final();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool AMBESettings::deserialize(const QByteArray& data)
 | 
			
		||||
{
 | 
			
		||||
    SimpleDeserializer d(data);
 | 
			
		||||
 | 
			
		||||
    if(!d.isValid())
 | 
			
		||||
    {
 | 
			
		||||
        resetToDefaults();
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if(d.getVersion() == 1)
 | 
			
		||||
    {
 | 
			
		||||
        QByteArray bytetmp;
 | 
			
		||||
        uint32_t utmp;
 | 
			
		||||
        QString strtmp;
 | 
			
		||||
 | 
			
		||||
        d.readString(1, &m_title, "Simple PTT");
 | 
			
		||||
        d.readU32(2, &m_rgbColor, QColor(255, 0, 0).rgb());
 | 
			
		||||
        d.readBool(7, &m_useReverseAPI, false);
 | 
			
		||||
        d.readString(8, &m_reverseAPIAddress, "127.0.0.1");
 | 
			
		||||
        d.readU32(9, &utmp, 0);
 | 
			
		||||
 | 
			
		||||
        if ((utmp > 1023) && (utmp < 65535)) {
 | 
			
		||||
            m_reverseAPIPort = utmp;
 | 
			
		||||
        } else {
 | 
			
		||||
            m_reverseAPIPort = 8888;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        d.readU32(10, &utmp, 0);
 | 
			
		||||
        m_reverseAPIFeatureSetIndex = utmp > 99 ? 99 : utmp;
 | 
			
		||||
        d.readU32(11, &utmp, 0);
 | 
			
		||||
        m_reverseAPIFeatureIndex = utmp > 99 ? 99 : utmp;
 | 
			
		||||
 | 
			
		||||
        if (m_rollupState)
 | 
			
		||||
        {
 | 
			
		||||
            d.readBlob(12, &bytetmp);
 | 
			
		||||
            m_rollupState->deserialize(bytetmp);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        d.readS32(18, &m_workspaceIndex, 0);
 | 
			
		||||
        d.readBlob(19, &m_geometryBytes);
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        resetToDefaults();
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,46 @@
 | 
			
		|||
///////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
// Copyright (C) 2022 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                  //
 | 
			
		||||
// (at your option) any later version.                                           //
 | 
			
		||||
//                                                                               //
 | 
			
		||||
// 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 <http://www.gnu.org/licenses/>.          //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
#ifndef INCLUDE_FEATURE_AMBESETTINGS_H_
 | 
			
		||||
#define INCLUDE_FEATURE_AMBESETTINGS_H_
 | 
			
		||||
 | 
			
		||||
#include <QByteArray>
 | 
			
		||||
#include <QString>
 | 
			
		||||
 | 
			
		||||
class Serializable;
 | 
			
		||||
 | 
			
		||||
struct AMBESettings
 | 
			
		||||
{
 | 
			
		||||
    QString m_title;
 | 
			
		||||
    quint32 m_rgbColor;
 | 
			
		||||
    bool m_useReverseAPI;
 | 
			
		||||
    QString m_reverseAPIAddress;
 | 
			
		||||
    uint16_t m_reverseAPIPort;
 | 
			
		||||
    uint16_t m_reverseAPIFeatureSetIndex;
 | 
			
		||||
    uint16_t m_reverseAPIFeatureIndex;
 | 
			
		||||
    Serializable *m_rollupState;
 | 
			
		||||
    int m_workspaceIndex;
 | 
			
		||||
    QByteArray m_geometryBytes;
 | 
			
		||||
 | 
			
		||||
    AMBESettings();
 | 
			
		||||
    void resetToDefaults();
 | 
			
		||||
    QByteArray serialize() const;
 | 
			
		||||
    bool deserialize(const QByteArray& data);
 | 
			
		||||
    void setRollupState(Serializable *rollupState) { m_rollupState = rollupState; }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // INCLUDE_FEATURE_AMBESETTINGS_H_
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,232 @@
 | 
			
		|||
///////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
// Copyright (C) 2019 F4EXB                                                      //
 | 
			
		||||
// written by Edouard Griffiths                                                  //
 | 
			
		||||
//                                                                               //
 | 
			
		||||
// 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                  //
 | 
			
		||||
// (at your option) any later version.                                           //
 | 
			
		||||
//                                                                               //
 | 
			
		||||
// 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 <http://www.gnu.org/licenses/>.          //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <chrono>
 | 
			
		||||
#include <thread>
 | 
			
		||||
 | 
			
		||||
#include "audio/audiofifo.h"
 | 
			
		||||
#include "ambeworker.h"
 | 
			
		||||
 | 
			
		||||
MESSAGE_CLASS_DEFINITION(AMBEWorker::MsgMbeDecode, Message)
 | 
			
		||||
MESSAGE_CLASS_DEFINITION(AMBEWorker::MsgTest, Message)
 | 
			
		||||
 | 
			
		||||
AMBEWorker::AMBEWorker() :
 | 
			
		||||
    m_running(false),
 | 
			
		||||
    m_currentGainIn(0),
 | 
			
		||||
    m_currentGainOut(0),
 | 
			
		||||
    m_upsamplerLastValue(0.0f),
 | 
			
		||||
    m_phase(0),
 | 
			
		||||
    m_upsampling(1),
 | 
			
		||||
    m_volume(1.0f)
 | 
			
		||||
{
 | 
			
		||||
    m_audioBuffer.resize(48000);
 | 
			
		||||
    m_audioBufferFill = 0;
 | 
			
		||||
    m_audioFifo = 0;
 | 
			
		||||
    std::fill(m_dvAudioSamples, m_dvAudioSamples+SerialDV::MBE_AUDIO_BLOCK_SIZE, 0);
 | 
			
		||||
    setVolumeFactors();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AMBEWorker::~AMBEWorker()
 | 
			
		||||
{}
 | 
			
		||||
 | 
			
		||||
bool AMBEWorker::open(const std::string& deviceRef)
 | 
			
		||||
{
 | 
			
		||||
    return m_dvController.open(deviceRef);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEWorker::close()
 | 
			
		||||
{
 | 
			
		||||
    m_dvController.close();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEWorker::process()
 | 
			
		||||
{
 | 
			
		||||
    m_running  = true;
 | 
			
		||||
    qDebug("AMBEWorker::process: started");
 | 
			
		||||
 | 
			
		||||
    while (m_running)
 | 
			
		||||
    {
 | 
			
		||||
        std::this_thread::sleep_for(std::chrono::seconds(1));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    qDebug("AMBEWorker::process: stopped");
 | 
			
		||||
    emit finished();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEWorker::stop()
 | 
			
		||||
{
 | 
			
		||||
    m_running = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEWorker::handleInputMessages()
 | 
			
		||||
{
 | 
			
		||||
    Message* message;
 | 
			
		||||
    m_audioBufferFill = 0;
 | 
			
		||||
    AudioFifo *audioFifo = 0;
 | 
			
		||||
 | 
			
		||||
    while ((message = m_inputMessageQueue.pop()) != 0)
 | 
			
		||||
    {
 | 
			
		||||
        if (MsgMbeDecode::match(*message))
 | 
			
		||||
        {
 | 
			
		||||
            MsgMbeDecode *decodeMsg = (MsgMbeDecode *) message;
 | 
			
		||||
            int dBVolume = (decodeMsg->getVolumeIndex() - 30) / 4;
 | 
			
		||||
            float volume = pow(10.0, dBVolume / 10.0f);
 | 
			
		||||
            int upsampling = decodeMsg->getUpsampling();
 | 
			
		||||
            upsampling = upsampling > 6 ? 6 : upsampling < 1 ? 1 : upsampling;
 | 
			
		||||
 | 
			
		||||
            if ((volume != m_volume) || (upsampling != m_upsampling))
 | 
			
		||||
            {
 | 
			
		||||
                m_volume = volume;
 | 
			
		||||
                m_upsampling = upsampling;
 | 
			
		||||
                setVolumeFactors();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            m_upsampleFilter.useHP(decodeMsg->getUseHP());
 | 
			
		||||
 | 
			
		||||
            if (m_dvController.decode(m_dvAudioSamples, decodeMsg->getMbeFrame(), decodeMsg->getMbeRate()))
 | 
			
		||||
            {
 | 
			
		||||
                if (upsampling > 1) {
 | 
			
		||||
                    upsample(upsampling, m_dvAudioSamples, SerialDV::MBE_AUDIO_BLOCK_SIZE, decodeMsg->getChannels());
 | 
			
		||||
                } else {
 | 
			
		||||
                    noUpsample(m_dvAudioSamples, SerialDV::MBE_AUDIO_BLOCK_SIZE, decodeMsg->getChannels());
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                audioFifo = decodeMsg->getAudioFifo();
 | 
			
		||||
 | 
			
		||||
                if (audioFifo && (m_audioBufferFill >= m_audioBuffer.size() - 960))
 | 
			
		||||
                {
 | 
			
		||||
                    uint res = audioFifo->write((const quint8*)&m_audioBuffer[0], m_audioBufferFill);
 | 
			
		||||
 | 
			
		||||
                    if (res != m_audioBufferFill) {
 | 
			
		||||
                        qDebug("AMBEWorker::handleInputMessages: %u/%u audio samples written", res, m_audioBufferFill);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    m_audioBufferFill = 0;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                qDebug("AMBEWorker::handleInputMessages: MsgMbeDecode: decode failed");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        delete message;
 | 
			
		||||
 | 
			
		||||
        if (m_inputMessageQueue.size() > 100)
 | 
			
		||||
        {
 | 
			
		||||
            qDebug("AMBEWorker::handleInputMessages: MsgMbeDecode: too many messages in queue. Flushing...");
 | 
			
		||||
            m_inputMessageQueue.clear();
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (audioFifo)
 | 
			
		||||
    {
 | 
			
		||||
        uint res = audioFifo->write((const quint8*)&m_audioBuffer[0], m_audioBufferFill);
 | 
			
		||||
 | 
			
		||||
        if (res != m_audioBufferFill) {
 | 
			
		||||
            qDebug("AMBEWorker::handleInputMessages: %u/%u audio samples written", res, m_audioBufferFill);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        m_audioBufferFill = 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    m_timestamp = QDateTime::currentDateTime();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEWorker::pushMbeFrame(const unsigned char *mbeFrame,
 | 
			
		||||
        int mbeRateIndex,
 | 
			
		||||
        int mbeVolumeIndex,
 | 
			
		||||
        unsigned char channels,
 | 
			
		||||
        bool useHP,
 | 
			
		||||
        int upsampling,
 | 
			
		||||
        AudioFifo *audioFifo)
 | 
			
		||||
{
 | 
			
		||||
    m_audioFifo = audioFifo;
 | 
			
		||||
    m_inputMessageQueue.push(MsgMbeDecode::create(mbeFrame, mbeRateIndex, mbeVolumeIndex, channels, useHP, upsampling, audioFifo));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool AMBEWorker::isAvailable()
 | 
			
		||||
{
 | 
			
		||||
	if (m_audioFifo == 0) {
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return m_timestamp.time().msecsTo(QDateTime::currentDateTime().time()) > 1000; // 1 second inactivity timeout
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool AMBEWorker::hasFifo(AudioFifo *audioFifo)
 | 
			
		||||
{
 | 
			
		||||
    return m_audioFifo == audioFifo;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEWorker::upsample(int upsampling, short *in, int nbSamplesIn, unsigned char channels)
 | 
			
		||||
{
 | 
			
		||||
    for (int i = 0; i < nbSamplesIn; i++)
 | 
			
		||||
    {
 | 
			
		||||
        //float cur = m_upsampleFilter.usesHP() ? m_upsampleFilter.runHP((float) m_compressor.compress(in[i])) : (float) m_compressor.compress(in[i]);
 | 
			
		||||
        float cur = m_upsampleFilter.usesHP() ? m_upsampleFilter.runHP((float) in[i]) : (float) in[i];
 | 
			
		||||
        float prev = m_upsamplerLastValue;
 | 
			
		||||
        qint16 upsample;
 | 
			
		||||
 | 
			
		||||
        for (int j = 1; j <= upsampling; j++)
 | 
			
		||||
        {
 | 
			
		||||
            upsample = (qint16) m_upsampleFilter.runLP(cur*m_upsamplingFactors[j] + prev*m_upsamplingFactors[upsampling-j]);
 | 
			
		||||
            m_audioBuffer[m_audioBufferFill].l = channels & 1 ? m_compressor.compress(upsample) : 0;
 | 
			
		||||
            m_audioBuffer[m_audioBufferFill].r = (channels>>1) & 1 ? m_compressor.compress(upsample) : 0;
 | 
			
		||||
 | 
			
		||||
            if (m_audioBufferFill < m_audioBuffer.size() - 1) {
 | 
			
		||||
                ++m_audioBufferFill;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        m_upsamplerLastValue = cur;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (m_audioBufferFill >= m_audioBuffer.size() - 1) {
 | 
			
		||||
        qDebug("AMBEWorker::upsample(%d): audio buffer is full check its size", upsampling);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEWorker::noUpsample(short *in, int nbSamplesIn, unsigned char channels)
 | 
			
		||||
{
 | 
			
		||||
    for (int i = 0; i < nbSamplesIn; i++)
 | 
			
		||||
    {
 | 
			
		||||
        float cur = m_upsampleFilter.usesHP() ? m_upsampleFilter.runHP((float) in[i]) : (float) in[i];
 | 
			
		||||
        m_audioBuffer[m_audioBufferFill].l = channels & 1 ? cur*m_upsamplingFactors[0] : 0;
 | 
			
		||||
        m_audioBuffer[m_audioBufferFill].r = (channels>>1) & 1 ? cur*m_upsamplingFactors[0] : 0;
 | 
			
		||||
 | 
			
		||||
        if (m_audioBufferFill < m_audioBuffer.size() - 1) {
 | 
			
		||||
            ++m_audioBufferFill;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (m_audioBufferFill >= m_audioBuffer.size() - 1) {
 | 
			
		||||
        qDebug("AMBEWorker::noUpsample: audio buffer is full check its size");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AMBEWorker::setVolumeFactors()
 | 
			
		||||
{
 | 
			
		||||
    m_upsamplingFactors[0] = m_volume;
 | 
			
		||||
 | 
			
		||||
    for (int i = 1; i <= m_upsampling; i++) {
 | 
			
		||||
        m_upsamplingFactors[i] = (i*m_volume) / (float) m_upsampling;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,156 @@
 | 
			
		|||
///////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
// Copyright (C) 2019 F4EXB                                                      //
 | 
			
		||||
// written by Edouard Griffiths                                                  //
 | 
			
		||||
//                                                                               //
 | 
			
		||||
// 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                  //
 | 
			
		||||
// (at your option) any later version.                                           //
 | 
			
		||||
//                                                                               //
 | 
			
		||||
// 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 <http://www.gnu.org/licenses/>.          //
 | 
			
		||||
///////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
#ifndef SDRBASE_AMBE_AMBEWORKER_H_
 | 
			
		||||
#define SDRBASE_AMBE_AMBEWORKER_H_
 | 
			
		||||
 | 
			
		||||
#include <QObject>
 | 
			
		||||
#include <QDebug>
 | 
			
		||||
#include <QDateTime>
 | 
			
		||||
 | 
			
		||||
#include "export.h"
 | 
			
		||||
#include "dvcontroller.h"
 | 
			
		||||
 | 
			
		||||
#include "util/messagequeue.h"
 | 
			
		||||
#include "util/message.h"
 | 
			
		||||
#include "dsp/filtermbe.h"
 | 
			
		||||
#include "dsp/dsptypes.h"
 | 
			
		||||
#include "audio/audiocompressor.h"
 | 
			
		||||
 | 
			
		||||
class AudioFifo;
 | 
			
		||||
 | 
			
		||||
class SDRBASE_API AMBEWorker : public QObject {
 | 
			
		||||
    Q_OBJECT
 | 
			
		||||
public:
 | 
			
		||||
    class MsgTest : public Message
 | 
			
		||||
    {
 | 
			
		||||
        MESSAGE_CLASS_DECLARATION
 | 
			
		||||
    public:
 | 
			
		||||
        static MsgTest* create() { return new MsgTest(); }
 | 
			
		||||
    private:
 | 
			
		||||
        MsgTest() {}
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    class MsgMbeDecode : public Message
 | 
			
		||||
    {
 | 
			
		||||
        MESSAGE_CLASS_DECLARATION
 | 
			
		||||
    public:
 | 
			
		||||
        const unsigned char *getMbeFrame() const { return m_mbeFrame; }
 | 
			
		||||
        SerialDV::DVRate getMbeRate() const { return m_mbeRate; }
 | 
			
		||||
        int getVolumeIndex() const { return m_volumeIndex; }
 | 
			
		||||
        unsigned char getChannels() const { return m_channels % 4; }
 | 
			
		||||
        bool getUseHP() const { return m_useHP; }
 | 
			
		||||
        int getUpsampling() const { return m_upsampling; }
 | 
			
		||||
        AudioFifo *getAudioFifo() { return m_audioFifo; }
 | 
			
		||||
 | 
			
		||||
        static MsgMbeDecode* create(
 | 
			
		||||
                const unsigned char *mbeFrame,
 | 
			
		||||
                int mbeRateIndex,
 | 
			
		||||
                int volumeIndex,
 | 
			
		||||
                unsigned char channels,
 | 
			
		||||
                bool useHP,
 | 
			
		||||
                int upsampling,
 | 
			
		||||
                AudioFifo *audioFifo)
 | 
			
		||||
        {
 | 
			
		||||
            return new MsgMbeDecode(mbeFrame, (SerialDV::DVRate) mbeRateIndex, volumeIndex, channels, useHP, upsampling, audioFifo);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
        unsigned char m_mbeFrame[SerialDV::MBE_FRAME_MAX_LENGTH_BYTES];
 | 
			
		||||
        SerialDV::DVRate m_mbeRate;
 | 
			
		||||
        int m_volumeIndex;
 | 
			
		||||
        unsigned char m_channels;
 | 
			
		||||
        bool m_useHP;
 | 
			
		||||
        int m_upsampling;
 | 
			
		||||
        AudioFifo *m_audioFifo;
 | 
			
		||||
 | 
			
		||||
        MsgMbeDecode(const unsigned char *mbeFrame,
 | 
			
		||||
                SerialDV::DVRate mbeRate,
 | 
			
		||||
                int volumeIndex,
 | 
			
		||||
                unsigned char channels,
 | 
			
		||||
                bool useHP,
 | 
			
		||||
                int upsampling,
 | 
			
		||||
                AudioFifo *audioFifo) :
 | 
			
		||||
            Message(),
 | 
			
		||||
            m_mbeRate(mbeRate),
 | 
			
		||||
            m_volumeIndex(volumeIndex),
 | 
			
		||||
            m_channels(channels),
 | 
			
		||||
            m_useHP(useHP),
 | 
			
		||||
            m_upsampling(upsampling),
 | 
			
		||||
            m_audioFifo(audioFifo)
 | 
			
		||||
        {
 | 
			
		||||
            memcpy((void *) m_mbeFrame, (const void *) mbeFrame, SerialDV::DVController::getNbMbeBytes(m_mbeRate));
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    AMBEWorker();
 | 
			
		||||
    ~AMBEWorker();
 | 
			
		||||
 | 
			
		||||
    void pushMbeFrame(const unsigned char *mbeFrame,
 | 
			
		||||
            int mbeRateIndex,
 | 
			
		||||
            int mbeVolumeIndex,
 | 
			
		||||
            unsigned char channels,
 | 
			
		||||
            bool useHP,
 | 
			
		||||
            int upsampling,
 | 
			
		||||
            AudioFifo *audioFifo);
 | 
			
		||||
 | 
			
		||||
    bool open(const std::string& deviceRef); //!< Either serial device or ip:port
 | 
			
		||||
    void close();
 | 
			
		||||
    void process();
 | 
			
		||||
    void stop();
 | 
			
		||||
    bool isAvailable();
 | 
			
		||||
    bool hasFifo(AudioFifo *audioFifo);
 | 
			
		||||
 | 
			
		||||
    void postTest()
 | 
			
		||||
    {
 | 
			
		||||
        //emit inputMessageReady();
 | 
			
		||||
        m_inputMessageQueue.push(MsgTest::create());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
    void finished();
 | 
			
		||||
 | 
			
		||||
public slots:
 | 
			
		||||
    void handleInputMessages();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    void upsample(int upsampling, short *in, int nbSamplesIn, unsigned char channels);
 | 
			
		||||
    void noUpsample(short *in, int nbSamplesIn, unsigned char channels);
 | 
			
		||||
    void setVolumeFactors();
 | 
			
		||||
 | 
			
		||||
    SerialDV::DVController m_dvController;
 | 
			
		||||
    AudioFifo *m_audioFifo;
 | 
			
		||||
    QDateTime m_timestamp;
 | 
			
		||||
    volatile bool m_running;
 | 
			
		||||
    int m_currentGainIn;
 | 
			
		||||
    int m_currentGainOut;
 | 
			
		||||
    short m_dvAudioSamples[SerialDV::MBE_AUDIO_BLOCK_SIZE];
 | 
			
		||||
    AudioVector m_audioBuffer;
 | 
			
		||||
    uint m_audioBufferFill;
 | 
			
		||||
    float m_upsamplerLastValue;
 | 
			
		||||
    float m_phase;
 | 
			
		||||
    MBEAudioInterpolatorFilter m_upsampleFilter;
 | 
			
		||||
    int m_upsampling;
 | 
			
		||||
    float m_volume;
 | 
			
		||||
    float m_upsamplingFactors[7];
 | 
			
		||||
    AudioCompressor m_compressor;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // SDRBASE_AMBE_AMBEWORKER_H_
 | 
			
		||||
| 
						 | 
				
			
			@ -44,3 +44,4 @@ MESSAGE_CLASS_DEFINITION(DSPSignalNotification, Message)
 | 
			
		|||
MESSAGE_CLASS_DEFINITION(DSPMIMOSignalNotification, Message)
 | 
			
		||||
MESSAGE_CLASS_DEFINITION(DSPConfigureChannelizer, Message)
 | 
			
		||||
MESSAGE_CLASS_DEFINITION(DSPConfigureAudio, Message)
 | 
			
		||||
MESSAGE_CLASS_DEFINITION(DSPPushMbeFrame, Message)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -345,4 +345,45 @@ private:
 | 
			
		|||
    AudioType m_autioType;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class SDRBASE_API DSPPushMbeFrame : public Message {
 | 
			
		||||
	MESSAGE_CLASS_DECLARATION
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	DSPPushMbeFrame(
 | 
			
		||||
        const unsigned char *mbeFrame,
 | 
			
		||||
        int mbeRateIndex,
 | 
			
		||||
        int mbeVolumeIndex,
 | 
			
		||||
        unsigned char channels,
 | 
			
		||||
        bool useHP,
 | 
			
		||||
        int upsampling,
 | 
			
		||||
        AudioFifo *audioFifo
 | 
			
		||||
    ) :
 | 
			
		||||
		Message(),
 | 
			
		||||
		m_mbeFrame(mbeFrame),
 | 
			
		||||
		m_mbeRateIndex(mbeRateIndex),
 | 
			
		||||
        m_mbeVolumeIndex(mbeVolumeIndex),
 | 
			
		||||
        m_channels(channels),
 | 
			
		||||
        m_useHP(useHP),
 | 
			
		||||
        m_upsampling(upsampling),
 | 
			
		||||
        m_audioFifo(audioFifo)
 | 
			
		||||
	{ }
 | 
			
		||||
 | 
			
		||||
	const unsigned char * getMbeFrame() const { return m_mbeFrame; }
 | 
			
		||||
	int getMbeRateIndex() const { return m_mbeRateIndex; }
 | 
			
		||||
    int getMbeVolumeIndex() const { return m_mbeVolumeIndex; }
 | 
			
		||||
    unsigned char getChannels() const { return m_channels; }
 | 
			
		||||
    bool getUseHP() const { return m_useHP; }
 | 
			
		||||
    int getUpsampling() const { return m_upsampling; }
 | 
			
		||||
    AudioFifo *getAudioFifo() const { return m_audioFifo; }
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    const unsigned char *m_mbeFrame;
 | 
			
		||||
    int m_mbeRateIndex;
 | 
			
		||||
    int m_mbeVolumeIndex;
 | 
			
		||||
    unsigned char m_channels;
 | 
			
		||||
    bool m_useHP;
 | 
			
		||||
    int m_upsampling;
 | 
			
		||||
    AudioFifo *m_audioFifo;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // INCLUDE_DSPCOMMANDS_H
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Ładowanie…
	
		Reference in New Issue