diff --git a/plugins/carlabase/carla.cpp b/plugins/carlabase/carla.cpp index 728a6d804af..ce651d86eb5 100644 --- a/plugins/carlabase/carla.cpp +++ b/plugins/carlabase/carla.cpp @@ -26,16 +26,29 @@ #include "Engine.h" #include "Song.h" -#include "gui_templates.h" +#include "GuiApplication.h" #include "InstrumentPlayHandle.h" #include "InstrumentTrack.h" #include "MidiEventToByteSeq.h" +#include "MainWindow.h" #include "Mixer.h" +#include "Song.h" +#include "gui_templates.h" #include +#include #include #include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include #include #include @@ -135,7 +148,8 @@ CarlaInstrument::CarlaInstrument(InstrumentTrack* const instrumentTrack, const D kIsPatchbay(isPatchbay), fHandle(NULL), fDescriptor(isPatchbay ? carla_get_native_patchbay_plugin() : carla_get_native_rack_plugin()), - fMidiEventCount(0) + fMidiEventCount(0), + m_paramModels() { fHost.handle = this; fHost.uiName = NULL; @@ -177,6 +191,24 @@ CarlaInstrument::CarlaInstrument(InstrumentTrack* const instrumentTrack, const D InstrumentPlayHandle * iph = new InstrumentPlayHandle( this, instrumentTrack ); Engine::mixer()->addPlayHandle( iph ); +#if CARLA_VERSION_HEX >= CARLA_MIN_PARAM_VERSION + // text filter completion + m_completerModel = new QStringListModel(this); + m_paramsCompleter = new QCompleter(m_completerModel, this); + m_paramsCompleter->setCaseSensitivity(Qt::CaseInsensitive); + m_paramsCompleter->setCompletionMode(QCompleter::PopupCompletion); + + // Add static amount of CarlaParamFloatModel's. + int paramCount = fDescriptor->get_parameter_count(fHandle); + m_paramModels.reserve(paramCount); + for (int i=0; i < paramCount; ++i) + { + m_paramModels.push_back(new CarlaParamFloatModel(this)); + connect(m_paramModels[i], &CarlaParamFloatModel::dataChanged, + this, [this, i]() {paramModelChanged(i);}, Qt::DirectConnection); + } +#endif + connect(Engine::mixer(), SIGNAL(sampleRateChanged()), this, SLOT(sampleRateChanged())); } @@ -206,6 +238,10 @@ CarlaInstrument::~CarlaInstrument() fDescriptor->cleanup(fHandle); fHandle = NULL; + +#if CARLA_VERSION_HEX >= CARLA_MIN_PARAM_VERSION + clearParamModels(); +#endif } // ------------------------------------------------------------------- @@ -230,8 +266,12 @@ const NativeTimeInfo* CarlaInstrument::handleGetTimeInfo() const return &fTimeInfo; } -void CarlaInstrument::handleUiParameterChanged(const uint32_t /*index*/, const float /*value*/) const +void CarlaInstrument::handleUiParameterChanged(const uint32_t index, const float value) const { + if (m_paramModels.count() > index) + { + m_paramModels[index]->setValue(value); + } } void CarlaInstrument::handleUiClosed() @@ -239,10 +279,24 @@ void CarlaInstrument::handleUiClosed() emit uiClosed(); } -intptr_t CarlaInstrument::handleDispatcher(const NativeHostDispatcherOpcode opcode, const int32_t, const intptr_t, void* const, const float) +intptr_t CarlaInstrument::handleDispatcher(const NativeHostDispatcherOpcode opcode, const int32_t index, + const intptr_t value, void* const ptr, const float opt) { intptr_t ret = 0; + // source/includes/CarlaNative.h + // NATIVE_HOST_OPCODE_NULL = 0, nothing + // NATIVE_HOST_OPCODE_UPDATE_PARAMETER = 1, uses index, -1 for all + // NATIVE_HOST_OPCODE_UPDATE_MIDI_PROGRAM = 2, uses index, -1 for all; may use value for channel + // NATIVE_HOST_OPCODE_RELOAD_PARAMETERS = 3, nothing + // NATIVE_HOST_OPCODE_RELOAD_MIDI_PROGRAMS = 4, nothing + // NATIVE_HOST_OPCODE_RELOAD_ALL = 5, nothing + // NATIVE_HOST_OPCODE_UI_UNAVAILABLE = 6, nothing + // NATIVE_HOST_OPCODE_HOST_IDLE = 7, nothing + // NATIVE_HOST_OPCODE_INTERNAL_PLUGIN = 8, nothing + // NATIVE_HOST_OPCODE_QUEUE_INLINE_DISPLAY = 9, nothing + // NATIVE_HOST_OPCODE_UI_TOUCH_PARAMETER = 10 uses index, value as bool + switch (opcode) { case NATIVE_HOST_OPCODE_UI_UNAVAILABLE: @@ -251,6 +305,29 @@ intptr_t CarlaInstrument::handleDispatcher(const NativeHostDispatcherOpcode opco case NATIVE_HOST_OPCODE_HOST_IDLE: qApp->processEvents(); break; +#if CARLA_VERSION_HEX >= CARLA_MIN_PARAM_VERSION + case NATIVE_HOST_OPCODE_UI_TOUCH_PARAMETER: + // param index, value as bool + // true = mousePress + // false = mouseRelease + if (!value) { + updateParamModel(index); + } + break; + case NATIVE_HOST_OPCODE_RELOAD_ALL: + refreshParams(); + break; + case NATIVE_HOST_OPCODE_UPDATE_PARAMETER: + break; + case NATIVE_HOST_OPCODE_RELOAD_PARAMETERS: + refreshParams(); + break; + case NATIVE_HOST_OPCODE_UPDATE_MIDI_PROGRAM: + case NATIVE_HOST_OPCODE_RELOAD_MIDI_PROGRAMS: + case NATIVE_HOST_OPCODE_INTERNAL_PLUGIN: + case NATIVE_HOST_OPCODE_QUEUE_INLINE_DISPLAY: + break; +#endif default: break; } @@ -289,6 +366,115 @@ void CarlaInstrument::saveSettings(QDomDocument& doc, QDomElement& parent) } std::free(state); + +#if CARLA_VERSION_HEX >= CARLA_MIN_PARAM_VERSION + for (uint32_t index = 0; index < m_paramModels.count(); ++index) + { + QString idStr = CARLA_SETTING_PREFIX + QString::number(index); + m_paramModels[index]->saveSettings(doc, parent, idStr); + } +#endif +} + +void CarlaInstrument::refreshParams(bool init) +{ + m_paramGroupCount = 0; + if (fDescriptor->get_parameter_count != nullptr && + fDescriptor->get_parameter_info != nullptr && + fDescriptor->get_parameter_value != nullptr && + fDescriptor->set_parameter_value != nullptr) + { + QList completerData; + QList groups; // used to count no. groups. + + uint32_t paramCount = fDescriptor->get_parameter_count(fHandle); + for (uint32_t i=0; i < paramCount; ++i) + { + const NativeParameter* paramInfo(fDescriptor->get_parameter_info(fHandle, i)); + + m_paramModels[i]->setOutput((paramInfo->hints & NATIVE_PARAMETER_IS_OUTPUT)); + m_paramModels[i]->setEnabled((paramInfo->hints & NATIVE_PARAMETER_IS_ENABLED)); + m_paramModels[i]->setValue(fDescriptor->get_parameter_value(fHandle, i)); + + // Get parameter name + QString name = "_NO_NAME_"; + if (paramInfo->name != nullptr) + { + name = paramInfo->name; + } + + if (paramInfo->groupName != nullptr) + { + m_paramModels[i]->setGroupName(paramInfo->groupName); + + if (m_paramModels[i]->enabled() && !groups.contains(paramInfo->groupName)) + { + groups.push_back(paramInfo->groupName); + m_paramGroupCount++; + } + m_paramModels[i]->setGroupId(groups.indexOf(paramInfo->groupName)); + } + + completerData.push_back(name); + + m_paramModels[i]->setDisplayName(name); + m_paramModels[i]->setRange(paramInfo->ranges.min, + paramInfo->ranges.max, + paramInfo->ranges.step); + + // Load settings into model. + if (init) + { + QString idStr = CARLA_SETTING_PREFIX + QString::number(i); + m_paramModels[i]->loadSettings(m_settingsElem, idStr); + } + } + // Set completer data + m_completerModel->setStringList(completerData); + } + emit paramsUpdated(); +} + +void CarlaInstrument::clearParamModels() +{ + //Delete the models, this also disconnects all connections (automation and controller connections) + for (uint32_t index=0; index < m_paramModels.count(); ++index) + { + delete m_paramModels[index]; + } + + //Clear the list + m_paramModels.clear(); + + m_paramGroupCount = 0; +} + +void CarlaInstrument::paramModelChanged(uint32_t index) +{ // Update Carla param (LMMS -> Carla) + if (!m_paramModels[index]->isOutput()) + { + if (fDescriptor->set_parameter_value != nullptr) + { + fDescriptor->set_parameter_value(fHandle, index, m_paramModels[index]->value()); + + } + + // TODO? Shouldn't Carla be doing this? + if (fDescriptor->ui_set_parameter_value != nullptr) + { + fDescriptor->ui_set_parameter_value(fHandle, index, m_paramModels[index]->value()); + } + } +} + +void CarlaInstrument::updateParamModel(uint32_t index) +{ // Called on param changed (Carla -> LMMS) + if (fDescriptor->get_parameter_value != nullptr) + { + m_paramModels[index]->setValue( + fDescriptor->get_parameter_value(fHandle, index) + ); + } } void CarlaInstrument::loadSettings(const QDomElement& elem) @@ -300,6 +486,12 @@ void CarlaInstrument::loadSettings(const QDomElement& elem) carlaDoc.appendChild(carlaDoc.importNode(elem.firstChildElement(), true )); fDescriptor->set_state(fHandle, carlaDoc.toString(0).toUtf8().constData()); + +#if CARLA_VERSION_HEX >= CARLA_MIN_PARAM_VERSION + // Store to load parameter knobs settings when added. + m_settingsElem = const_cast(elem); + refreshParams(true); +#endif } void CarlaInstrument::play(sampleFrame* workingBuffer) @@ -342,7 +534,14 @@ void CarlaInstrument::play(sampleFrame* workingBuffer) { const QMutexLocker ml(&fMutex); +// TODO FIXME this is just here so it compiles. +// https://github.com/falkTX/Carla/blob/8bceb9ed173a10b29038f8abb4383710c0e497c1/source/includes/CarlaNative.h +// FIXME for v3.0, use const for the input buffer +#if CARLA_VERSION_HEX >= CARLA_VERSION_HEX_3 + fDescriptor->process(fHandle, (const float**)rBuf, rBuf, bufsize, fMidiEvents, fMidiEventCount); +#else fDescriptor->process(fHandle, rBuf, rBuf, bufsize, fMidiEvents, fMidiEventCount); +#endif fMidiEventCount = 0; } @@ -405,7 +604,11 @@ CarlaInstrumentView::CarlaInstrumentView(CarlaInstrument* const instrument, QWid : InstrumentViewFixedSize(instrument, parent), fHandle(instrument->fHandle), fDescriptor(instrument->fDescriptor), - fTimerId(fHandle != NULL && fDescriptor->ui_idle != NULL ? startTimer(30) : 0) + fTimerId(fHandle != NULL && fDescriptor->ui_idle != NULL ? startTimer(30) : 0), + m_carlaInstrument(instrument), + m_parent(parent), + m_paramsSubWindow(nullptr), + m_paramsView(nullptr) { setAutoFillBackground(true); @@ -413,10 +616,12 @@ CarlaInstrumentView::CarlaInstrumentView(CarlaInstrument* const instrument, QWid pal.setBrush(backgroundRole(), instrument->kIsPatchbay ? PLUGIN_NAME::getIconPixmap("artwork-patchbay") : PLUGIN_NAME::getIconPixmap("artwork-rack")); setPalette(pal); - QVBoxLayout * l = new QVBoxLayout( this ); + QHBoxLayout* l = new QHBoxLayout(this); l->setContentsMargins( 20, 180, 10, 10 ); - l->setSpacing( 10 ); + l->setSpacing(3); + l->setAlignment(Qt::AlignTop); + // Show GUI button m_toggleUIButton = new QPushButton( tr( "Show GUI" ), this ); m_toggleUIButton->setCheckable( true ); m_toggleUIButton->setChecked( false ); @@ -424,16 +629,44 @@ CarlaInstrumentView::CarlaInstrumentView(CarlaInstrument* const instrument, QWid m_toggleUIButton->setFont( pointSize<8>( m_toggleUIButton->font() ) ); connect( m_toggleUIButton, SIGNAL( clicked(bool) ), this, SLOT( toggleUI( bool ) ) ); + m_toggleUIButton->setToolTip( + tr("Click here to show or hide the graphical user interface (GUI) of Carla.")); + + // Open params sub window button + m_toggleParamsWindowButton = new QPushButton(tr("Params"), this); + m_toggleParamsWindowButton->setIcon(embed::getIconPixmap("controller")); + m_toggleParamsWindowButton->setCheckable(true); + m_toggleParamsWindowButton->setFont(pointSize<8>(m_toggleParamsWindowButton->font())); +#if CARLA_VERSION_HEX < CARLA_MIN_PARAM_VERSION + m_toggleParamsWindowButton->setEnabled(false); + m_toggleParamsWindowButton->setToolTip(tr("Available from Carla version 2.1 and up.")); +#else + connect(m_toggleParamsWindowButton, SIGNAL(clicked(bool)), this, SLOT(toggleParamsWindow())); +#endif + + // Add widgets to layout l->addWidget( m_toggleUIButton ); - l->addStretch(); + l->addWidget(m_toggleParamsWindowButton); + // Connect signals + connect(m_toggleUIButton, SIGNAL(clicked(bool)), this, SLOT(toggleUI(bool))); connect(instrument, SIGNAL(uiClosed()), this, SLOT(uiClosed())); } CarlaInstrumentView::~CarlaInstrumentView() { if (m_toggleUIButton->isChecked()) + { toggleUI(false); + } + +#if CARLA_VERSION_HEX >= CARLA_MIN_PARAM_VERSION + if (m_paramsView) + { + delete m_paramsView; + m_paramsView = nullptr; + } +#endif } void CarlaInstrumentView::toggleUI(bool visible) @@ -471,5 +704,408 @@ void CarlaInstrumentView::timerEvent(QTimerEvent* event) InstrumentView::timerEvent(event); } +void CarlaInstrumentView::toggleParamsWindow() +{ + if (!m_paramsSubWindow) + { + m_paramsView = new CarlaParamsView(this, m_parent); + connect(m_paramsSubWindow, SIGNAL(uiClosed()), this, SLOT(paramsUiClosed())); + } + else + { + if (m_paramsSubWindow->isVisible()) + { + m_paramsSubWindow->hide(); + } + else + { + m_paramsSubWindow->show(); + } + } +} + +void CarlaInstrumentView::paramsUiClosed() +{ + m_toggleParamsWindowButton->setChecked(false); +} + // ------------------------------------------------------------------- +CarlaParamsView::CarlaParamsView(CarlaInstrumentView* const instrumentView, QWidget* const parent) + : InstrumentView(instrumentView->m_carlaInstrument, parent), + m_carlaInstrument(instrumentView->m_carlaInstrument), + m_carlaInstrumentView(instrumentView), + m_maxColumns(6), + m_curColumn(0), + m_curRow(0), + m_curOutColumn(0), + m_curOutRow(0) +{ + QWidget* centralWidget = new QWidget(this); + QVBoxLayout* verticalLayout = new QVBoxLayout(centralWidget); + + // -- Toolbar + m_toolBarLayout = new QHBoxLayout(); + + // Toolbar widgets + QSizePolicy sizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); + sizePolicy.setHorizontalStretch(0); + sizePolicy.setVerticalStretch(0); + + // Params filter line edit + m_paramsFilterLineEdit = new QLineEdit(this); + m_paramsFilterLineEdit->setPlaceholderText(tr("Search..")); + m_paramsFilterLineEdit->setCompleter(m_carlaInstrument->m_paramsCompleter); + + // Clear filter line edit button + m_clearFilterButton = new QPushButton(tr(""), this); + m_clearFilterButton->setIcon(embed::getIconPixmap("edit_erase")); + m_clearFilterButton->setToolTip(tr("Clear filter text")); + sizePolicy.setHeightForWidth(m_clearFilterButton->sizePolicy().hasHeightForWidth()); + m_clearFilterButton->setSizePolicy(sizePolicy); + + // Show automated only button + m_automatedOnlyButton = new QPushButton(tr(""), this); + m_automatedOnlyButton->setIcon(embed::getIconPixmap("automation")); + m_automatedOnlyButton->setToolTip( + tr("Only show knobs with a connection.")); + m_automatedOnlyButton->setCheckable(true); + sizePolicy.setHeightForWidth(m_automatedOnlyButton->sizePolicy().hasHeightForWidth()); + m_automatedOnlyButton->setSizePolicy(sizePolicy); + + // Group name combobox + m_groupFilterCombo = new QComboBox(this); + m_groupFilterModel = new QStringListModel(this); + m_groupFilterCombo->setModel(m_groupFilterModel); + + // Add stuff to toolbar + m_toolBarLayout->addWidget(m_paramsFilterLineEdit); + m_toolBarLayout->addWidget(m_clearFilterButton); + m_toolBarLayout->addWidget(m_automatedOnlyButton); + m_toolBarLayout->addWidget(m_groupFilterCombo); + + // -- Input params + QFrame* inputFrame = new QFrame(this); + QVBoxLayout* inputLayout = new QVBoxLayout(inputFrame); + QLabel* inputLabel = new QLabel("Input parameters", inputFrame); + + m_inputScrollArea = new QScrollArea(inputFrame); + m_inputScrollAreaWidgetContent = new QWidget(); + m_inputScrollAreaLayout = new QGridLayout(m_inputScrollAreaWidgetContent); + + m_inputScrollAreaWidgetContent->setLayout(m_inputScrollAreaLayout); + m_inputScrollAreaWidgetContent->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); + + m_inputScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + m_inputScrollArea->setWidget(m_inputScrollAreaWidgetContent); + m_inputScrollArea->setWidgetResizable(true); + m_inputScrollArea->setFrameShadow(QFrame::Plain); + m_inputScrollArea->setFrameShape(QFrame::NoFrame); + + m_inputScrollAreaLayout->setContentsMargins(3, 3, 3, 3); + m_inputScrollAreaLayout->setVerticalSpacing(12); + m_inputScrollAreaLayout->setHorizontalSpacing(6); + m_inputScrollAreaLayout->setColumnStretch(m_maxColumns, 1); + + inputLayout->addWidget(inputLabel); + inputLayout->addWidget(m_inputScrollArea); + + // -- Output params + QFrame* outputFrame = new QFrame(this); + QVBoxLayout* outputLayout = new QVBoxLayout(outputFrame); + QLabel* outputLabel = new QLabel("Output parameters", outputFrame); + + m_outputScrollArea = new QScrollArea(outputFrame); + m_outputScrollAreaWidgetContent = new QWidget(); + m_outputScrollAreaLayout = new QGridLayout(m_outputScrollAreaWidgetContent); + + m_outputScrollAreaWidgetContent->setLayout(m_outputScrollAreaLayout); + m_outputScrollAreaWidgetContent->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); + + m_outputScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + m_outputScrollArea->setWidget(m_outputScrollAreaWidgetContent); + m_outputScrollArea->setWidgetResizable(true); + m_outputScrollArea->setFrameShadow(QFrame::Plain); + m_outputScrollArea->setFrameShape(QFrame::NoFrame); + + m_outputScrollAreaLayout->setContentsMargins(3, 28, 3, 3); + m_outputScrollAreaLayout->setVerticalSpacing(12); + m_outputScrollAreaLayout->setHorizontalSpacing(6); + m_outputScrollAreaLayout->setColumnStretch(m_maxColumns, 1); + + outputLayout->addWidget(outputLabel); + outputLayout->addWidget(m_outputScrollArea); + + // -- QSplitter + QSplitter* splitter = new QSplitter(Qt::Vertical, this); + + // -- Add layout and widgets. + verticalLayout->addLayout(m_toolBarLayout); + splitter->addWidget(inputFrame); + splitter->addWidget(outputFrame); + verticalLayout->addWidget(splitter); + + // -- Sub window + CarlaParamsSubWindow* win = new CarlaParamsSubWindow(gui->mainWindow()->workspace()->viewport(), Qt::SubWindow | + Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowSystemMenuHint); + m_carlaInstrumentView->m_paramsSubWindow = gui->mainWindow()->workspace()->addSubWindow(win); + m_carlaInstrumentView->m_paramsSubWindow->setSizePolicy( + QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + m_carlaInstrumentView->m_paramsSubWindow->setMinimumHeight(200); + m_carlaInstrumentView->m_paramsSubWindow->setMinimumWidth(200); + m_carlaInstrumentView->m_paramsSubWindow->resize(600, 400); + m_carlaInstrumentView->m_paramsSubWindow->setWidget(centralWidget); + centralWidget->setWindowTitle(m_carlaInstrument->instrumentTrack()->name() + tr(" - Parameters")); + + // -- Connect signals + connect(m_carlaInstrumentView->m_paramsSubWindow, SIGNAL(resized()), this, SLOT(windowResized())); + connect(m_paramsFilterLineEdit, SIGNAL(textChanged(const QString)), this, SLOT(filterKnobs())); + connect(m_clearFilterButton, SIGNAL(clicked(bool)), this, SLOT(clearFilterText())); + connect(m_automatedOnlyButton, SIGNAL(toggled(bool)), this, SLOT(filterKnobs())); + connect(m_groupFilterCombo, SIGNAL(currentTextChanged(const QString)), this, SLOT(filterKnobs())); + connect(m_carlaInstrument, SIGNAL(paramsUpdated()), this, SLOT(refreshKnobs())); + + m_carlaInstrumentView->m_paramsSubWindow->show(); // Show the subwindow + + // Add knobs if there are any already. + // Call this after show() so the m_inputScrollArea->width() is set properly. + refreshKnobs(); // Will trigger filterKnobs() due m_groupFilterCombo->setCurrentIndex(0) +} + +CarlaParamsView::~CarlaParamsView() +{ + // Close and delete m_paramsSubWindow + if (m_carlaInstrumentView->m_paramsSubWindow) + { + m_carlaInstrumentView->m_paramsSubWindow->setAttribute(Qt::WA_DeleteOnClose); + m_carlaInstrumentView->m_paramsSubWindow->close(); + + delete m_carlaInstrumentView->m_paramsSubWindow; + m_carlaInstrumentView->m_paramsSubWindow = nullptr; + } + + m_carlaInstrumentView->m_paramsView = nullptr; + + // Clear models + if (m_carlaInstrument->m_paramModels.isEmpty() == false) + { + m_carlaInstrument->clearParamModels(); + } +} + +void CarlaParamsView::clearFilterText() +{ + m_paramsFilterLineEdit->setText(""); +} + +void CarlaParamsView::filterKnobs() +{ + clearKnobs(); // Remove all knobs from the layout. + + if (!m_carlaInstrument->m_paramGroupCount) + { + return; + } + + // Calc how many knobs will fit horizontal in the params window. + uint16_t maxKnobWidth = m_maxKnobWidthPerGroup[m_groupFilterCombo->currentIndex()]; + maxKnobWidth += m_inputScrollAreaLayout->spacing(); + if (!maxKnobWidth) + { + // Prevent possible division by zero. + return; + } + m_maxColumns = m_inputScrollArea->width() / maxKnobWidth; + + QString text = m_paramsFilterLineEdit->text(); + for (uint32_t i=0; i < m_knobs.count(); ++i) + { + // Don't show disabled (unused) knobs. + if (!m_carlaInstrument->m_paramModels[i]->enabled()) + { + continue; + } + + // Filter on automation only + if (m_automatedOnlyButton->isChecked()) + { + if (! m_carlaInstrument->m_paramModels[i]->isAutomatedOrControlled()) + { + continue; + } + } + + // Filter on group name + if (m_groupFilterCombo->currentText() != m_carlaInstrument->m_paramModels[i]->groupName()) + { + continue; + } + + // Filter on text + if (text != "") + { + if (m_knobs[i]->objectName().contains(text, Qt::CaseInsensitive)) + { + addKnob(i); + } + } + else + { + addKnob(i); + } + } + + // Add spacer so all knobs go to top + QSpacerItem* verticalSpacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding); + m_inputScrollAreaLayout->addItem(verticalSpacer, m_curRow+1, 0, 1, 1); +} + +void CarlaParamsView::refreshKnobs() +{ + // Make sure all the knobs are deleted. + for (uint32_t i=0; i < m_knobs.count(); ++i) + { + delete m_knobs[i]; // Delete knob widgets itself. + } + m_knobs.clear(); // Clear the pointer list. + + // Reset position data. + m_curColumn = 0; + m_curRow = 0; + + m_curOutColumn = 0; + m_curOutRow = 0; + + // Clear max knob width per group + m_maxKnobWidthPerGroup.clear(); + m_maxKnobWidthPerGroup.reserve(m_carlaInstrument->m_paramGroupCount); + for (uint8_t i = 0; i < m_carlaInstrument->m_paramGroupCount; i++) + { + m_maxKnobWidthPerGroup[i] = 0; + } + + if (!m_carlaInstrument->m_paramModels.count()) { return; } + + // Make room in QList m_knobs + m_knobs.reserve(m_carlaInstrument->m_paramModels.count()); + + QStringList groupNameList; + groupNameList.reserve(m_carlaInstrument->m_paramGroupCount); + + for (uint32_t i=0; i < m_carlaInstrument->m_paramModels.count(); ++i) + { + bool enabled = m_carlaInstrument->m_paramModels[i]->enabled(); + m_knobs.push_back(new Knob(knobDark_28, m_inputScrollAreaWidgetContent)); + QString name = (*m_carlaInstrument->m_paramModels[i]).displayName(); + m_knobs[i]->setHintText(name, ""); + m_knobs[i]->setLabel(name); + m_knobs[i]->setObjectName(name); // this is being used for filtering the knobs. + + // Set the newly created model to the knob. + m_knobs[i]->setModel(m_carlaInstrument->m_paramModels[i]); + m_knobs[i]->setEnabled(enabled); + + if (enabled) + { + // Collect group names + if (!groupNameList.contains(m_carlaInstrument->m_paramModels[i]->groupName())) + { + groupNameList.append(m_carlaInstrument->m_paramModels[i]->groupName()); + } + + // Store biggest knob width per group (so we can calc how many + // knobs we can horizontaly fit) + uint8_t groupId = m_carlaInstrument->m_paramModels[i]->groupId(); + if (m_maxKnobWidthPerGroup[groupId] < m_knobs[i]->width()) + { + m_maxKnobWidthPerGroup[groupId] = m_knobs[i]->width(); + } + } + } + + // Set new list with group names to the model + if (!groupNameList.count()) + { + groupNameList.append("No params"); + } + m_groupFilterModel->setStringList(groupNameList); + m_groupFilterCombo->setCurrentIndex(0); +} + + +void CarlaParamsView::windowResized() +{ + filterKnobs(); +} + + +void CarlaParamsView::addKnob(uint32_t index) +{ + bool output = m_carlaInstrument->m_paramModels[index]->isOutput(); + if (output) + { + m_outputScrollAreaLayout->addWidget( + m_knobs[index], m_curOutRow, m_curOutColumn, Qt::AlignHCenter | Qt::AlignTop); + m_knobs[index]->setEnabled(false); // We should not be able to adjust output. + m_knobs[index]->show(); + if (m_curOutColumn < m_maxColumns - 1) + { + m_curOutColumn++; + } + else + { + m_curOutColumn = 0; + m_curOutRow++; + } + } + else + { + // Add the new knob to layout + m_inputScrollAreaLayout->addWidget(m_knobs[index], m_curRow, m_curColumn, Qt::AlignHCenter | Qt::AlignTop); + m_inputScrollAreaLayout->setColumnStretch(m_curColumn, 1); + + // Chances that we did close() on the widget is big, so show it. + m_knobs[index]->show(); + + // Keep track of current column and row index. + if (m_curColumn < m_maxColumns - 1) + { + m_curColumn++; + } + else + { + m_curColumn = 0; + m_curRow++; + } + } +} +void CarlaParamsView::clearKnobs() +{ + // Remove knobs from layout. + for (uint16_t i=0; i < m_knobs.count(); ++i) + { + m_knobs[i]->close(); + } + + // Remove spacers + QLayoutItem* item; + for (int16_t i=m_inputScrollAreaLayout->count() - 1; i > 0; i--) + { + item = m_inputScrollAreaLayout->takeAt(i); + if (item->widget()) {continue;} + delete item; + } + for (int16_t i=m_outputScrollAreaLayout->count() - 1; i > 0; i--) + { + item = m_outputScrollAreaLayout->takeAt(i); + if (item->widget()) {continue;} + delete item; + } + + // Reset position data. + m_curColumn = 0; + m_curRow = 0; + + m_curOutColumn = 0; + m_curOutRow = 0; +} diff --git a/plugins/carlabase/carla.h b/plugins/carlabase/carla.h index 7efecf681d6..6de8e3379a0 100644 --- a/plugins/carlabase/carla.h +++ b/plugins/carlabase/carla.h @@ -25,10 +25,21 @@ #ifndef CARLA_H #define CARLA_H +#define CARLA_SETTING_PREFIX "PARAM_KNOB_" +#define CARLA_MIN_PARAM_VERSION 0x020090 +#define CARLA_VERSION_HEX_3 0x30000 + +// qt +#include +#include +#include +#include +#include +#include #include -#include "carlabase_export.h" -#include "CarlaNative.h" +// carla/source/includes +#include "carlabase_export.h" #if CARLA_VERSION_HEX >= 0x010911 #include "CarlaNativePlugin.h" #else @@ -42,10 +53,163 @@ const NativePluginDescriptor* carla_get_native_rack_plugin(); #endif +// lmms/include/ +#include "EffectControls.h" #include "Instrument.h" #include "InstrumentView.h" +#include "Knob.h" +#include "SubWindow.h" class QPushButton; +class QComboBox; + +class CarlaParamsView; + +class CarlaParamFloatModel : public FloatModel +{ +public: + CarlaParamFloatModel(Model * parent): + FloatModel(0.0, 0.0, 1.0, 0.001, parent, "Unused"), + m_isOutput(false), + m_isEnabled(false) + { + } + + // From AutomatableModel.h, it's private there. + inline static bool mustQuoteName(const QString &name) + { + QRegExp reg("^[A-Za-z0-9._-]+$"); + return !reg.exactMatch(name); + } + + inline virtual void loadSettings(const QDomElement& element, const QString& name = QString("value")) override + { + AutomatableModel::loadSettings(element, name); + bool mustQuote = mustQuoteName(name); + QDomElement me = element.firstChildElement(mustQuote ? QString("automatablemodel") : name); + if (!me.isNull()) { + m_isOutput = (bool)me.attribute("output", "0").toInt(); + m_groupName = QString(me.attribute("groupName", "")); + } + } + + inline virtual void saveSettings(QDomDocument& doc, QDomElement& element, + const QString& name = QString( "value" )) override + { + if (m_isEnabled) + { + AutomatableModel::saveSettings(doc, element, name); + bool mustQuote = mustQuoteName(name); + QDomElement me = element.firstChildElement(mustQuote ? QString("automatablemodel") : name); + if (!me.isNull()) + { + me.setAttribute("output", m_isOutput); + me.setAttribute("groupName", m_groupName); + } + } + } + + inline const bool enabled() + { + return m_isEnabled; + } + + inline const bool isOutput() + { + return m_isOutput; + } + + inline void setOutput(bool state = true) + { + m_isOutput = state; + } + + inline void setEnabled(bool state = true) + { + m_isEnabled = state; + } + + inline void setGroupName(QString groupName) + { + m_groupName = groupName; + } + + inline void setGroupId(uint8_t groupId) + { + m_groupId = groupId; + } + + virtual QString groupName() const + { + return m_groupName; + } + + virtual uint8_t groupId() const + { + return m_groupId; + } + +private: + bool m_isOutput; + bool m_isEnabled; + uint8_t m_groupId; + QString m_groupName; +}; + +// ------------------------------------------------------------------- + +class CarlaParamsSubWindow : public SubWindow +{ + Q_OBJECT + +signals: + void uiClosed(); + void resized(); + +public: + CarlaParamsSubWindow(QWidget * _parent, Qt::WindowFlags windowFlags) : + SubWindow(_parent) + { + setAttribute(Qt::WA_DeleteOnClose, false); + setWindowFlags(windowFlags); + } + + virtual void resizeEvent(QResizeEvent * event) override + { + if (mousePress) { + resizing = true; + } + SubWindow::resizeEvent(event); + } + + virtual void mousePressEvent(QMouseEvent * event) override + { + mousePress = true; + SubWindow::mousePressEvent(event); + } + + virtual void mouseReleaseEvent(QMouseEvent * event) override + { + if (resizing) { + resizing = false; + mousePress = false; + emit resized(); + } + SubWindow::mouseReleaseEvent(event); + } + + virtual void closeEvent(QCloseEvent * event) override + { + emit uiClosed(); + event->accept(); + } + +private: + bool resizing = false; + bool mousePress = false; +}; + +// ------------------------------------------------------------------- class CARLABASE_EXPORT CarlaInstrument : public Instrument { @@ -77,9 +241,14 @@ class CARLABASE_EXPORT CarlaInstrument : public Instrument signals: void uiClosed(); + void paramsUpdated(); private slots: void sampleRateChanged(); + void refreshParams(bool init = false); + void clearParamModels(); + void paramModelChanged(uint32_t index); + void updateParamModel(uint32_t index); private: const bool kIsPatchbay; @@ -95,9 +264,20 @@ private slots: // this is only needed because note-offs are being sent during play QMutex fMutex; + uint8_t m_paramGroupCount; + QList m_paramModels; + QDomElement m_settingsElem; + + QCompleter* m_paramsCompleter; + QStringListModel* m_completerModel; + friend class CarlaInstrumentView; + friend class CarlaParamsView; }; + +// ------------------------------------------------------------------- + class CarlaInstrumentView : public InstrumentViewFixedSize { Q_OBJECT @@ -109,6 +289,8 @@ class CarlaInstrumentView : public InstrumentViewFixedSize private slots: void toggleUI(bool); void uiClosed(); + void toggleParamsWindow(); + void paramsUiClosed(); private: virtual void modelChanged(); @@ -118,7 +300,66 @@ private slots: const NativePluginDescriptor* fDescriptor; int fTimerId; - QPushButton * m_toggleUIButton; + CarlaInstrument* const m_carlaInstrument; + QWidget* const m_parent; + + QMdiSubWindow* m_paramsSubWindow; + CarlaParamsView* m_paramsView; + + QPushButton* m_toggleUIButton; + QPushButton* m_toggleParamsWindowButton; + + friend class CarlaParamsView; +}; + +// ------------------------------------------------------------------- + +class CarlaParamsView : public InstrumentView +{ + Q_OBJECT +public: + CarlaParamsView(CarlaInstrumentView* const instrumentView, QWidget* const parent); + virtual ~CarlaParamsView(); + +signals: + void uiClosed(); + +private slots: + void refreshKnobs(); + void filterKnobs(); + void clearFilterText(); + void windowResized(); + +private: + void adjustWindowWidth(); + void addKnob(uint32_t index); + void clearKnobs(); + + CarlaInstrument* const m_carlaInstrument; + CarlaInstrumentView* const m_carlaInstrumentView; + QList m_knobs; + + // Keep track of the biggest knob width per group + QList m_maxKnobWidthPerGroup; + + uint32_t m_maxColumns; + uint32_t m_curColumn; + uint32_t m_curRow; + uint32_t m_curOutColumn; + uint32_t m_curOutRow; + + QScrollArea* m_inputScrollArea; + QGridLayout* m_inputScrollAreaLayout; + QWidget* m_inputScrollAreaWidgetContent; + QScrollArea* m_outputScrollArea; + QGridLayout* m_outputScrollAreaLayout; + QWidget* m_outputScrollAreaWidgetContent; + QHBoxLayout* m_toolBarLayout; + QLineEdit* m_paramsFilterLineEdit; + QPushButton* m_clearFilterButton; + QPushButton* m_automatedOnlyButton; + QComboBox* m_groupFilterCombo; + QStringListModel* m_groupFilterModel; }; #endif