Start delay
This commit is contained in:
commit
94ec5fff54
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
**/.DS_Store
|
||||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "dsp"]
|
||||||
|
path = dsp
|
||||||
|
url = https://signalsmith-audio.co.uk/code/dsp.git/
|
||||||
23
LICENSE.txt
Normal file
23
LICENSE.txt
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
Boost Software License - Version 1.0 - August 17th, 2003
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person or organization
|
||||||
|
obtaining a copy of the software and accompanying documentation covered by
|
||||||
|
this license (the "Software") to use, reproduce, display, distribute,
|
||||||
|
execute, and transmit the Software, and to prepare derivative works of the
|
||||||
|
Software, and to permit third-parties to whom the Software is furnished to
|
||||||
|
do so, all subject to the following:
|
||||||
|
|
||||||
|
The copyright notices in the Software and this entire statement, including
|
||||||
|
the above license grant, this restriction and the following disclaimer,
|
||||||
|
must be included in all copies of the Software, in whole or in part, and
|
||||||
|
all derivative works of the Software, unless such copies or derivative
|
||||||
|
works are solely in the form of machine-executable object code generated by
|
||||||
|
a source language processor.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||||
|
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||||
|
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
179
delay.h
Normal file
179
delay.h
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
/* Copyright 2022 Signalsmith Audio Ltd. / Geraint Luff
|
||||||
|
Released under the Boost Software License (see LICENSE.txt) */
|
||||||
|
#ifndef SIGNALSMITH_BASICS_DELAY_H
|
||||||
|
#define SIGNALSMITH_BASICS_DELAY_H
|
||||||
|
|
||||||
|
#include "./dsp/delay.h"
|
||||||
|
SIGNALSMITH_DSP_VERSION_CHECK(1, 1, 0)
|
||||||
|
|
||||||
|
#include "./stfx-library.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace signalsmith { namespace basics {
|
||||||
|
|
||||||
|
template<typename Sample, class BaseEffect>
|
||||||
|
class DelaySTFX : public BaseEffect {
|
||||||
|
using typename BaseEffect::ParamRange;
|
||||||
|
using typename BaseEffect::ParamSteps;
|
||||||
|
|
||||||
|
// Unit conversions
|
||||||
|
static double kHz_hz(double kHz) {
|
||||||
|
return kHz*1000;
|
||||||
|
}
|
||||||
|
static double hz_kHz(double hz) {
|
||||||
|
return hz*0.001;
|
||||||
|
}
|
||||||
|
static double db_gain(double db) {
|
||||||
|
return std::pow(10, db*0.05);
|
||||||
|
}
|
||||||
|
static double gain_db(double gain) {
|
||||||
|
return std::log10(std::max<double>(gain, 1e-10))*20;
|
||||||
|
}
|
||||||
|
static double pc_linear(double percent) {
|
||||||
|
return percent*0.01;
|
||||||
|
}
|
||||||
|
static double linear_pc(double linear) {
|
||||||
|
return linear*100;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EchoSpec {
|
||||||
|
double maxDelayMs = 0;
|
||||||
|
|
||||||
|
ParamRange ms{0};
|
||||||
|
ParamRange gain{0.4};
|
||||||
|
ParamRange highpass{200};
|
||||||
|
ParamRange lowpass{12000};
|
||||||
|
|
||||||
|
ParamSteps steps{2};
|
||||||
|
|
||||||
|
template<class Storage>
|
||||||
|
void state(Storage &storage) {
|
||||||
|
storage.param("ms", ms)
|
||||||
|
.info("delay", "echo spacing (fixed time)")
|
||||||
|
.range(0, 150, maxDelayMs)
|
||||||
|
.unit("ms", 0);
|
||||||
|
storage.param("steps", steps)
|
||||||
|
.info("delay", "echo spacing (tempo-dependent)")
|
||||||
|
.range(0, 8)
|
||||||
|
.unit("steps", 0);
|
||||||
|
storage.param("gain", gain)
|
||||||
|
.info("gain", "echo decay")
|
||||||
|
.range(0, 0.2, 0.99)
|
||||||
|
.exact(0, "off")
|
||||||
|
.unit("dB", 1, db_gain, gain_db)
|
||||||
|
.unit("", 2);
|
||||||
|
storage.param("highpass", highpass)
|
||||||
|
.info("highpass", "highpass")
|
||||||
|
.range(10, 500, 20000)
|
||||||
|
.unit("kHz", 1, kHz_hz, hz_kHz, 1000, 1e10)
|
||||||
|
.unit("Hz", 0);
|
||||||
|
storage.param("lowpass", lowpass)
|
||||||
|
.info("lowpass", "lowpass")
|
||||||
|
.range(10, 8000, 20000)
|
||||||
|
.unit("kHz", 1, kHz_hz, hz_kHz, 1000, 1e10)
|
||||||
|
.unit("Hz", 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
EchoSpec initial;
|
||||||
|
EchoSpec feedback;
|
||||||
|
|
||||||
|
ParamRange wobbleRate{0.4};
|
||||||
|
ParamRange wobbleDepth{0};
|
||||||
|
ParamRange wobbleVariation{0.4};
|
||||||
|
|
||||||
|
enum {steps4, steps4t, steps8, steps8t, steps16, steps16t, steps32, steps32t, stepEnumCount};
|
||||||
|
static constexpr Sample stepFactors[stepEnumCount] = {1.0, 2.0/3, 0.5, 1.0/3, 0.25, 0.5/3, 0.125, 0.25/3};
|
||||||
|
ParamSteps beatSteps = steps16;
|
||||||
|
|
||||||
|
int channels = 0;
|
||||||
|
int maxDelaySamples = 0;
|
||||||
|
signalsmith::delay::MultiBuffer<Sample> multiBuffer;
|
||||||
|
signalsmith::delay::Reader<Sample, signalsmith::delay::InterpolatorLagrange7> reader;
|
||||||
|
public:
|
||||||
|
|
||||||
|
DelaySTFX(double maxDelayMs=2000) {
|
||||||
|
initial.maxDelayMs = feedback.maxDelayMs = maxDelayMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Storage>
|
||||||
|
void state(Storage &storage) {
|
||||||
|
storage.info("Basics: Delay", "A delay with feedback, filters and modulation");
|
||||||
|
int version = storage.version(2);
|
||||||
|
if (version < 2) return;
|
||||||
|
|
||||||
|
storage("initial", initial);
|
||||||
|
storage("feedback", feedback);
|
||||||
|
|
||||||
|
storage.param("beatSteps", beatSteps)
|
||||||
|
.info("beat step", "How long a tempo-dependent \"step\" is")
|
||||||
|
.names("1/4", "1/4 T", "1/8", "1/8 T", "1/16", "1/16 T", "1/32", "1/32 T");
|
||||||
|
storage.param("wobbleRate", wobbleRate)
|
||||||
|
.info("wobble", "LFO rate")
|
||||||
|
.range(0.1, 1, 10)
|
||||||
|
.unit("Hz", 1);
|
||||||
|
storage.param("wobbleDepth", wobbleDepth)
|
||||||
|
.info("depth", "LFO detuning")
|
||||||
|
.range(0, 10, 50)
|
||||||
|
.unit("cents", 0);
|
||||||
|
storage.param("wobbleVariation", wobbleVariation)
|
||||||
|
.info("variation", "LFO variation")
|
||||||
|
.range(0, 0.25, 1)
|
||||||
|
.unit("%", 0, pc_linear, linear_pc);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Config>
|
||||||
|
void configure(Config &config) {
|
||||||
|
channels = config.outputChannels = config.inputChannels;
|
||||||
|
config.auxInputs.resize(0);
|
||||||
|
config.auxOutputs.resize(0);
|
||||||
|
|
||||||
|
maxDelaySamples = std::ceil(initial.maxDelayMs*0.001*config.sampleRate);
|
||||||
|
multiBuffer.resize(channels, maxDelaySamples + reader.inputLength + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
multiBuffer.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class Io, class Config, class Block>
|
||||||
|
void process(Io &io, const Config &config, const Block &block) {
|
||||||
|
Sample stepSamplesFrom = 60*config.sampleRate/this->bpm.from()*stepFactors[beatSteps.from()];
|
||||||
|
Sample stepSamplesTo = 60*config.sampleRate/this->bpm.to()*stepFactors[beatSteps.to()];
|
||||||
|
|
||||||
|
auto samplesDelay = [&](double ms, double steps, double stepSamples) {
|
||||||
|
return std::max<Sample>(1, std::min<Sample>(maxDelaySamples, ms*0.001*config.sampleRate + steps*stepSamples));
|
||||||
|
};
|
||||||
|
Sample initialDelayFrom = samplesDelay(initial.ms.from(), initial.steps.from(), stepSamplesFrom);
|
||||||
|
Sample initialDelayTo = samplesDelay(initial.ms.to(), initial.steps.to(), stepSamplesTo);
|
||||||
|
Sample feedbackDelayFrom = samplesDelay(feedback.ms.from(), feedback.steps.from(), stepSamplesFrom);
|
||||||
|
Sample feedbackDelayTo = samplesDelay(feedback.ms.to(), feedback.steps.to(), stepSamplesTo);
|
||||||
|
bool delayChanging = (initialDelayFrom != initialDelayTo) || (feedbackDelayFrom != feedbackDelayTo);
|
||||||
|
|
||||||
|
auto smoothInitialGain = block.smooth(initial.gain);
|
||||||
|
auto smoothFeedbackGain = block.smooth(feedback.gain);
|
||||||
|
for (int i = 0; i < block.length; ++i) {
|
||||||
|
for (int c = 0; c < channels; ++c) {
|
||||||
|
Sample value = io.input[c][i];
|
||||||
|
multiBuffer[c][0] = value;
|
||||||
|
Sample initialDelayed = reader.read(multiBuffer[c], initialDelayTo);
|
||||||
|
Sample feedbackDelayed = reader.read(multiBuffer[c], feedbackDelayTo);
|
||||||
|
if (delayChanging) {
|
||||||
|
Sample initialDelayedFrom = reader.read(multiBuffer[c], initialDelayFrom);
|
||||||
|
initialDelayed = block.fade(i, initialDelayedFrom, initialDelayed);
|
||||||
|
Sample feedbackDelayedFrom = reader.read(multiBuffer[c], feedbackDelayFrom);
|
||||||
|
feedbackDelayed = block.fade(i, feedbackDelayedFrom, feedbackDelayed);
|
||||||
|
}
|
||||||
|
multiBuffer[c][0] = value + feedbackDelayed*smoothFeedbackGain.at(i);
|
||||||
|
io.output[c][i] = value + initialDelayed*smoothInitialGain.at(i);
|
||||||
|
}
|
||||||
|
++multiBuffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using Delay = stfx::LibraryEffect<float, DelaySTFX>;
|
||||||
|
|
||||||
|
}} // namespace
|
||||||
|
|
||||||
|
#endif // include guard
|
||||||
1
dsp
Submodule
1
dsp
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 763fd4751da69ce412878a31ffd383811e562f5f
|
||||||
423
stfx-library.h
Normal file
423
stfx-library.h
Normal file
@ -0,0 +1,423 @@
|
|||||||
|
/* Copyright 2021 Geraint Luff / Signalsmith Audio Ltd
|
||||||
|
|
||||||
|
This file is released under the MIT License - if you need anything else, let us know. 🙂
|
||||||
|
|
||||||
|
The main thing you need is `stfx::LibraryEffect<Sample, EffectTemplate>`
|
||||||
|
which produces a simple effect class from an STFX effect template.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef STFX_LIBRARY_H
|
||||||
|
#define STFX_LIBRARY_H
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace stfx {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// A value which changes linearly within a given index range (e.g. a block)
|
||||||
|
class LinearSegment {
|
||||||
|
double offset, gradient;
|
||||||
|
public:
|
||||||
|
LinearSegment(double offset, double gradient) : offset(offset), gradient(gradient) {}
|
||||||
|
|
||||||
|
double at(int i) {
|
||||||
|
return offset + i*gradient;
|
||||||
|
}
|
||||||
|
bool changing() {
|
||||||
|
return gradient != 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** A processing block (not including IO).
|
||||||
|
|
||||||
|
There are two fades happening within each block:
|
||||||
|
* automation: changes are smoothed linearly across the block
|
||||||
|
* A/B fade: a slower transition for parameters which need to change slowly or do cross-fades
|
||||||
|
*/
|
||||||
|
class Block {
|
||||||
|
// slow fade start/rate
|
||||||
|
double fadeStart, fadeStep;
|
||||||
|
// per-block automation gradient rate
|
||||||
|
double blockFade;
|
||||||
|
// we might need some additional setup on the fiand/or after a directly
|
||||||
|
bool firstBlockAfterReset = false;
|
||||||
|
|
||||||
|
template<class Param>
|
||||||
|
bool paramsChanging(Param ¶m) const {
|
||||||
|
return param.from() != param.to();
|
||||||
|
}
|
||||||
|
template<class Param, class ...Others>
|
||||||
|
bool paramsChanging(Param ¶m, Others &...others) const {
|
||||||
|
return paramsChanging(param) || paramsChanging(others...);
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
// Block length in samples
|
||||||
|
int length;
|
||||||
|
|
||||||
|
Block(int length, double fadeStart, double fadeStep, bool firstBlockAfterReset=false) : fadeStart(fadeStart), fadeStep(fadeStep), blockFade(1.0/length), firstBlockAfterReset(firstBlockAfterReset), length(length) {}
|
||||||
|
// Not copyable, because that's probably a mistake
|
||||||
|
Block(const Block &) = delete;
|
||||||
|
Block & operator =(const Block&) = delete;
|
||||||
|
|
||||||
|
/// Fade ratio at a given sample index
|
||||||
|
double fade(int i) const {
|
||||||
|
return fadeStart + i*fadeStep;
|
||||||
|
}
|
||||||
|
/// Mix two values according to the fade ratio
|
||||||
|
template<class Value=double>
|
||||||
|
Value fade(int i, Value from, Value to) const {
|
||||||
|
return from + (to - from)*fade(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is there a fade currently active?
|
||||||
|
bool fading() const {
|
||||||
|
return fadeStep != 0;
|
||||||
|
}
|
||||||
|
/// Is a fade happening, and are any of the parameters included in it?
|
||||||
|
template<class Param, class ...Others>
|
||||||
|
bool fading(Param ¶m, Others &...others) const {
|
||||||
|
return fading() && paramsChanging(param, others...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set up an A/B fade. Executes once at the beginning of a fade, and also once directly after a `.reset()`.
|
||||||
|
template<class Fn>
|
||||||
|
void setupFade(Fn fn) const {
|
||||||
|
setupFade(fading(), fn);
|
||||||
|
}
|
||||||
|
/// Same as above, but only run on fade-start if `fading` is true. Mostly useful when used with `.fading(params...)`
|
||||||
|
template<class Fn>
|
||||||
|
void setupFade(bool fading, Fn fn) const {
|
||||||
|
if (firstBlockAfterReset) fn();
|
||||||
|
if (fading && fadeStart == 0) fn();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Produce a linear segment corresponding to A/B fading a parameter
|
||||||
|
template<class Param>
|
||||||
|
LinearSegment smooth(Param ¶m) const {
|
||||||
|
return smooth(param.from(), param.to());
|
||||||
|
}
|
||||||
|
/// Same as above, but the parameter's values can be mapped
|
||||||
|
template<class Param, class Map>
|
||||||
|
LinearSegment smooth(Param ¶m, Map map) const {
|
||||||
|
return smooth(map(param.from()), map(param.to()));
|
||||||
|
}
|
||||||
|
/// Produce a linear segment corresponding to A/B fading two values.
|
||||||
|
/// These values should _not_ generally change every block, but only when a new fade starts. They should probably be derived from `param.from()` and `param.to()`, and this method is mostly useful for combining multiple parameters.
|
||||||
|
LinearSegment smooth(double from, double to) const {
|
||||||
|
double diff = to - from;
|
||||||
|
return {from + diff*fadeStart, diff*fadeStep};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Automation provides a curve for a parameter, and is also an event list where the events update the curve.
|
||||||
|
/// This pattern allows sample-accurate automation in environments where that's supported.
|
||||||
|
struct BlockAutomation : public LinearSegment {
|
||||||
|
BlockAutomation(const LinearSegment &smoothed) : LinearSegment(smoothed) {}
|
||||||
|
|
||||||
|
// For this implementation, we just provide a linear segment and no update events.
|
||||||
|
static constexpr int size() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
struct DoNothingEvent {
|
||||||
|
int offset = 0;
|
||||||
|
void operator ()() {}
|
||||||
|
};
|
||||||
|
DoNothingEvent operator [](int) {
|
||||||
|
return DoNothingEvent();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/// Get an automation curve for a parameter
|
||||||
|
template<class Param>
|
||||||
|
BlockAutomation automation(Param ¶m) const {
|
||||||
|
double start = param._libPrevBlock();
|
||||||
|
auto diff = param._libCurrent() - start;
|
||||||
|
return LinearSegment{start, diff*blockFade};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Blocks can be processed in sub-blocks, which are split up by events.
|
||||||
|
/// This method can return a sub-block (with `.split()` and `.forEach()` methods).
|
||||||
|
template<class EventList>
|
||||||
|
const Block & split(EventList &&list, int count) const {
|
||||||
|
for (int i = 0; i < count; ++i) list[i]();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
template<class EventList, class ...Others>
|
||||||
|
const Block & split(EventList &&list, Others &&...others) const {
|
||||||
|
return split(list).split(std::forward<Others>(others)...);
|
||||||
|
}
|
||||||
|
/// Base-case for the templated recursion
|
||||||
|
const Block & split() const {
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
/// Execute the callback once per sub-block, with sample-index arguments: `callback(int start, int end)`
|
||||||
|
template<class Callback>
|
||||||
|
void forEach(Callback callback) const {
|
||||||
|
callback(0, length);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parameters can be assigned using `=`, and store their own history for transitions
|
||||||
|
template<typename Value>
|
||||||
|
class LibraryParam {
|
||||||
|
Value current, prevBlock;
|
||||||
|
// Used for the 20ms parameter fade
|
||||||
|
Value _from, _to;
|
||||||
|
public:
|
||||||
|
LibraryParam(const Value &initial) : current(initial), prevBlock(initial), _from(initial), _to(initial) {}
|
||||||
|
operator Value () const {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
LibraryParam & operator =(const Value &v) {
|
||||||
|
current = v;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
// Return the
|
||||||
|
Value from() const {
|
||||||
|
return _from;
|
||||||
|
}
|
||||||
|
Value to() const {
|
||||||
|
return _to;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shuffle the internal values along to start a new fade, return whether it's actually changing
|
||||||
|
bool _libStartFade() {
|
||||||
|
_from = _to;
|
||||||
|
_to = current;
|
||||||
|
return (_to != _from);
|
||||||
|
}
|
||||||
|
// Store previous value for block-level automation
|
||||||
|
void _libEndBlock() {
|
||||||
|
prevBlock = current;
|
||||||
|
}
|
||||||
|
Value _libCurrent() {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
Value _libPrevBlock() {
|
||||||
|
return prevBlock;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// When we want to ignore parameter info
|
||||||
|
struct PInfoPlaceholder {
|
||||||
|
PInfoPlaceholder & info(const char*, const char*) {
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
PInfoPlaceholder & info(std::string, std::string) {
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
template<typename V1, typename V2>
|
||||||
|
PInfoPlaceholder & range(V1, V2) {
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
template<typename V1, typename V2, typename V3>
|
||||||
|
PInfoPlaceholder & range(V1, V2, V3) {
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
template<class ...Args>
|
||||||
|
PInfoPlaceholder & unit(Args...) {
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
PInfoPlaceholder & exact(double, const char *) {
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
template<typename ...Args>
|
||||||
|
PInfoPlaceholder & names(Args ...) {
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Base class for our effect to inherit from. Provides parameter classes and some default config.
|
||||||
|
class LibraryEffectBase {
|
||||||
|
protected:
|
||||||
|
using ParamRange = LibraryParam<double>;
|
||||||
|
using ParamSteps = LibraryParam<int>;
|
||||||
|
|
||||||
|
template<typename Enum, int size>
|
||||||
|
class ParamEnum : public ParamSteps {
|
||||||
|
static Enum castToEnum(int v) {return (Enum)v;};
|
||||||
|
public:
|
||||||
|
using Value = Enum;
|
||||||
|
ParamEnum(Enum value) : ParamSteps((int)value) {}
|
||||||
|
operator Enum() {
|
||||||
|
return (Enum)this->latest;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
double paramFadeMs() {
|
||||||
|
return 20;
|
||||||
|
}
|
||||||
|
int tailSamples() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Creates an effect class from an effect template, with optional extra config.
|
||||||
|
/// The effect template takes `EffectTemplate<Sample, BaseClass, ...ExtraConfig>`
|
||||||
|
template<typename Sample, template <class, class, class...> class EffectTemplate, class ...ExtraConfig>
|
||||||
|
class LibraryEffect : public EffectTemplate<Sample, stfx::LibraryEffectBase, ExtraConfig...> {
|
||||||
|
using EffectClass = EffectTemplate<Sample, stfx::LibraryEffectBase, ExtraConfig...>;
|
||||||
|
|
||||||
|
// This is passed to the effect's `.state()` method during initialisation, and collects pointers to the effect's parameters
|
||||||
|
class CollectParams {
|
||||||
|
public:
|
||||||
|
std::vector<LibraryParam<double> *> rangeParams;
|
||||||
|
std::vector<LibraryParam<int> *> stepParams;
|
||||||
|
|
||||||
|
// Add registered parameters to the list
|
||||||
|
PInfoPlaceholder param(const char *, LibraryParam<double> ¶m, const char *codeExpr=nullptr) {
|
||||||
|
(void)codeExpr;
|
||||||
|
rangeParams.push_back(¶m);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
PInfoPlaceholder param(const char *, LibraryParam<int> ¶m, const char *codeExpr=nullptr) {
|
||||||
|
(void)codeExpr;
|
||||||
|
stepParams.push_back(¶m);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// The effect might ask us to store/fetch the serialisation version, we just echo it back
|
||||||
|
static int version(int v) {return v;}
|
||||||
|
|
||||||
|
// We ignore any basic type
|
||||||
|
void operator ()(bool) {}
|
||||||
|
void operator ()(int) {}
|
||||||
|
void operator ()(long) {}
|
||||||
|
void operator ()(double) {}
|
||||||
|
void operator ()(float) {}
|
||||||
|
// Assume all other arguments have a `.state()`, and recurse into it
|
||||||
|
template<class OtherObject>
|
||||||
|
void operator ()(OtherObject &obj) {
|
||||||
|
obj.state(*this);
|
||||||
|
}
|
||||||
|
// Drop all names/labels we're given
|
||||||
|
template<class V>
|
||||||
|
void operator ()(const char *, V &v) {
|
||||||
|
(*this)(v);
|
||||||
|
}
|
||||||
|
template<class Fn>
|
||||||
|
void group(const char *, Fn fn) {
|
||||||
|
fn(*this);
|
||||||
|
}
|
||||||
|
// Drop any name/description we're given
|
||||||
|
template<class ...Args>
|
||||||
|
void info(Args...) {}
|
||||||
|
} params;
|
||||||
|
|
||||||
|
bool justHadReset = true;
|
||||||
|
// Keep track of the A/B fade state
|
||||||
|
double fadeRatio = 0;
|
||||||
|
public:
|
||||||
|
template<class ...Args>
|
||||||
|
LibraryEffect(Args &&...args) : EffectClass(std::forward<Args>(args)...) {
|
||||||
|
EffectClass::state(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Config {
|
||||||
|
double sampleRate = 48000;
|
||||||
|
int inputChannels = 2, outputChannels = 2;
|
||||||
|
std::vector<int> auxInputs, auxOutputs;
|
||||||
|
int maxBlockSize = 256;
|
||||||
|
|
||||||
|
bool operator ==(const Config &other) {
|
||||||
|
return sampleRate == other.sampleRate
|
||||||
|
&& inputChannels == other.inputChannels
|
||||||
|
&& outputChannels == other.outputChannels
|
||||||
|
&& auxInputs == other.auxInputs
|
||||||
|
&& auxOutputs == other.auxOutputs
|
||||||
|
&& maxBlockSize == other.maxBlockSize;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/// The current (proposed) effect configuration
|
||||||
|
Config config;
|
||||||
|
/// 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);
|
||||||
|
if (config == prevConfig) {
|
||||||
|
reset();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/// Attempts to find a valid configuration by iteration
|
||||||
|
bool configurePersistent(int attempts=10) {
|
||||||
|
for (int i = 0; i < attempts; ++i) {
|
||||||
|
if (configure()) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/// Returns true if the effect was successfully configured with _exactly_ these parameters
|
||||||
|
bool configure(double sampleRate, int maxBlockSize, int channels=2, int outputChannels=-1) {
|
||||||
|
if (outputChannels < 0) outputChannels = channels;
|
||||||
|
config.sampleRate = sampleRate;
|
||||||
|
config.inputChannels = channels;
|
||||||
|
config.outputChannels = outputChannels;
|
||||||
|
config.maxBlockSize = maxBlockSize;
|
||||||
|
|
||||||
|
return configure();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears effect buffers and resets parameters
|
||||||
|
void reset() {
|
||||||
|
for (auto param : params.rangeParams) {
|
||||||
|
param->_libStartFade();
|
||||||
|
param->_libEndBlock();
|
||||||
|
}
|
||||||
|
for (auto param : params.stepParams) {
|
||||||
|
param->_libStartFade();
|
||||||
|
param->_libEndBlock();
|
||||||
|
}
|
||||||
|
EffectClass::reset();
|
||||||
|
justHadReset = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wraps the common `process(float** inputs, float** outputs, int length)` call into the `.process(io, config, block)`.
|
||||||
|
/// It actually accepts any objects which support `inputs[channel][index]`, so you could write adapters for interleaved buffers etc.
|
||||||
|
template<class Inputs, class Outputs>
|
||||||
|
void process(Inputs inputs, Outputs outputs, int blockLength) {
|
||||||
|
// How long should the parameter fade take?
|
||||||
|
double fadeSamples = EffectClass::paramFadeMs()*0.001*config.sampleRate;
|
||||||
|
// Fade position at the end of the block
|
||||||
|
double fadeRatioEnd = fadeRatio + blockLength/fadeSamples;
|
||||||
|
// If the fade will finish this block, get there exactly
|
||||||
|
double fadeRatioStep = (fadeRatioEnd >= 1) ? (1 - fadeRatio)/blockLength : 1/fadeSamples;
|
||||||
|
|
||||||
|
// If we're just starting a new fade, move all the parameter values along
|
||||||
|
if (fadeRatio == 0) {
|
||||||
|
bool needsFade = false;
|
||||||
|
for (auto param : params.rangeParams) {
|
||||||
|
if (param->_libStartFade()) needsFade = true;
|
||||||
|
}
|
||||||
|
for (auto param : params.stepParams) {
|
||||||
|
if (param->_libStartFade()) needsFade = true;
|
||||||
|
}
|
||||||
|
// None of our parameters are actually fading, just skip it
|
||||||
|
if (!needsFade) fadeRatioStep = fadeRatioEnd = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Io {
|
||||||
|
Inputs input;
|
||||||
|
Outputs output;
|
||||||
|
};
|
||||||
|
Io io{inputs, outputs};
|
||||||
|
Block block(blockLength, fadeRatio, fadeRatioStep, justHadReset);
|
||||||
|
|
||||||
|
((EffectClass *)this)->process(io, (const Config &)config, (const Block &)block);
|
||||||
|
|
||||||
|
if (fadeRatioEnd >= 1) {
|
||||||
|
// Fade just finished, so we reset
|
||||||
|
fadeRatio = 0;
|
||||||
|
} else {
|
||||||
|
fadeRatio = fadeRatioEnd;
|
||||||
|
}
|
||||||
|
justHadReset = false;
|
||||||
|
for (auto param : params.rangeParams) param->_libEndBlock();
|
||||||
|
for (auto param : params.stepParams) param->_libEndBlock();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // stfx::
|
||||||
|
|
||||||
|
#endif // include guard
|
||||||
Loading…
x
Reference in New Issue
Block a user