diff --git a/SConstruct b/SConstruct index 74f40bd6ad5..f571b00c827 100644 --- a/SConstruct +++ b/SConstruct @@ -57,6 +57,7 @@ available_features = [features.Mad, features.AutoDjCrates, features.ColorDiagnostics, features.AddressSanitizer, + features.Lilv, # "Features" of dubious quality features.PerfTools, diff --git a/build/depends.py b/build/depends.py index d07c72c5803..84aa6ab0e00 100644 --- a/build/depends.py +++ b/build/depends.py @@ -555,6 +555,7 @@ def sources(self, build): "dlgprefnovinyl.cpp", "dlgabout.cpp", "dlgprefeq.cpp", + "dlgpreflv2.cpp", "dlgprefcrossfader.cpp", "dlgtagfetcher.cpp", "dlgtrackinfo.cpp", @@ -700,6 +701,7 @@ def sources(self, build): "widget/wdisplay.cpp", "widget/wvumeter.cpp", "widget/wpushbutton.cpp", + "widget/weffectpushbutton.cpp", "widget/wslidercomposed.cpp", "widget/wstatuslight.cpp", "widget/woverview.cpp", @@ -962,6 +964,7 @@ def sources(self, build): 'dlgprefcrossfaderdlg.ui', 'dlgprefkeydlg.ui', 'dlgprefeqdlg.ui', + 'dlgpreflv2dlg.ui', 'dlgpreferencesdlg.ui', 'dlgprefnovinyldlg.ui', 'dlgpreflibrarydlg.ui', diff --git a/build/features.py b/build/features.py index fd64eed59c6..97a1ead1cc1 100644 --- a/build/features.py +++ b/build/features.py @@ -1311,3 +1311,35 @@ def configure(self, build, conf): if not self.enabled(build): return build.env.Append(CPPDEFINES='__MACAPPSTORE__') + +class Lilv(Feature): + def description(self): + return "Lilv library for LV2 support" + + def enabled(self, build): + build.flags['lilv'] = util.get_flags(build.env, 'lilv', 0) + if int(build.flags['lilv']): + return True + return False + + def add_options(self, build, vars): + vars.Add('lilv', 'Set to 1 to enable Lilv library for LV2 support', 1) + + def configure(self, build, conf): + if not self.enabled(build): + return + + if build.platform_is_linux or build.platform_is_osx \ + or build.platform_is_bsd: + # Check for liblilv-0 + if not conf.CheckForPKG('lilv-0', '0.5'): + raise Exception('Missing liblilv-0 (needs at least 0.5)') + + build.env.Append(CPPDEFINES='__LILV__') + build.env.ParseConfig('pkg-config lilv-0 --silence-errors \ + --cflags --libs') + + def sources(self, build): + return ['effects/lv2/lv2backend.cpp', + 'effects/lv2/lv2effectprocessor.cpp', + 'effects/lv2/lv2manifest.cpp'] diff --git a/src/dlgpreferences.cpp b/src/dlgpreferences.cpp index 9f70d4f1164..25e4a03222a 100644 --- a/src/dlgpreferences.cpp +++ b/src/dlgpreferences.cpp @@ -50,6 +50,7 @@ #include "dlgprefcrossfader.h" #include "dlgprefrecord.h" #include "dlgprefreplaygain.h" +#include "dlgpreflv2.h" #include "mixxx.h" #include "controllers/controllermanager.h" #include "skin/skinloader.h" @@ -58,7 +59,8 @@ DlgPreferences::DlgPreferences(MixxxMainWindow * mixxx, SkinLoader* pSkinLoader, SoundManager * soundman, PlayerManager* pPlayerManager, ControllerManager * controllers, VinylControlManager *pVCManager, - ConfigObject* pConfig, Library *pLibrary) + LV2Backend* pLV2Backend, ConfigObject* pConfig, + Library *pLibrary) : m_pConfig(pConfig), m_pageSizeHint(QSize(0, 0)), m_preferencesUpdated(ConfigKey("[Preferences]", "updated"), false) { @@ -100,6 +102,8 @@ DlgPreferences::DlgPreferences(MixxxMainWindow * mixxx, SkinLoader* pSkinLoader, addPageWidget(m_wautodj); m_weq = new DlgPrefEQ(this, m_pConfig); addPageWidget(m_weq); + m_wlv2 = new DlgPrefLV2(this, pLV2Backend, m_pConfig); + addPageWidget(m_wlv2); m_wcrossfader = new DlgPrefCrossfader(this, m_pConfig); addPageWidget(m_wcrossfader); @@ -186,6 +190,12 @@ void DlgPreferences::createIcons() { m_pEqButton->setTextAlignment(0, Qt::AlignLeft | Qt::AlignVCenter); m_pEqButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + m_pLV2Button = new QTreeWidgetItem(contentsTreeWidget, QTreeWidgetItem::Type); + m_pLV2Button->setIcon(0, QIcon(":/images/preferences/ic_preferences_lv2.png")); + m_pLV2Button->setText(0, tr("LV2 Plugins")); + m_pLV2Button->setTextAlignment(0, Qt::AlignLeft | Qt::AlignVCenter); + m_pLV2Button->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + m_pCrossfaderButton = new QTreeWidgetItem(contentsTreeWidget, QTreeWidgetItem::Type); m_pCrossfaderButton->setIcon(0, QIcon(":/images/preferences/ic_preferences_crossfader.png")); m_pCrossfaderButton->setText(0, tr("Crossfader")); @@ -273,6 +283,8 @@ void DlgPreferences::changePage(QTreeWidgetItem* current, QTreeWidgetItem* previ switchToPage(m_wautodj); } else if (current == m_pEqButton) { switchToPage(m_weq); + } else if (current == m_pLV2Button) { + switchToPage(m_wlv2); } else if (current == m_pCrossfaderButton) { switchToPage(m_wcrossfader); } else if (current == m_pRecordingButton) { diff --git a/src/dlgpreferences.h b/src/dlgpreferences.h index 9c06c333fdc..4b9c413ee96 100644 --- a/src/dlgpreferences.h +++ b/src/dlgpreferences.h @@ -46,11 +46,13 @@ class DlgPrefVinyl; class DlgPrefNoVinyl; class DlgPrefShoutcast; class DlgPrefReplayGain; +class DlgPrefLV2; class ControllerManager; class SkinLoader; class PlayerManager; class Library; class VinylControlManager; +class LV2Backend; #ifdef __MODPLUG__ class DlgPrefModplug; #endif @@ -60,8 +62,8 @@ class DlgPreferences : public QDialog, public Ui::DlgPreferencesDlg { public: DlgPreferences(MixxxMainWindow* mixxx, SkinLoader* pSkinLoader, SoundManager* soundman, PlayerManager* pPlayerManager, ControllerManager* controllers, - VinylControlManager* pVCManager, ConfigObject* pConfig, - Library *pLibrary); + VinylControlManager* pVCManager, LV2Backend* pLV2Backend, + ConfigObject* pConfig, Library *pLibrary); virtual ~DlgPreferences(); void addPageWidget(DlgPreferencePage* pWidget); @@ -113,6 +115,7 @@ class DlgPreferences : public QDialog, public Ui::DlgPreferencesDlg { DlgPrefNoVinyl* m_wnovinylcontrol; DlgPrefShoutcast* m_wshoutcast; DlgPrefReplayGain* m_wreplaygain; + DlgPrefLV2* m_wlv2; #ifdef __MODPLUG__ DlgPrefModplug* m_wmodplug; #endif @@ -123,6 +126,7 @@ class DlgPreferences : public QDialog, public Ui::DlgPreferencesDlg { QTreeWidgetItem* m_pWaveformButton; QTreeWidgetItem* m_pAutoDJButton; QTreeWidgetItem* m_pEqButton; + QTreeWidgetItem* m_pLV2Button; QTreeWidgetItem* m_pCrossfaderButton; QTreeWidgetItem* m_pRecordingButton; QTreeWidgetItem* m_pBeatDetectionButton; diff --git a/src/dlgpreflv2.cpp b/src/dlgpreflv2.cpp new file mode 100644 index 00000000000..528a3701828 --- /dev/null +++ b/src/dlgpreflv2.cpp @@ -0,0 +1,233 @@ +#include +#include +#include +#include +#include +#include + +#include "dlgpreflv2.h" +#include "engine/enginefilterbessel4.h" +#include "controlobject.h" +#include "util/math.h" + +DlgPrefLV2::DlgPrefLV2(QWidget* pParent, LV2Backend* lv2Backend, + ConfigObject* pConfig) + : DlgPreferencePage(pParent), + m_pLV2Backend(lv2Backend), + m_iCheckedParameters(0), + m_iCheckedButtonParameters(0) { + Q_UNUSED(pConfig); + + setupUi(this); + + if (!m_pLV2Backend) { + return; + } + + QList allPlugins = m_pLV2Backend->getDiscoveredPluginIds().toList(); + // Display them alphabetically + qSort(allPlugins.begin(), allPlugins.end()); + + foreach (QString effectId, allPlugins) { + EffectManifest effectManifest = m_pLV2Backend->getManifest(effectId); + LV2Manifest* lv2Manifest = m_pLV2Backend->getLV2Manifest(effectId); + QPushButton* button = new QPushButton(this); + button->setText(effectManifest.name()); + + if (!m_pLV2Backend->canInstantiateEffect(effectId)) { + // Tooltip displaying why this effect is disabled + LV2Manifest::Status status = lv2Manifest->getStatus(); + switch (status) { + case LV2Manifest::IO_NOT_STEREO: + button->setToolTip(QObject::tr("This plugin does not support " + "stereo samples as input/output")); + break; + case LV2Manifest::HAS_REQUIRED_FEATURES: + button->setToolTip(QObject::tr("This plugin has features " + "which are not yet supported")); + break; + default: + button->setToolTip(QObject::tr("Unknown status")); + } + button->setDisabled(true); + } else { + button->setDisabled(false); + } + + lv2_vertical_layout_left->addWidget(button); + button->setProperty("id", QVariant(effectManifest.id())); + connect(button, SIGNAL(clicked()), this, SLOT(slotDisplayParameters())); + connect(button, SIGNAL(clicked()), this, SLOT(slotDisplayButtonParameters())); + } +} + +DlgPrefLV2::~DlgPrefLV2() { +} + +void DlgPrefLV2::slotDisplayParameters() { + // Set the number of checked parameters to 0 because new parameters are + // displayed + + // Clear the right vertical layout + foreach (QCheckBox* box, m_pluginParameters) { + delete box; + } + m_pluginParameters.clear(); + + QLayoutItem* item; + while ((item = lv2_vertical_layout_params->takeAt(1)) != 0) { + lv2_vertical_layout_params->removeWidget(item->widget()); + delete item->widget(); + } + + QPushButton* button = qobject_cast(sender()); + QString pluginId = button->property("id").toString(); + m_currentEffectId = pluginId; + + EffectManifest& currentEffectManifest = m_pLV2Backend->getManifestReference(pluginId); + QList parameterList = currentEffectManifest.parameters(); + int parameterListSize = parameterList.size(); + + QHash isActive; + for (int i = 0; i < 8 && i < parameterListSize; i++) { + isActive[currentEffectManifest.getActiveParameter(i)] = true; + } + + for (int i = 0; i < parameterListSize; i++) { + QCheckBox* entry = new QCheckBox(this); + entry->setText(parameterList[i].name()); + if (isActive[i]) { + entry->setChecked(true); + } else { + entry->setChecked(false); + entry->setEnabled(false); + } + lv2_vertical_layout_params->addWidget(entry); + m_pluginParameters.append(entry); + connect(entry, SIGNAL(stateChanged(int)), + this, SLOT(slotUpdateOnParameterCheck(int))); + } + lv2_vertical_layout_params->addStretch(); + + m_iCheckedParameters = parameterListSize < 8 ? parameterListSize : 8; +} + +void DlgPrefLV2::slotApply() { + EffectManifest& currentEffectManifest = m_pLV2Backend->getManifestReference(m_currentEffectId); + // It displays the first 8 checked parameters + int visible = 0; + int hidden = m_iCheckedParameters; + for (int i = 0; i < m_pluginParameters.size(); i++) { + if (m_pluginParameters[i]->isChecked()) { + currentEffectManifest.setActiveParameter(visible, i); + visible++; + } else { + currentEffectManifest.setActiveParameter(hidden, i); + hidden++; + } + } + + visible = 0; + hidden = m_iCheckedButtonParameters; + for (int i = 0; i < m_pluginButtonParameters.size(); i++) { + if (m_pluginButtonParameters[i]->isChecked()) { + currentEffectManifest.setActiveButtonParameter(visible, i); + visible++; + } else { + currentEffectManifest.setActiveButtonParameter(hidden, i); + hidden++; + } + } +} + +void DlgPrefLV2::slotUpdateOnParameterCheck(int state) { + if (state == Qt::Checked) { + m_iCheckedParameters++; + } else { + m_iCheckedParameters--; + } + + // If 8 parameters are already checked, disable all other checkboxes + if (m_iCheckedParameters >= 8) { + foreach (QCheckBox* box, m_pluginParameters) { + if (!box->isChecked()) { + box->setEnabled(false); + } + } + } else { + foreach (QCheckBox* box, m_pluginParameters) { + if (!box->isChecked()) { + box->setEnabled(true); + } + } + } +} + +void DlgPrefLV2::slotDisplayButtonParameters() { + // Clear the right vertical layout + foreach (QCheckBox* box, m_pluginButtonParameters) { + delete box; + } + m_pluginButtonParameters.clear(); + + QLayoutItem* item; + while ((item = lv2_vertical_layout_button_params->takeAt(1)) != 0) { + lv2_vertical_layout_button_params->removeWidget(item->widget()); + delete item->widget(); + } + + QPushButton* button = qobject_cast(sender()); + QString pluginId = button->property("id").toString(); + m_currentEffectId = pluginId; + + EffectManifest& currentEffectManifest = m_pLV2Backend->getManifestReference(pluginId); + QList buttonParameterList = currentEffectManifest.buttonParameters(); + int buttonParameterListSize = buttonParameterList.size(); + + QHash isActive; + for (int i = 0; i < 8 && i < buttonParameterListSize; i++) { + isActive[currentEffectManifest.getActiveButtonParameter(i)] = true; + } + + for (int i = 0; i < buttonParameterListSize; i++) { + QCheckBox* entry = new QCheckBox(this); + entry->setText(buttonParameterList[i].name()); + if (isActive[i]) { + entry->setChecked(true); + } else { + entry->setChecked(false); + entry->setEnabled(false); + } + lv2_vertical_layout_button_params->addWidget(entry); + m_pluginButtonParameters.append(entry); + connect(entry, SIGNAL(stateChanged(int)), + this, SLOT(slotUpdateOnButtonParameterCheck(int))); + } + lv2_vertical_layout_button_params->addStretch(); + + m_iCheckedButtonParameters = buttonParameterListSize < 8 ? + buttonParameterListSize : 8; +} + +void DlgPrefLV2::slotUpdateOnButtonParameterCheck(int state) { + if (state == Qt::Checked) { + m_iCheckedButtonParameters++; + } else { + m_iCheckedButtonParameters--; + } + + // If 8 parameters are already checked, disable all other checkboxes + if (m_iCheckedButtonParameters >= 8) { + foreach (QCheckBox* box, m_pluginButtonParameters) { + if (!box->isChecked()) { + box->setEnabled(false); + } + } + } else { + foreach (QCheckBox* box, m_pluginButtonParameters) { + if (!box->isChecked()) { + box->setEnabled(true); + } + } + } +} diff --git a/src/dlgpreflv2.h b/src/dlgpreflv2.h new file mode 100644 index 00000000000..1de90660606 --- /dev/null +++ b/src/dlgpreflv2.h @@ -0,0 +1,37 @@ +#ifndef DLGPREFLV2_H +#define DLGPREFLV2_H + +#include +#include + +#include "ui_dlgpreflv2dlg.h" +#include "configobject.h" +#include "preferences/dlgpreferencepage.h" +#include "effects/lv2/lv2backend.h" + +class DlgPrefLV2 : public DlgPreferencePage, public Ui::DlgPrefLV2Dlg { + Q_OBJECT + public: + DlgPrefLV2(QWidget *parent, LV2Backend* lv2Backend, + ConfigObject* _config); + virtual ~DlgPrefLV2(); + + public slots: + void slotApply(); + + private slots: + void slotDisplayParameters(); + void slotUpdateOnParameterCheck(int state); + void slotDisplayButtonParameters(); + void slotUpdateOnButtonParameterCheck(int state); + + private: + LV2Backend* m_pLV2Backend; + QString m_currentEffectId; + QList m_pluginParameters; + int m_iCheckedParameters; + QList m_pluginButtonParameters; + int m_iCheckedButtonParameters; +}; + +#endif diff --git a/src/dlgpreflv2dlg.ui b/src/dlgpreflv2dlg.ui new file mode 100644 index 00000000000..2aa1632593b --- /dev/null +++ b/src/dlgpreflv2dlg.ui @@ -0,0 +1,112 @@ + + + DlgPrefLV2Dlg + + + + 0 + 0 + 529 + 434 + + + + Equalizer Preferences + + + + + + + + + + + 75 + true + + + + Discovered LV2 effects + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Maximum + + + + 40 + 20 + + + + + + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Parameters + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + + + + + 75 + true + + + + Button Parameters + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + + + + + + diff --git a/src/effects/effect.cpp b/src/effects/effect.cpp index ba7c514efed..7fb50ac6662 100644 --- a/src/effects/effect.cpp +++ b/src/effects/effect.cpp @@ -159,16 +159,22 @@ EffectParameter* Effect::getButtonParameterById(const QString& id) const { return pParameter; } -EffectParameter* Effect::getParameter(unsigned int parameterNumber) { +EffectParameter* Effect::getParameterForSlot(unsigned int slotNumber) { // It's normal to ask for a parameter that doesn't exist. Callers must check // for NULL. - return m_parameters.value(parameterNumber, NULL); + if (static_cast(m_parameters.size()) <= slotNumber) { + return NULL; + } + return m_parameters.value(m_manifest.getActiveParameter(slotNumber)); } -EffectParameter* Effect::getButtonParameter(unsigned int parameterNumber) { +EffectParameter* Effect::getButtonParameterForSlot(unsigned int slotNumber) { // It's normal to ask for a parameter that doesn't exist. Callers must check // for NULL. - return m_buttonParameters.value(parameterNumber, NULL); + if (static_cast(m_buttonParameters.size()) <= slotNumber) { + return NULL; + } + return m_buttonParameters.value(m_manifest.getActiveButtonParameter(slotNumber)); } QDomElement Effect::toXML(QDomDocument* doc) const { diff --git a/src/effects/effect.h b/src/effects/effect.h index 3184cfcba62..6400e2c1578 100644 --- a/src/effects/effect.h +++ b/src/effects/effect.h @@ -33,8 +33,8 @@ class Effect : public QObject { unsigned int numParameters() const; unsigned int numButtonParameters() const; - EffectParameter* getParameter(unsigned int parameterNumber); - EffectParameter* getButtonParameter(unsigned int parameterNumber); + EffectParameter* getParameterForSlot(unsigned int slotNumber); + EffectParameter* getButtonParameterForSlot(unsigned int slotNumber); EffectParameter* getParameterById(const QString& id) const; EffectParameter* getButtonParameterById(const QString& id) const; diff --git a/src/effects/effectbuttonparameterslot.cpp b/src/effects/effectbuttonparameterslot.cpp index 56c5b44b907..8285e7aed2a 100644 --- a/src/effects/effectbuttonparameterslot.cpp +++ b/src/effects/effectbuttonparameterslot.cpp @@ -8,10 +8,10 @@ EffectButtonParameterSlot::EffectButtonParameterSlot(const unsigned int iRackNumber, const unsigned int iChainNumber, const unsigned int iSlotNumber, - const unsigned int iParameterNumber) + const unsigned int iParameterSlotNumber) : EffectParameterSlotBase(iRackNumber, iChainNumber, iSlotNumber, - iParameterNumber) { - QString itemPrefix = formatItemPrefix(iParameterNumber); + iParameterSlotNumber) { + QString itemPrefix = formatItemPrefix(iParameterSlotNumber); m_pControlLoaded = new ControlObject( ConfigKey(m_group, itemPrefix + QString("_loaded"))); m_pControlLinkType = new ControlPushButton( @@ -49,9 +49,12 @@ void EffectButtonParameterSlot::loadEffect(EffectPointer pEffect) { if (pEffect) { m_pEffect = pEffect; // Returns null if it doesn't have a parameter for that number - m_pEffectParameter = pEffect->getButtonParameter(m_iParameterNumber); + m_pEffectParameter = pEffect->getButtonParameterForSlot(m_iParameterSlotNumber); if (m_pEffectParameter) { + // Set the number of states + int numStates = m_pEffectParameter->manifest().getOptions().size(); + m_pControlValue->setStates(numStates); //qDebug() << debugString() << "Loading effect parameter" << m_pEffectParameter->name(); double dValue = m_pEffectParameter->getValue().toDouble(); double dMinimum = m_pEffectParameter->getMinimum().toDouble(); @@ -111,6 +114,13 @@ void EffectButtonParameterSlot::slotParameterValueChanged(QVariant value) { m_pControlValue->set(value.toDouble()); } +void EffectButtonParameterSlot::slotValueChanged(double v) { + if (m_pEffectParameter) { + // Call setValue with type 11 (SET_PARAMETER_BUTTON_PARAMETERS) + m_pEffectParameter->setValue(v, 11); + } +} + void EffectButtonParameterSlot::onChainParameterChanged(double parameter) { m_dChainParameter = parameter; if (m_pEffectParameter != NULL) { diff --git a/src/effects/effectbuttonparameterslot.h b/src/effects/effectbuttonparameterslot.h index 258a6017e4c..928d69ea9f0 100644 --- a/src/effects/effectbuttonparameterslot.h +++ b/src/effects/effectbuttonparameterslot.h @@ -22,11 +22,11 @@ class EffectButtonParameterSlot : public EffectParameterSlotBase { EffectButtonParameterSlot(const unsigned int iRackNumber, const unsigned int iChainNumber, const unsigned int iSlotNumber, - const unsigned int iParameterNumber); + const unsigned int iParameterSlotNumber); virtual ~EffectButtonParameterSlot(); - static QString formatItemPrefix(const unsigned int iParameterNumber) { - return QString("button_parameter%1").arg(iParameterNumber + 1); + static QString formatItemPrefix(const unsigned int iParameterSlotNumber) { + return QString("button_parameter%1").arg(iParameterSlotNumber + 1); } // Load the parameter of the given effect into this EffectButtonParameterSlot @@ -37,10 +37,11 @@ class EffectButtonParameterSlot : public EffectParameterSlotBase { private slots: // Solely for handling control changes void slotParameterValueChanged(QVariant value); + void slotValueChanged(double v); private: QString debugString() const { - return QString("EffectButtonParameterSlot(%1,%2)").arg(m_group).arg(m_iParameterNumber); + return QString("EffectButtonParameterSlot(%1,%2)").arg(m_group).arg(m_iParameterSlotNumber); } // Clear the currently loaded effect diff --git a/src/effects/effectinstantiator.h b/src/effects/effectinstantiator.h index f6813109265..39a19b6386b 100644 --- a/src/effects/effectinstantiator.h +++ b/src/effects/effectinstantiator.h @@ -5,6 +5,8 @@ #include "effects/effectmanifest.h" #include "effects/effectprocessor.h" +#include "effects/lv2/lv2effectprocessor.h" +#include class EngineEffect; @@ -25,4 +27,25 @@ class EffectProcessorInstantiator : public EffectInstantiator { } }; +class LV2EffectProcessorInstantiator : public EffectInstantiator { + public: + LV2EffectProcessorInstantiator(const LilvPlugin* plugin, + QList audioPortIndices, + QList controlPortIndices) + : m_pPlugin(plugin), + audioPortIndices(audioPortIndices), + controlPortIndices(controlPortIndices) { } + + EffectProcessor* instantiate(EngineEffect* pEngineEffect, + const EffectManifest& manifest) { + return new LV2EffectProcessor(pEngineEffect, manifest, m_pPlugin, + audioPortIndices, controlPortIndices); + } + private: + const LilvPlugin* m_pPlugin; + QList audioPortIndices; + QList controlPortIndices; + +}; + #endif /* EFFECTINSTANTIATOR_H */ diff --git a/src/effects/effectmanifest.h b/src/effects/effectmanifest.h index dace34d0192..93d90ad28fd 100644 --- a/src/effects/effectmanifest.h +++ b/src/effects/effectmanifest.h @@ -21,7 +21,9 @@ // example, a database-backed manifest) class EffectManifest { public: - EffectManifest() { } + EffectManifest() { + } + virtual ~EffectManifest() { //qDebug() << debugString() << "deleted"; } @@ -65,8 +67,8 @@ class EffectManifest { return m_parameters; } - virtual EffectManifestParameter* addParameter() { + m_activeParameters.append(m_parameters.size()); m_parameters.append(EffectManifestParameter()); return &m_parameters.last(); } @@ -76,10 +78,27 @@ class EffectManifest { } virtual EffectManifestParameter* addButtonParameter() { + m_activeButtonParameters.append(m_buttonParameters.size()); m_buttonParameters.append(EffectManifestParameter()); return &m_buttonParameters.last(); } + virtual void setActiveParameter(int index, unsigned int value) { + m_activeParameters[index] = value; + } + + virtual unsigned int getActiveParameter(int index) const { + return m_activeParameters[index]; + } + + virtual void setActiveButtonParameter(int index, unsigned int value) { + m_activeButtonParameters[index] = value; + } + + virtual unsigned int getActiveButtonParameter(int index) const { + return m_activeButtonParameters[index]; + } + private: QString debugString() const { return QString("EffectManifest(%1)").arg(m_id); @@ -92,6 +111,13 @@ class EffectManifest { QString m_description; QList m_parameters; QList m_buttonParameters; + + // These two lists store the mapping between the parameter slot and + // the effective parameter which is loaded onto the slot. + // When a manifest is created, this mapping is the identity + // function (list[i] = i) + QList m_activeParameters; + QList m_activeButtonParameters; }; #endif /* EFFECTMANIFEST_H */ diff --git a/src/effects/effectmanifestparameter.h b/src/effects/effectmanifestparameter.h index 5b6a560cfc8..32820167e94 100644 --- a/src/effects/effectmanifestparameter.h +++ b/src/effects/effectmanifestparameter.h @@ -11,7 +11,8 @@ class EffectManifestParameter { VALUE_UNKNOWN = 0, VALUE_BOOLEAN, VALUE_INTEGRAL, - VALUE_FLOAT + VALUE_FLOAT, + VALUE_ENUMERATION }; enum ControlHint { @@ -152,6 +153,13 @@ class EffectManifestParameter { m_maximum = maximum; } + virtual void insertOption(QPair option) { + m_enumerationOptions.append(option); + } + virtual QList > getOptions() const { + return m_enumerationOptions; + } + private: QString debugString() const { return QString("EffectManifestParameter(%1)").arg(m_id); @@ -170,6 +178,11 @@ class EffectManifestParameter { QVariant m_default; QVariant m_minimum; QVariant m_maximum; + + // Useful data for enumeration parameters; each pair has the following form: + // description - value + QList > m_enumerationOptions; + }; QDebug operator<<(QDebug dbg, const EffectManifestParameter& parameter); diff --git a/src/effects/effectparameter.cpp b/src/effects/effectparameter.cpp index 4c0418b4117..d601a6700b0 100644 --- a/src/effects/effectparameter.cpp +++ b/src/effects/effectparameter.cpp @@ -56,6 +56,7 @@ EffectParameter::EffectParameter(Effect* pEffect, EffectsManager* pEffectsManage break; case EffectManifestParameter::VALUE_UNKNOWN: // Treat unknown like float case EffectManifestParameter::VALUE_FLOAT: + case EffectManifestParameter::VALUE_ENUMERATION: m_minimum = m_parameter.hasMinimum() && m_parameter.getMinimum().canConvert() ? m_parameter.getMinimum() : QVariant(0.0f); m_maximum = m_parameter.hasMaximum() && m_parameter.getMinimum().canConvert() ? @@ -132,6 +133,16 @@ bool EffectParameter::clampValue(EffectManifestParameter::ValueHint valueHint, Q return true; } break; + case EffectManifestParameter::VALUE_ENUMERATION: + if (value.toDouble() < minimum.toDouble()) { + value = maximum; + return true; + } else if (value.toDouble() > maximum.toDouble()) { + value = minimum; + return true; + } + break; + default: qWarning() << "ERROR: Unhandled valueHint"; break; @@ -155,6 +166,7 @@ bool EffectParameter::checkType(const QVariant& value) const { return value.canConvert(); case EffectManifestParameter::VALUE_FLOAT: case EffectManifestParameter::VALUE_UNKNOWN: + case EffectManifestParameter::VALUE_ENUMERATION: return value.canConvert(); default: qWarning() << debugString() << "ERROR: Unhandled valueHint"; @@ -215,6 +227,7 @@ void EffectParameter::setValue(QVariant value, int type) { break; case EffectManifestParameter::VALUE_UNKNOWN: // treat unknown as float case EffectManifestParameter::VALUE_FLOAT: + case EffectManifestParameter::VALUE_ENUMERATION: // TODO(XXX) Handle inf, -inf, and nan m_value = value.toDouble(); break; @@ -249,6 +262,7 @@ void EffectParameter::setDefault(QVariant dflt) { break; case EffectManifestParameter::VALUE_UNKNOWN: case EffectManifestParameter::VALUE_FLOAT: + case EffectManifestParameter::VALUE_ENUMERATION: m_default = dflt.toDouble(); break; default: @@ -301,6 +315,7 @@ void EffectParameter::setMinimum(QVariant minimum) { break; case EffectManifestParameter::VALUE_UNKNOWN: case EffectManifestParameter::VALUE_FLOAT: + case EffectManifestParameter::VALUE_ENUMERATION: m_minimum = minimum.toDouble(); if (m_parameter.hasMinimum() && m_minimum.toDouble() < m_parameter.getMinimum().toDouble()) { @@ -376,6 +391,8 @@ void EffectParameter::setMaximum(QVariant maximum) { break; case EffectManifestParameter::VALUE_UNKNOWN: case EffectManifestParameter::VALUE_FLOAT: + case EffectManifestParameter::VALUE_ENUMERATION: + m_maximum = maximum.toDouble(); if (m_parameter.hasMaximum() && m_maximum.toDouble() > m_parameter.getMaximum().toDouble()) { diff --git a/src/effects/effectparameterslot.cpp b/src/effects/effectparameterslot.cpp index ce399fc8794..ccd9e2c8086 100644 --- a/src/effects/effectparameterslot.cpp +++ b/src/effects/effectparameterslot.cpp @@ -9,10 +9,10 @@ EffectParameterSlot::EffectParameterSlot(const unsigned int iRackNumber, const unsigned int iChainNumber, const unsigned int iSlotNumber, - const unsigned int iParameterNumber) + const unsigned int iParameterSlotNumber) : EffectParameterSlotBase(iRackNumber, iChainNumber, iSlotNumber, - iParameterNumber) { - QString itemPrefix = formatItemPrefix(iParameterNumber); + iParameterSlotNumber) { + QString itemPrefix = formatItemPrefix(iParameterSlotNumber); m_pControlLoaded = new ControlObject( ConfigKey(m_group, itemPrefix + QString("_loaded"))); m_pControlLinkType = new ControlPushButton( @@ -52,7 +52,7 @@ void EffectParameterSlot::loadEffect(EffectPointer pEffect) { clear(); if (pEffect) { // Returns null if it doesn't have a parameter for that number - m_pEffectParameter = pEffect->getParameter(m_iParameterNumber); + m_pEffectParameter = pEffect->getParameterForSlot(m_iParameterSlotNumber); if (m_pEffectParameter) { //qDebug() << debugString() << "Loading effect parameter" << m_pEffectParameter->name(); @@ -118,6 +118,12 @@ void EffectParameterSlot::slotParameterValueChanged(QVariant value) { m_pControlValue->set(value.toDouble()); } +void EffectParameterSlot::slotValueChanged(double v) { + if (m_pEffectParameter) { + m_pEffectParameter->setValue(v); + } +} + void EffectParameterSlot::onChainParameterChanged(double parameter) { m_dChainParameter = parameter; if (m_pEffectParameter != NULL) { diff --git a/src/effects/effectparameterslot.h b/src/effects/effectparameterslot.h index 96a070501c2..c738a1df357 100644 --- a/src/effects/effectparameterslot.h +++ b/src/effects/effectparameterslot.h @@ -24,11 +24,11 @@ class EffectParameterSlot : public EffectParameterSlotBase { EffectParameterSlot(const unsigned int iRackNumber, const unsigned int iChainNumber, const unsigned int iSlotNumber, - const unsigned int iParameterNumber); + const unsigned int iParameterSlotNumber); virtual ~EffectParameterSlot(); - static QString formatItemPrefix(const unsigned int iParameterNumber) { - return QString("parameter%1").arg(iParameterNumber + 1); + static QString formatItemPrefix(const unsigned int iParameterSlotNumber) { + return QString("parameter%1").arg(iParameterSlotNumber + 1); } // Load the parameter of the given effect into this EffectParameterSlot @@ -42,10 +42,11 @@ class EffectParameterSlot : public EffectParameterSlotBase { private slots: // Solely for handling control changes void slotParameterValueChanged(QVariant value); + void slotValueChanged(double v); private: QString debugString() const { - return QString("EffectParameterSlot(%1,%2)").arg(m_group).arg(m_iParameterNumber); + return QString("EffectParameterSlot(%1,%2)").arg(m_group).arg(m_iParameterSlotNumber); } // Clear the currently loaded effect diff --git a/src/effects/effectparameterslotbase.cpp b/src/effects/effectparameterslotbase.cpp index 68664b6ff79..24fc60064f3 100644 --- a/src/effects/effectparameterslotbase.cpp +++ b/src/effects/effectparameterslotbase.cpp @@ -8,11 +8,11 @@ EffectParameterSlotBase::EffectParameterSlotBase(const unsigned int iRackNumber, const unsigned int iChainNumber, const unsigned int iSlotNumber, - const unsigned int iParameterNumber) + const unsigned int iParameterSlotNumber) : m_iRackNumber(iRackNumber), m_iChainNumber(iChainNumber), m_iSlotNumber(iSlotNumber), - m_iParameterNumber(iParameterNumber), + m_iParameterSlotNumber(iParameterSlotNumber), m_group(formatGroupString(m_iRackNumber, m_iChainNumber, m_iSlotNumber)), m_pEffectParameter(NULL), @@ -57,15 +57,15 @@ void EffectParameterSlotBase::slotLinkType(double v) { } } -void EffectParameterSlotBase::slotValueChanged(double v) { - //qDebug() << debugString() << "slotValueChanged" << v; - if (m_pEffectParameter) { - m_pEffectParameter->setValue(v); - } -} - void EffectParameterSlotBase::slotValueType(double v) { Q_UNUSED(v); //qDebug() << debugString() << "slotValueType" << v; qWarning() << "WARNING: value_type is a read-only control."; } + +const EffectManifestParameter EffectParameterSlotBase::getManifest() { + if (m_pEffectParameter) { + return m_pEffectParameter->manifest(); + } + return EffectManifestParameter(); +} diff --git a/src/effects/effectparameterslotbase.h b/src/effects/effectparameterslotbase.h index f729f36439e..714e051d49a 100644 --- a/src/effects/effectparameterslotbase.h +++ b/src/effects/effectparameterslotbase.h @@ -21,7 +21,7 @@ class EffectParameterSlotBase : public QObject { EffectParameterSlotBase(const unsigned int iRackNumber, const unsigned int iChainNumber, const unsigned int iSlotNumber, - const unsigned int iParameterNumber); + const unsigned int iParameterSlotNumber); virtual ~EffectParameterSlotBase(); static QString formatGroupString(const unsigned int iRackNumber, @@ -35,6 +35,7 @@ class EffectParameterSlotBase : public QObject { QString name() const; QString description() const; + const EffectManifestParameter getManifest(); signals: // Signal that indicates that the EffectParameterSlotBase has been updated. @@ -44,14 +45,13 @@ class EffectParameterSlotBase : public QObject { // Solely for handling control changes void slotLoaded(double v); void slotLinkType(double v); - void slotValueChanged(double v); void slotValueType(double v); protected: const unsigned int m_iRackNumber; const unsigned int m_iChainNumber; const unsigned int m_iSlotNumber; - const unsigned int m_iParameterNumber; + const unsigned int m_iParameterSlotNumber; QString m_group; EffectPointer m_pEffect; EffectParameter* m_pEffectParameter; diff --git a/src/effects/effectprocessor.h b/src/effects/effectprocessor.h index e19fc6d46cc..ca06f700652 100644 --- a/src/effects/effectprocessor.h +++ b/src/effects/effectprocessor.h @@ -3,6 +3,8 @@ #include #include +#include +#include #include "util/types.h" #include "engine/effects/groupfeaturestate.h" @@ -11,7 +13,8 @@ class EngineEffect; class EffectProcessor { public: - virtual ~EffectProcessor() { } + virtual ~EffectProcessor() { + } virtual void initialize(const QSet& registeredGroups) = 0; diff --git a/src/effects/lv2/lv2backend.cpp b/src/effects/lv2/lv2backend.cpp new file mode 100644 index 00000000000..6b765c778af --- /dev/null +++ b/src/effects/lv2/lv2backend.cpp @@ -0,0 +1,92 @@ +#include "effects/lv2/lv2backend.h" +#include "effects/lv2/lv2manifest.h" + +LV2Backend::LV2Backend(QObject* pParent) + : EffectsBackend(pParent, tr("LV2")) { + m_pWorld = lilv_world_new(); + initializeProperties(); + lilv_world_load_all(m_pWorld); +} + +LV2Backend::~LV2Backend() { + foreach(LilvNode* node, m_properties) { + lilv_node_free(node); + } + lilv_world_free(m_pWorld); + foreach(LV2Manifest* lv2Manifest, m_registeredEffects) { + delete lv2Manifest; + } +} + +void LV2Backend::enumeratePlugins() { + const LilvPlugins *plugs = lilv_world_get_all_plugins(m_pWorld); + LILV_FOREACH(plugins, i, plugs) { + const LilvPlugin *plug = lilv_plugins_get(plugs, i); + LV2Manifest* lv2Manifest = new LV2Manifest(plug, m_properties); + m_registeredEffects.insert(lv2Manifest->getEffectManifest().id(), + lv2Manifest); + } +} + +void LV2Backend::initializeProperties() { + m_properties["audio_port"] = lilv_new_uri(m_pWorld, LV2_CORE__AudioPort); + m_properties["input_port"] = lilv_new_uri(m_pWorld, LV2_CORE__InputPort); + m_properties["output_port"] = lilv_new_uri(m_pWorld, LV2_CORE__OutputPort); + m_properties["control_port"] = lilv_new_uri(m_pWorld, LV2_CORE__ControlPort); + m_properties["button_port"] = lilv_new_uri(m_pWorld, LV2_CORE__toggled); + m_properties["integer_port"] = lilv_new_uri(m_pWorld, LV2_CORE__integer); + m_properties["enumeration_port"] = lilv_new_uri(m_pWorld, LV2_CORE__enumeration); +} + +const QSet LV2Backend::getEffectIds() const { + QSet availableEffects; + foreach (LV2Manifest* lv2Manifest, m_registeredEffects) { + if (lv2Manifest->isValid()) { + availableEffects.insert(lv2Manifest->getEffectManifest().id()); + } + } + return availableEffects; +} + +const QSet LV2Backend::getDiscoveredPluginIds() const { + return m_registeredEffects.keys().toSet(); +} + +bool LV2Backend::canInstantiateEffect(const QString& effectId) const { + if (m_registeredEffects.contains(effectId) && + m_registeredEffects[effectId]->isValid()) { + return true; + } + return false; +} + +EffectManifest LV2Backend::getManifest(const QString& effectId) const { + if (m_registeredEffects.contains(effectId)) { + return m_registeredEffects[effectId]->getEffectManifest(); + } + return EffectManifest(); +} + +LV2Manifest* LV2Backend::getLV2Manifest(const QString& effectId) const { + return m_registeredEffects[effectId]; +} + +EffectManifest& LV2Backend::getManifestReference(const QString& effectId) { + return m_registeredEffects[effectId]->getEffectManifestReference(); +} + +EffectPointer LV2Backend::instantiateEffect(EffectsManager* pEffectsManager, + const QString& effectId) { + if (!canInstantiateEffect(effectId)) { + qWarning() << "WARNING: Effect" << effectId << "is not registered."; + return EffectPointer(); + } + LV2Manifest* lv2manifest = m_registeredEffects[effectId]; + + return EffectPointer(new Effect(this, pEffectsManager, + lv2manifest->getEffectManifest(), + EffectInstantiatorPointer(new + LV2EffectProcessorInstantiator(lv2manifest->getPlugin(), + lv2manifest->getAudioPortIndices(), + lv2manifest->getControlPortIndices())))); +} diff --git a/src/effects/lv2/lv2backend.h b/src/effects/lv2/lv2backend.h new file mode 100644 index 00000000000..633ee0d47f2 --- /dev/null +++ b/src/effects/lv2/lv2backend.h @@ -0,0 +1,35 @@ +#ifndef LV2BACKEND_H +#define LV2BACKEND_H + +#include "effects/effectsbackend.h" +#include "effects/lv2/lv2manifest.h" +#include + +class LV2Backend : public EffectsBackend { + Q_OBJECT + public: + LV2Backend(QObject* pParent=NULL); + virtual ~LV2Backend(); + + void enumeratePlugins(); + const QSet getEffectIds() const; + const QSet getDiscoveredPluginIds() const; + EffectManifest getManifest(const QString& effectId) const; + LV2Manifest* getLV2Manifest(const QString& effectId) const; + EffectManifest& getManifestReference(const QString& effectId); + bool canInstantiateEffect(const QString& effectId) const; + EffectPointer instantiateEffect(EffectsManager* pEffectsManager, + const QString& effectId); + + private: + void initializeProperties(); + LilvWorld* m_pWorld; + QHash m_properties; + QHash m_registeredEffects; + + QString debugString() const { + return "LV2Backend"; + } +}; + +#endif // LV2BACKEND_H diff --git a/src/effects/lv2/lv2effectprocessor.cpp b/src/effects/lv2/lv2effectprocessor.cpp new file mode 100644 index 00000000000..e3fa2468cb3 --- /dev/null +++ b/src/effects/lv2/lv2effectprocessor.cpp @@ -0,0 +1,88 @@ +#include "effects/lv2/lv2effectprocessor.h" +#include "engine/effects/engineeffect.h" + +#define MAX_BUFFER_LEN 160000 + +LV2EffectProcessor::LV2EffectProcessor(EngineEffect* pEngineEffect, + const EffectManifest& manifest, + const LilvPlugin* plugin, + QList audioPortIndices, + QList controlPortIndices) { + // TODO: Use the real sample rate + m_sampleRate = 44100; + inputL = new float[MAX_BUFFER_LEN]; + inputR = new float[MAX_BUFFER_LEN]; + outputL = new float[MAX_BUFFER_LEN]; + outputR = new float[MAX_BUFFER_LEN]; + int totalParams = manifest.parameters().size() + + manifest.buttonParameters().size(); + params = new float[totalParams]; + + handle = lilv_plugin_instantiate(plugin, m_sampleRate, NULL); + const QList effectManifestParameterList = + manifest.parameters(); + const QList effectManifestButtonParameterList = + manifest.buttonParameters(); + + // Initialize EngineEffectParameters + foreach (EffectManifestParameter param, effectManifestParameterList) { + m_parameters.append(pEngineEffect->getParameterById(param.id())); + } + + foreach (EffectManifestParameter param, effectManifestButtonParameterList) { + m_parameters.append(pEngineEffect->getButtonParameterById(param.id())); + } + + for (int i = 0; i < m_parameters.size(); i++) { + params[i] = m_parameters[i]->value().toFloat(); + lilv_instance_connect_port(handle, controlPortIndices[i], ¶ms[i]); + } + + // We assume the audio ports are in the following order: + // input_left, input_right, output_left, output_right + lilv_instance_connect_port(handle, audioPortIndices[0], inputL); + lilv_instance_connect_port(handle, audioPortIndices[1], inputR); + lilv_instance_connect_port(handle, audioPortIndices[2], outputL); + lilv_instance_connect_port(handle, audioPortIndices[3], outputR); + + lilv_instance_activate(handle); +} + +LV2EffectProcessor::~LV2EffectProcessor() { + lilv_instance_deactivate(handle); + lilv_instance_free(handle); +} + +void LV2EffectProcessor::initialize(const QSet& registeredGroups) { + Q_UNUSED(registeredGroups); + +} + +void LV2EffectProcessor::process(const QString& group, + const CSAMPLE* pInput, CSAMPLE* pOutput, + const unsigned int numSamples, + const unsigned int sampleRate, + const GroupFeatureState& groupFeatures) { + Q_UNUSED(groupFeatures); + Q_UNUSED(group); + Q_UNUSED(sampleRate); + for (int i = 0; i < m_parameters.size(); i++) { + params[i] = m_parameters[i]->value().toFloat(); + } + + int j = 0; + for (unsigned int i = 0; i < numSamples; i += 2) { + inputL[j] = pInput[i]; + inputR[j] = pInput[i + 1]; + j++; + } + + lilv_instance_run(handle, numSamples / 2); + + j = 0; + for (unsigned int i = 0; i < numSamples; i += 2) { + pOutput[i] = outputL[j]; + pOutput[i + 1] = outputR[j]; + j++; + } +} diff --git a/src/effects/lv2/lv2effectprocessor.h b/src/effects/lv2/lv2effectprocessor.h new file mode 100644 index 00000000000..94767e4c50f --- /dev/null +++ b/src/effects/lv2/lv2effectprocessor.h @@ -0,0 +1,37 @@ +#ifndef LV2EFFECTPROCESSOR_H +#define LV2EFFECTPROCESSOR_H + +#include "effects/effectprocessor.h" +#include "effects/effectmanifest.h" +#include "engine/effects/engineeffectparameter.h" +#include + +class LV2EffectProcessor : public EffectProcessor { + public: + LV2EffectProcessor(EngineEffect* pEngineEffect, + const EffectManifest& manifest, + const LilvPlugin* plugin, + QList audioPortIndices, + QList controlPortIndices); + ~LV2EffectProcessor(); + + void initialize(const QSet& registeredGroups); + virtual void process(const QString& group, + const CSAMPLE* pInput, CSAMPLE* pOutput, + const unsigned int numSamples, + const unsigned int sampleRate, + const GroupFeatureState& groupFeatures); + private: + QList m_parameters; + float* inputL; + float* inputR; + float* outputL; + float* outputR; + float* params; + LilvInstance* handle; + + int m_sampleRate; +}; + + +#endif // LV2EFFECTPROCESSOR_H diff --git a/src/effects/lv2/lv2manifest.cpp b/src/effects/lv2/lv2manifest.cpp new file mode 100644 index 00000000000..4724e1a61bc --- /dev/null +++ b/src/effects/lv2/lv2manifest.cpp @@ -0,0 +1,207 @@ +#include "effects/lv2/lv2manifest.h" +#include "effects/effectmanifestparameter.h" +#include + +LV2Manifest::LV2Manifest(const LilvPlugin* plug, + QHash& properties) + : m_status(AVAILABLE) { + + m_pLV2plugin = plug; + + // Get and set the ID + const LilvNode* id = lilv_plugin_get_uri(m_pLV2plugin); + m_effectManifest.setId(lilv_node_as_string(id)); + + // Get and set the name + LilvNode* info = lilv_plugin_get_name(m_pLV2plugin); + m_effectManifest.setName(lilv_node_as_string(info)); + lilv_node_free(info); + + // Get and set the author + info = lilv_plugin_get_author_name(m_pLV2plugin); + m_effectManifest.setAuthor(lilv_node_as_string(info)); + lilv_node_free(info); + + int numPorts = lilv_plugin_get_num_ports(plug); + m_minimum = new float[numPorts]; + m_maximum = new float[numPorts]; + m_default = new float[numPorts]; + lilv_plugin_get_port_ranges_float(m_pLV2plugin, m_minimum, m_maximum, + m_default); + + // Counters to determine the type of the plug in + int inputPorts = 0; + int outputPorts = 0; + + for (int i = 0; i < numPorts; i++) { + const LilvPort *port = lilv_plugin_get_port_by_index(plug, i); + + if (lilv_port_is_a(m_pLV2plugin, port, properties["audio_port"])) { + if (lilv_port_is_a(m_pLV2plugin, port, properties["input_port"])) { + audioPortIndices.append(i); + inputPorts++; + info = lilv_port_get_name(m_pLV2plugin, port); + QString paramName = lilv_node_as_string(info); + } else if (lilv_port_is_a(m_pLV2plugin, port, properties["output_port"])) { + audioPortIndices.append(i); + outputPorts++; + info = lilv_port_get_name(m_pLV2plugin, port); + QString paramName = lilv_node_as_string(info); + } + } + + if (lilv_port_is_a(m_pLV2plugin, port, properties["control_port"]) && + !lilv_port_has_property(m_pLV2plugin, port, properties["enumeration_port"]) && + !lilv_port_has_property(m_pLV2plugin, port, properties["button_port"])) { + controlPortIndices.append(i); + EffectManifestParameter* param = m_effectManifest.addParameter(); + + // Get and set the parameter name + info = lilv_port_get_name(m_pLV2plugin, port); + QString paramName = lilv_node_as_string(info); + param->setName(paramName); + lilv_node_free(info); + + // Build and set the parameter id from its name + // Append its index to avoid duplicate ids + param->setId(paramName.trimmed().toLower().replace(' ', '_').append(i + '0')); + param->setSemanticHint(EffectManifestParameter::SEMANTIC_UNKNOWN); + param->setUnitsHint(EffectManifestParameter::UNITS_UNKNOWN); + param->setDefault(m_default[i]); + param->setMinimum(m_minimum[i]); + param->setMaximum(m_maximum[i]); + + // Set the appropriate Hints + if (lilv_port_has_property(m_pLV2plugin, port, properties["button_port"])) { + param->setControlHint(EffectManifestParameter::CONTROL_TOGGLE); + } else { + param->setControlHint(EffectManifestParameter::CONTROL_KNOB_LINEAR); + } + + if (lilv_port_has_property(m_pLV2plugin, port, properties["enumeration_port"])) { + buildEnumerationOptions(port, param); + param->setValueHint(EffectManifestParameter::VALUE_ENUMERATION); + } else if (lilv_port_has_property(m_pLV2plugin, port, properties["integer_port"])) { + param->setValueHint(EffectManifestParameter::VALUE_INTEGRAL); + } else { + param->setValueHint(EffectManifestParameter::VALUE_FLOAT); + } + } + } + + // Hack for putting enum parameters to the end of controlportindices + for (int i = 0; i < numPorts; i++) { + const LilvPort *port = lilv_plugin_get_port_by_index(plug, i); + + if (lilv_port_is_a(m_pLV2plugin, port, properties["control_port"]) && + (lilv_port_has_property(m_pLV2plugin, port, properties["enumeration_port"]) || + lilv_port_has_property(m_pLV2plugin, port, properties["button_port"]))) { + controlPortIndices.append(i); + EffectManifestParameter* param = m_effectManifest.addButtonParameter(); + + // Get and set the parameter name + info = lilv_port_get_name(m_pLV2plugin, port); + QString paramName = lilv_node_as_string(info); + param->setName(paramName); + lilv_node_free(info); + + // Build and set the parameter id from its name + // Append its index to avoid duplicate ids + // Set the appropriate Hints + param->setId(paramName.trimmed().toLower().replace(' ', '_').append(i + '0')); + param->setSemanticHint(EffectManifestParameter::SEMANTIC_UNKNOWN); + param->setUnitsHint(EffectManifestParameter::UNITS_UNKNOWN); + param->setValueHint(EffectManifestParameter::VALUE_ENUMERATION); + if (lilv_port_has_property(m_pLV2plugin, port, properties["enumeration_port"])) { + buildEnumerationOptions(port, param); + } else { + param->insertOption(qMakePair(QString("Inactive"), QVariant(0.0))); + param->insertOption(qMakePair(QString("Active"), QVariant(1.0))); + } + + // Some plugins don't specify minimum, maximum and default values + // In this case set the minimum and default values to 0 and + // the maximum to the number of scale points + if (isnan(m_default[i])) { + param->setDefault(0); + } else { + param->setDefault(m_default[i]); + } + + if (isnan(m_minimum[i])) { + param->setMinimum(0); + } else { + param->setMinimum(m_minimum[i]); + } + + if (isnan(m_maximum[i])) { + param->setMaximum(param->getOptions().size() - 1); + } else { + param->setMaximum(m_maximum[i]); + } + } + } + + // We only support the case when the input and output samples are stereo + if (inputPorts != 2 || outputPorts != 2) { + m_status = IO_NOT_STEREO; + } + + // We don't support any features + LilvNodes* features = lilv_plugin_get_required_features(m_pLV2plugin); + if (lilv_nodes_size(features) > 0) { + m_status = HAS_REQUIRED_FEATURES; + } + lilv_nodes_free(features); +} + +LV2Manifest::~LV2Manifest() { + delete m_minimum; + delete m_maximum; + delete m_default; +} + +EffectManifest LV2Manifest::getEffectManifest() { + return m_effectManifest; +} + +EffectManifest& LV2Manifest::getEffectManifestReference() { + return m_effectManifest; +} + +QList LV2Manifest::getAudioPortIndices() { + return audioPortIndices; +} + +QList LV2Manifest::getControlPortIndices() { + return controlPortIndices; +} + +const LilvPlugin* LV2Manifest::getPlugin() { + return m_pLV2plugin; +} + +LV2Manifest::Status LV2Manifest::getStatus() { + return m_status; +} + +bool LV2Manifest::isValid() { + return m_status == AVAILABLE; +} + +void LV2Manifest::buildEnumerationOptions(const LilvPort* port, + EffectManifestParameter* param) { + LilvScalePoints* options = lilv_port_get_scale_points(m_pLV2plugin, port); + LILV_FOREACH(scale_points, iterator, options) { + const LilvScalePoint* option = lilv_scale_points_get(options, iterator); + const LilvNode* description = lilv_scale_point_get_label(option); + const LilvNode* value = lilv_scale_point_get_value(option); + QString strDescription(lilv_node_as_string(description)); + QVariant varValue(lilv_node_as_float(value)); + param->insertOption(qMakePair(strDescription, varValue)); + } + + if (options != NULL) { + lilv_scale_points_free(options); + } +} diff --git a/src/effects/lv2/lv2manifest.h b/src/effects/lv2/lv2manifest.h new file mode 100644 index 00000000000..79c13639500 --- /dev/null +++ b/src/effects/lv2/lv2manifest.h @@ -0,0 +1,48 @@ +#ifndef LV2MANIFEST_H +#define LV2MANIFEST_H + +#include "effects/effectmanifest.h" +#include + +class LV2Manifest { + public: + enum Status { + AVAILABLE, + IO_NOT_STEREO, + HAS_REQUIRED_FEATURES + }; + + LV2Manifest(const LilvPlugin* plug, QHash& properties); + ~LV2Manifest(); + EffectManifest getEffectManifest(); + EffectManifest& getEffectManifestReference(); + QList getAudioPortIndices(); + QList getControlPortIndices(); + const LilvPlugin* getPlugin(); + bool isValid(); + Status getStatus(); + + private: + void buildEnumerationOptions(const LilvPort* port, + EffectManifestParameter* param); + const LilvPlugin* m_pLV2plugin; + EffectManifest m_effectManifest; + + // This list contains: + // position 0 -> input_left port index + // position 1 -> input_right port index + // position 2 -> output_left port index + // position 3 -> output_right port index + QList audioPortIndices; + // This list contains the control port indices + QList controlPortIndices; + + // Arrays used for storing minimum, maximum and default parameter values + float* m_minimum; + float* m_maximum; + float* m_default; + Status m_status; + bool m_isValid; +}; + +#endif // LV2MANIFEST_H diff --git a/src/mixxx.cpp b/src/mixxx.cpp index 6fb745e738b..2551bb13317 100644 --- a/src/mixxx.cpp +++ b/src/mixxx.cpp @@ -38,6 +38,9 @@ #include "engine/enginemicrophone.h" #include "effects/effectsmanager.h" #include "effects/native/nativebackend.h" +#ifdef __LILV__ +#include "effects/lv2/lv2backend.h" +#endif #include "engine/engineaux.h" #include "library/library.h" #include "library/library_preferences.h" @@ -143,6 +146,13 @@ MixxxMainWindow::MixxxMainWindow(QApplication* pApp, const CmdlineArgs& args) // effect backends to refer to controls that are produced by the engine. NativeBackend* pNativeBackend = new NativeBackend(m_pEffectsManager); m_pEffectsManager->addEffectsBackend(pNativeBackend); +#ifdef __LILV__ + LV2Backend* pLV2Backend = new LV2Backend(m_pEffectsManager); + m_pEffectsManager->addEffectsBackend(pLV2Backend); + pLV2Backend->enumeratePlugins(); +#else + LV2Backend* pLV2Backend = 0; +#endif // Sets up the default EffectChains and EffectRack. m_pEffectsManager->setupDefaults(); @@ -329,7 +339,8 @@ MixxxMainWindow::MixxxMainWindow(QApplication* pApp, const CmdlineArgs& args) // Initialize preference dialog m_pPrefDlg = new DlgPreferences(this, m_pSkinLoader, m_pSoundManager, m_pPlayerManager, - m_pControllerManager, m_pVCManager, m_pConfig, m_pLibrary); + m_pControllerManager, m_pVCManager, pLV2Backend, + m_pConfig, m_pLibrary); m_pPrefDlg->setWindowIcon(QIcon(":/images/ic_mixxx_window.png")); m_pPrefDlg->setHidden(true); diff --git a/src/skin/legacyskinparser.cpp b/src/skin/legacyskinparser.cpp index 35966d2f5d5..f1bca6fab94 100644 --- a/src/skin/legacyskinparser.cpp +++ b/src/skin/legacyskinparser.cpp @@ -36,6 +36,7 @@ #include "widget/wknobcomposed.h" #include "widget/wslidercomposed.h" #include "widget/wpushbutton.h" +#include "widget/weffectpushbutton.h" #include "widget/wdisplay.h" #include "widget/wvumeter.h" #include "widget/wstatuslight.h" @@ -404,6 +405,8 @@ QList LegacySkinParser::parseNode(QDomElement node) { result = wrapWidget(parseStandardWidget(node)); } else if (nodeName == "PushButton") { result = wrapWidget(parseStandardWidget(node)); + } else if (nodeName == "EffectPushButton") { + result = wrapWidget(parseEffectPushButton(node)); } else if (nodeName == "ComboBox") { result = wrapWidget(parseStandardWidget(node)); } else if (nodeName == "Overview") { @@ -1275,6 +1278,19 @@ QWidget* LegacySkinParser::parseEffectName(QDomElement node) { return pEffect; } +QWidget* LegacySkinParser::parseEffectPushButton(QDomElement element) { + WEffectPushButton* pWidget = new WEffectPushButton(m_pParent, m_pEffectsManager); + setupConnections(element, pWidget); + setupBaseWidget(element, pWidget); + setupWidget(element, pWidget); + pWidget->setup(element, *m_pContext); + pWidget->installEventFilter(m_pKeyboard); + pWidget->installEventFilter( + m_pControllerManager->getControllerLearningEventFilter()); + pWidget->Init(); + return pWidget; +} + QWidget* LegacySkinParser::parseEffectParameterName(QDomElement node) { WEffectParameterBase* pEffectParameter = new WEffectParameter(m_pParent, m_pEffectsManager); setupLabelWidget(node, pEffectParameter); diff --git a/src/skin/legacyskinparser.h b/src/skin/legacyskinparser.h index 94cbc935e3e..5c34c12761b 100644 --- a/src/skin/legacyskinparser.h +++ b/src/skin/legacyskinparser.h @@ -76,6 +76,7 @@ class LegacySkinParser : public QObject, public SkinParser { QWidget* parseEffectName(QDomElement node); QWidget* parseEffectParameterName(QDomElement node); QWidget* parseEffectButtonParameterName(QDomElement node); + QWidget* parseEffectPushButton(QDomElement node); // Legacy pre-1.12.0 skin support. QWidget* parseBackground(QDomElement node, QWidget* pOuterWidget, QWidget* pInnerWidget); diff --git a/src/widget/weffectpushbutton.cpp b/src/widget/weffectpushbutton.cpp new file mode 100644 index 00000000000..c49cdd7ae56 --- /dev/null +++ b/src/widget/weffectpushbutton.cpp @@ -0,0 +1,498 @@ +#include "widget/weffectpushbutton.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "widget/wpixmapstore.h" +#include "controlobject.h" +#include "controlpushbutton.h" +#include "control/controlbehavior.h" +#include "util/debug.h" + +WEffectPushButton::WEffectPushButton(QWidget* pParent, EffectsManager* pEffectsManager) + : WWidget(pParent), + m_leftButtonMode(ControlPushButton::PUSH), + m_rightButtonMode(ControlPushButton::PUSH), + m_pEffectsManager(pEffectsManager) { + setStates(0); +} + +WEffectPushButton::WEffectPushButton(QWidget* pParent, ControlPushButton::ButtonMode leftButtonMode, + ControlPushButton::ButtonMode rightButtonMode) + : WWidget(pParent), + m_leftButtonMode(leftButtonMode), + m_rightButtonMode(rightButtonMode) { + setStates(0); +} + +WEffectPushButton::~WEffectPushButton() { +} + +void WEffectPushButton::setup(QDomNode node, const SkinContext& context) { + // Number of states + int iNumStates = context.selectInt(node, "NumberStates"); + setStates(iNumStates); + + // Set background pixmap if available + if (context.hasNode(node, "BackPath")) { + QString mode_str = context.selectAttributeString( + context.selectElement(node, "BackPath"), "scalemode", "TILE"); + QString backPath = context.getPixmapPath(context.selectNode(node, "BackPath")); + if (!backPath.isEmpty()) { + setPixmapBackground(backPath, Paintable::DrawModeFromString(mode_str)); + } + } + + // Load pixmaps for associated states + QDomNode state = context.selectNode(node, "State"); + while (!state.isNull()) { + if (state.isElement() && state.nodeName() == "State") { + int iState = context.selectInt(state, "Number"); + if (iState < m_iNoStates) { + QString pixmapPath; + + pixmapPath = context.getPixmapPath(context.selectNode(state, "Unpressed")); + if (!pixmapPath.isEmpty()) { + setPixmap(iState, false, pixmapPath); + } + + pixmapPath = context.getPixmapPath(context.selectNode(state, "Pressed")); + if (!pixmapPath.isEmpty()) { + setPixmap(iState, true, pixmapPath); + } + + m_text.replace(iState, context.selectString(state, "Text")); + QString alignment = context.selectString(state, "Alignment"); + if (alignment == "left") { + m_align.replace(iState, Qt::AlignLeft); + } else if (alignment == "right") { + m_align.replace(iState, Qt::AlignRight); + } else { + // Default is center. + m_align.replace(iState, Qt::AlignCenter); + } + } + } + state = state.nextSibling(); + } + + ControlParameterWidgetConnection* leftConnection = NULL; + if (m_leftConnections.isEmpty()) { + if (!m_connections.isEmpty()) { + // If no left connection is set, the this is the left connection + leftConnection = m_connections.at(0); + } + } else { + leftConnection = m_leftConnections.at(0); + } + + if (leftConnection) { + bool leftClickForcePush = context.selectBool(node, "LeftClickIsPushButton", false); + m_leftButtonMode = ControlPushButton::PUSH; + if (!leftClickForcePush) { + const ConfigKey& configKey = leftConnection->getKey(); + ControlPushButton* p = dynamic_cast( + ControlObject::getControl(configKey)); + if (p) { + m_leftButtonMode = p->getButtonMode(); + } + } + if (leftConnection->getEmitOption() & + ControlParameterWidgetConnection::EMIT_DEFAULT) { + switch (m_leftButtonMode) { + case ControlPushButton::PUSH: + case ControlPushButton::LONGPRESSLATCHING: + case ControlPushButton::POWERWINDOW: + leftConnection->setEmitOption( + ControlParameterWidgetConnection::EMIT_ON_PRESS_AND_RELEASE); + break; + default: + leftConnection->setEmitOption( + ControlParameterWidgetConnection::EMIT_ON_PRESS); + break; + } + } + if (leftConnection->getDirectionOption() & + ControlParameterWidgetConnection::DIR_DEFAULT) { + if (m_pDisplayConnection == leftConnection) { + leftConnection->setDirectionOption(ControlParameterWidgetConnection::DIR_FROM_AND_TO_WIDGET); + } else { + leftConnection->setDirectionOption(ControlParameterWidgetConnection::DIR_FROM_WIDGET); + if (m_pDisplayConnection->getDirectionOption() & + ControlParameterWidgetConnection::DIR_DEFAULT) { + m_pDisplayConnection->setDirectionOption(ControlParameterWidgetConnection::DIR_TO_WIDGET); + } + } + } + } + + if (!m_rightConnections.isEmpty()) { + ControlParameterWidgetConnection* rightConnection = m_rightConnections.at(0); + bool rightClickForcePush = context.selectBool(node, "RightClickIsPushButton", false); + m_rightButtonMode = ControlPushButton::PUSH; + if (!rightClickForcePush) { + const ConfigKey configKey = rightConnection->getKey(); + ControlPushButton* p = dynamic_cast( + ControlObject::getControl(configKey)); + if (p) { + m_rightButtonMode = p->getButtonMode(); + if (m_rightButtonMode != ControlPushButton::PUSH) { + qWarning() + << "WEffectPushButton::setup: Connecting a Pushbutton not in PUSH mode is not implemented\n" + << "Please set true"; + } + } + } + if (rightConnection->getEmitOption() & + ControlParameterWidgetConnection::EMIT_DEFAULT) { + switch (m_rightButtonMode) { + case ControlPushButton::PUSH: + case ControlPushButton::LONGPRESSLATCHING: + case ControlPushButton::POWERWINDOW: + leftConnection->setEmitOption( + ControlParameterWidgetConnection::EMIT_ON_PRESS_AND_RELEASE); + break; + default: + leftConnection->setEmitOption( + ControlParameterWidgetConnection::EMIT_ON_PRESS); + break; + } + } + if (rightConnection->getDirectionOption() & + ControlParameterWidgetConnection::DIR_DEFAULT) { + rightConnection->setDirectionOption(ControlParameterWidgetConnection::DIR_FROM_WIDGET); + } + } + + m_pButtonMenu = new QMenu(this); + connect(m_pButtonMenu, SIGNAL(triggered(QAction*)), + this, SLOT(slotActionChosen(QAction*))); + bool rackOk = false; + int rackNumber = context.selectInt(node, "EffectRack", &rackOk) - 1; + bool chainOk = false; + int chainNumber = context.selectInt(node, "EffectUnit", &chainOk) - 1; + bool effectOk = false; + int effectNumber = context.selectInt(node, "Effect", &effectOk) - 1; + bool parameterOk = false; + int parameterNumber = context.selectInt(node, "EffectButtonParameter", ¶meterOk) - 1; + + // Tolerate no . Use the default one. + if (!rackOk) { + rackNumber = 0; + } + + if (!chainOk) { + qDebug() << "EffectPushButton node had invalid EffectUnit number:" << chainNumber; + } + + if (!effectOk) { + qDebug() << "EffectPushButton node had invalid Effect number:" << effectNumber; + } + + if (!parameterOk) { + qDebug() << "EffectPushButton node had invalid ButtonParameter number:" << parameterNumber; + } + + EffectRackPointer pRack = m_pEffectsManager->getEffectRack(rackNumber); + if (pRack) { + EffectChainSlotPointer pChainSlot = pRack->getEffectChainSlot(chainNumber); + if (pChainSlot) { + EffectSlotPointer pEffectSlot = pChainSlot->getEffectSlot(effectNumber); + if (pEffectSlot) { + EffectParameterSlotBasePointer pParameterSlot = + pEffectSlot->getEffectButtonParameterSlot(parameterNumber); + if (pParameterSlot) { + m_pEffectParameterSlot = pParameterSlot; + connect(pParameterSlot.data(), SIGNAL(updated()), + this, SLOT(parameterUpdated())); + parameterUpdated(); + } else { + qDebug() << "EffectPushButton node had invalid ButtonParameter number:" << parameterNumber; + } + } else { + qDebug() << "EffectPushButton node had invalid Effect number:" << effectNumber; + } + } else { + qDebug() << "EffectPushButton node had invalid EffectUnit number:" << chainNumber; + } + } else { + qDebug() << "EffectPushButton node had invalid EffectRack number:" << rackNumber; + } +} + +void WEffectPushButton::setStates(int iStates) { + m_bPressed = false; + m_iNoStates = iStates; + m_activeTouchButton = Qt::NoButton; + + m_pressedPixmaps.resize(iStates); + m_unpressedPixmaps.resize(iStates); + m_text.resize(iStates); + m_align.resize(iStates); +} + +void WEffectPushButton::setPixmap(int iState, bool bPressed, const QString& filename) { + QVector& pixmaps = bPressed ? + m_pressedPixmaps : m_unpressedPixmaps; + + if (iState < 0 || iState >= pixmaps.size()) { + return; + } + + PaintablePointer pPixmap = WPixmapStore::getPaintable(filename, + Paintable::STRETCH); + + if (pPixmap.isNull() || pPixmap->isNull()) { + // Only log if it looks like the user tried to specify a pixmap. + if (!filename.isEmpty()) { + qDebug() << "WEffectPushButton: Error loading pixmap:" << filename; + } + } else { + // Set size of widget equal to pixmap size + setFixedSize(pPixmap->size()); + } + pixmaps.replace(iState, pPixmap); +} + +void WEffectPushButton::setPixmapBackground(const QString &filename, + Paintable::DrawMode mode) { + // Load background pixmap + m_pPixmapBack = WPixmapStore::getPaintable(filename, mode); + if (!filename.isEmpty() && + (m_pPixmapBack.isNull() || m_pPixmapBack->isNull())) { + // Only log if it looks like the user tried to specify a pixmap. + qDebug() << "WEffectPushButton: Error loading background pixmap:" << filename; + } +} + +void WEffectPushButton::onConnectedControlChanged(double dParameter, double dValue) { + Q_UNUSED(dParameter); + // Enums are not currently represented using parameter space so it doesn't + // make sense to use the parameter here yet. + if (m_iNoStates == 1) { + m_bPressed = (dValue == 1.0); + } + + double value = getControlParameterDisplay(); + int idx = static_cast(value) % m_iNoStates; + setProperty("displayValue", idx); + // According to http://stackoverflow.com/a/3822243 this is the least + // expensive way to restyle just this widget. + // Since we expect button connections to not change at high frequency we + // don't try to detect whether things have changed for WEffectPushButton, we just + // re-render. + style()->unpolish(this); + style()->polish(this); + // These calls don't always trigger the repaint, so call it explicitly. + repaint(); +} + +void WEffectPushButton::paintEvent(QPaintEvent* e) { + Q_UNUSED(e); + QStyleOption option; + option.initFrom(this); + QStylePainter p(this); + p.drawPrimitive(QStyle::PE_Widget, option); + + if (m_iNoStates == 0) { + return; + } + + if (m_pPixmapBack) { + m_pPixmapBack->draw(0, 0, &p); + } + + const QVector& pixmaps = m_bPressed ? + m_pressedPixmaps : m_unpressedPixmaps; + + + // m_text, m_pressedPixmaps and m_unpressedPixmaps are all the same size (as + // per setup()) so if one is empty, all are empty. + if (pixmaps.isEmpty()) { + return; + } + + int idx = readDisplayValue(); + // Just in case m_iNoStates is somehow different from pixmaps.size(). + if (idx < 0) { + idx = 0; + } else if (idx >= pixmaps.size()) { + idx = pixmaps.size() - 1; + } + + PaintablePointer pPixmap = pixmaps.at(idx); + if (!pPixmap.isNull() && !pPixmap->isNull()) { + pPixmap->draw(0, 0, &p); + } + + QString text = m_text.at(idx); + if (!text.isEmpty()) { + p.drawText(rect(), m_align.at(idx), text); + } +} + +void WEffectPushButton::mousePressEvent(QMouseEvent * e) { + const bool leftClick = e->button() == Qt::LeftButton; + const bool rightClick = e->button() == Qt::RightButton; + + if (m_leftButtonMode == ControlPushButton::POWERWINDOW + && m_iNoStates == 2) { + if (leftClick) { + if (getControlParameterLeft() == 0.0) { + m_clickTimer.setSingleShot(true); + m_clickTimer.start(ControlPushButtonBehavior::kPowerWindowTimeMillis); + } + m_bPressed = true; + setControlParameterLeftDown(1.0); + update(); + } + // discharge right clicks here, because is used for latching in POWERWINDOW mode + return; + } + + if (rightClick) { + m_pButtonMenu->exec(e->globalPos()); + // This is the secondary button function allways a Pushbutton + // due the leak of visual feedback we do not allow a toggle function + if (m_rightButtonMode == ControlPushButton::PUSH + || m_iNoStates == 1) { + m_bPressed = true; + setControlParameterRightDown(1.0); + update(); + } + return; + } + + if (leftClick) { + double emitValue; + if (m_leftButtonMode == ControlPushButton::PUSH + || m_iNoStates == 1) { + // This is either forced to behave like a push button on left-click + // or this is a push button. + emitValue = 1.0; + } else { + // Toggle thru the states + emitValue = static_cast(getControlParameterLeft() + 1.0) % m_iNoStates; + if (m_leftButtonMode == ControlPushButton::LONGPRESSLATCHING) { + m_clickTimer.setSingleShot(true); + m_clickTimer.start(ControlPushButtonBehavior::kLongPressLatchingTimeMillis); + } + + // Check the corresponding QAction + foreach (QAction* action, m_pButtonMenu->actions()) { + if (action->data().toDouble() == emitValue) { + action->setChecked(true); + break; + } + } + } + m_bPressed = true; + setControlParameterLeftDown(emitValue); + update(); + } +} + +void WEffectPushButton::focusOutEvent(QFocusEvent* e) { + Q_UNUSED(e); + if (e->reason() != Qt::MouseFocusReason) { + // Since we support multi touch there is no reason to reset + // the pressed flag if the Primary touch point is moved to an + // other widget + m_bPressed = false; + update(); + } +} + +void WEffectPushButton::mouseReleaseEvent(QMouseEvent * e) { + const bool leftClick = e->button() == Qt::LeftButton; + const bool rightClick = e->button() == Qt::RightButton; + + if (m_leftButtonMode == ControlPushButton::POWERWINDOW + && m_iNoStates == 2) { + if (leftClick) { + const bool rightButtonDown = QApplication::mouseButtons() & Qt::RightButton; + if (m_bPressed && !m_clickTimer.isActive() && !rightButtonDown) { + // Release button after timer, but not if right button is clicked + setControlParameterLeftUp(0.0); + } + m_bPressed = false; + } else if (rightClick) { + m_bPressed = false; + } + update(); + return; + } + + if (rightClick) { + // This is the secondary clickButton function, + // due the leak of visual feedback we do not allow a toggle + // function + if (m_rightButtonMode == ControlPushButton::PUSH + || m_iNoStates == 1) { + m_bPressed = false; + setControlParameterRightUp(0.0); + update(); + } + return; + } + + if (leftClick) { + double emitValue = getControlParameterLeft(); + if (m_leftButtonMode == ControlPushButton::PUSH + || m_iNoStates == 1) { + // This is a Pushbutton + emitValue = 0.0; + } else { + if (m_leftButtonMode == ControlPushButton::LONGPRESSLATCHING + && m_clickTimer.isActive() && emitValue >= 1.0) { + // revert toggle if button is released too early + emitValue = static_cast(emitValue - 1.0) % m_iNoStates; + } else { + // Nothing special happens when releasing a normal toggle button + } + } + m_bPressed = false; + setControlParameterLeftUp(emitValue); + update(); + } +} + +void WEffectPushButton::fillDebugTooltip(QStringList* debug) { + WWidget::fillDebugTooltip(debug); + *debug << QString("NumberStates: %1").arg(m_iNoStates) + << QString("LeftCurrentState: %1").arg( + static_cast(getControlParameterLeft()) % + (m_iNoStates > 0 ? m_iNoStates : 1)) + << QString("Pressed: %1").arg(toDebugString(m_bPressed)) + << QString("LeftButtonMode: %1") + .arg(ControlPushButton::buttonModeToString(m_leftButtonMode)) + << QString("RightButtonMode: %1") + .arg(ControlPushButton::buttonModeToString(m_rightButtonMode)); +} + +void WEffectPushButton::parameterUpdated() { + m_pButtonMenu->clear(); + QList > options = m_pEffectParameterSlot->getManifest().getOptions(); + qDebug() << " HERE IS THE OPTIONS SIZE: " << options.size(); + QActionGroup* actionGroup = new QActionGroup(m_pButtonMenu); + actionGroup->setExclusive(true); + for (int i = 0; i < options.size(); i++) { + // action is added automatically to actionGroup + QAction* action = new QAction(actionGroup); + action->setText(options[i].first); + action->setData(options[i].second); + action->setCheckable(true); + m_pButtonMenu->addAction(action); + } +} + +void WEffectPushButton::slotActionChosen(QAction* action) { + action->setChecked(true); + setControlParameter(action->data().toDouble()); +} diff --git a/src/widget/weffectpushbutton.h b/src/widget/weffectpushbutton.h new file mode 100644 index 00000000000..1a51d6839e3 --- /dev/null +++ b/src/widget/weffectpushbutton.h @@ -0,0 +1,99 @@ +#ifndef WEFFECTPUSHBUTTON_H +#define WEFFECTPUSHBUTTON_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "widget/wwidget.h" +#include "widget/wpixmapstore.h" +#include "controlpushbutton.h" +#include "skin/skincontext.h" +#include "controlwidgetconnection.h" +#include "effects/effectsmanager.h" + +class WEffectPushButton : public WWidget { + Q_OBJECT + public: + WEffectPushButton(QWidget* pParent, EffectsManager* pEffectsManager); + // Used by WEffectPushButtonTest. + WEffectPushButton(QWidget* pParent, ControlPushButton::ButtonMode leftButtonMode, + ControlPushButton::ButtonMode rightButtonMode); + virtual ~WEffectPushButton(); + + Q_PROPERTY(bool pressed READ isPressed); + + bool isPressed() const { + return m_bPressed; + } + + // The displayValue property is used to restyle the pushbutton with CSS. + // The declaration #MyButton[displayValue="0"] { } will define the style + // when the widget is in state 0. This allows for effects like reversing + // background and foreground colors to indicate enabled/disabled state. + Q_PROPERTY(int displayValue READ readDisplayValue) + + int readDisplayValue() const { + double value = getControlParameterDisplay(); + int idx = static_cast(value) % m_iNoStates; + return idx; + } + + void setup(QDomNode node, const SkinContext& context); + + // Sets the number of states associated with this button, and removes + // associated pixmaps. + void setStates(int iStatesW); + + public slots: + void onConnectedControlChanged(double dParameter, double dValue); + + protected: + virtual void paintEvent(QPaintEvent*); + virtual void mousePressEvent(QMouseEvent* e); + virtual void mouseReleaseEvent(QMouseEvent* e); + virtual void focusOutEvent(QFocusEvent* e); + void fillDebugTooltip(QStringList* debug); + + private slots: + void parameterUpdated(); + void slotActionChosen(QAction* action); + + private: + // Associates a pixmap of a given state of the button with the widget + void setPixmap(int iState, bool bPressed, const QString &filename); + + // Associates a background pixmap with the widget. This is only needed if + // the button pixmaps contains alpha channel values. + void setPixmapBackground(const QString &filename, Paintable::DrawMode mode); + + // True, if the button is currently pressed + bool m_bPressed; + + // Array of associated pixmaps + int m_iNoStates; + QVector m_text; + QVector m_pressedPixmaps; + QVector m_unpressedPixmaps; + + // Associated background pixmap + PaintablePointer m_pPixmapBack; + + // short click toggle button long click push button + ControlPushButton::ButtonMode m_leftButtonMode; + ControlPushButton::ButtonMode m_rightButtonMode; + QTimer m_clickTimer; + QVector m_align; + + EffectsManager* m_pEffectsManager; + EffectParameterSlotBasePointer m_pEffectParameterSlot; + QMenu* m_pButtonMenu; +}; + +#endif // WEFFECTPUSHBUTTON_H