commit 7272da7faf555835968263f924212d4603edcc41
parent 7eef2ed5887b9b03f5b512be349c2dfe0657e106
Author: Alexandre BIQUE <bique.alexandre@gmail.com>
Date: Sat, 22 May 2021 09:31:15 +0200
Initial work on quick controls
Diffstat:
10 files changed, 412 insertions(+), 15 deletions(-)
diff --git a/examples/host/CMakeLists.txt b/examples/host/CMakeLists.txt
@@ -22,6 +22,10 @@ add_executable(clap-host EXCLUDE_FROM_ALL
param-queue.hh
plugin-host.cc
plugin-host.hh
+ plugin-quick-control-widget.cc
+ plugin-quick-control-widget.hh
+ plugin-quick-controls-widget.cc
+ plugin-quick-controls-widget.hh
CMakeLists.txt
device-reference.cc
diff --git a/examples/host/main-window.cc b/examples/host/main-window.cc
@@ -13,6 +13,7 @@
#include "engine.hh"
#include "main-window.hh"
#include "plugin-parameters-widget.hh"
+#include "plugin-quick-controls-widget.hh"
#include "settings-dialog.hh"
#include "settings.hh"
@@ -31,10 +32,16 @@ MainWindow::MainWindow(Application &app)
connect(settingsDialog_, SIGNAL(accepted()), &application_, SLOT(restartEngine()));
+ auto &pluginHost = app.engine()->pluginHost();
+
pluginParametersWindow_ = new QMainWindow(this);
- pluginParametersWidget_ =
- new PluginParametersWidget(pluginParametersWindow_, app.engine()->pluginHost());
+ pluginParametersWidget_ = new PluginParametersWidget(pluginParametersWindow_, pluginHost);
pluginParametersWindow_->setCentralWidget(pluginParametersWidget_);
+
+ pluginQuickControlsWindow_ = new QMainWindow(this);
+ pluginQuickControlsWidget_ =
+ new PluginQuickControlsWidget(pluginQuickControlsWindow_, pluginHost);
+ pluginQuickControlsWindow_->setCentralWidget(pluginQuickControlsWidget_);
}
MainWindow::~MainWindow() {}
@@ -60,6 +67,10 @@ void MainWindow::createMenu() {
&QAction::triggered,
this,
&MainWindow::showPluginParametersWindow);
+ connect(windowsMenu->addAction(tr("Show Quick Controls")),
+ &QAction::triggered,
+ this,
+ &MainWindow::showPluginQuickControlsWindow);
QMenu *helpMenu = menuBar->addMenu(tr("Help"));
helpMenu->addAction(tr("Manual"));
@@ -73,6 +84,7 @@ void MainWindow::showSettingsDialog() {
}
void MainWindow::showPluginParametersWindow() { pluginParametersWindow_->show(); }
+void MainWindow::showPluginQuickControlsWindow() { pluginQuickControlsWindow_->show(); }
WId MainWindow::getEmbedWindowId() { return pluginViewWidget_->winId(); }
diff --git a/examples/host/main-window.hh b/examples/host/main-window.hh
@@ -5,6 +5,7 @@
class Application;
class SettingsDialog;
class PluginParametersWidget;
+class PluginQuickControlsWidget;
class MainWindow : public QMainWindow {
Q_OBJECT
@@ -18,6 +19,7 @@ public:
public:
void showSettingsDialog();
void showPluginParametersWindow();
+ void showPluginQuickControlsWindow();
void resizePluginView(int width, int height);
private:
@@ -30,4 +32,7 @@ private:
QMainWindow * pluginParametersWindow_ = nullptr;
PluginParametersWidget *pluginParametersWidget_ = nullptr;
+
+ QMainWindow * pluginQuickControlsWindow_ = nullptr;
+ PluginQuickControlsWidget *pluginQuickControlsWidget_ = nullptr;
};
diff --git a/examples/host/plugin-host.cc b/examples/host/plugin-host.cc
@@ -28,10 +28,10 @@ PluginHost::PluginHost(Engine &engine) : QObject(&engine), engine_(engine) {
host_.host_data = this;
host_.clap_version = CLAP_VERSION;
host_.extension = PluginHost::clapHostExtension;
- host_.name = "Mini Test Host";
+ host_.name = "Clap Test Host";
host_.version = "0.0.1";
- host_.vendor = "u-he";
- host_.url = "https://www.u-he.com";
+ host_.vendor = "clap";
+ host_.url = "https://github.com/free-audio/clap";
hostLog_.log = PluginHost::clapHostLog;
@@ -53,6 +53,9 @@ PluginHost::PluginHost(Engine &engine) : QObject(&engine), engine_(engine) {
hostParams_.adjust = PluginHost::clapParamsAdjust;
hostParams_.rescan = PluginHost::clapParamsRescan;
+ hostQuickControls_.pages_changed = PluginHost::clapQuickControlsPagesChanged;
+ hostQuickControls_.selected_page_changed = PluginHost::clapQuickControlsSelectedPageChanged;
+
initThreadPool();
}
@@ -148,6 +151,7 @@ bool PluginHost::load(const QString &path, int pluginIndex) {
initPluginExtensions();
scanParams();
+ scanQuickControls();
return true;
}
@@ -156,6 +160,7 @@ void PluginHost::initPluginExtensions() {
return;
initPluginExtension(pluginParams_, CLAP_EXT_PARAMS);
+ initPluginExtension(pluginQuickControls_, CLAP_EXT_QUICK_CONTROLS);
initPluginExtension(pluginAudioPorts_, CLAP_EXT_AUDIO_PORTS);
initPluginExtension(pluginGui_, CLAP_EXT_GUI);
initPluginExtension(pluginGuiX11_, CLAP_EXT_GUI_X11);
@@ -295,7 +300,8 @@ const void *PluginHost::clapHostExtension(clap_host *host, const char *extension
return &h->hostEventLoop_;
if (!strcmp(extension, CLAP_EXT_PARAMS))
return &h->hostParams_;
-
+ if (!strcmp(extension, CLAP_EXT_QUICK_CONTROLS))
+ return &h->hostQuickControls_;
return nullptr;
}
@@ -902,6 +908,119 @@ clap_param_value PluginHost::getParamValue(const clap_param_info &info) {
throw std::logic_error(msg.str());
}
+void PluginHost::scanQuickControls() {
+ checkForMainThread();
+
+ if (!pluginQuickControls_)
+ return;
+
+ if (!pluginQuickControls_->get_page || !pluginQuickControls_->page_count) {
+ std::ostringstream msg;
+ msg << "clap_plugin_quick_controls is partially implemented.";
+ throw std::logic_error(msg.str());
+ }
+
+ quickControlsSetSelectedPage(CLAP_INVALID_ID);
+ quickControlsPages_.clear();
+
+ const auto N = pluginQuickControls_->page_count(plugin_);
+ if (N == 0)
+ return;
+
+ quickControlsPages_.reserve(N);
+
+ clap_id firstPageId = CLAP_INVALID_ID;
+ for (int i = 0; i < N; ++i) {
+ auto page = std::make_unique<clap_quick_controls_page>();
+ if (!pluginQuickControls_->get_page(plugin_, i, page.get())) {
+ std::ostringstream msg;
+ msg << "clap_plugin_quick_controls.get_page(" << i << ") failed, while the page count is "
+ << N;
+ throw std::logic_error(msg.str());
+ }
+
+ if (page->id == CLAP_INVALID_ID) {
+ std::ostringstream msg;
+ msg << "clap_plugin_quick_controls.get_page(" << i << ") gave an invalid page_id";
+ throw std::invalid_argument(msg.str());
+ }
+
+ if (i == 0)
+ firstPageId = page->id;
+
+ auto it = quickControlsPages_.find(page->id);
+ if (it != quickControlsPages_.end()) {
+ std::ostringstream msg;
+ msg << "clap_plugin_quick_controls.get_page(" << i
+ << ") gave twice the same page_id:" << page->id << std::endl
+ << " 1. name: " << it->second->name << std::endl
+ << " 2. name: " << page->name;
+ throw std::invalid_argument(msg.str());
+ }
+
+ quickControlsPages_.emplace(page->id, std::move(page));
+ }
+
+ quickControlsPagesChanged();
+
+ auto pageId = pluginQuickControls_->get_selected_page(plugin_);
+ quickControlsSetSelectedPage(pageId == CLAP_INVALID_ID ? firstPageId : pageId);
+}
+
+void PluginHost::quickControlsSetSelectedPage(clap_id pageId) {
+ if (pageId == quickControlsSelectedPage_)
+ return;
+
+ if (pageId != CLAP_INVALID_ID) {
+ auto it = quickControlsPages_.find(pageId);
+ if (it == quickControlsPages_.end()) {
+ std::ostringstream msg;
+ msg << "quick control page_id " << pageId << " not found";
+ throw std::invalid_argument(msg.str());
+ }
+ }
+
+ quickControlsSelectedPage_ = pageId;
+ quickControlsSelectedPageChanged();
+}
+
+void PluginHost::setQuickControlsSelectedPageByHost(clap_id page_id) {
+ Q_ASSERT(page_id != CLAP_INVALID_ID);
+
+ checkForMainThread();
+
+ quickControlsSelectedPage_ = page_id;
+
+ if (pluginQuickControls_ && pluginQuickControls_->select_page)
+ pluginQuickControls_->select_page(plugin_, page_id);
+}
+
+void PluginHost::clapQuickControlsPagesChanged(clap_host *host) {
+ checkForMainThread();
+
+ auto h = fromHost(host);
+ if (!h->pluginQuickControls_) {
+ std::ostringstream msg;
+ msg << "Plugin called clap_host_quick_controls.pages_changed() but does not provide "
+ "clap_plugin_quick_controls";
+ throw std::logic_error(msg.str());
+ }
+ h->scanQuickControls();
+}
+
+void PluginHost::clapQuickControlsSelectedPageChanged(clap_host *host, clap_id page_id) {
+ checkForMainThread();
+
+ auto h = fromHost(host);
+ if (!h->pluginQuickControls_) {
+ std::ostringstream msg;
+ msg << "Plugin called clap_host_quick_controls.selected_page_changed() but does not provide "
+ "clap_plugin_quick_controls";
+ throw std::logic_error(msg.str());
+ }
+ h->quickControlsSetSelectedPage(page_id);
+}
+
void PluginHost::setPluginState(PluginState state) {
switch (state) {
case Inactive:
diff --git a/examples/host/plugin-host.hh b/examples/host/plugin-host.hh
@@ -55,12 +55,17 @@ public:
void setParamValueByHost(PluginParam ¶m, clap_param_value value);
auto ¶ms() const { return params_; }
+ auto &quickControlsPages() const { return quickControlsPages_; }
+ auto quickControlsSelectedPage() const { return quickControlsSelectedPage_; }
+ void setQuickControlsSelectedPageByHost(clap_id page_id);
static void checkForMainThread();
static void checkForAudioThread();
signals:
void paramsChanged();
+ void quickControlsPagesChanged();
+ void quickControlsSelectedPageChanged();
private:
static PluginHost *fromHost(clap_host *host);
@@ -91,6 +96,11 @@ private:
return flags & (CLAP_PARAM_RESCAN_ALL | CLAP_PARAM_RESCAN_INFO);
}
+ void scanQuickControls();
+ void quickControlsSetSelectedPage(clap_id pageId);
+ static void clapQuickControlsPagesChanged(clap_host *host);
+ static void clapQuickControlsSelectedPageChanged(clap_host *host, clap_id page_id);
+
static bool clapEventLoopRegisterTimer(clap_host *host, uint32_t period_ms, clap_id *timer_id);
static bool clapEventLoopUnregisterTimer(clap_host *host, clap_id timer_id);
static bool clapEventLoopRegisterFd(clap_host *host, clap_fd fd, uint32_t flags);
@@ -110,18 +120,20 @@ private:
QLibrary library_;
- clap_host host_;
- clap_host_log hostLog_;
- clap_host_gui hostGui_;
- clap_host_audio_ports hostAudioPorts_;
- clap_host_params hostParams_;
- clap_host_event_loop hostEventLoop_;
- clap_host_thread_check hostThreadCheck_;
- clap_host_thread_pool hostThreadPool_;
+ clap_host host_;
+ clap_host_log hostLog_;
+ clap_host_gui hostGui_;
+ clap_host_audio_ports hostAudioPorts_;
+ clap_host_params hostParams_;
+ clap_host_quick_controls hostQuickControls_;
+ clap_host_event_loop hostEventLoop_;
+ clap_host_thread_check hostThreadCheck_;
+ clap_host_thread_pool hostThreadPool_;
const struct clap_plugin_entry * pluginEntry_ = nullptr;
clap_plugin * plugin_ = nullptr;
const clap_plugin_params * pluginParams_ = nullptr;
+ const clap_plugin_quick_controls * pluginQuickControls_ = nullptr;
const clap_plugin_audio_ports * pluginAudioPorts_ = nullptr;
const clap_plugin_gui * pluginGui_ = nullptr;
const clap_plugin_gui_x11 * pluginGuiX11_ = nullptr;
@@ -164,6 +176,9 @@ private:
ParamQueue appToEngineQueue_;
ParamQueue engineToAppQueue_;
+ std::unordered_map<clap_id, std::unique_ptr<clap_quick_controls_page>> quickControlsPages_;
+ clap_id quickControlsSelectedPage_ = CLAP_INVALID_ID;
+
/* delayed actions */
enum PluginState {
// The plugin is inactive, only the main thread uses it
diff --git a/examples/host/plugin-quick-control-widget.cc b/examples/host/plugin-quick-control-widget.cc
@@ -0,0 +1,106 @@
+#include <QDial>
+#include <QLabel>
+#include <QVBoxLayout>
+
+#include "plugin-host.hh"
+#include "plugin-param.hh"
+#include "plugin-quick-control-widget.hh"
+
+PluginQuickControlWidget::PluginQuickControlWidget(QWidget *parent, PluginHost &pluginHost)
+ : QWidget(parent), pluginHost_(pluginHost) {
+ dial_ = new QDial(this);
+ label_ = new QLabel(this);
+
+ auto layout = new QVBoxLayout(this);
+ layout->addWidget(dial_);
+ layout->addWidget(label_);
+ setLayout(layout);
+
+ setPluginParam(nullptr);
+}
+
+void PluginQuickControlWidget::setPluginParam(PluginParam *param) {
+ if (param_ == param)
+ return;
+
+ if (param_)
+ disconnectFromParam();
+
+ Q_ASSERT(!param_);
+
+ connectToParam(param);
+}
+
+void PluginQuickControlWidget::connectToParam(PluginParam *param) {
+ Q_ASSERT(!param_);
+
+ param_ = param;
+ connect(param_, &PluginParam::infoChanged, this, &PluginQuickControlWidget::paramInfoChanged);
+ connect(param_, &PluginParam::valueChanged, this, &PluginQuickControlWidget::paramValueChanged);
+ updateAll();
+}
+
+void PluginQuickControlWidget::disconnectFromParam() {
+ Q_ASSERT(param_);
+
+ disconnect(param_, &PluginParam::infoChanged, this, &PluginQuickControlWidget::paramInfoChanged);
+ disconnect(
+ param_, &PluginParam::valueChanged, this, &PluginQuickControlWidget::paramValueChanged);
+
+ updateAll();
+}
+
+void PluginQuickControlWidget::paramInfoChanged() { updateParamInfo(); }
+
+void PluginQuickControlWidget::paramValueChanged() { updateParamValue(); }
+
+void PluginQuickControlWidget::dialValueChanged(int newValue) {
+ if (!param_)
+ return;
+
+ if (!dial_->isSliderDown())
+ return;
+
+ auto &info = param_->info();
+
+ clap_param_value value;
+ switch (info.type) {
+ case CLAP_PARAM_FLOAT:
+ value.d = newValue * (info.max_value.d - info.min_value.d) / DIAL_RANGE + info.min_value.d;
+ pluginHost_.setParamValueByHost(*param_, value);
+ break;
+ case CLAP_PARAM_INT:
+ value.i = (newValue * (info.max_value.i - info.min_value.i)) / DIAL_RANGE + info.min_value.i;
+ pluginHost_.setParamValueByHost(*param_, value);
+ break;
+ }
+}
+void PluginQuickControlWidget::updateParamValue() {
+ if (!param_)
+ return;
+
+ if (dial_->isSliderDown())
+ return;
+
+ auto info = param_->info();
+ auto v = param_->value();
+ switch (info.type) {
+ case CLAP_PARAM_FLOAT:
+ dial_->setValue(DIAL_RANGE * (v.d - info.min_value.d) /
+ (info.max_value.d - info.min_value.d));
+ break;
+ case CLAP_PARAM_INT:
+ dial_->setValue((DIAL_RANGE * (v.i - info.min_value.i)) /
+ (info.max_value.i - info.min_value.i));
+ break;
+ }
+}
+
+void PluginQuickControlWidget::updateParamInfo() {
+ label_->setText(param_ ? param_->info().name : "-");
+}
+
+void PluginQuickControlWidget::updateAll() {
+ updateParamInfo();
+ updateParamValue();
+}
diff --git a/examples/host/plugin-quick-control-widget.hh b/examples/host/plugin-quick-control-widget.hh
@@ -0,0 +1,38 @@
+#pragma once
+
+#include <QWidget>
+
+#include <clap/all.h>
+
+class QDial;
+class QLabel;
+class PluginHost;
+class PluginParam;
+class PluginQuickControlWidget : public QWidget {
+ Q_OBJECT;
+
+public:
+ PluginQuickControlWidget(QWidget *parent, PluginHost &pluginHost);
+
+ void setPluginParam(PluginParam *param);
+
+private:
+ void paramInfoChanged();
+ void paramValueChanged();
+ void dialValueChanged(int newValue);
+
+ void disconnectFromParam();
+ void connectToParam(PluginParam *param);
+
+ void updateParamValue();
+ void updateParamInfo();
+ void updateAll();
+
+ static const constexpr int DIAL_RANGE = 10000;
+
+ PluginHost &pluginHost_;
+
+ QDial * dial_ = nullptr;
+ QLabel * label_ = nullptr;
+ PluginParam *param_ = nullptr;
+};
+\ No newline at end of file
diff --git a/examples/host/plugin-quick-controls-widget.cc b/examples/host/plugin-quick-controls-widget.cc
@@ -0,0 +1,66 @@
+#include <QComboBox>
+#include <QGridLayout>
+#include <QVBoxLayout>
+
+#include "plugin-host.hh"
+#include "plugin-quick-control-widget.hh"
+#include "plugin-quick-controls-widget.hh"
+
+PluginQuickControlsWidget::PluginQuickControlsWidget(QWidget *parent, PluginHost &pluginHost)
+ : QWidget(parent), pluginHost_(pluginHost) {
+ chooser_ = new QComboBox(this);
+ connect(chooser_,
+ &QComboBox::activated,
+ this,
+ &PluginQuickControlsWidget::selectPageFromChooser);
+
+ for (auto &qc : controls_)
+ qc = new PluginQuickControlWidget(this, pluginHost);
+
+ auto grid = new QGridLayout(this);
+ grid->setSpacing(3);
+
+ const auto rowSize = CLAP_QUICK_CONTROLS_COUNT / 2;
+ for (int i = 0; i < CLAP_QUICK_CONTROLS_COUNT; ++i)
+ grid->addWidget(controls_[i], i / rowSize, i % rowSize);
+
+ auto vbox = new QVBoxLayout(this);
+ vbox->addWidget(chooser_);
+ vbox->addLayout(grid);
+ vbox->setSpacing(3);
+ setLayout(vbox);
+}
+
+void PluginQuickControlsWidget::pagesChanged() {
+ auto &pages = pluginHost_.quickControlsPages();
+ chooser_->clear();
+ for (auto &it : pages)
+ chooser_->addItem(it.second->name, it.second->id);
+
+ selectedPageChanged();
+}
+
+void PluginQuickControlsWidget::selectedPageChanged() {
+ auto pageId = pluginHost_.quickControlsSelectedPage();
+ auto &pages = pluginHost_.quickControlsPages();
+ auto ¶ms = pluginHost_.params();
+ auto it = pages.find(pageId);
+
+ for (int i = 0; i < CLAP_QUICK_CONTROLS_COUNT; ++i) {
+ PluginParam *param = nullptr;
+
+ if (it != pages.end()) {
+ auto paramId = it->second->param_ids[i];
+ auto paramIt = params.find(paramId);
+ if (paramIt != params.end())
+ param = paramIt->second.get();
+ }
+
+ controls_[i]->setPluginParam(param);
+ }
+}
+
+void PluginQuickControlsWidget::selectPageFromChooser(int index) {
+ clap_id pageId = chooser_->currentData().toUInt();
+ pluginHost_.setQuickControlsSelectedPageByHost(pageId);
+}
diff --git a/examples/host/plugin-quick-controls-widget.hh b/examples/host/plugin-quick-controls-widget.hh
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <QWidget>
+
+#include <clap/all.h>
+
+class QComboBox;
+class PluginHost;
+class PluginQuickControlWidget;
+class PluginQuickControlsWidget : public QWidget {
+ Q_OBJECT;
+
+public:
+ PluginQuickControlsWidget(QWidget *parent, PluginHost &pluginHost);
+
+ void pagesChanged();
+ void selectedPageChanged();
+
+private:
+ void selectPageFromChooser(int index);
+
+ PluginHost &pluginHost_;
+
+ QComboBox *chooser_ = nullptr;
+ std::array<PluginQuickControlWidget *, CLAP_QUICK_CONTROLS_COUNT> controls_;
+};
+\ No newline at end of file
diff --git a/include/clap/ext/draft/quick-controls.h b/include/clap/ext/draft/quick-controls.h
@@ -8,11 +8,15 @@ extern "C" {
#define CLAP_EXT_QUICK_CONTROLS "clap/draft/quick-controls"
+enum {
+ CLAP_QUICK_CONTROLS_COUNT = 8
+};
+
typedef struct clap_quick_controls_page {
clap_id id;
char name[CLAP_NAME_SIZE];
char keywords[CLAP_KEYWORDS_SIZE];
- clap_id param_id[8];
+ clap_id param_ids[CLAP_QUICK_CONTROLS_COUNT];
} clap_quick_controls_page;
typedef struct clap_plugin_quick_controls {