#include "clap/clap.h" #include #include #include #include #include #include "./param-info.h" namespace stfx { namespace clap { // A CLAP plugin made from an STFX template template class EffectSTFX> struct Plugin; // A helper to make a CLAP plugin factory from STFX templates struct Plugins { template class EffectSTFX, class ...Args> void add(clap_plugin_descriptor desc, std::initializer_list features, Args ...args) { size_t index = featureLists.size(); featureLists.emplace_back(features); featureLists[index].push_back(nullptr); desc.features = featureLists[index].data(); descriptors.push_back(desc); creates.push_back([=](const clap_host *host){ return new Plugin(*this, &descriptors[index], host, args...); }); } bool clap_init(const char *path) { modulePath = path; return true; } void clap_deinit() {} const void * clap_get_factory(const char *id) { if (!std::strcmp(id, CLAP_PLUGIN_FACTORY_ID)) { // static variables like this are thread-safe (since C++11) // https://en.cppreference.com/w/cpp/language/storage_duration.html#Static_block_variables static PluginFactory factory{*this}; return &factory; } return nullptr; } std::string modulePath; private: std::vector> featureLists; std::vector descriptors; std::vector> creates; struct PluginFactory : public clap_plugin_factory { Plugins &plugins; PluginFactory(Plugins &plugins) : plugins(plugins) { get_plugin_count = static_get_plugin_count; get_plugin_descriptor = static_get_plugin_descriptor; create_plugin = static_create_plugin; } static uint32_t static_get_plugin_count(const clap_plugin_factory *factory) { const auto &plugins = ((PluginFactory *)factory)->plugins; return uint32_t(plugins.creates.size()); } static const clap_plugin_descriptor * static_get_plugin_descriptor(const clap_plugin_factory *factory, uint32_t index) { const auto &plugins = ((PluginFactory *)factory)->plugins; if (index >= plugins.descriptors.size()) return nullptr; return &plugins.descriptors[index]; } static const clap_plugin * static_create_plugin(const clap_plugin_factory *factory, const clap_host *host, const char *pluginId) { const auto &plugins = ((PluginFactory *)factory)->plugins; for (size_t index = 0; index < plugins.descriptors.size(); ++index) { auto &desc = plugins.descriptors[index]; if (!std::strcmp(pluginId, desc.id)) { return plugins.creates[index](host); } } return nullptr; } }; }; struct Crc32 { void add(uint8_t byte) { uint32_t val = (crc^byte)&0xFF; for (int i = 0; i < 8; ++i) { val = (val&1) ? (val>>1)^0xEDB88320 : (val>>1); } crc = val^(crc>>8); } Crc32 & addString(const char *str, bool includeNull=true) { while (*str) { add(uint8_t(*str)); ++str; } if (includeNull) add(0); return *this; } uint32_t done() const { return crc^0xFFFFFFFFu; } private: uint32_t crc = 0xFFFFFFFFu; }; template class EffectSTFX> struct Plugin : public clap_plugin { const Plugins &plugins; const clap_host *host; using Effect = stfx::LibraryEffect; Effect effect; template Plugin(const Plugins &plugins, const clap_plugin_descriptor *desc, const clap_host *host, Args ...args) : plugins(plugins), host(host), effect(args...) { this->desc = desc; this->plugin_data = nullptr; this->init = plugin_init; this->destroy = plugin_destroy; this->activate = plugin_activate; this->deactivate = plugin_deactivate; this->start_processing = plugin_start_processing; this->stop_processing = plugin_stop_processing; this->reset = plugin_reset; this->process = plugin_process; this->get_extension = plugin_get_extension; this->on_main_thread = plugin_on_main_thread; } // Configuration state, used by multiple extensions bool isConfigured = false; // Main plugin methods static bool plugin_init(const clap_plugin *obj) { auto &plugin = *(Plugin *)obj; LOG_EXPR(plugin.plugins.modulePath); return true; } static void plugin_destroy(const clap_plugin *obj) { delete (Plugin *)obj; } static bool plugin_activate(const clap_plugin *obj, double sampleRate, uint32_t minFrames, uint32_t maxFrames) { auto &plugin = *(Plugin *)obj; if (!plugin.isConfigured || sampleRate != plugin.effect.config.sampleRate) { auto prevConfig = plugin.effect.config; plugin.isConfigured = plugin.effect.configure(); if (!plugin.isConfigured) { // Can't change config (e.g. sample-rate/ports/etc.) here plugin.effect.config = prevConfig; } } auto &inputBuffers = plugin.inputBuffers; inputBuffers.resize(plugin.effect.config.inputChannels); for (auto &a : plugin.effect.config.auxInputs) { inputBuffers.resize(inputBuffers.size() + a); } auto &outputBuffers = plugin.outputBuffers; outputBuffers.resize(plugin.effect.config.outputChannels); for (auto &a : plugin.effect.config.auxOutputs) { outputBuffers.resize(outputBuffers.size() + a); } return plugin.isConfigured; } static void plugin_deactivate(const clap_plugin *obj) {} static bool plugin_start_processing(const clap_plugin *obj) { auto &plugin = *(Plugin *)obj; return plugin.isConfigured; } static void plugin_stop_processing(const clap_plugin *obj) {} static void plugin_reset(const clap_plugin *obj) { auto &plugin = *(Plugin *)obj; if (plugin.isConfigured) plugin.effect.reset(); } static const void * plugin_get_extension(const clap_plugin *obj, const char *extId) { if (!std::strcmp(extId, CLAP_EXT_PARAMS)) { static struct clap_plugin_params ext{ params_count, params_get_info, params_get_value, params_value_to_text, params_text_to_value, params_flush }; return &ext; } else if (!std::strcmp(extId, CLAP_EXT_AUDIO_PORTS)) { static struct clap_plugin_audio_ports ext{ audio_ports_count, audio_ports_get }; return &ext; } return nullptr; } std::vector inputBuffers; std::vector outputBuffers; static clap_process_status plugin_process(const clap_plugin *obj, const clap_process *process) { auto &plugin = *(Plugin *)obj; auto &inputBuffers = plugin.inputBuffers; inputBuffers.resize(0); for (uint32_t i = 0; i < process->audio_inputs_count; ++i) { auto &buffer = process->audio_inputs[i]; for (uint32_t c = 0; c < buffer.channel_count; ++c) { inputBuffers.push_back(buffer.data32[c]); } } auto &outputBuffers = plugin.outputBuffers; outputBuffers.resize(0); for (uint32_t i = 0; i < process->audio_outputs_count; ++i) { auto &buffer = process->audio_outputs[i]; for (uint32_t c = 0; c < buffer.channel_count; ++c) { outputBuffers.push_back(buffer.data32[c]); } } plugin.effect.process(inputBuffers.data(), outputBuffers.data(), process->frames_count); return CLAP_PROCESS_CONTINUE; } static void plugin_on_main_thread(const clap_plugin *obj) {} // parameters struct Param : public clap_param_info { typename Effect::ParamRange *rangeParam = nullptr; std::unique_ptr rangeInfo; typename Effect::ParamStepped *steppedParam = nullptr; std::unique_ptr steppedInfo; }; std::vector params; void scanParams() { params.resize(0); } static uint32_t params_count(const clap_plugin *obj) { auto &plugin = *(Plugin *)obj; return plugin.params.size(); } static bool params_get_info(const clap_plugin *obj, uint32_t index, clap_param_info *info) { auto &plugin = *(Plugin *)obj; if (index >= plugin.params.size()) return false; *info = plugin.params[index]; return true; } static bool params_get_value(const clap_plugin *obj, clap_id paramId, double *value) { auto &plugin = *(Plugin *)obj; for (auto ¶m : plugin.params) { if (param.id == paramId) { if (param.rangeParam) { *value = (double)*param.rangeParam; } else { *value = (int)*param.steppedParam; } return true; } } return false; } static bool params_value_to_text(const clap_plugin *obj, clap_id paramId, double value, char *outBuffer, uint32_t outCapacity) { return false; } static bool params_text_to_value(const clap_plugin *obj, clap_id paramId, const char *text, double *value) { return false; } static void params_flush(const clap_plugin *obj, const clap_input_events *inEvents, const clap_output_events *outEvents) { // not implemented yet } static uint32_t audio_ports_count(const clap_plugin *obj, bool inputPorts) { auto &plugin = *(Plugin *)obj; if (!plugin.isConfigured) { plugin.isConfigured = plugin.effect.configurePersistent(); if (!plugin.isConfigured) return 0; } auto &config = plugin.effect.config; if (inputPorts) { return config.auxInputs.size() + (config.inputChannels > 0); } else { return config.auxOutputs.size() + (config.outputChannels > 0); } } static bool audio_ports_get(const clap_plugin *obj, uint32_t index, bool inputPorts, clap_audio_port_info *info) { auto &plugin = *(Plugin *)obj; if (!plugin.isConfigured) return false; auto &config = plugin.effect.config; clap_id portIdBase = (inputPorts ? 0x1000000 : 0x2000000); auto main = uint32_t(inputPorts ? config.inputChannels : config.outputChannels); auto &aux = (inputPorts ? config.auxInputs : config.auxOutputs); auto auxIndex = index; if (main) { if (index == 0) { *info = { .id=portIdBase, .name={'m', 'a', 'i', 'n'}, .flags=CLAP_AUDIO_PORT_IS_MAIN, .channel_count=main, .port_type=nullptr, .in_place_pair=CLAP_INVALID_ID }; return true; } --auxIndex; } if (auxIndex < aux.size()) { *info = { .id=portIdBase + index, .name={'a', 'u', 'x'}, .flags=CLAP_AUDIO_PORT_IS_MAIN, .channel_count=main, .port_type=nullptr, .in_place_pair=CLAP_INVALID_ID }; if (aux.size() > 1) { info->name[3] = '1' + auxIndex; } return true; } return false; } }; }} // namespace