diff --git a/.github/workflows/pullreq.yml b/.github/workflows/pullreq.yml index a9d916b..ac0c521 100644 --- a/.github/workflows/pullreq.yml +++ b/.github/workflows/pullreq.yml @@ -31,11 +31,11 @@ jobs: # name: Windows gcc/minGW # exe: .exe - - os: windows-latest - cmakeargs: -GNinja -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang - install_ninja: true - name: Windows clang - exe: .exe +# - os: windows-latest +# cmakeargs: -GNinja -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang +# install_ninja: true +# name: Windows clang +# exe: .exe - os: ubuntu-latest cmakeargs: -DCMAKE_CXX_COMPILER=g++-12 -DCMAKE_C_COMPILER=gcc-12 -DCLAP_HELPERS_TESTS_CXX_STANDARD=11 diff --git a/.gitignore b/.gitignore index 4ee5684..96a5983 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,6 @@ ignore/ build/ out/ .DS_Store + +# Meson +.meson-subproject* diff --git a/CMakeLists.txt b/CMakeLists.txt index e27afe4..3d70fec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,6 +78,7 @@ if (${CLAP_HELPERS_BUILD_TESTS}) tests/preset-discovery-indexer.cc tests/preset-discovery-provider.cc tests/preset-discovery-metadata-receiver.cc + tests/use-custom-extension.cc ) set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD ${CLAP_HELPERS_TESTS_CXX_STANDARD}) set_target_properties(${PROJECT_NAME}-tests PROPERTIES CXX_STANDARD ${CLAP_HELPERS_TESTS_CXX_STANDARD}) diff --git a/include/clap/helpers/checking-level.hh b/include/clap/helpers/checking-level.hh index b0a9619..8336261 100644 --- a/include/clap/helpers/checking-level.hh +++ b/include/clap/helpers/checking-level.hh @@ -6,4 +6,4 @@ namespace clap { namespace helpers { Minimal, Maximal, }; -}} // namespace clap::helpers \ No newline at end of file +}} // namespace clap::helpers diff --git a/include/clap/helpers/host-proxy.hh b/include/clap/helpers/host-proxy.hh index ae82eac..14748d6 100644 --- a/include/clap/helpers/host-proxy.hh +++ b/include/clap/helpers/host-proxy.hh @@ -213,11 +213,18 @@ namespace clap { namespace helpers { bool canUseMiniCurveDisplay() const noexcept; void miniCurveDisplaySetDynamic(bool is_dynamic) const noexcept; void miniCurveDisplayChanged(uint32_t flags) const noexcept; + bool miniCurveDisplayGetHints(uint32_t kind, + clap_mini_curve_display_curve_hints_t *hints) const noexcept; + + ////////////////////////////// + // Custom extension helpers // + ////////////////////////////// - protected: void ensureMainThread(const char *method) const noexcept; void ensureAudioThread(const char *method) const noexcept; + const clap_host *clapHost() const noexcept { return _host; } + protected: const clap_host *const _host; const clap_host_log *_hostLog = nullptr; diff --git a/include/clap/helpers/host-proxy.hxx b/include/clap/helpers/host-proxy.hxx index 9fd10c0..ac9f640 100644 --- a/include/clap/helpers/host-proxy.hxx +++ b/include/clap/helpers/host-proxy.hxx @@ -806,4 +806,18 @@ namespace clap { namespace helpers { _hostMiniCurveDisplay->changed(_host, flags); } + template + bool HostProxy::miniCurveDisplayGetHints( + uint32_t kind, clap_mini_curve_display_curve_hints_t *hints) const noexcept { + assert(canUseMiniCurveDisplay()); + ensureMainThread("mini_curve_display_get_hints"); + + if (!hints) { + pluginMisbehaving("mini_curve_display_get_hints() called with a null hints pointer"); + return false; + } + + return _hostMiniCurveDisplay->get_hints(_host, kind, hints); + } + }} // namespace clap::helpers diff --git a/include/clap/helpers/host.hh b/include/clap/helpers/host.hh index 71f3b74..4a9a9e7 100644 --- a/include/clap/helpers/host.hh +++ b/include/clap/helpers/host.hh @@ -51,6 +51,7 @@ namespace clap { namespace helpers { virtual void requestCallback() noexcept = 0; virtual bool enableDraftExtensions() const noexcept { return false; } + virtual const void* getExtension(const char* extensionId) const noexcept { return nullptr; } // clap_host_audio_ports virtual bool implementsAudioPorts() const noexcept { return false; } @@ -107,6 +108,10 @@ namespace clap { namespace helpers { virtual bool implementsThreadPool() const noexcept { return false; } virtual bool threadPoolRequestExec(uint32_t numTasks) noexcept { return false; } + // clap_host_surround + virtual bool implementsSurround() const noexcept { return false; } + virtual void surroundChanged() noexcept {} + ///////////////////// // Thread Checking // ///////////////////// @@ -186,6 +191,9 @@ namespace clap { namespace helpers { // clap_host_thread_pool static bool clapThreadPoolRequestExec(const clap_host *host, uint32_t num_tasks) noexcept; + // clap_host_surround + static void clapSurroundChanged(const clap_host_t *host) noexcept; + // interfaces static const clap_host_audio_ports _hostAudioPorts; static const clap_host_gui _hostGui; @@ -199,5 +207,6 @@ namespace clap { namespace helpers { static const clap_host_tail _hostTail; static const clap_host_thread_check _hostThreadCheck; static const clap_host_thread_pool _hostThreadPool; + static const clap_host_surround _hostSurround; }; }} // namespace clap::helpers diff --git a/include/clap/helpers/host.hxx b/include/clap/helpers/host.hxx index 428ebce..c466d32 100644 --- a/include/clap/helpers/host.hxx +++ b/include/clap/helpers/host.hxx @@ -81,6 +81,11 @@ namespace clap { namespace helpers { clapThreadPoolRequestExec, }; + template + const clap_host_surround Host::_hostSurround = { + clapSurroundChanged, + }; + template Host::Host(const char *name, const char *vendor, const char *url, const char *version) : _host{ @@ -132,6 +137,8 @@ namespace clap { namespace helpers { const void *Host::clapGetExtension(const clap_host_t *host, const char *extension_id) noexcept { auto &self = from(host); + if (auto ext = self.getExtension(extension_id)) + return ext; if (!std::strcmp(extension_id, CLAP_EXT_AUDIO_PORTS) && self.implementsAudioPorts()) return &_hostAudioPorts; if (!std::strcmp(extension_id, CLAP_EXT_GUI) && self.implementsGui()) @@ -158,10 +165,13 @@ namespace clap { namespace helpers { return &_hostThreadCheck; if (!strcmp(extension_id, CLAP_EXT_THREAD_POOL) && self.implementsThreadPool()) return &_hostThreadPool; + if (!std::strcmp(extension_id, CLAP_EXT_SURROUND) && self.implementsSurround()) + return &_hostSurround; if (self.enableDraftExtensions()) { // put draft ext here } + return nullptr; } @@ -394,6 +404,16 @@ namespace clap { namespace helpers { return self.threadPoolRequestExec(num_tasks); } + //--------------------// + // clap_host_surround // + //--------------------// + template + void Host::clapSurroundChanged(const clap_host *host) noexcept { + auto &self = from(host); + self.ensureMainThread("surround.changed"); + self.surroundChanged(); + } + ///////////////////// // Thread Checking // ///////////////////// diff --git a/include/clap/helpers/misbehaviour-handler.hh b/include/clap/helpers/misbehaviour-handler.hh index 754a46e..c627d2b 100644 --- a/include/clap/helpers/misbehaviour-handler.hh +++ b/include/clap/helpers/misbehaviour-handler.hh @@ -5,4 +5,4 @@ namespace clap { namespace helpers { Ignore, Terminate, }; -}} // namespace clap::helpers \ No newline at end of file +}} // namespace clap::helpers diff --git a/include/clap/helpers/param-queue.hh b/include/clap/helpers/param-queue.hh index 155d427..4f72702 100644 --- a/include/clap/helpers/param-queue.hh +++ b/include/clap/helpers/param-queue.hh @@ -71,4 +71,4 @@ namespace clap { namespace helpers { std::atomic _writeOffset = {0}; std::atomic _readOffset = {0}; }; -}} // namespace clap::helpers \ No newline at end of file +}} // namespace clap::helpers diff --git a/include/clap/helpers/plugin.hh b/include/clap/helpers/plugin.hh index 56482cc..e7a5c26 100644 --- a/include/clap/helpers/plugin.hh +++ b/include/clap/helpers/plugin.hh @@ -163,6 +163,18 @@ namespace clap { namespace helpers { return false; } + //----------------------// + // clap_plugin_surround // + //----------------------// + virtual bool implementsSurround() const noexcept { return false; } + virtual bool isChannelMaskSupported(uint64_t channel_mask) const noexcept { return false; } + virtual uint32_t getChannelMap(bool is_input, + uint32_t port_index, + uint8_t *channel_map, + uint32_t channel_map_capacity) const noexcept { + return 0; + } + //--------------------// // clap_plugin_params // //--------------------// @@ -339,12 +351,16 @@ namespace clap { namespace helpers { // clap_plugin_mini_curve_display // //--------------------------------// virtual bool implementsMiniCurveDisplay() const noexcept { return false; } - virtual bool miniCurveDisplayRender(uint16_t *data, uint32_t data_size) noexcept { - return false; + virtual uint32_t miniCurveDisplayGetCurveCount() const noexcept { return 0; } + virtual uint32_t miniCurveDisplayRender(clap_mini_curve_display_curve_data_t *curves, + uint32_t curves_size) noexcept { + return 0; } virtual void miniCurveDisplaySetObserved(bool is_observed) noexcept {} - virtual bool - miniCurveDisplayGetAxisName(char *x_name, char *y_name, uint32_t name_capacity) noexcept { + virtual bool miniCurveDisplayGetAxisName(uint32_t curve_index, + char *x_name, + char *y_name, + uint32_t name_capacity) noexcept { return false; } @@ -501,6 +517,15 @@ namespace clap { namespace helpers { const clap_audio_port_configuration_request *requests, uint32_t request_count) noexcept; + // clap_plugin_surround + static bool clapSurroundIsChannelMaskSupported(const clap_plugin_t *plugin, + uint64_t channel_mask) noexcept; + static uint32_t clapSurroundGetChannelMap(const clap_plugin_t *plugin, + bool is_input, + uint32_t port_index, + uint8_t *channel_map, + uint32_t channel_map_capacity) noexcept; + // clap_plugin_params static uint32_t clapParamsCount(const clap_plugin *plugin) noexcept; static bool clapParamsInfo(const clap_plugin *plugin, @@ -640,12 +665,14 @@ namespace clap { namespace helpers { static double clapGainAdjustmentMeteringGet(const clap_plugin_t *plugin) noexcept; // clap_plugin_mini_curve_display - static bool clapMiniCurveDisplayRender(const clap_plugin_t *plugin, - uint16_t *data, - uint32_t data_size) noexcept; + static uint32_t clapMiniCurveDisplayGetCurveCount(const clap_plugin_t *plugin) noexcept; + static uint32_t clapMiniCurveDisplayRender(const clap_plugin_t *plugin, + clap_mini_curve_display_curve_data_t *curves, + uint32_t curves_size) noexcept; static void clapMiniCurveDisplaySetObserved(const clap_plugin_t *plugin, bool is_observed) noexcept; static bool clapMiniCurveDisplayGetAxisName(const clap_plugin_t *plugin, + uint32_t curve_index, char *x_name, char *y_name, uint32_t name_capacity) noexcept; @@ -655,6 +682,7 @@ namespace clap { namespace helpers { static const clap_plugin_audio_ports_config _pluginAudioPortsConfig; static const clap_plugin_audio_ports_activation _pluginAudioPortsActivation; static const clap_plugin_configurable_audio_ports _pluginConfigurableAudioPorts; + static const clap_plugin_surround_t _pluginSurroundConfig; static const clap_plugin_gui _pluginGui; static const clap_plugin_latency _pluginLatency; static const clap_plugin_note_name _pluginNoteName; diff --git a/include/clap/helpers/plugin.hxx b/include/clap/helpers/plugin.hxx index 8b15261..f844101 100644 --- a/include/clap/helpers/plugin.hxx +++ b/include/clap/helpers/plugin.hxx @@ -69,6 +69,12 @@ namespace clap { namespace helpers { clapConfigurableAudioPortsApplyConfiguration, }; + template + const clap_plugin_surround_t Plugin::_pluginSurroundConfig = { + clapSurroundIsChannelMaskSupported, + clapSurroundGetChannelMap, + }; + template const clap_plugin_params Plugin::_pluginParams = { clapParamsCount, @@ -183,6 +189,7 @@ namespace clap { namespace helpers { template const clap_plugin_mini_curve_display Plugin::_pluginMiniCurveDisplay = { + clapMiniCurveDisplayGetCurveCount, clapMiniCurveDisplayRender, clapMiniCurveDisplaySetObserved, clapMiniCurveDisplayGetAxisName, @@ -483,6 +490,8 @@ namespace clap { namespace helpers { auto &self = from(plugin); self.ensureInitialized("extension"); + if (auto ext = self.extension(id)) + return ext; if (!strcmp(id, CLAP_EXT_STATE) && self.implementsState()) return &_pluginState; if (!strcmp(id, CLAP_EXT_STATE_CONTEXT) && self.implementsStateContext() && @@ -506,6 +515,8 @@ namespace clap { namespace helpers { return &_pluginAudioPortsActivation; if (!strcmp(id, CLAP_EXT_AUDIO_PORTS_CONFIG) && self.implementsAudioPortsConfig()) return &_pluginAudioPortsConfig; + if (!strcmp(id, CLAP_EXT_SURROUND) && self.implementsSurround()) + return &_pluginSurroundConfig; if (!strcmp(id, CLAP_EXT_CONFIGURABLE_AUDIO_PORTS) && self.implementsConfigurableAudioPorts()) return &_pluginConfigurableAudioPorts; if (!strcmp(id, CLAP_EXT_PARAMS) && self.implementsParams()) @@ -553,7 +564,7 @@ namespace clap { namespace helpers { return &_pluginMiniCurveDisplay; } - return self.extension(id); + return nullptr; } //-------------------// @@ -885,6 +896,42 @@ namespace clap { namespace helpers { return applyConfigurationSuccess; } + //----------------------// + // clap_plugin_surround // + //----------------------// + template + bool Plugin::clapSurroundIsChannelMaskSupported(const clap_plugin_t *plugin, + uint64_t channel_mask) noexcept { + auto &self = from(plugin); + auto methodName = "clap_plugin_surround.is_channel_mask_supported"; + self.ensureMainThread(methodName); + + return self.isChannelMaskSupported(channel_mask); + } + + template + uint32_t Plugin::clapSurroundGetChannelMap(const clap_plugin_t *plugin, + bool is_input, + uint32_t port_index, + uint8_t *channel_map, + uint32_t channel_map_capacity) noexcept { + auto &self = from(plugin); + auto methodName = "clap_plugin_surround.get_channel_map"; + self.ensureMainThread(methodName); + + const uint32_t channel_count = + self.getChannelMap(is_input, port_index, channel_map, channel_map_capacity); + + if (l >= CheckingLevel::Minimal && channel_count > channel_map_capacity) { + self._host.pluginMisbehaving( + "Plugin's functions clap_plugin_surround.get_channel_map cannot return a channel " + "count greater than channel_map_capacity."); + return channel_map_capacity; + } + + return channel_count; + } + //--------------------// // clap_plugin_params // //--------------------// @@ -1870,27 +1917,27 @@ namespace clap { namespace helpers { // clap_plugin_mini_curve_display // //--------------------------------// template - bool Plugin::clapMiniCurveDisplayRender(const clap_plugin_t *plugin, - uint16_t *data, - uint32_t data_size) noexcept { + uint32_t Plugin::clapMiniCurveDisplayRender(const clap_plugin_t *plugin, + clap_mini_curve_display_curve_data_t *curves, + uint32_t curves_size) noexcept { auto &self = from(plugin); self.ensureMainThread("clap_plugin_mini_curve_display.render"); if (l >= CheckingLevel::Minimal) { - if (!data) { + if (!curves) { self.hostMisbehaving( "clap_plugin_mini_curve_display.render() called with null data pointer!!!"); return false; } - if (data_size == 0) { + if (curves_size == 0) { self.hostMisbehaving("clap_plugin_mini_curve_display.render() called an empty data " "array, meaningless..."); return false; } } - return self.miniCurveDisplayRender(data, data_size); + return self.miniCurveDisplayRender(curves, curves_size); } template @@ -1902,8 +1949,17 @@ namespace clap { namespace helpers { self.miniCurveDisplaySetObserved(is_observed); } + template + uint32_t Plugin::clapMiniCurveDisplayGetCurveCount(const clap_plugin_t *plugin) noexcept { + auto &self = from(plugin); + self.ensureMainThread("clap_plugin_mini_curve_display.get_curve_count"); + + return self.miniCurveDisplayGetCurveCount(); + } + template bool Plugin::clapMiniCurveDisplayGetAxisName(const clap_plugin_t *plugin, + uint32_t curve_index, char *x_name, char *y_name, uint32_t name_capacity) noexcept { @@ -1916,9 +1972,18 @@ namespace clap { namespace helpers { "clap_plugin_mini_curve_display.get_axis_name() called with null name pointer(s)"); return false; } + + const auto curve_count = self.miniCurveDisplayGetCurveCount(); + if (curve_index >= curve_count) { + std::ostringstream os; + os << "clap_plugin_mini_curve_display.get_axis_name() called with curve_index = " + << curve_index << ", while the curve count is " << curve_count; + self.hostMisbehaving(os.str()); + return false; + } } - return self.miniCurveDisplayGetAxisName(x_name, y_name, name_capacity); + return self.miniCurveDisplayGetAxisName(curve_index, x_name, y_name, name_capacity); } ///////////// diff --git a/include/clap/helpers/preset-discovery-indexer.hxx b/include/clap/helpers/preset-discovery-indexer.hxx index bbffc75..64455fc 100644 --- a/include/clap/helpers/preset-discovery-indexer.hxx +++ b/include/clap/helpers/preset-discovery-indexer.hxx @@ -61,4 +61,4 @@ namespace clap { namespace helpers { auto &self = from(indexer); return self.extension(id); } -}} // namespace clap::helpers \ No newline at end of file +}} // namespace clap::helpers diff --git a/include/clap/helpers/reducing-param-queue.hh b/include/clap/helpers/reducing-param-queue.hh index 13c3934..04c3aa0 100644 --- a/include/clap/helpers/reducing-param-queue.hh +++ b/include/clap/helpers/reducing-param-queue.hh @@ -45,4 +45,4 @@ namespace clap { namespace helpers { std::atomic _producer = nullptr; std::atomic _consumer = nullptr; }; -}} // namespace clap::helpers \ No newline at end of file +}} // namespace clap::helpers diff --git a/include/clap/helpers/reducing-param-queue.hxx b/include/clap/helpers/reducing-param-queue.hxx index 99e5936..180d55d 100644 --- a/include/clap/helpers/reducing-param-queue.hxx +++ b/include/clap/helpers/reducing-param-queue.hxx @@ -69,4 +69,4 @@ namespace clap { namespace helpers { _free = _consumer.load(); _consumer = nullptr; } -}} // namespace clap::helpers \ No newline at end of file +}} // namespace clap::helpers diff --git a/tests/create-an-actual-plugin.cc b/tests/create-an-actual-plugin.cc index f6f625c..a223569 100644 --- a/tests/create-an-actual-plugin.cc +++ b/tests/create-an-actual-plugin.cc @@ -39,4 +39,4 @@ CATCH_TEST_CASE("Create an Actual Plugin") { CATCH_REQUIRE(std::is_constructible::value); } -} \ No newline at end of file +} diff --git a/tests/event-list-tests.cc b/tests/event-list-tests.cc index 80935cf..454e0a5 100644 --- a/tests/event-list-tests.cc +++ b/tests/event-list-tests.cc @@ -40,4 +40,4 @@ namespace { CATCH_CHECK(ev->buffer[i] == i); } -} // namespace \ No newline at end of file +} // namespace diff --git a/tests/hex-encoder.cc b/tests/hex-encoder.cc index 9c4c485..65b41f2 100644 --- a/tests/hex-encoder.cc +++ b/tests/hex-encoder.cc @@ -26,4 +26,4 @@ namespace { CATCH_TEST_CASE("hex decoder - bad length") { CATCH_CHECK_THROWS(clap::helpers::hex_decode("f3d")); } -} // namespace \ No newline at end of file +} // namespace diff --git a/tests/main.cc b/tests/main.cc index cbcca02..2c344d5 100644 --- a/tests/main.cc +++ b/tests/main.cc @@ -1,2 +1,2 @@ #define CATCH_CONFIG_MAIN -#include \ No newline at end of file +#include diff --git a/tests/param-queue-tests.cc b/tests/param-queue-tests.cc index ee449fa..b12d32a 100644 --- a/tests/param-queue-tests.cc +++ b/tests/param-queue-tests.cc @@ -69,4 +69,4 @@ namespace { CATCH_CHECK(!failed); } -} // namespace \ No newline at end of file +} // namespace diff --git a/tests/plugin.cc b/tests/plugin.cc index 18267c9..13b3ad1 100644 --- a/tests/plugin.cc +++ b/tests/plugin.cc @@ -34,4 +34,4 @@ CATCH_TEST_CASE("Plugin - Link") { clap::helpers::CheckingLevel::None> *p2 = nullptr; clap::helpers::Plugin *p3 = nullptr; -} \ No newline at end of file +} diff --git a/tests/preset-discovery-indexer.cc b/tests/preset-discovery-indexer.cc index fe58905..937ffd2 100644 --- a/tests/preset-discovery-indexer.cc +++ b/tests/preset-discovery-indexer.cc @@ -20,4 +20,4 @@ CATCH_TEST_CASE("PresetDiscoveryIndexer - Link") { clap::helpers::CheckingLevel::None> *p2 = nullptr; clap::helpers::PresetDiscoveryIndexer *p3 = nullptr; -} \ No newline at end of file +} diff --git a/tests/preset-discovery-metadata-receiver.cc b/tests/preset-discovery-metadata-receiver.cc index 713b07a..91c846b 100644 --- a/tests/preset-discovery-metadata-receiver.cc +++ b/tests/preset-discovery-metadata-receiver.cc @@ -27,4 +27,4 @@ CATCH_TEST_CASE("PresetDiscoveryMetadataReceiver - Link") { clap::helpers::PresetDiscoveryMetadataReceiver *p3 = nullptr; -} \ No newline at end of file +} diff --git a/tests/preset-discovery-provider.cc b/tests/preset-discovery-provider.cc index 9ee48ba..4218f75 100644 --- a/tests/preset-discovery-provider.cc +++ b/tests/preset-discovery-provider.cc @@ -20,4 +20,4 @@ CATCH_TEST_CASE("PresetDiscoveryProvider - Link") { clap::helpers::CheckingLevel::None> *p2 = nullptr; clap::helpers::PresetDiscoveryProvider *p3 = nullptr; -} \ No newline at end of file +} diff --git a/tests/use-custom-extension.cc b/tests/use-custom-extension.cc new file mode 100644 index 0000000..d896762 --- /dev/null +++ b/tests/use-custom-extension.cc @@ -0,0 +1,199 @@ +#include +#include +#include + +#include + +namespace { + ////////////////////// + // custom extension // + ////////////////////// + + static CLAP_CONSTEXPR const char CLAP_HELPERS_EXT_TEST_PING_PONG[] = + "clap-helpers.test.ping-pong"; + + extern "C" { + typedef struct clap_plugin_test_ping_pong { + // Request the plugin to send back a pong + // [main-thread] + void(CLAP_ABI *ping)(const clap_plugin_t *plugin); + // Respond to a ping received from the plugin + // [main-thread] + void(CLAP_ABI *pong)(const clap_plugin_t *plugin); + } clap_plugin_ping_ping_t; + + typedef struct clap_host_test_ping_pong { + // Request the host to send back a pong + // [main-thread] + void(CLAP_ABI *ping)(const clap_host_t *host); + // Respond to a ping received from the host + // [main-thread] + void(CLAP_ABI *pong)(const clap_host_t *host); + } clap_host_test_ping_pong_t; + } + + ///////////////////////// + // template parameters // + ///////////////////////// + + static constexpr auto test_mh = clap::helpers::MisbehaviourHandler::Terminate; + static constexpr auto test_cl = clap::helpers::CheckingLevel::Maximal; + + ////////////////////////////// + // test host implementation // + ////////////////////////////// + + using test_plugin_proxy_base = clap::helpers::PluginProxy; + using test_host_base = clap::helpers::Host; + + struct test_plugin_proxy : test_plugin_proxy_base { + const clap_plugin_test_ping_pong *_pluginPingPong{}; + + test_plugin_proxy(const clap_plugin &plugin, const test_host_base &host) + : test_plugin_proxy_base(plugin, host) {} + + bool init() noexcept { + if (!test_plugin_proxy_base::init()) + return false; + getExtension(_pluginPingPong, CLAP_HELPERS_EXT_TEST_PING_PONG); + return true; + } + + bool canUsePingPong() const { + return _pluginPingPong && _pluginPingPong->ping && _pluginPingPong->pong; + } + void ping() const { + ensureMainThread("test_ping_pong.ping"); + _pluginPingPong->ping(&_plugin); + } + void pong() const { + ensureMainThread("test_ping_pong.ping"); + _pluginPingPong->pong(&_plugin); + } + }; + + struct test_host : test_host_base { + std::unique_ptr _pluginProxy; + bool _pluginCalledPong{}; + + test_host() + : test_host_base("Test Case Host", "Free Audio", "http://cleveraudio.org", "1.0.0") {} + ~test_host() { _pluginProxy->destroy(); } + + bool threadCheckIsMainThread() const noexcept override { return true; }; + bool threadCheckIsAudioThread() const noexcept override { return false; }; + void requestRestart() noexcept override {}; + void requestProcess() noexcept override {}; + void requestCallback() noexcept override {}; + + const void *getExtension(const char *extensionId) const noexcept override { + if (!strcmp(extensionId, CLAP_HELPERS_EXT_TEST_PING_PONG)) { + static const clap_host_test_ping_pong hostPingPong{ + [](const clap_host *host) { + auto &self = *static_cast(host->host_data); + self.ensureMainThread("clap_host_test_ping_pong.ping"); + if (self._pluginProxy->canUsePingPong()) + self._pluginProxy->pong(); + }, + [](const clap_host *host) { + auto &self = *static_cast(host->host_data); + self.ensureMainThread("clap_host_test_ping_pong.pong"); + self._pluginCalledPong = true; + }, + }; + return &hostPingPong; + } + return nullptr; + } + }; + + //////////////////////////////// + // test plugin implementation // + //////////////////////////////// + + using test_host_proxy = clap::helpers::HostProxy; + using test_plugin_base = clap::helpers::Plugin; + + struct test_host_custom_extension_proxy { + test_host_proxy &_hostProxy; + const clap_host_test_ping_pong *_hostPingPong{}; + + test_host_custom_extension_proxy(test_host_proxy &hostProxy) : _hostProxy(hostProxy) {} + void init() { _hostProxy.getExtension(_hostPingPong, CLAP_HELPERS_EXT_TEST_PING_PONG); } + + bool canUsePingPong() const { + return _hostPingPong && _hostPingPong->ping && _hostPingPong->pong; + } + void ping() const noexcept { + _hostProxy.ensureMainThread("test_ping_pong.ping"); + _hostPingPong->ping(_hostProxy.clapHost()); + } + void pong() const noexcept { + _hostProxy.ensureMainThread("test_ping_pong.ping"); + _hostPingPong->pong(_hostProxy.clapHost()); + } + }; + + struct test_plugin : test_plugin_base { + test_host_proxy _hostProxy; + test_host_custom_extension_proxy _hostExtensionProxy; + bool _hostCalledPong{}; + + static clap_plugin_descriptor &dummyDesc() { + static clap_plugin_descriptor d{}; + return d; + } + test_plugin(const clap_host *host) + : _hostProxy(host), test_plugin_base(&dummyDesc(), host), _hostExtensionProxy(_hostProxy) { + } + + const void *extension(const char *id) noexcept override { + if (!strcmp(id, CLAP_HELPERS_EXT_TEST_PING_PONG)) { + static const clap_plugin_test_ping_pong pluginPingPong{ + [](const clap_plugin *plugin) { + auto &self = *static_cast(&from(plugin)); + self.ensureMainThread("clap_plugin_test_ping_pong.ping"); + self._hostExtensionProxy.pong(); + }, + [](const clap_plugin *plugin) { + auto &self = *static_cast(&from(plugin)); + self.ensureMainThread("clap_plugin_test_ping_pong.pong"); + self._hostCalledPong = true; + }, + }; + return &pluginPingPong; + } + return nullptr; + } + + bool init() noexcept override { + _hostExtensionProxy.init(); + return true; + } + }; +} // namespace + +CATCH_TEST_CASE("Use a custom extension") { + CATCH_WHEN("host and plugin are initialized") { + test_host host{}; + auto plugin = new test_plugin(host.clapHost()); + host._pluginProxy = std::make_unique(*plugin->clapPlugin(), host); + CATCH_REQUIRE(host._pluginProxy->init()); + + CATCH_THEN("no pong responds are registered yet") { + CATCH_REQUIRE_FALSE(plugin->_hostCalledPong); + CATCH_REQUIRE_FALSE(host._pluginCalledPong); + } + + CATCH_AND_WHEN("host calls ping on plugin") { + host._pluginProxy->ping(); + CATCH_THEN("plugin responds with pong") { CATCH_CHECK(host._pluginCalledPong); } + } + + CATCH_AND_WHEN("plugin calls ping on host") { + CATCH_REQUIRE(plugin->_hostExtensionProxy.canUsePingPong()); + plugin->_hostExtensionProxy.ping(); + CATCH_THEN("host responds with pong") { CATCH_CHECK(plugin->_hostCalledPong); } + } + } +}