clap

CLAP Audio Plugin API
Log | Files | Refs | README | LICENSE

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:
Mexamples/host/CMakeLists.txt | 4++++
Mexamples/host/main-window.cc | 16++++++++++++++--
Mexamples/host/main-window.hh | 5+++++
Mexamples/host/plugin-host.cc | 127++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mexamples/host/plugin-host.hh | 31+++++++++++++++++++++++--------
Aexamples/host/plugin-quick-control-widget.cc | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aexamples/host/plugin-quick-control-widget.hh | 39+++++++++++++++++++++++++++++++++++++++
Aexamples/host/plugin-quick-controls-widget.cc | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aexamples/host/plugin-quick-controls-widget.hh | 27+++++++++++++++++++++++++++
Minclude/clap/ext/draft/quick-controls.h | 6+++++-
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 &param, clap_param_value value); auto &params() 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 &params = 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 {