327 lines
9.9 KiB
C++
327 lines
9.9 KiB
C++
#include "clap/clap.h"
|
|
|
|
#include <vector>
|
|
#include <string>
|
|
#include <functional>
|
|
#include <initializer_list>
|
|
#include <cstdio>
|
|
|
|
#include "./param-info.h"
|
|
|
|
namespace stfx { namespace clap {
|
|
|
|
// A CLAP plugin made from an STFX template
|
|
template<template<class> class EffectSTFX>
|
|
struct Plugin;
|
|
|
|
// A helper to make a CLAP plugin factory from STFX templates
|
|
struct Plugins {
|
|
template<template<class> class EffectSTFX, class ...Args>
|
|
void add(clap_plugin_descriptor desc, std::initializer_list<const char *> 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<EffectSTFX>(*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<std::vector<const char *>> featureLists;
|
|
std::vector<clap_plugin_descriptor> descriptors;
|
|
std::vector<std::function<const clap_plugin_t *(const clap_host *)>> 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<template<class> class EffectSTFX>
|
|
struct Plugin : public clap_plugin {
|
|
const Plugins &plugins;
|
|
const clap_host *host;
|
|
using Effect = stfx::LibraryEffect<float, EffectSTFX>;
|
|
Effect effect;
|
|
|
|
template<class ...Args>
|
|
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<const float *> inputBuffers;
|
|
std::vector<float *> 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<RangeParamInfo> rangeInfo;
|
|
typename Effect::ParamStepped *steppedParam = nullptr;
|
|
std::unique_ptr<SteppedParamInfo> steppedInfo;
|
|
};
|
|
std::vector<Param> 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
|