Expose Carla parameters for automation (#5846)

A new button called "Params" is added to the Carla instrument window; on press
it will open a new sub-window where Carla parameters can be found, ready for
connecting to a automation-track or controller. The exposed parameters in the
sub-window can be filtered by text and there is the ability to display only
parameters that are connected to a automation-track or controller. When there
are multiple plugins loaded inside Carla, the combo-box inside the (LMMS)
parameters sub-window can be used to switch between parameters of a specific
plugin (Carla).

Notes:

 - Available when compiled with Carla version 2.1 and up.
 - Currently Carla (2.1) will expose a maximum of 110 parameters.
 - The param window state isn't stored yet in a LMMS project, this is still
   TODO.
 - Connected paramters will NOT instantly disappear when they are disconnected
   with the "Show only knobs with a connection" filter enabled. See
   https://github.com/LMMS/lmms/pull/5846#issuecomment-762666428
This commit is contained in:
cyber-bridge
2021-06-30 19:59:54 +02:00
committed by GitHub
parent c71e408a82
commit f8d7fa3b87
2 changed files with 890 additions and 13 deletions

View File

@@ -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 <QApplication>
#include <QComboBox>
#include <QFileDialog>
#include <QFileInfo>
#include <QHBoxLayout>
#include <QMdiArea>
#include <QMessageBox>
#include <QPushButton>
#include <QScrollArea>
#include <QSizePolicy>
#include <QSpacerItem>
#include <QSplitter>
#include <QString>
#include <QStringList>
#include <QTimerEvent>
#include <QVBoxLayout>
@@ -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<QString> completerData;
QList<QString> 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<QDomElement&>(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 ) ) );
l->addWidget( m_toggleUIButton );
l->addStretch();
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->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;
}

View File

@@ -25,10 +25,21 @@
#ifndef CARLA_H
#define CARLA_H
#include <QtCore/QMutex>
#include "carlabase_export.h"
#define CARLA_SETTING_PREFIX "PARAM_KNOB_"
#define CARLA_MIN_PARAM_VERSION 0x020090
#define CARLA_VERSION_HEX_3 0x30000
#include "CarlaNative.h"
// qt
#include <QCloseEvent>
#include <QCompleter>
#include <QGridLayout>
#include <QLineEdit>
#include <QScrollArea>
#include <QStringListModel>
#include <QtCore/QMutex>
// 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 @@ public:
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:
// this is only needed because note-offs are being sent during play
QMutex fMutex;
uint8_t m_paramGroupCount;
QList<CarlaParamFloatModel*> 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 @@ public:
private slots:
void toggleUI(bool);
void uiClosed();
void toggleParamsWindow();
void paramsUiClosed();
private:
virtual void modelChanged();
@@ -118,7 +300,66 @@ private:
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<Knob*> m_knobs;
// Keep track of the biggest knob width per group
QList<uint16_t> 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