Add filter and wobble
This commit is contained in:
parent
94ec5fff54
commit
3a8b805837
138
delay.h
138
delay.h
@ -4,6 +4,8 @@ Released under the Boost Software License (see LICENSE.txt) */
|
|||||||
#define SIGNALSMITH_BASICS_DELAY_H
|
#define SIGNALSMITH_BASICS_DELAY_H
|
||||||
|
|
||||||
#include "./dsp/delay.h"
|
#include "./dsp/delay.h"
|
||||||
|
#include "./dsp/filters.h"
|
||||||
|
#include "./dsp/envelopes.h"
|
||||||
SIGNALSMITH_DSP_VERSION_CHECK(1, 1, 0)
|
SIGNALSMITH_DSP_VERSION_CHECK(1, 1, 0)
|
||||||
|
|
||||||
#include "./stfx-library.h"
|
#include "./stfx-library.h"
|
||||||
@ -16,6 +18,10 @@ namespace signalsmith { namespace basics {
|
|||||||
class DelaySTFX : public BaseEffect {
|
class DelaySTFX : public BaseEffect {
|
||||||
using typename BaseEffect::ParamRange;
|
using typename BaseEffect::ParamRange;
|
||||||
using typename BaseEffect::ParamSteps;
|
using typename BaseEffect::ParamSteps;
|
||||||
|
|
||||||
|
static constexpr double initialFilterBandwidth = 1.9; // Butterworth
|
||||||
|
static constexpr double feedbackFilterBandwidth = 3; // Softer edge
|
||||||
|
static constexpr Sample feedbackLfoMultiplier = 0.5; // Feedback should wobble less
|
||||||
|
|
||||||
// Unit conversions
|
// Unit conversions
|
||||||
static double kHz_hz(double kHz) {
|
static double kHz_hz(double kHz) {
|
||||||
@ -42,7 +48,7 @@ namespace signalsmith { namespace basics {
|
|||||||
|
|
||||||
ParamRange ms{0};
|
ParamRange ms{0};
|
||||||
ParamRange gain{0.4};
|
ParamRange gain{0.4};
|
||||||
ParamRange highpass{200};
|
ParamRange highpass{100};
|
||||||
ParamRange lowpass{12000};
|
ParamRange lowpass{12000};
|
||||||
|
|
||||||
ParamSteps steps{2};
|
ParamSteps steps{2};
|
||||||
@ -50,16 +56,16 @@ namespace signalsmith { namespace basics {
|
|||||||
template<class Storage>
|
template<class Storage>
|
||||||
void state(Storage &storage) {
|
void state(Storage &storage) {
|
||||||
storage.param("ms", ms)
|
storage.param("ms", ms)
|
||||||
.info("delay", "echo spacing (fixed time)")
|
.info("time", "echo spacing (fixed time)")
|
||||||
.range(0, 150, maxDelayMs)
|
.range(0, 150, maxDelayMs)
|
||||||
.unit("ms", 0);
|
.unit("ms", 0);
|
||||||
storage.param("steps", steps)
|
storage.param("steps", steps)
|
||||||
.info("delay", "echo spacing (tempo-dependent)")
|
.info("tempo", "echo spacing (tempo-dependent)")
|
||||||
.range(0, 8)
|
.range(0, 8)
|
||||||
.unit("steps", 0);
|
.unit("steps", 0);
|
||||||
storage.param("gain", gain)
|
storage.param("gain", gain)
|
||||||
.info("gain", "echo decay")
|
.info("gain", "echo decay")
|
||||||
.range(0, 0.2, 0.99)
|
.range(0, 0.4, 0.99)
|
||||||
.exact(0, "off")
|
.exact(0, "off")
|
||||||
.unit("dB", 1, db_gain, gain_db)
|
.unit("dB", 1, db_gain, gain_db)
|
||||||
.unit("", 2);
|
.unit("", 2);
|
||||||
@ -70,7 +76,7 @@ namespace signalsmith { namespace basics {
|
|||||||
.unit("Hz", 0);
|
.unit("Hz", 0);
|
||||||
storage.param("lowpass", lowpass)
|
storage.param("lowpass", lowpass)
|
||||||
.info("lowpass", "lowpass")
|
.info("lowpass", "lowpass")
|
||||||
.range(10, 8000, 20000)
|
.range(10, 4000, 20000)
|
||||||
.unit("kHz", 1, kHz_hz, hz_kHz, 1000, 1e10)
|
.unit("kHz", 1, kHz_hz, hz_kHz, 1000, 1e10)
|
||||||
.unit("Hz", 0);
|
.unit("Hz", 0);
|
||||||
}
|
}
|
||||||
@ -78,9 +84,27 @@ namespace signalsmith { namespace basics {
|
|||||||
EchoSpec initial;
|
EchoSpec initial;
|
||||||
EchoSpec feedback;
|
EchoSpec feedback;
|
||||||
|
|
||||||
ParamRange wobbleRate{0.4};
|
struct Wobble {
|
||||||
ParamRange wobbleDepth{0};
|
ParamRange rate{2};
|
||||||
ParamRange wobbleVariation{0.4};
|
ParamRange detune{0};
|
||||||
|
ParamRange variation{0.4};
|
||||||
|
|
||||||
|
template<class Storage>
|
||||||
|
void state(Storage &storage) {
|
||||||
|
storage.param("rate", rate)
|
||||||
|
.info("rate", "LFO rate")
|
||||||
|
.range(0.1, 1, 10)
|
||||||
|
.unit("Hz", 1);
|
||||||
|
storage.param("detune", detune)
|
||||||
|
.info("detune", "LFO detuning")
|
||||||
|
.range(0, 20, 200)
|
||||||
|
.unit("cents", 0);
|
||||||
|
storage.param("variation", variation)
|
||||||
|
.info("variation", "LFO variation")
|
||||||
|
.range(0, 0.25, 1)
|
||||||
|
.unit("%", 0, pc_linear, linear_pc);
|
||||||
|
}
|
||||||
|
} wobble;
|
||||||
|
|
||||||
enum {steps4, steps4t, steps8, steps8t, steps16, steps16t, steps32, steps32t, stepEnumCount};
|
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};
|
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};
|
||||||
@ -90,6 +114,36 @@ namespace signalsmith { namespace basics {
|
|||||||
int maxDelaySamples = 0;
|
int maxDelaySamples = 0;
|
||||||
signalsmith::delay::MultiBuffer<Sample> multiBuffer;
|
signalsmith::delay::MultiBuffer<Sample> multiBuffer;
|
||||||
signalsmith::delay::Reader<Sample, signalsmith::delay::InterpolatorLagrange7> reader;
|
signalsmith::delay::Reader<Sample, signalsmith::delay::InterpolatorLagrange7> reader;
|
||||||
|
|
||||||
|
struct FilterAB {
|
||||||
|
signalsmith::filters::BiquadStatic<Sample> lowpassFrom, lowpassTo;
|
||||||
|
signalsmith::filters::BiquadStatic<Sample> highpassFrom, highpassTo;
|
||||||
|
|
||||||
|
void swap() {
|
||||||
|
std::swap(lowpassFrom, lowpassTo);
|
||||||
|
std::swap(highpassFrom, highpassTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
Sample process(Sample v) {
|
||||||
|
return highpassTo(lowpassTo(v));
|
||||||
|
}
|
||||||
|
Sample process(Sample v, Sample fade) {
|
||||||
|
double vFrom = highpassFrom(lowpassFrom(v));
|
||||||
|
double vTo = highpassTo(lowpassTo(v));
|
||||||
|
return vFrom + (vTo - vFrom)*fade;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
lowpassFrom.reset();
|
||||||
|
lowpassTo.reset();
|
||||||
|
highpassFrom.reset();
|
||||||
|
highpassTo.reset();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
std::vector<FilterAB> initialFilters;
|
||||||
|
std::vector<FilterAB> feedbackFilters;
|
||||||
|
|
||||||
|
std::vector<signalsmith::envelopes::CubicLfo> channelLfos;
|
||||||
public:
|
public:
|
||||||
|
|
||||||
DelaySTFX(double maxDelayMs=2000) {
|
DelaySTFX(double maxDelayMs=2000) {
|
||||||
@ -108,18 +162,8 @@ namespace signalsmith { namespace basics {
|
|||||||
storage.param("beatSteps", beatSteps)
|
storage.param("beatSteps", beatSteps)
|
||||||
.info("beat step", "How long a tempo-dependent \"step\" is")
|
.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");
|
.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")
|
storage("wobble", wobble);
|
||||||
.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>
|
template<class Config>
|
||||||
@ -130,40 +174,84 @@ namespace signalsmith { namespace basics {
|
|||||||
|
|
||||||
maxDelaySamples = std::ceil(initial.maxDelayMs*0.001*config.sampleRate);
|
maxDelaySamples = std::ceil(initial.maxDelayMs*0.001*config.sampleRate);
|
||||||
multiBuffer.resize(channels, maxDelaySamples + reader.inputLength + 1);
|
multiBuffer.resize(channels, maxDelaySamples + reader.inputLength + 1);
|
||||||
|
|
||||||
|
initialFilters.resize(channels);
|
||||||
|
feedbackFilters.resize(channels);
|
||||||
|
channelLfos.resize(channels);
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
multiBuffer.reset();
|
multiBuffer.reset();
|
||||||
|
for (auto &f : initialFilters) f.reset();
|
||||||
|
for (auto &f : feedbackFilters) f.reset();
|
||||||
|
for (int c = 0; c < channels; ++c) {
|
||||||
|
channelLfos[c] = {c*1000}; // use channel number as seed
|
||||||
|
channelLfos[c].set(0, 0, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Io, class Config, class Block>
|
template <class Io, class Config, class Block>
|
||||||
void process(Io &io, const Config &config, const Block &block) {
|
void process(Io &io, const Config &config, const Block &block) {
|
||||||
|
Sample lfoRate = wobble.rate/config.sampleRate;
|
||||||
|
Sample lfoDepthSamples = wobble.detune/lfoRate*0.00015; // Magic tuning factor for cents
|
||||||
|
Sample depthFactor = 0.5 + std::min<double>(0.5, wobble.variation);
|
||||||
|
for (auto &c : channelLfos) {
|
||||||
|
c.set(lfoDepthSamples*-depthFactor, lfoDepthSamples*depthFactor, lfoRate, wobble.variation, wobble.variation);
|
||||||
|
}
|
||||||
|
|
||||||
Sample stepSamplesFrom = 60*config.sampleRate/this->bpm.from()*stepFactors[beatSteps.from()];
|
Sample stepSamplesFrom = 60*config.sampleRate/this->bpm.from()*stepFactors[beatSteps.from()];
|
||||||
Sample stepSamplesTo = 60*config.sampleRate/this->bpm.to()*stepFactors[beatSteps.to()];
|
Sample stepSamplesTo = 60*config.sampleRate/this->bpm.to()*stepFactors[beatSteps.to()];
|
||||||
|
|
||||||
auto samplesDelay = [&](double ms, double steps, double stepSamples) {
|
auto samplesDelay = [&](double ms, double steps, double stepSamples) {
|
||||||
return std::max<Sample>(1, std::min<Sample>(maxDelaySamples, ms*0.001*config.sampleRate + steps*stepSamples));
|
return maxDelaySamples, ms*0.001*config.sampleRate + steps*stepSamples;
|
||||||
};
|
};
|
||||||
Sample initialDelayFrom = samplesDelay(initial.ms.from(), initial.steps.from(), stepSamplesFrom);
|
Sample initialDelayFrom = samplesDelay(initial.ms.from(), initial.steps.from(), stepSamplesFrom);
|
||||||
Sample initialDelayTo = samplesDelay(initial.ms.to(), initial.steps.to(), stepSamplesTo);
|
Sample initialDelayTo = samplesDelay(initial.ms.to(), initial.steps.to(), stepSamplesTo);
|
||||||
Sample feedbackDelayFrom = samplesDelay(feedback.ms.from(), feedback.steps.from(), stepSamplesFrom);
|
Sample feedbackDelayFrom = samplesDelay(feedback.ms.from(), feedback.steps.from(), stepSamplesFrom);
|
||||||
Sample feedbackDelayTo = samplesDelay(feedback.ms.to(), feedback.steps.to(), stepSamplesTo);
|
Sample feedbackDelayTo = samplesDelay(feedback.ms.to(), feedback.steps.to(), stepSamplesTo);
|
||||||
bool delayChanging = (initialDelayFrom != initialDelayTo) || (feedbackDelayFrom != feedbackDelayTo);
|
bool delayChanging = (initialDelayFrom != initialDelayTo) || (feedbackDelayFrom != feedbackDelayTo);
|
||||||
|
|
||||||
|
bool filtersChanging = block.fading(initial.highpass, initial.lowpass, feedback.highpass, feedback.lowpass);
|
||||||
|
block.setupFade(filtersChanging, [&](){
|
||||||
|
for (auto &f : initialFilters) {
|
||||||
|
f.swap();
|
||||||
|
f.lowpassTo.lowpass(initial.lowpass.to()/config.sampleRate, initialFilterBandwidth);
|
||||||
|
f.highpassTo.highpass(initial.highpass.to()/config.sampleRate, initialFilterBandwidth);
|
||||||
|
}
|
||||||
|
for (auto &f : feedbackFilters) {
|
||||||
|
f.swap();
|
||||||
|
f.lowpassTo.lowpass(feedback.lowpass.to()/config.sampleRate, feedbackFilterBandwidth);
|
||||||
|
f.highpassTo.highpass(feedback.highpass.to()/config.sampleRate, feedbackFilterBandwidth);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
auto smoothInitialGain = block.smooth(initial.gain);
|
auto smoothInitialGain = block.smooth(initial.gain);
|
||||||
auto smoothFeedbackGain = block.smooth(feedback.gain);
|
auto smoothFeedbackGain = block.smooth(feedback.gain);
|
||||||
for (int i = 0; i < block.length; ++i) {
|
for (int i = 0; i < block.length; ++i) {
|
||||||
for (int c = 0; c < channels; ++c) {
|
for (int c = 0; c < channels; ++c) {
|
||||||
|
Sample lfoDelay = channelLfos[c].next();
|
||||||
|
Sample initialDelayToLfo = std::max<Sample>(1, std::min<Sample>(maxDelaySamples, initialDelayTo + lfoDelay));
|
||||||
|
Sample initialDelayFromLfo = std::max<Sample>(1, std::min<Sample>(maxDelaySamples, initialDelayFrom + lfoDelay));
|
||||||
|
Sample feedbackDelayToLfo = std::max<Sample>(1, std::min<Sample>(maxDelaySamples, feedbackDelayTo + lfoDelay*feedbackLfoMultiplier));
|
||||||
|
Sample feedbackDelayFromLfo = std::max<Sample>(1, std::min<Sample>(maxDelaySamples, feedbackDelayFrom + lfoDelay*feedbackLfoMultiplier));
|
||||||
|
|
||||||
Sample value = io.input[c][i];
|
Sample value = io.input[c][i];
|
||||||
multiBuffer[c][0] = value;
|
multiBuffer[c][0] = value;
|
||||||
Sample initialDelayed = reader.read(multiBuffer[c], initialDelayTo);
|
Sample initialDelayed = reader.read(multiBuffer[c], initialDelayToLfo);
|
||||||
Sample feedbackDelayed = reader.read(multiBuffer[c], feedbackDelayTo);
|
Sample feedbackDelayed = reader.read(multiBuffer[c], feedbackDelayToLfo);
|
||||||
if (delayChanging) {
|
if (delayChanging) {
|
||||||
Sample initialDelayedFrom = reader.read(multiBuffer[c], initialDelayFrom);
|
Sample initialDelayedFrom = reader.read(multiBuffer[c], initialDelayFromLfo);
|
||||||
initialDelayed = block.fade(i, initialDelayedFrom, initialDelayed);
|
initialDelayed = block.fade(i, initialDelayedFrom, initialDelayed);
|
||||||
Sample feedbackDelayedFrom = reader.read(multiBuffer[c], feedbackDelayFrom);
|
Sample feedbackDelayedFrom = reader.read(multiBuffer[c], feedbackDelayFromLfo);
|
||||||
feedbackDelayed = block.fade(i, feedbackDelayedFrom, feedbackDelayed);
|
feedbackDelayed = block.fade(i, feedbackDelayedFrom, feedbackDelayed);
|
||||||
}
|
}
|
||||||
|
if (filtersChanging) {
|
||||||
|
initialDelayed = initialFilters[c].process(initialDelayed, block.fade(i));
|
||||||
|
feedbackDelayed = feedbackFilters[c].process(feedbackDelayed, block.fade(i));
|
||||||
|
} else {
|
||||||
|
initialDelayed = initialFilters[c].process(initialDelayed);
|
||||||
|
feedbackDelayed = feedbackFilters[c].process(feedbackDelayed);
|
||||||
|
}
|
||||||
multiBuffer[c][0] = value + feedbackDelayed*smoothFeedbackGain.at(i);
|
multiBuffer[c][0] = value + feedbackDelayed*smoothFeedbackGain.at(i);
|
||||||
io.output[c][i] = value + initialDelayed*smoothInitialGain.at(i);
|
io.output[c][i] = value + initialDelayed*smoothInitialGain.at(i);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user