Compare commits
No commits in common. "4dd9d55c62aeac39ae02b40ebc62dbc1dd065b08" and "417dbc1944d10ef8816da1df0a78c45f9cbd1e34" have entirely different histories.
4dd9d55c62
...
417dbc1944
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1 @@
|
|||||||
**/.DS_Store
|
**/.DS_Store
|
||||||
**/env.sh
|
|
||||||
|
|||||||
@ -40,7 +40,7 @@ include(FetchContent)
|
|||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
clap-wrapper
|
clap-wrapper
|
||||||
GIT_REPOSITORY https://github.com/free-audio/clap-wrapper
|
GIT_REPOSITORY https://github.com/free-audio/clap-wrapper
|
||||||
GIT_TAG v0.12.1 # first version with WCLAP stuff
|
GIT_TAG 0.12.1 # first version with WCLAP stuff
|
||||||
GIT_SHALLOW ON
|
GIT_SHALLOW ON
|
||||||
)
|
)
|
||||||
FetchContent_MakeAvailable(clap-wrapper)
|
FetchContent_MakeAvailable(clap-wrapper)
|
||||||
@ -62,12 +62,8 @@ target_link_libraries(${NAME}_static PUBLIC
|
|||||||
clap
|
clap
|
||||||
clap-helpers
|
clap-helpers
|
||||||
)
|
)
|
||||||
target_include_directories(${NAME}_static PRIVATE
|
target_sources(${NAME}_static PUBLIC
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../include
|
${CMAKE_CURRENT_SOURCE_DIR}/source/${name}.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/../modules
|
|
||||||
)
|
|
||||||
target_sources(${NAME}_static PRIVATE
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/source/${NAME}.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
make_clapfirst_plugins(
|
make_clapfirst_plugins(
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
.PHONY: build build-emscripten emsdk
|
.PHONY: build build-emscripten emsdk
|
||||||
PROJECT := plugins
|
PROJECT := plugins
|
||||||
PLUGIN := basics
|
PLUGIN := example
|
||||||
|
|
||||||
CMAKE_PARAMS := -DCMAKE_LIBRARY_OUTPUT_DIRECTORY=.. -G Xcode # -DCMAKE_BUILD_TYPE=Release
|
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;
|
# define LOG_EXPR(expr) std::cout << #expr " = " << (expr) << std::endl;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "signalsmith-basics/crunch.h"
|
#include "../crunch.h"
|
||||||
#include "signalsmith-basics/limiter.h"
|
#include "../limiter.h"
|
||||||
#include "signalsmith-basics/reverb.h"
|
#include "../reverb.h"
|
||||||
|
|
||||||
#include "../../stfx/clap/stfx-clap.h"
|
#include "./clap-stfx.h"
|
||||||
|
|
||||||
static stfx::clap::Plugins plugins;
|
static stfx::clap::Plugins plugins;
|
||||||
bool clap_init(const char *path) {
|
bool clap_init(const char *path) {
|
||||||
@ -55,7 +55,7 @@ bool clap_init(const char *path) {
|
|||||||
return plugins.clap_init(path);
|
return plugins.clap_init(path);
|
||||||
}
|
}
|
||||||
void clap_deinit() {
|
void clap_deinit() {
|
||||||
plugins.clap_deinit();
|
plugins.clap_deinit(path);
|
||||||
}
|
}
|
||||||
const void * clap_get_factory(const char *id) {
|
const void * clap_get_factory(const char *id) {
|
||||||
return plugins.clap_get_factory(id);
|
return plugins.clap_get_factory(id);
|
||||||
|
|||||||
41
clap/source/clap-stfx.h
Normal file
41
clap/source/clap-stfx.h
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#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,10 +4,9 @@ See LICENSE.txt and SUPPORT.txt */
|
|||||||
#define SIGNALSMITH_BASICS_CRUNCH_H
|
#define SIGNALSMITH_BASICS_CRUNCH_H
|
||||||
|
|
||||||
#include "dsp/rates.h"
|
#include "dsp/rates.h"
|
||||||
#include "dsp/filters.h"
|
|
||||||
SIGNALSMITH_DSP_VERSION_CHECK(1, 4, 1)
|
SIGNALSMITH_DSP_VERSION_CHECK(1, 4, 1)
|
||||||
|
|
||||||
#include "stfx/stfx-library.h"
|
#include "./stfx-library.h"
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
@ -20,14 +19,14 @@ using CrunchFloat = stfx::LibraryEffect<float, CrunchSTFX>;
|
|||||||
using CrunchDouble = stfx::LibraryEffect<double, CrunchSTFX>;
|
using CrunchDouble = stfx::LibraryEffect<double, CrunchSTFX>;
|
||||||
|
|
||||||
template<class BaseEffect>
|
template<class BaseEffect>
|
||||||
struct CrunchSTFX : public BaseEffect {
|
class CrunchSTFX : public BaseEffect {
|
||||||
|
static constexpr int oversampleHalfLatency = 16;
|
||||||
|
static constexpr Sample autoGainLevel = 0.1;
|
||||||
|
|
||||||
using typename BaseEffect::Sample;
|
using typename BaseEffect::Sample;
|
||||||
using typename BaseEffect::ParamRange;
|
using typename BaseEffect::ParamRange;
|
||||||
using typename BaseEffect::ParamStepped;
|
using typename BaseEffect::ParamStepped;
|
||||||
|
|
||||||
static constexpr int oversampleHalfLatency = 16;
|
|
||||||
static constexpr Sample autoGainLevel = 0.1;
|
|
||||||
|
|
||||||
ParamRange drive{4};
|
ParamRange drive{4};
|
||||||
ParamRange fuzz{0};
|
ParamRange fuzz{0};
|
||||||
ParamRange toneHz{2000};
|
ParamRange toneHz{2000};
|
||||||
@ -42,20 +41,27 @@ struct CrunchSTFX : public BaseEffect {
|
|||||||
int version = storage.version(0);
|
int version = storage.version(0);
|
||||||
if (version != 0) return;
|
if (version != 0) return;
|
||||||
|
|
||||||
using stfx::units::dbToGain;
|
using namespace signalsmith::units;
|
||||||
stfx::units::rangeGain(storage.range("drive", drive)
|
storage.range("drive", drive)
|
||||||
.info("drive", "pre-distortion input gain")
|
.info("drive", "pre-distortion input gain")
|
||||||
.range(dbToGain(-12), 4, dbToGain(40)));
|
.range(dbToGain(-12), 4, dbToGain(40))
|
||||||
stfx::units::rangePercent(storage.range("fuzz", fuzz)
|
.unit("dB", 1, dbToGain, gainToDb)
|
||||||
|
.unit("");
|
||||||
|
storage.range("fuzz", fuzz)
|
||||||
.info("fuzz", "amplitude-independent distortion")
|
.info("fuzz", "amplitude-independent distortion")
|
||||||
.range(0, 0.5, 1));
|
.range(0, 0.5, 1)
|
||||||
stfx::units::rangeHz(storage.range("toneHz", toneHz)
|
.unit("%", 0, pcToRatio, ratioToPc);
|
||||||
|
storage.range("toneHz", toneHz)
|
||||||
.info("tone", "limits the brightness of the distortion")
|
.info("tone", "limits the brightness of the distortion")
|
||||||
.range(100, 4000, 20000));
|
.range(100, 4000, 20000)
|
||||||
|
.unit("Hz", 0, 0, 1000)
|
||||||
|
.unit("kHz", 1, kHzToHz, hzToKHz, 1000, 1e100);
|
||||||
|
|
||||||
stfx::units::rangeGain(storage.range("outGain", outGain)
|
storage.range("outGain", outGain)
|
||||||
.info("out", "output gain")
|
.info("out", "output gain")
|
||||||
.range(dbToGain(-12), 1, dbToGain(24)));
|
.range(dbToGain(-12), 1, dbToGain(24))
|
||||||
|
.unit("dB", 1, dbToGain, gainToDb)
|
||||||
|
.unit("");
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class Config>
|
template<class Config>
|
||||||
@ -114,7 +120,6 @@ struct CrunchSTFX : public BaseEffect {
|
|||||||
oversampler.downChannel(c, io.output[c], block.length);
|
oversampler.downChannel(c, io.output[c], block.length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int channels = 0;
|
int channels = 0;
|
||||||
signalsmith::rates::Oversampler2xFIR<Sample> oversampler;
|
signalsmith::rates::Oversampler2xFIR<Sample> oversampler;
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
#include "../../crunch.h"
|
|
||||||
@ -1 +0,0 @@
|
|||||||
#include "../../limiter.h"
|
|
||||||
@ -1 +0,0 @@
|
|||||||
#include "../../reverb.h"
|
|
||||||
65
limiter.h
65
limiter.h
@ -7,26 +7,26 @@ Released under the Boost Software License (see LICENSE.txt) */
|
|||||||
#include "dsp/envelopes.h"
|
#include "dsp/envelopes.h"
|
||||||
SIGNALSMITH_DSP_VERSION_CHECK(1, 1, 0)
|
SIGNALSMITH_DSP_VERSION_CHECK(1, 1, 0)
|
||||||
|
|
||||||
#include "stfx/stfx-library.h"
|
#include "./units.h"
|
||||||
|
#include "./stfx-library.h"
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
namespace signalsmith { namespace basics {
|
namespace signalsmith { namespace basics {
|
||||||
|
|
||||||
template<class BaseEffect>
|
template<class BaseEffect>
|
||||||
class LimiterSTFX;
|
class LimiterSTFX : public BaseEffect {
|
||||||
|
|
||||||
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::Sample;
|
||||||
using typename BaseEffect::ParamRange;
|
using typename BaseEffect::ParamRange;
|
||||||
using typename BaseEffect::ParamStepped;
|
using typename BaseEffect::ParamStepped;
|
||||||
|
|
||||||
|
int channels = 0;
|
||||||
|
double maxDelayMs = 0;
|
||||||
|
signalsmith::delay::MultiBuffer<Sample> multiBuffer;
|
||||||
|
|
||||||
|
public:
|
||||||
ParamRange inputGain{1};
|
ParamRange inputGain{1};
|
||||||
ParamRange outputLimit{stfx::units::dbToGain(-3)};
|
ParamRange outputLimit{signalsmith::units::dbToGain(-3)};
|
||||||
ParamRange attackMs{20}, holdMs{0}, releaseMs{0};
|
ParamRange attackMs{20}, holdMs{0}, releaseMs{0};
|
||||||
|
|
||||||
ParamStepped smoothingStages{1};
|
ParamStepped smoothingStages{1};
|
||||||
@ -36,33 +36,43 @@ struct LimiterSTFX : public BaseEffect {
|
|||||||
|
|
||||||
template<class Storage>
|
template<class Storage>
|
||||||
void state(Storage &storage) {
|
void state(Storage &storage) {
|
||||||
|
using namespace signalsmith::units;
|
||||||
storage.info("[Basics] Limiter", "A simple lookahead limiter");
|
storage.info("[Basics] Limiter", "A simple lookahead limiter");
|
||||||
int version = storage.version(4);
|
int version = storage.version(4);
|
||||||
if (version != 4) return;
|
if (version != 4) return;
|
||||||
|
|
||||||
using stfx::units::dbToGain;
|
storage.range("inputGain", inputGain)
|
||||||
stfx::units::rangeGain(storage.range("inputGain", inputGain)
|
|
||||||
.info("pre-gain", "amplifies the input before limiting")
|
.info("pre-gain", "amplifies the input before limiting")
|
||||||
.range(dbToGain(-12), 1, dbToGain(24)));
|
.range(dbToGain(-12), 1, dbToGain(24))
|
||||||
stfx::units::rangeGain(storage.range("outputLimit", outputLimit)
|
.unit("dB", 1, dbToGain, gainToDb)
|
||||||
|
.unit("");
|
||||||
|
storage.range("outputLimit", outputLimit)
|
||||||
.info("limit", "maximum output amplitude")
|
.info("limit", "maximum output amplitude")
|
||||||
.range(dbToGain(-24), dbToGain(-12), 1));
|
.range(dbToGain(-24), dbToGain(-12), 1)
|
||||||
stfx::units::rangeMs(storage.range("attackMs", attackMs)
|
.unit("dB", 1, dbToGain, gainToDb)
|
||||||
|
// Extra resolution between -1dB and 0dB
|
||||||
|
.unit("dB", 2, dbToGain, gainToDb, dbToGain(-1), 1)
|
||||||
|
.unit("");
|
||||||
|
storage.range("attackMs", attackMs)
|
||||||
.info("attack", "envelope smoothing time")
|
.info("attack", "envelope smoothing time")
|
||||||
.range(1, 10, maxDelayMs/2));
|
.range(1, 10, maxDelayMs/2)
|
||||||
stfx::units::rangeMs(storage.range("holdMs", holdMs)
|
.unit("ms", 0);
|
||||||
|
storage.range("holdMs", holdMs)
|
||||||
.info("hold", "hold constant after peaks")
|
.info("hold", "hold constant after peaks")
|
||||||
.range(0, 10, maxDelayMs/2));
|
.range(0, 10, maxDelayMs/2)
|
||||||
stfx::units::rangeMs(storage.range("releaseMs", releaseMs)
|
.unit("ms", 0);
|
||||||
|
storage.range("releaseMs", releaseMs)
|
||||||
.info("release", "extra release time (in addition to attack + hold)")
|
.info("release", "extra release time (in addition to attack + hold)")
|
||||||
.range(0, 10, 250));
|
.range(0, 10, 250)
|
||||||
|
.unit("ms", 0);
|
||||||
|
|
||||||
storage.stepped("smoothingStages", smoothingStages)
|
storage.stepped("smoothingStages", smoothingStages)
|
||||||
.info("smoothing", "smoothing filter(s) used for attack-smoothing")
|
.info("smoothing", "smoothing filter(s) used for attack-smoothing")
|
||||||
.label(1, "rect", "double");
|
.label(1, "rect", "double");
|
||||||
stfx::units::rangePercent(storage.range("linkChannels", linkChannels)
|
storage.range("linkChannels", linkChannels)
|
||||||
.info("link", "link channel gains together")
|
.info("link", "link channel gains together")
|
||||||
.range(0, 0.5, 1));
|
.range(0, 0.5, 1)
|
||||||
|
.unit("%", 0, pcToRatio, ratioToPc);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gain envelopes are calculated per-channel
|
// Gain envelopes are calculated per-channel
|
||||||
@ -100,7 +110,7 @@ struct LimiterSTFX : public BaseEffect {
|
|||||||
Sample sampleRate;
|
Sample sampleRate;
|
||||||
std::vector<Sample> channelGains;
|
std::vector<Sample> channelGains;
|
||||||
template<class Config>
|
template<class Config>
|
||||||
void configureSTFX(Config &config) {
|
void configure(Config &config) {
|
||||||
channels = config.outputChannels = config.inputChannels;
|
channels = config.outputChannels = config.inputChannels;
|
||||||
config.auxInputs.resize(0);
|
config.auxInputs.resize(0);
|
||||||
config.auxOutputs.resize(0);
|
config.auxOutputs.resize(0);
|
||||||
@ -181,12 +191,9 @@ struct LimiterSTFX : public BaseEffect {
|
|||||||
}
|
}
|
||||||
multiBuffer += block.length;
|
multiBuffer += block.length;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
using Limiter = stfx::LibraryEffect<float, LimiterSTFX>;
|
||||||
int channels = 0;
|
|
||||||
double maxDelayMs = 0;
|
|
||||||
signalsmith::delay::MultiBuffer<Sample> multiBuffer;
|
|
||||||
};
|
|
||||||
|
|
||||||
}} // namespace
|
}} // namespace
|
||||||
|
|
||||||
|
|||||||
69
reverb.h
69
reverb.h
@ -8,21 +8,16 @@ Released under the Boost Software License (see LICENSE.txt) */
|
|||||||
#include "dsp/filters.h"
|
#include "dsp/filters.h"
|
||||||
SIGNALSMITH_DSP_VERSION_CHECK(1, 3, 3)
|
SIGNALSMITH_DSP_VERSION_CHECK(1, 3, 3)
|
||||||
|
|
||||||
#include "stfx/stfx-library.h"
|
#include "./stfx-library.h"
|
||||||
|
#include "./units.h"
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <random>
|
#include <random>
|
||||||
|
|
||||||
namespace signalsmith { namespace basics {
|
namespace signalsmith { namespace basics {
|
||||||
|
|
||||||
template<class BaseEffect>
|
template<class BaseEffect>
|
||||||
struct ReverbSTFX;
|
struct ReverbSTFX : public BaseEffect {
|
||||||
|
|
||||||
using ReverbFloat = stfx::LibraryEffect<float, ReverbSTFX>;
|
|
||||||
using ReverbDouble = stfx::LibraryEffect<double, ReverbSTFX>;
|
|
||||||
|
|
||||||
template<class BaseEffect>
|
|
||||||
struct ReverbSTFX : public BaseEffect {
|
|
||||||
using typename BaseEffect::Sample;
|
using typename BaseEffect::Sample;
|
||||||
using typename BaseEffect::ParamRange;
|
using typename BaseEffect::ParamRange;
|
||||||
using typename BaseEffect::ParamStepped;
|
using typename BaseEffect::ParamStepped;
|
||||||
@ -42,48 +37,63 @@ struct ReverbSTFX : public BaseEffect {
|
|||||||
|
|
||||||
template<class Storage>
|
template<class Storage>
|
||||||
void state(Storage &storage) {
|
void state(Storage &storage) {
|
||||||
|
using namespace signalsmith::units;
|
||||||
|
|
||||||
storage.info("[Basics] Reverb", "An FDN reverb");
|
storage.info("[Basics] Reverb", "An FDN reverb");
|
||||||
int version = storage.version(5);
|
int version = storage.version(5);
|
||||||
if (version != 5) return;
|
if (version != 5) return;
|
||||||
|
|
||||||
using stfx::units::dbToGain;
|
storage.range("dry", dry)
|
||||||
stfx::units::rangeGain(storage.range("dry", dry)
|
|
||||||
.info("dry", "dry signal gain")
|
.info("dry", "dry signal gain")
|
||||||
.range(0, 1, 4));
|
.range(0, 1, 4)
|
||||||
|
.unit("dB", 1, dbToGain, gainToDb)
|
||||||
|
.unit("%", 0, pcToRatio, ratioToPc)
|
||||||
|
.exact(0, "off")
|
||||||
|
.unit("");
|
||||||
|
|
||||||
stfx::units::rangeGain(storage.range("wet", wet)
|
storage.range("wet", wet)
|
||||||
.info("wet", "reverb tail gain")
|
.info("wet", "reverb tail gain")
|
||||||
.range(0, 1, 4));
|
.range(0, 1, 4)
|
||||||
|
.unit("dB", 1, dbToGain, gainToDb)
|
||||||
|
.unit("%", 0, pcToRatio, ratioToPc)
|
||||||
|
.exact(0, "off")
|
||||||
|
.unit("");
|
||||||
|
|
||||||
stfx::units::rangeMs(storage.range("roomMs", roomMs)
|
storage.range("roomMs", roomMs)
|
||||||
.info("room", "room size (1ms ~ 1 foot)")
|
.info("room", "room size (1ms ~ 1 foot)")
|
||||||
.range(10, 100, maxRoomMs));
|
.range(10, 100, maxRoomMs)
|
||||||
|
.unit("ms", 0);
|
||||||
|
|
||||||
stfx::units::rangeSec(storage.range("rt20", rt20)
|
storage.range("rt20", rt20)
|
||||||
.info("decay", "RT20: decay time to -20dB")
|
.info("decay", "RT20: decay time to -20dB")
|
||||||
.range(0.01, 2, 30));
|
.range(0.01, 2, 30)
|
||||||
|
.unit("seconds", 2, 0, 1)
|
||||||
|
.unit("seconds", 1, 1, 1e100);
|
||||||
|
|
||||||
stfx::units::rangePercent(storage.range("early", early)
|
storage.range("early", early)
|
||||||
.info("early", "Early reflections")
|
.info("early", "Early reflections")
|
||||||
.range(0, 1, 2.5));
|
.range(0, 1, 2.5)
|
||||||
|
.unit("%", 0, pcToRatio, ratioToPc);
|
||||||
|
|
||||||
storage.range("detune", detune)
|
storage.range("detune", detune)
|
||||||
.info("detune", "Detuning rate (inside feedback loop)")
|
.info("detune", "Detuning rate (inside feedback loop)")
|
||||||
.range(0, 5, 50)
|
.range(0, 5, 50)
|
||||||
.unit("", 1);
|
.unit("", 1);
|
||||||
|
|
||||||
stfx::units::rangeHz(storage.range("lowCutHz", lowCutHz)
|
storage.range("lowCutHz", lowCutHz)
|
||||||
.info("low cut", "Removes low frequencies")
|
.info("low cut", "Removes low frequencies")
|
||||||
.range(10, 80, 500));
|
.range(10, 80, 500)
|
||||||
|
.unit("Hz", 0);
|
||||||
storage.range("lowDampRate", lowDampRate)
|
storage.range("lowDampRate", lowDampRate)
|
||||||
.info("low damp", "Reduce low frequencies over time")
|
.info("low damp", "Reduce low frequencies over time")
|
||||||
.range(1, 2, 10)
|
.range(1, 2, 10)
|
||||||
.unit("", 1);
|
.unit("", 1);
|
||||||
|
|
||||||
stfx::units::rangeHz(storage.range("highCutHz", highCutHz)
|
storage.range("highCutHz", highCutHz)
|
||||||
.info("high cut", "Removes high frequencies")
|
.info("high cut", "Removes high frequencies")
|
||||||
.range(1000, 5000, 20000));
|
.range(1000, 5000, 20000)
|
||||||
|
.unit("kHz", 1, kHzToHz, hzToKHz, 1000, 1e100)
|
||||||
|
.unit("Hz", 0);
|
||||||
storage.range("highDampRate", highDampRate)
|
storage.range("highDampRate", highDampRate)
|
||||||
.info("high damp", "Reduce high frequencies over time")
|
.info("high damp", "Reduce high frequencies over time")
|
||||||
.range(1, 2, 10)
|
.range(1, 2, 10)
|
||||||
@ -106,7 +116,7 @@ struct ReverbSTFX : public BaseEffect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<class Config>
|
template<class Config>
|
||||||
void configureSTFX(Config &config) {
|
void configure(Config &config) {
|
||||||
sampleRate = config.sampleRate;
|
sampleRate = config.sampleRate;
|
||||||
config.outputChannels = config.inputChannels = 2; // stereo effect only
|
config.outputChannels = config.inputChannels = 2; // stereo effect only
|
||||||
config.auxInputs.resize(0);
|
config.auxInputs.resize(0);
|
||||||
@ -165,7 +175,7 @@ struct ReverbSTFX : public BaseEffect {
|
|||||||
Sample scalingFactor = stereoMixer.scalingFactor2()*0.015625; // 4 Hadamard mixes
|
Sample scalingFactor = stereoMixer.scalingFactor2()*0.015625; // 4 Hadamard mixes
|
||||||
auto smoothedWetGain = block.smooth(wet.from(), wet.to());
|
auto smoothedWetGain = block.smooth(wet.from(), wet.to());
|
||||||
|
|
||||||
using stfx::units::dbToGain;
|
using signalsmith::units::dbToGain;
|
||||||
double decayGainFrom = dbToGain(getDecayDb(rt20.from(), roomMs.from()));
|
double decayGainFrom = dbToGain(getDecayDb(rt20.from(), roomMs.from()));
|
||||||
double decayGainTo = dbToGain(getDecayDb(rt20.to(), roomMs.to()));
|
double decayGainTo = dbToGain(getDecayDb(rt20.to(), roomMs.to()));
|
||||||
auto smoothedDecayGain = block.smooth(decayGainFrom, decayGainTo);
|
auto smoothedDecayGain = block.smooth(decayGainFrom, decayGainTo);
|
||||||
@ -271,7 +281,7 @@ struct ReverbSTFX : public BaseEffect {
|
|||||||
detuneLfoPhase -= std::floor(detuneLfoPhase);
|
detuneLfoPhase -= std::floor(detuneLfoPhase);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int channels = 0;
|
int channels = 0;
|
||||||
double sampleRate = 1;
|
double sampleRate = 1;
|
||||||
double maxRoomMs, detuneDepthMs;
|
double maxRoomMs, detuneDepthMs;
|
||||||
@ -405,7 +415,10 @@ private:
|
|||||||
delayFeedback.updateLengthsExponential(roomSamples);
|
delayFeedback.updateLengthsExponential(roomSamples);
|
||||||
delayEarly.updateLengths(0x0BDDE171, roomSamples);
|
delayEarly.updateLengths(0x0BDDE171, roomSamples);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using Reverb = stfx::LibraryEffect<float, ReverbSTFX>;
|
||||||
|
|
||||||
}} // namespace
|
}} // namespace
|
||||||
|
|
||||||
#endif // include guard
|
#endif // include guard
|
||||||
|
|||||||
@ -1,342 +0,0 @@
|
|||||||
#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
|
|
||||||
@ -1,454 +0,0 @@
|
|||||||
#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) {
|
|
||||||
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> ¶ms;
|
|
||||||
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 ¶m) {
|
|
||||||
params.emplace_back();
|
|
||||||
auto &entry = params.back();
|
|
||||||
entry.id = crc.copy().addString(key, true).done();
|
|
||||||
entry.rangeParam = ¶m;
|
|
||||||
return entry.rangeInfo.emplace(param);
|
|
||||||
}
|
|
||||||
template<class PS>
|
|
||||||
SteppedParamInfo & stepped(const char *key, PS ¶m) {
|
|
||||||
params.emplace_back();
|
|
||||||
auto &entry = params.back();
|
|
||||||
entry.id = crc.copy().addString(key, true).done();
|
|
||||||
entry.steppedParam = ¶m;
|
|
||||||
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 ¶m : 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 ¶m : plugin.params) {
|
|
||||||
if (param.id == paramId) {
|
|
||||||
if (param.rangeParam) {
|
|
||||||
auto str = param.rangeInfo->toString(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 ¶m : 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
|
|
||||||
@ -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.
|
/// 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() {
|
bool configure() {
|
||||||
Config prevConfig = config;
|
Config prevConfig = config;
|
||||||
EffectClass::configureSTFX(config);
|
EffectClass::configure(config);
|
||||||
if (config == prevConfig) {
|
if (config == prevConfig) {
|
||||||
reset();
|
reset();
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user