CLAP front-end processes audio (no parameters though)
This commit is contained in:
parent
417dbc1944
commit
89aebf5d9e
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
||||
**/.DS_Store
|
||||
**/env.sh
|
||||
|
||||
@ -40,7 +40,7 @@ include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
clap-wrapper
|
||||
GIT_REPOSITORY https://github.com/free-audio/clap-wrapper
|
||||
GIT_TAG 0.12.1 # first version with WCLAP stuff
|
||||
GIT_TAG v0.12.1 # first version with WCLAP stuff
|
||||
GIT_SHALLOW ON
|
||||
)
|
||||
FetchContent_MakeAvailable(clap-wrapper)
|
||||
@ -62,8 +62,12 @@ target_link_libraries(${NAME}_static PUBLIC
|
||||
clap
|
||||
clap-helpers
|
||||
)
|
||||
target_sources(${NAME}_static PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/source/${name}.cpp
|
||||
target_include_directories(${NAME}_static PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../modules
|
||||
)
|
||||
target_sources(${NAME}_static PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/source/${NAME}.cpp
|
||||
)
|
||||
|
||||
make_clapfirst_plugins(
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
.PHONY: build build-emscripten emsdk
|
||||
PROJECT := plugins
|
||||
PLUGIN := example
|
||||
PLUGIN := basics
|
||||
|
||||
CMAKE_PARAMS := -DCMAKE_LIBRARY_OUTPUT_DIRECTORY=.. -G Xcode # -DCMAKE_BUILD_TYPE=Release
|
||||
|
||||
|
||||
@ -3,11 +3,11 @@
|
||||
# define LOG_EXPR(expr) std::cout << #expr " = " << (expr) << std::endl;
|
||||
#endif
|
||||
|
||||
#include "../crunch.h"
|
||||
#include "../limiter.h"
|
||||
#include "../reverb.h"
|
||||
#include "signalsmith-basics/crunch.h"
|
||||
#include "signalsmith-basics/limiter.h"
|
||||
#include "signalsmith-basics/reverb.h"
|
||||
|
||||
#include "./clap-stfx.h"
|
||||
#include "../../stfx/clap/stfx-clap.h"
|
||||
|
||||
static stfx::clap::Plugins plugins;
|
||||
bool clap_init(const char *path) {
|
||||
@ -55,7 +55,7 @@ bool clap_init(const char *path) {
|
||||
return plugins.clap_init(path);
|
||||
}
|
||||
void clap_deinit() {
|
||||
plugins.clap_deinit(path);
|
||||
plugins.clap_deinit();
|
||||
}
|
||||
const void * clap_get_factory(const char *id) {
|
||||
return plugins.clap_get_factory(id);
|
||||
|
||||
@ -1,41 +0,0 @@
|
||||
#include "clap/clap.h"
|
||||
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
|
||||
namespace stfx { namespace clap {
|
||||
|
||||
template<template<class> class EffectSTFX>
|
||||
struct Plugin : public clap_plugin {
|
||||
template<class ...Args>
|
||||
Plugin(const clap_plugin_descriptor *desc, Args ...args) : effect(args...) {
|
||||
this->desc = desc;
|
||||
this->plugin_data = nullptr;
|
||||
}
|
||||
private:
|
||||
stfx::LibraryEffect<float, EffectSTFX> effect;
|
||||
};
|
||||
|
||||
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([=](){
|
||||
return new Plugin<EffectSTFX>(&descriptors[index], args...);
|
||||
});
|
||||
}
|
||||
private:
|
||||
std::vector<std::vector<const char *>> featureLists;
|
||||
std::vector<clap_plugin_descriptor> descriptors;
|
||||
std::vector<std::function<const clap_plugin_t *()>> creates;
|
||||
};
|
||||
|
||||
}} // namespace
|
||||
37
crunch.h
37
crunch.h
@ -4,9 +4,10 @@ See LICENSE.txt and SUPPORT.txt */
|
||||
#define SIGNALSMITH_BASICS_CRUNCH_H
|
||||
|
||||
#include "dsp/rates.h"
|
||||
#include "dsp/filters.h"
|
||||
SIGNALSMITH_DSP_VERSION_CHECK(1, 4, 1)
|
||||
|
||||
#include "./stfx-library.h"
|
||||
#include "stfx/stfx-library.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
@ -19,14 +20,14 @@ using CrunchFloat = stfx::LibraryEffect<float, CrunchSTFX>;
|
||||
using CrunchDouble = stfx::LibraryEffect<double, CrunchSTFX>;
|
||||
|
||||
template<class BaseEffect>
|
||||
class CrunchSTFX : public BaseEffect {
|
||||
static constexpr int oversampleHalfLatency = 16;
|
||||
static constexpr Sample autoGainLevel = 0.1;
|
||||
|
||||
struct CrunchSTFX : public BaseEffect {
|
||||
using typename BaseEffect::Sample;
|
||||
using typename BaseEffect::ParamRange;
|
||||
using typename BaseEffect::ParamStepped;
|
||||
|
||||
static constexpr int oversampleHalfLatency = 16;
|
||||
static constexpr Sample autoGainLevel = 0.1;
|
||||
|
||||
ParamRange drive{4};
|
||||
ParamRange fuzz{0};
|
||||
ParamRange toneHz{2000};
|
||||
@ -41,27 +42,20 @@ class CrunchSTFX : public BaseEffect {
|
||||
int version = storage.version(0);
|
||||
if (version != 0) return;
|
||||
|
||||
using namespace signalsmith::units;
|
||||
storage.range("drive", drive)
|
||||
using stfx::units::dbToGain;
|
||||
stfx::units::rangeGain(storage.range("drive", drive)
|
||||
.info("drive", "pre-distortion input gain")
|
||||
.range(dbToGain(-12), 4, dbToGain(40))
|
||||
.unit("dB", 1, dbToGain, gainToDb)
|
||||
.unit("");
|
||||
storage.range("fuzz", fuzz)
|
||||
.range(dbToGain(-12), 4, dbToGain(40)));
|
||||
stfx::units::rangePercent(storage.range("fuzz", fuzz)
|
||||
.info("fuzz", "amplitude-independent distortion")
|
||||
.range(0, 0.5, 1)
|
||||
.unit("%", 0, pcToRatio, ratioToPc);
|
||||
storage.range("toneHz", toneHz)
|
||||
.range(0, 0.5, 1));
|
||||
stfx::units::rangeHz(storage.range("toneHz", toneHz)
|
||||
.info("tone", "limits the brightness of the distortion")
|
||||
.range(100, 4000, 20000)
|
||||
.unit("Hz", 0, 0, 1000)
|
||||
.unit("kHz", 1, kHzToHz, hzToKHz, 1000, 1e100);
|
||||
.range(100, 4000, 20000));
|
||||
|
||||
storage.range("outGain", outGain)
|
||||
stfx::units::rangeGain(storage.range("outGain", outGain)
|
||||
.info("out", "output gain")
|
||||
.range(dbToGain(-12), 1, dbToGain(24))
|
||||
.unit("dB", 1, dbToGain, gainToDb)
|
||||
.unit("");
|
||||
.range(dbToGain(-12), 1, dbToGain(24)));
|
||||
}
|
||||
|
||||
template<class Config>
|
||||
@ -120,6 +114,7 @@ class CrunchSTFX : public BaseEffect {
|
||||
oversampler.downChannel(c, io.output[c], block.length);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int channels = 0;
|
||||
signalsmith::rates::Oversampler2xFIR<Sample> oversampler;
|
||||
|
||||
1
include/signalsmith-basics/crunch.h
Normal file
1
include/signalsmith-basics/crunch.h
Normal file
@ -0,0 +1 @@
|
||||
#include "../../crunch.h"
|
||||
1
include/signalsmith-basics/limiter.h
Normal file
1
include/signalsmith-basics/limiter.h
Normal file
@ -0,0 +1 @@
|
||||
#include "../../limiter.h"
|
||||
1
include/signalsmith-basics/reverb.h
Normal file
1
include/signalsmith-basics/reverb.h
Normal file
@ -0,0 +1 @@
|
||||
#include "../../reverb.h"
|
||||
63
limiter.h
63
limiter.h
@ -7,26 +7,26 @@ Released under the Boost Software License (see LICENSE.txt) */
|
||||
#include "dsp/envelopes.h"
|
||||
SIGNALSMITH_DSP_VERSION_CHECK(1, 1, 0)
|
||||
|
||||
#include "./units.h"
|
||||
#include "./stfx-library.h"
|
||||
#include "stfx/stfx-library.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace signalsmith { namespace basics {
|
||||
|
||||
template<class BaseEffect>
|
||||
class LimiterSTFX : public BaseEffect {
|
||||
class LimiterSTFX;
|
||||
|
||||
using LimiterFloat = stfx::LibraryEffect<float, LimiterSTFX>;
|
||||
using LimiterDouble = stfx::LibraryEffect<double, LimiterSTFX>;
|
||||
|
||||
template<class BaseEffect>
|
||||
struct LimiterSTFX : public BaseEffect {
|
||||
using typename BaseEffect::Sample;
|
||||
using typename BaseEffect::ParamRange;
|
||||
using typename BaseEffect::ParamStepped;
|
||||
|
||||
int channels = 0;
|
||||
double maxDelayMs = 0;
|
||||
signalsmith::delay::MultiBuffer<Sample> multiBuffer;
|
||||
|
||||
public:
|
||||
ParamRange inputGain{1};
|
||||
ParamRange outputLimit{signalsmith::units::dbToGain(-3)};
|
||||
ParamRange outputLimit{stfx::units::dbToGain(-3)};
|
||||
ParamRange attackMs{20}, holdMs{0}, releaseMs{0};
|
||||
|
||||
ParamStepped smoothingStages{1};
|
||||
@ -36,43 +36,33 @@ namespace signalsmith { namespace basics {
|
||||
|
||||
template<class Storage>
|
||||
void state(Storage &storage) {
|
||||
using namespace signalsmith::units;
|
||||
storage.info("[Basics] Limiter", "A simple lookahead limiter");
|
||||
int version = storage.version(4);
|
||||
if (version != 4) return;
|
||||
|
||||
storage.range("inputGain", inputGain)
|
||||
using stfx::units::dbToGain;
|
||||
stfx::units::rangeGain(storage.range("inputGain", inputGain)
|
||||
.info("pre-gain", "amplifies the input before limiting")
|
||||
.range(dbToGain(-12), 1, dbToGain(24))
|
||||
.unit("dB", 1, dbToGain, gainToDb)
|
||||
.unit("");
|
||||
storage.range("outputLimit", outputLimit)
|
||||
.range(dbToGain(-12), 1, dbToGain(24)));
|
||||
stfx::units::rangeGain(storage.range("outputLimit", outputLimit)
|
||||
.info("limit", "maximum output amplitude")
|
||||
.range(dbToGain(-24), dbToGain(-12), 1)
|
||||
.unit("dB", 1, dbToGain, gainToDb)
|
||||
// Extra resolution between -1dB and 0dB
|
||||
.unit("dB", 2, dbToGain, gainToDb, dbToGain(-1), 1)
|
||||
.unit("");
|
||||
storage.range("attackMs", attackMs)
|
||||
.range(dbToGain(-24), dbToGain(-12), 1));
|
||||
stfx::units::rangeMs(storage.range("attackMs", attackMs)
|
||||
.info("attack", "envelope smoothing time")
|
||||
.range(1, 10, maxDelayMs/2)
|
||||
.unit("ms", 0);
|
||||
storage.range("holdMs", holdMs)
|
||||
.range(1, 10, maxDelayMs/2));
|
||||
stfx::units::rangeMs(storage.range("holdMs", holdMs)
|
||||
.info("hold", "hold constant after peaks")
|
||||
.range(0, 10, maxDelayMs/2)
|
||||
.unit("ms", 0);
|
||||
storage.range("releaseMs", releaseMs)
|
||||
.range(0, 10, maxDelayMs/2));
|
||||
stfx::units::rangeMs(storage.range("releaseMs", releaseMs)
|
||||
.info("release", "extra release time (in addition to attack + hold)")
|
||||
.range(0, 10, 250)
|
||||
.unit("ms", 0);
|
||||
.range(0, 10, 250));
|
||||
|
||||
storage.stepped("smoothingStages", smoothingStages)
|
||||
.info("smoothing", "smoothing filter(s) used for attack-smoothing")
|
||||
.label(1, "rect", "double");
|
||||
storage.range("linkChannels", linkChannels)
|
||||
stfx::units::rangePercent(storage.range("linkChannels", linkChannels)
|
||||
.info("link", "link channel gains together")
|
||||
.range(0, 0.5, 1)
|
||||
.unit("%", 0, pcToRatio, ratioToPc);
|
||||
.range(0, 0.5, 1));
|
||||
}
|
||||
|
||||
// Gain envelopes are calculated per-channel
|
||||
@ -110,7 +100,7 @@ namespace signalsmith { namespace basics {
|
||||
Sample sampleRate;
|
||||
std::vector<Sample> channelGains;
|
||||
template<class Config>
|
||||
void configure(Config &config) {
|
||||
void configureSTFX(Config &config) {
|
||||
channels = config.outputChannels = config.inputChannels;
|
||||
config.auxInputs.resize(0);
|
||||
config.auxOutputs.resize(0);
|
||||
@ -191,9 +181,12 @@ namespace signalsmith { namespace basics {
|
||||
}
|
||||
multiBuffer += block.length;
|
||||
}
|
||||
};
|
||||
|
||||
using Limiter = stfx::LibraryEffect<float, LimiterSTFX>;
|
||||
private:
|
||||
int channels = 0;
|
||||
double maxDelayMs = 0;
|
||||
signalsmith::delay::MultiBuffer<Sample> multiBuffer;
|
||||
};
|
||||
|
||||
}} // namespace
|
||||
|
||||
|
||||
61
reverb.h
61
reverb.h
@ -8,14 +8,19 @@ Released under the Boost Software License (see LICENSE.txt) */
|
||||
#include "dsp/filters.h"
|
||||
SIGNALSMITH_DSP_VERSION_CHECK(1, 3, 3)
|
||||
|
||||
#include "./stfx-library.h"
|
||||
#include "./units.h"
|
||||
#include "stfx/stfx-library.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <random>
|
||||
|
||||
namespace signalsmith { namespace basics {
|
||||
|
||||
template<class BaseEffect>
|
||||
struct ReverbSTFX;
|
||||
|
||||
using ReverbFloat = stfx::LibraryEffect<float, ReverbSTFX>;
|
||||
using ReverbDouble = stfx::LibraryEffect<double, ReverbSTFX>;
|
||||
|
||||
template<class BaseEffect>
|
||||
struct ReverbSTFX : public BaseEffect {
|
||||
using typename BaseEffect::Sample;
|
||||
@ -37,63 +42,48 @@ namespace signalsmith { namespace basics {
|
||||
|
||||
template<class Storage>
|
||||
void state(Storage &storage) {
|
||||
using namespace signalsmith::units;
|
||||
|
||||
storage.info("[Basics] Reverb", "An FDN reverb");
|
||||
int version = storage.version(5);
|
||||
if (version != 5) return;
|
||||
|
||||
storage.range("dry", dry)
|
||||
using stfx::units::dbToGain;
|
||||
stfx::units::rangeGain(storage.range("dry", dry)
|
||||
.info("dry", "dry signal gain")
|
||||
.range(0, 1, 4)
|
||||
.unit("dB", 1, dbToGain, gainToDb)
|
||||
.unit("%", 0, pcToRatio, ratioToPc)
|
||||
.exact(0, "off")
|
||||
.unit("");
|
||||
.range(0, 1, 4));
|
||||
|
||||
storage.range("wet", wet)
|
||||
stfx::units::rangeGain(storage.range("wet", wet)
|
||||
.info("wet", "reverb tail gain")
|
||||
.range(0, 1, 4)
|
||||
.unit("dB", 1, dbToGain, gainToDb)
|
||||
.unit("%", 0, pcToRatio, ratioToPc)
|
||||
.exact(0, "off")
|
||||
.unit("");
|
||||
.range(0, 1, 4));
|
||||
|
||||
storage.range("roomMs", roomMs)
|
||||
stfx::units::rangeMs(storage.range("roomMs", roomMs)
|
||||
.info("room", "room size (1ms ~ 1 foot)")
|
||||
.range(10, 100, maxRoomMs)
|
||||
.unit("ms", 0);
|
||||
.range(10, 100, maxRoomMs));
|
||||
|
||||
storage.range("rt20", rt20)
|
||||
stfx::units::rangeSec(storage.range("rt20", rt20)
|
||||
.info("decay", "RT20: decay time to -20dB")
|
||||
.range(0.01, 2, 30)
|
||||
.unit("seconds", 2, 0, 1)
|
||||
.unit("seconds", 1, 1, 1e100);
|
||||
.range(0.01, 2, 30));
|
||||
|
||||
storage.range("early", early)
|
||||
stfx::units::rangePercent(storage.range("early", early)
|
||||
.info("early", "Early reflections")
|
||||
.range(0, 1, 2.5)
|
||||
.unit("%", 0, pcToRatio, ratioToPc);
|
||||
.range(0, 1, 2.5));
|
||||
|
||||
storage.range("detune", detune)
|
||||
.info("detune", "Detuning rate (inside feedback loop)")
|
||||
.range(0, 5, 50)
|
||||
.unit("", 1);
|
||||
|
||||
storage.range("lowCutHz", lowCutHz)
|
||||
stfx::units::rangeHz(storage.range("lowCutHz", lowCutHz)
|
||||
.info("low cut", "Removes low frequencies")
|
||||
.range(10, 80, 500)
|
||||
.unit("Hz", 0);
|
||||
.range(10, 80, 500));
|
||||
storage.range("lowDampRate", lowDampRate)
|
||||
.info("low damp", "Reduce low frequencies over time")
|
||||
.range(1, 2, 10)
|
||||
.unit("", 1);
|
||||
|
||||
storage.range("highCutHz", highCutHz)
|
||||
stfx::units::rangeHz(storage.range("highCutHz", highCutHz)
|
||||
.info("high cut", "Removes high frequencies")
|
||||
.range(1000, 5000, 20000)
|
||||
.unit("kHz", 1, kHzToHz, hzToKHz, 1000, 1e100)
|
||||
.unit("Hz", 0);
|
||||
.range(1000, 5000, 20000));
|
||||
storage.range("highDampRate", highDampRate)
|
||||
.info("high damp", "Reduce high frequencies over time")
|
||||
.range(1, 2, 10)
|
||||
@ -116,7 +106,7 @@ namespace signalsmith { namespace basics {
|
||||
}
|
||||
|
||||
template<class Config>
|
||||
void configure(Config &config) {
|
||||
void configureSTFX(Config &config) {
|
||||
sampleRate = config.sampleRate;
|
||||
config.outputChannels = config.inputChannels = 2; // stereo effect only
|
||||
config.auxInputs.resize(0);
|
||||
@ -175,7 +165,7 @@ namespace signalsmith { namespace basics {
|
||||
Sample scalingFactor = stereoMixer.scalingFactor2()*0.015625; // 4 Hadamard mixes
|
||||
auto smoothedWetGain = block.smooth(wet.from(), wet.to());
|
||||
|
||||
using signalsmith::units::dbToGain;
|
||||
using stfx::units::dbToGain;
|
||||
double decayGainFrom = dbToGain(getDecayDb(rt20.from(), roomMs.from()));
|
||||
double decayGainTo = dbToGain(getDecayDb(rt20.to(), roomMs.to()));
|
||||
auto smoothedDecayGain = block.smooth(decayGainFrom, decayGainTo);
|
||||
@ -417,8 +407,5 @@ namespace signalsmith { namespace basics {
|
||||
}
|
||||
};
|
||||
|
||||
using Reverb = stfx::LibraryEffect<float, ReverbSTFX>;
|
||||
|
||||
}} // namespace
|
||||
|
||||
#endif // include guard
|
||||
|
||||
342
stfx/clap/param-info.h
Normal file
342
stfx/clap/param-info.h
Normal file
@ -0,0 +1,342 @@
|
||||
#ifndef SIGNALSMITH_STFX_STFX2_PARAM_INFO_H
|
||||
#define SIGNALSMITH_STFX_STFX2_PARAM_INFO_H
|
||||
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <map>
|
||||
|
||||
namespace stfx {
|
||||
struct RangeParamInfo {
|
||||
double defaultValue;
|
||||
double low, mid, high;
|
||||
std::string name, description;
|
||||
|
||||
RangeParamInfo(double defaultValue) : defaultValue(defaultValue), low(defaultValue), mid(defaultValue), high(defaultValue) {}
|
||||
RangeParamInfo(const RangeParamInfo &other) = delete;
|
||||
RangeParamInfo(RangeParamInfo &&other) = default;
|
||||
|
||||
RangeParamInfo & info(std::string pName, std::string pDescription) {
|
||||
name = pName;
|
||||
description = pDescription;
|
||||
return *this;
|
||||
}
|
||||
RangeParamInfo & range(double pLow, double pHigh) {
|
||||
return range(pLow, pHigh, (pLow + pHigh)*0.5);
|
||||
}
|
||||
RangeParamInfo & range(double pLow, double pMid, double pHigh) {
|
||||
low = pLow;
|
||||
mid = pMid;
|
||||
high = pHigh;
|
||||
// sanity check in case we mix the argument order up
|
||||
if ((mid < high) != (low < high)) std::swap(mid, high); // middle crosses the high
|
||||
if ((low < mid) != (low < high)) std::swap(low, mid); // middle crosses the low
|
||||
rangeMap = {low, mid, high};
|
||||
return *this;
|
||||
}
|
||||
|
||||
typedef double((*DoubleMap)(double));
|
||||
RangeParamInfo & unit(std::string suffix, int precision=2, DoubleMap fromUnit=identityMap, DoubleMap toUnit=identityMap, double validLow=doubleLowest, double validHigh=doubleMax) {
|
||||
unitOptions.emplace_back(suffix, fromUnit, toUnit, validLow, validHigh, precision);
|
||||
return *this;
|
||||
}
|
||||
RangeParamInfo & unit(std::string suffix, DoubleMap fromUnit, DoubleMap toUnit, double validLow=doubleLowest, double validHigh=doubleMax) {
|
||||
return unit(suffix, 2, fromUnit, toUnit, validLow, validHigh);
|
||||
}
|
||||
RangeParamInfo & unit(std::string suffix, int precision, double validLow, double validHigh) {
|
||||
return unit(suffix, precision, identityMap, identityMap, validLow, validHigh);
|
||||
}
|
||||
RangeParamInfo & unit(std::string suffix, double validLow, double validHigh) {
|
||||
return unit(suffix, identityMap, identityMap, validLow, validHigh);
|
||||
}
|
||||
|
||||
RangeParamInfo & exact(double v, std::string valueName) {
|
||||
exactOptions.push_back({v, valueName});
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::string toString(double value) const {
|
||||
for (auto &option : exactOptions) {
|
||||
if (value == option.value) return option.name;
|
||||
}
|
||||
for (const auto &unit : unitOptions) {
|
||||
if (unit.valid(value)) return unit.toString(value);
|
||||
}
|
||||
if (unitOptions.empty()) return std::to_string(value);
|
||||
return unitOptions[0].toString(value);
|
||||
}
|
||||
void toString(double value, std::string &valueString, std::string &unitString) const {
|
||||
for (auto &option : exactOptions) {
|
||||
if (value == option.value) {
|
||||
valueString = option.name;
|
||||
unitString = "";
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (const auto &unit : unitOptions) {
|
||||
if (unit.valid(value)) return unit.toString(value, valueString, unitString);
|
||||
}
|
||||
if (unitOptions.empty()) {
|
||||
valueString = std::to_string(value);
|
||||
unitString = "";
|
||||
}
|
||||
unitOptions[0].toString(value, valueString, unitString);
|
||||
}
|
||||
double fromString(const std::string &str) const {
|
||||
bool hasDecimal = false, hasDigit = false;;
|
||||
{ // check for a parseable number
|
||||
size_t pos = 0;
|
||||
while (str[pos] == '\t' || str[pos] == ' ') ++pos; // strip leading whitespace
|
||||
if (str[pos] == '-') ++pos;
|
||||
while (pos < str.length()) {
|
||||
if (!hasDecimal && (str[pos] == '.' || str[pos] == ',')) {
|
||||
hasDecimal = true;
|
||||
} else if (str[pos] >= '0' && str[pos] <= '9') {
|
||||
hasDigit = true;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
// It's not a number - look for an exact match
|
||||
if (!hasDigit) {
|
||||
int longestMatch = -1;
|
||||
double result = defaultValue;
|
||||
for (auto &option : exactOptions) {
|
||||
for (size_t i = 0; i < option.name.size() && i < str.size(); ++i) {
|
||||
if (option.name[i] == str[i]) {
|
||||
if (int(i) > longestMatch) {
|
||||
longestMatch = i;
|
||||
result = option.value;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t pos = 0;
|
||||
double result = std::stod(str, 0);
|
||||
// Skip whitespace after the number
|
||||
while (pos < str.length() && (str[pos] == ' ' || str[pos] == '\t')) ++pos;
|
||||
size_t end = str.length(); // and at the end
|
||||
while (end > pos && end > 0 && (str[end - 1] == ' ' || str[end - 1] == '\t')) --end;
|
||||
std::string unit = str.substr(pos, end - pos);
|
||||
|
||||
for (const auto &u : unitOptions) {
|
||||
if (u.unit == unit) {
|
||||
return u.fromUnit(result);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string> getUnits() const {
|
||||
std::vector<std::string> result;
|
||||
for (size_t i = 0; i < unitOptions.size(); ++i) {
|
||||
bool duplicate = false;
|
||||
for (size_t j = 0; j < i; ++j) {
|
||||
if (unitOptions[i].unit == unitOptions[j].unit) {
|
||||
duplicate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!duplicate) result.push_back(unitOptions[i].unit);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
double toUnit(double value) const {
|
||||
return rangeMap.toUnitRange(value);
|
||||
}
|
||||
double fromUnit(double unit) const {
|
||||
return rangeMap.fromUnitRange(unit);
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr double doubleLowest = std::numeric_limits<double>::lowest();
|
||||
static constexpr double doubleMax = std::numeric_limits<double>::max();
|
||||
static double identityMap(double v) {
|
||||
return v;
|
||||
}
|
||||
|
||||
// A map from [0, 1] to another range with specified midpoint, based on a 1/x curve
|
||||
class UnitRangeMapReciprocal {
|
||||
double vMin, vTopFactor, vBottomFactor;
|
||||
public:
|
||||
UnitRangeMapReciprocal() : vMin(0), vTopFactor(1), vBottomFactor(0) {} // identity
|
||||
UnitRangeMapReciprocal(double min, double mid, double max) {
|
||||
vMin = min;
|
||||
double k = (mid - min)/(max - mid);
|
||||
vTopFactor = max*k - min;
|
||||
vBottomFactor = k - 1;
|
||||
}
|
||||
|
||||
double toUnitRange(double value) const {
|
||||
return (value - vMin)/(vTopFactor - value*vBottomFactor);
|
||||
}
|
||||
double fromUnitRange(double unit) const {
|
||||
return (vMin + unit*vTopFactor)/(1 + unit*vBottomFactor);
|
||||
}
|
||||
};
|
||||
UnitRangeMapReciprocal rangeMap;
|
||||
|
||||
protected:
|
||||
struct ExactEntry {
|
||||
double value;
|
||||
std::string name;
|
||||
};
|
||||
std::vector<ExactEntry> exactOptions;
|
||||
struct UnitEntry {
|
||||
std::string fixedDisplay = "";
|
||||
std::string unit;
|
||||
bool addSpace = false, useFixed = false, keepZeros = false;
|
||||
DoubleMap fromUnit, toUnit;
|
||||
double validLow, validHigh;
|
||||
int precision;
|
||||
double precisionOffset = 0;
|
||||
|
||||
UnitEntry(std::string unit, DoubleMap fromUnit, DoubleMap toUnit, double validLow, double validHigh, int precision=2) : unit(unit), fromUnit(fromUnit), toUnit(toUnit), validLow(validLow), validHigh(validHigh), precision(precision) {
|
||||
if (validLow > validHigh) {
|
||||
std::swap(validLow, validHigh);
|
||||
keepZeros = true;
|
||||
}
|
||||
if (unit[0] == ' ') {
|
||||
addSpace = true;
|
||||
this->unit = unit.substr(1, unit.size() - 1);
|
||||
}
|
||||
precisionOffset = 0.4999*std::pow(10, -precision);
|
||||
}
|
||||
|
||||
bool valid(double value) const {
|
||||
return value >= validLow && value <= validHigh;
|
||||
}
|
||||
|
||||
void toString(double value, std::string &valueString, std::string &unitString) const {
|
||||
std::ostringstream oss;
|
||||
oss.precision(precision);
|
||||
oss << std::fixed;
|
||||
double offset = 0;
|
||||
if (precision > 0) {
|
||||
oss << toUnit(value) + offset;
|
||||
} else {
|
||||
oss << (int)(toUnit(value) + offset);
|
||||
}
|
||||
valueString = oss.str();
|
||||
// Strip trailing zeroes
|
||||
if (!keepZeros) for (int i = 0; i < (int)valueString.size(); ++i) {
|
||||
if (valueString[i] == '.') {
|
||||
int zeros = valueString.size();
|
||||
while (zeros > i && valueString[zeros - 1] == '0') --zeros;
|
||||
if (zeros == i + 1) --zeros;
|
||||
valueString = valueString.substr(0, zeros);
|
||||
break;
|
||||
}
|
||||
}
|
||||
unitString = unit;
|
||||
}
|
||||
std::string toString(double value) const {
|
||||
std::string valueString, unitString;
|
||||
toString(value, valueString, unitString);
|
||||
return valueString + (addSpace ? " " : "") + unitString;
|
||||
}
|
||||
};
|
||||
std::vector<UnitEntry> unitOptions;
|
||||
};
|
||||
|
||||
struct SteppedParamInfo {
|
||||
int defaultValue;
|
||||
int low, high;
|
||||
std::string name, description;
|
||||
|
||||
SteppedParamInfo(int defaultValue) : defaultValue(defaultValue), low(defaultValue), high(defaultValue), nameIndex(defaultValue) {}
|
||||
SteppedParamInfo(const SteppedParamInfo &other) = delete;
|
||||
SteppedParamInfo(SteppedParamInfo &&other) = default;
|
||||
|
||||
SteppedParamInfo & info(std::string pName, std::string pDescription) {
|
||||
name = pName;
|
||||
description = pDescription;
|
||||
return *this;
|
||||
}
|
||||
SteppedParamInfo & range(int pLow, int pHigh) {
|
||||
low = pLow;
|
||||
high = pHigh;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SteppedParamInfo & label(const char *n) {
|
||||
if (nameIndex > high && high >= low) high = nameIndex;
|
||||
if (nameIndex > low && low > high) low = nameIndex;
|
||||
nameMap[nameIndex] = n;
|
||||
valueMap[n] = nameIndex;
|
||||
return *this;
|
||||
}
|
||||
bool fullyLabelled() const {
|
||||
return int(nameMap.size()) == (high - low + 1);
|
||||
}
|
||||
|
||||
template<class ...Args>
|
||||
SteppedParamInfo & label(const char *first, Args... others) {
|
||||
label(first);
|
||||
++nameIndex;
|
||||
return label(others...);
|
||||
}
|
||||
|
||||
template<class ...Args>
|
||||
SteppedParamInfo & label(int start, const char *first, Args... others) {
|
||||
nameIndex = start;
|
||||
return label(first, others...);
|
||||
}
|
||||
|
||||
const std::string * getLabel(int value) const {
|
||||
auto pair = nameMap.find(value);
|
||||
if (pair != nameMap.end()) {
|
||||
return &pair->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string toString(int value) const {
|
||||
const std::string *maybeLabel = getLabel(value);
|
||||
if (maybeLabel) return *maybeLabel;
|
||||
return std::to_string(value);
|
||||
}
|
||||
int fromString(const std::string &str) const {
|
||||
auto pair = valueMap.find(str);
|
||||
if (pair != valueMap.end()) return pair->second;
|
||||
|
||||
size_t pos = 0;
|
||||
if (str[0] == '-') ++pos;
|
||||
while (pos < str.length() && str[pos] >= '0' && str[pos] <= '9') ++pos;
|
||||
if (pos >= str.length() || pos == 0 || (pos == 1 && str[0] == '-')) {
|
||||
return defaultValue;
|
||||
}
|
||||
int result = std::stoi(str, &pos);
|
||||
if (high >= low) {
|
||||
return std::max<int>(std::min<int>(high, result), low);
|
||||
} else {
|
||||
return std::max<int>(std::min<int>(low, result), high);
|
||||
}
|
||||
}
|
||||
|
||||
double toUnit(int value) const {
|
||||
return (value - low)/double(high - low);
|
||||
}
|
||||
int fromUnit(double unit) const {
|
||||
double value = low + unit*(high - low);
|
||||
return int(std::round(value));
|
||||
}
|
||||
private:
|
||||
int nameIndex;
|
||||
protected:
|
||||
std::map<int, std::string> nameMap;
|
||||
std::map<std::string, int> valueMap;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
#endif // include guard
|
||||
326
stfx/clap/stfx-clap.h
Normal file
326
stfx/clap/stfx-clap.h
Normal file
@ -0,0 +1,326 @@
|
||||
#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
|
||||
@ -430,7 +430,7 @@ namespace stfx {
|
||||
/// Returns `true` if the current `.config` was accepted. Otherwise, you can check how `.config` was modified, make your own adjustments (if needed) and try again.
|
||||
bool configure() {
|
||||
Config prevConfig = config;
|
||||
EffectClass::configure(config);
|
||||
EffectClass::configureSTFX(config);
|
||||
if (config == prevConfig) {
|
||||
reset();
|
||||
return true;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user