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