1
0
basics/delay.h
2022-06-12 00:05:04 +01:00

180 lines
5.9 KiB
C++

/* 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