1
0
basics/stfx/clap/stfx-clap.h

478 lines
14 KiB
C++

#include "clap/clap.h"
#include <vector>
#include <string>
#include <functional>
#include <initializer_list>
#include <cstdio>
#include <optional>
#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;
}
Crc32 copy() {
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;
scanParams();
}
// plugin state
bool isConfigured = false;
// for library STFX, all inputs/outputs (main and aux) get concatenated together
std::vector<const float *> inputBuffers;
std::vector<float *> outputBuffers;
void processEvent(const clap_event_header *header) {
if (header->space_id != CLAP_CORE_EVENT_SPACE_ID) return; // only core events supported atm
if (header->type == CLAP_EVENT_PARAM_VALUE) {
auto *event = (clap_event_param_value *)header;
auto *param = (Param *)event->cookie;
if (!param) {
auto paramId = event->param_id;
for (auto &p : params) {
if (p.id == paramId) {
param = &p;
break;
}
}
if (!param) return; // invalid parameter
} else if (param->id != event->param_id) {
return; // inconsistent ID / cookie
}
if (param->rangeParam) {
*param->rangeParam = param->rangeInfo->fromUnit(event->value);
} else {
*param->steppedParam = int(std::round(event->value));
}
} else {
LOG_EXPR(header->size);
LOG_EXPR(header->time);
LOG_EXPR(header->space_id);
LOG_EXPR(header->type);
LOG_EXPR(header->flags);
}
}
// CLAP 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;
auto &config = plugin.effect.config;
if (!plugin.isConfigured || sampleRate != plugin.effect.config.sampleRate) {
auto prevConfig = config;
plugin.isConfigured = plugin.effect.configure();
if (!plugin.isConfigured) {
// Can't change config (e.g. sample-rate/ports/etc.) here, return to previous
config = prevConfig;
}
}
size_t inputChannels = config.inputChannels;
for (auto &a : config.auxInputs) inputChannels += a;
plugin.inputBuffers.reserve(inputChannels);
size_t outputChannels = config.outputChannels;
for (auto &a : config.auxOutputs) a += outputChannels;
plugin.outputBuffers.reserve(outputChannels);
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;
}
static clap_process_status plugin_process(const clap_plugin *obj, const clap_process *process) {
auto &plugin = *(Plugin *)obj;
auto &inputBuffers = plugin.inputBuffers;
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;
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]);
}
}
size_t length = process->frames_count;
auto inputEventCount = process->in_events->size(process->in_events);
size_t offset = 0, nextEventIndex = 0;
while (offset < length) {
size_t nextEventOffset = length;
const clap_event_header *event = nullptr;
if (nextEventIndex < inputEventCount) {
event = process->in_events->get(process->in_events, nextEventIndex);
nextEventOffset = std::max<size_t>(offset, event->time);
}
// process up until the next event (or end)
if (nextEventOffset > offset) {
auto delta = nextEventOffset - offset;
plugin.effect.process(inputBuffers.data(), outputBuffers.data(), delta);
offset = nextEventOffset;
for (auto &p : inputBuffers) p += delta;
for (auto &p : outputBuffers) p += delta;
}
if (event) {
plugin.processEvent(event);
++nextEventIndex;
}
}
inputBuffers.resize(0);
outputBuffers.resize(0);
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::optional<RangeParamInfo> rangeInfo;
typename Effect::ParamStepped *steppedParam = nullptr;
std::optional<SteppedParamInfo> steppedInfo;
void setClapInfo() {
flags |= CLAP_PARAM_IS_AUTOMATABLE;
cookie = this;
if (rangeParam) {
std::strncpy(name, rangeInfo->name.c_str(), CLAP_NAME_SIZE);
// STFX range params are mapped to [0, 1] so we can give them a nonlinear shape
min_value = 0;
max_value = 1;
default_value = rangeInfo->toUnit(rangeInfo->defaultValue);
} else {
std::strncpy(name, steppedInfo->name.c_str(), CLAP_NAME_SIZE);
flags |= CLAP_PARAM_IS_STEPPED;
min_value = steppedInfo->low;
max_value = steppedInfo->high;
default_value = steppedInfo->defaultValue;
}
}
};
std::vector<Param> params;
struct ParamScanner {
std::vector<Param> &params;
Crc32 crc;
// Do nothing for most types
template<class V>
void operator()(const char *key, V &v) {
LOG_EXPR(key);
if constexpr (std::is_void_v<decltype(v.state(*this))>) {
LOG_EXPR("recursing");
ParamScanner recurse{*this};
recurse.crc.addString(key, true);
v.state(recurse);
}
}
// Extra methods we add for STFX storage
void info(const char *, const char *) {}
int version(int v) {
return v;
}
template<class PR>
RangeParamInfo & range(const char *key, PR &param) {
params.emplace_back();
auto &entry = params.back();
entry.id = crc.copy().addString(key, true).done();
entry.rangeParam = &param;
return entry.rangeInfo.emplace(param);
}
template<class PS>
SteppedParamInfo & stepped(const char *key, PS &param) {
params.emplace_back();
auto &entry = params.back();
entry.id = crc.copy().addString(key, true).done();
entry.steppedParam = &param;
return entry.steppedInfo.emplace(param);
}
};
void scanParams() {
params.resize(0);
ParamScanner scanner{params};
effect.state(scanner);
for (auto &entry : params) entry.setClapInfo();
}
// CLAP parameter methods
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 &param : plugin.params) {
if (param.id == paramId) {
if (param.rangeParam) {
*value = param.rangeInfo->toUnit((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 *text, uint32_t textCapacity) {
auto &plugin = *(Plugin *)obj;
for (auto &param : plugin.params) {
if (param.id == paramId) {
if (param.rangeParam) {
auto str = param.rangeInfo->toString(param.rangeInfo->fromUnit(value));
std::strncpy(text, str.c_str(), textCapacity);
} else {
auto str = param.steppedInfo->toString(int(std::round(value)));
std::strncpy(text, str.c_str(), textCapacity);
}
return true;
}
}
return false;
}
static bool params_text_to_value(const clap_plugin *obj, clap_id paramId, const char *text, double *value) {
auto &plugin = *(Plugin *)obj;
for (auto &param : plugin.params) {
if (param.id == paramId) {
std::string str = text;
if (param.rangeParam) {
*value = param.rangeInfo->toUnit(param.rangeInfo->fromString(str));
} else {
*value = param.steppedInfo->fromString(str);
}
return true;
}
}
return false;
}
static void params_flush(const clap_plugin *obj, const clap_input_events *inEvents, const clap_output_events *outEvents) {
auto &plugin = *(Plugin *)obj;
auto count = inEvents->size(inEvents);
for (uint32_t i = 0; i < count; ++i) {
auto *header = inEvents->get(inEvents, i);
plugin.processEvent(header);
}
}
// CLAP audio port methods
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