Started reverb
This commit is contained in:
parent
d5b0bea6b0
commit
6e41b252a7
282
delay.h
282
delay.h
@ -1,282 +0,0 @@
|
||||
/* 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"
|
||||
#include "./dsp/filters.h"
|
||||
#include "./dsp/envelopes.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;
|
||||
|
||||
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
|
||||
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{100};
|
||||
ParamRange lowpass{12000};
|
||||
|
||||
ParamSteps steps{2};
|
||||
|
||||
template<class Storage>
|
||||
void state(Storage &storage) {
|
||||
storage.param("ms", ms)
|
||||
.info("time", "echo spacing (fixed time)")
|
||||
.range(0, 150, maxDelayMs)
|
||||
.unit("ms", 0);
|
||||
storage.param("steps", steps)
|
||||
.info("tempo", "echo spacing (tempo-dependent)")
|
||||
.range(0, 8)
|
||||
.unit("steps", 0);
|
||||
storage.param("gain", gain)
|
||||
.info("gain", "echo decay")
|
||||
.range(0, 0.4, 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, 4000, 20000)
|
||||
.unit("kHz", 1, kHz_hz, hz_kHz, 1000, 1e10)
|
||||
.unit("Hz", 0);
|
||||
}
|
||||
};
|
||||
EchoSpec initial;
|
||||
EchoSpec feedback;
|
||||
|
||||
struct Wobble {
|
||||
ParamRange rate{2};
|
||||
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};
|
||||
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;
|
||||
signalsmith::delay::Reader<Sample, signalsmith::delay::InterpolatorNearest> readerNearest;
|
||||
|
||||
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:
|
||||
|
||||
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("wobble", wobble);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
initialFilters.resize(channels);
|
||||
feedbackFilters.resize(channels);
|
||||
channelLfos.resize(channels);
|
||||
}
|
||||
|
||||
void 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>
|
||||
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 stepSamplesTo = 60*config.sampleRate/this->bpm.to()*stepFactors[beatSteps.to()];
|
||||
|
||||
auto samplesDelay = [&](double ms, double steps, double stepSamples) {
|
||||
return int(ms*0.001*config.sampleRate + steps*stepSamples);
|
||||
};
|
||||
int initialDelayFrom = samplesDelay(initial.ms.from(), initial.steps.from(), stepSamplesFrom);
|
||||
int initialDelayTo = samplesDelay(initial.ms.to(), initial.steps.to(), stepSamplesTo);
|
||||
int feedbackDelayFrom = samplesDelay(feedback.ms.from(), feedback.steps.from(), stepSamplesFrom);
|
||||
int feedbackDelayTo = samplesDelay(feedback.ms.to(), feedback.steps.to(), stepSamplesTo);
|
||||
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 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 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 initialDelayed, feedbackDelayed;
|
||||
if (lfoDepthSamples > 0) {
|
||||
initialDelayed = reader.read(multiBuffer[c], initialDelayToLfo);
|
||||
feedbackDelayed = reader.read(multiBuffer[c], feedbackDelayToLfo);
|
||||
if (delayChanging) {
|
||||
Sample initialDelayedFrom = reader.read(multiBuffer[c], initialDelayFromLfo);
|
||||
initialDelayed = block.fade(i, initialDelayedFrom, initialDelayed);
|
||||
Sample feedbackDelayedFrom = reader.read(multiBuffer[c], feedbackDelayFromLfo);
|
||||
feedbackDelayed = block.fade(i, feedbackDelayedFrom, feedbackDelayed);
|
||||
}
|
||||
} else { // use cheaper delay-reader
|
||||
initialDelayed = readerNearest.read(multiBuffer[c], initialDelayToLfo);
|
||||
feedbackDelayed = readerNearest.read(multiBuffer[c], feedbackDelayToLfo);
|
||||
if (delayChanging) {
|
||||
Sample initialDelayedFrom = readerNearest.read(multiBuffer[c], initialDelayFromLfo);
|
||||
initialDelayed = block.fade(i, initialDelayedFrom, initialDelayed);
|
||||
Sample feedbackDelayedFrom = readerNearest.read(multiBuffer[c], feedbackDelayFromLfo);
|
||||
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);
|
||||
io.output[c][i] = value + initialDelayed*smoothInitialGain.at(i);
|
||||
}
|
||||
++multiBuffer;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using Delay = stfx::LibraryEffect<float, DelaySTFX>;
|
||||
|
||||
}} // namespace
|
||||
|
||||
#endif // include guard
|
||||
@ -38,7 +38,7 @@ namespace signalsmith { namespace basics {
|
||||
|
||||
public:
|
||||
ParamRange inputGain{1};
|
||||
ParamRange outputLimit{db_gain(-12)};
|
||||
ParamRange outputLimit{db_gain(-3)};
|
||||
ParamRange attackMs{20}, holdMs{0}, releaseMs{0};
|
||||
|
||||
ParamSteps smoothingStages{1};
|
||||
@ -145,7 +145,7 @@ namespace signalsmith { namespace basics {
|
||||
}
|
||||
|
||||
template <class Io, class Config, class Block>
|
||||
void process(Io &io, Config &, Block &block) {
|
||||
void processSTFX(Io &io, Config &, Block &block) {
|
||||
Sample thresholdAmp = outputLimit;
|
||||
auto smoothedPreGain = block.smooth(inputGain);
|
||||
|
||||
|
||||
289
reverb.h
Normal file
289
reverb.h
Normal file
@ -0,0 +1,289 @@
|
||||
/* Copyright 2022 Signalsmith Audio Ltd. / Geraint Luff
|
||||
Released under the Boost Software License (see LICENSE.txt) */
|
||||
#ifndef SIGNALSMITH_BASICS_REVERB_H
|
||||
#define SIGNALSMITH_BASICS_REVERB_H
|
||||
|
||||
#include "dsp/delay.h"
|
||||
#include "dsp/mix.h"
|
||||
SIGNALSMITH_DSP_VERSION_CHECK(1, 3, 3)
|
||||
|
||||
#include "./stfx-library.h"
|
||||
#include "./units.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <random>
|
||||
|
||||
namespace signalsmith { namespace basics {
|
||||
|
||||
template<typename Sample, class BaseEffect>
|
||||
struct ReverbSTFX : public BaseEffect {
|
||||
using typename BaseEffect::ParamRange;
|
||||
using typename BaseEffect::ParamSteps;
|
||||
using Array = std::array<Sample, 8>;
|
||||
|
||||
ParamRange dry{1}, wet{0.25};
|
||||
ParamRange roomMs{100};
|
||||
ParamRange rt20{3};
|
||||
ParamRange early{1};
|
||||
|
||||
ReverbSTFX(double maxRoomMs=200) : maxRoomMs(maxRoomMs) {}
|
||||
|
||||
template<class Storage>
|
||||
void state(Storage &storage) {
|
||||
using namespace signalsmith::units;
|
||||
|
||||
storage.info("[Basics] Reverb", "An FDN reverb");
|
||||
int version = storage.version(3);
|
||||
if (version != 3) return;
|
||||
|
||||
storage.param("dry", dry)
|
||||
.info("dry", "dry signal gain")
|
||||
.range(0, 1, 4)
|
||||
.unit("dB", 1, dbToGain, gainToDb)
|
||||
.unit("%", 0, pcToRatio, ratioToPc)
|
||||
.exact(0, "off")
|
||||
.unit("");
|
||||
|
||||
storage.param("wet", wet)
|
||||
.info("wet", "reverb tail gain")
|
||||
.range(0, 1, 4)
|
||||
.unit("dB", 1, dbToGain, gainToDb)
|
||||
.unit("%", 0, pcToRatio, ratioToPc)
|
||||
.exact(0, "off")
|
||||
.unit("");
|
||||
|
||||
storage.param("roomMs", roomMs)
|
||||
.info("room", "room size (1ms ~ 1 foot)")
|
||||
.range(10, 100, maxRoomMs)
|
||||
.unit("ms", 0);
|
||||
|
||||
storage.param("rt20", rt20)
|
||||
.info("decay", "RT20: decay time to -20dB")
|
||||
.range(0.1, 3, 30)
|
||||
.unit("seconds", 2, 0, 1)
|
||||
.unit("seconds", 1, 1, 1e100);
|
||||
|
||||
storage.param("early", early)
|
||||
.info("early", "Early reflections")
|
||||
.range(0, 1, 2)
|
||||
.unit("%", 0, pcToRatio, ratioToPc);
|
||||
}
|
||||
|
||||
template<class Config>
|
||||
void configure(Config &config) {
|
||||
config.outputChannels = config.inputChannels = 2;
|
||||
config.auxInputs.resize(0);
|
||||
config.auxOutputs.resize(0);
|
||||
|
||||
double maxRoomSamples = maxRoomMs*0.001*config.sampleRate;
|
||||
delay1.configure(maxRoomSamples, 0.125);
|
||||
delay2.configure(maxRoomSamples, 1);
|
||||
delay3.configure(maxRoomSamples, 0.5);
|
||||
delay4.configure(maxRoomSamples, 0.25);
|
||||
delayFeedback.configure(maxRoomSamples*2, 1);
|
||||
delayEarly.configure(maxRoomSamples, 0.25);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
delay1.reset();
|
||||
delay2.reset();
|
||||
delay3.reset();
|
||||
delayFeedback.reset();
|
||||
delayEarly.reset();
|
||||
}
|
||||
|
||||
int latencySamples() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<class Io, class Config, class Block>
|
||||
void processSTFX(Io &io, Config &config, Block &block) {
|
||||
using Hadamard = signalsmith::mix::Hadamard<Sample, 8>;
|
||||
using Householder = signalsmith::mix::Householder<Sample, 8>;
|
||||
|
||||
auto &&inputLeft = io.input[0];
|
||||
auto &&inputRight = io.input[1];
|
||||
auto &&outputLeft = io.output[0];
|
||||
auto &&outputRight = io.output[1];
|
||||
|
||||
auto smoothedDryGain = block.smooth(dry);
|
||||
Sample scalingFactor = stereoMixer.scalingFactor2()*0.015625; // 4 Hadamard mixes
|
||||
auto smoothedWetGain = block.smooth(wet.from(), wet.to());
|
||||
auto smoothedInputGain = block.smooth( // tuned by ear: smaller feedback loops with longer decays sound louder
|
||||
scalingFactor*std::sqrt(roomMs.from()/(rt20.from()*50 + roomMs.from())),
|
||||
scalingFactor*std::sqrt(roomMs.to()/(rt20.to()*50 + roomMs.to()))
|
||||
);
|
||||
using signalsmith::units::dbToGain;
|
||||
auto smoothedDecayGain = block.smooth(
|
||||
dbToGain(getDecayDb(rt20.from(), roomMs.from())),
|
||||
dbToGain(getDecayDb(rt20.to(), roomMs.to()))
|
||||
);
|
||||
auto smoothedEarlyGain = block.smooth(early, [&](double g) {
|
||||
return g*0.35; // tuned by ear
|
||||
});
|
||||
|
||||
block.setupFade([&](){
|
||||
updateDelays(roomMs.to()*0.001*config.sampleRate);
|
||||
});
|
||||
bool fading = block.fading();
|
||||
|
||||
for (int i = 0; i < block.length; ++i) {
|
||||
Sample inputGain = smoothedInputGain.at(i);
|
||||
Sample decayGain = smoothedDecayGain.at(i);
|
||||
Sample earlyGain = smoothedEarlyGain.at(i);
|
||||
|
||||
Array samples;
|
||||
std::array<Sample, 2> stereoIn = {inputLeft[i]*inputGain, inputRight[i]*inputGain};
|
||||
stereoMixer.stereoToMulti(stereoIn, samples);
|
||||
|
||||
if (fading) {
|
||||
Sample fade = block.fade(i);
|
||||
samples = delay1.write(samples).read(fade);
|
||||
Hadamard::unscaledInPlace(samples);
|
||||
samples = delay2.write(samples).read(fade);
|
||||
Hadamard::unscaledInPlace(samples);
|
||||
|
||||
Array feedback = delayFeedback.read(fade);
|
||||
Householder::inPlace(feedback);
|
||||
Array feedbackInput;
|
||||
for (int c = 0; c < 8; ++c) feedbackInput[c] = samples[c] + feedback[c]*decayGain;
|
||||
delayFeedback.write(feedbackInput);
|
||||
|
||||
Array earlyReflections = delayEarly.write(samples).read(fade);
|
||||
Hadamard::unscaledInPlace(earlyReflections);
|
||||
for (int c = 0; c < 8; ++c) samples[c] = feedback[c] + earlyReflections[c]*earlyGain;
|
||||
|
||||
samples = delay3.write(samples).read(fade);
|
||||
Hadamard::unscaledInPlace(samples);
|
||||
samples = delay4.write(samples).read(fade);
|
||||
} else {
|
||||
samples = delay1.write(samples).read();
|
||||
Hadamard::unscaledInPlace(samples);
|
||||
samples = delay2.write(samples).read();
|
||||
Hadamard::unscaledInPlace(samples);
|
||||
|
||||
Array feedback = delayFeedback.read();
|
||||
Householder::inPlace(feedback);
|
||||
Array feedbackInput;
|
||||
for (int c = 0; c < 8; ++c) feedbackInput[c] = samples[c] + feedback[c]*decayGain;
|
||||
delayFeedback.write(feedbackInput);
|
||||
|
||||
Array earlyReflections = delayEarly.write(samples).read();
|
||||
Hadamard::unscaledInPlace(earlyReflections);
|
||||
for (int c = 0; c < 8; ++c) samples[c] = feedback[c] + earlyReflections[c]*earlyGain;
|
||||
|
||||
samples = delay3.write(samples).read();
|
||||
Hadamard::unscaledInPlace(samples);
|
||||
samples = delay4.write(samples).read();
|
||||
}
|
||||
|
||||
std::array<Sample, 2> stereoOut;
|
||||
stereoMixer.multiToStereo(samples, stereoOut);
|
||||
|
||||
Sample dryGain = smoothedDryGain.at(i);
|
||||
Sample wetGain = smoothedWetGain.at(i);
|
||||
outputLeft[i] = stereoIn[0]*dryGain + stereoOut[0]*wetGain;
|
||||
outputRight[i] = stereoIn[1]*dryGain + stereoOut[1]*wetGain;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int channels = 0;
|
||||
double maxRoomMs;
|
||||
|
||||
static Sample getDecayDb(Sample rt20, Sample loopMs) {
|
||||
Sample dbPerSecond = -20/rt20;
|
||||
Sample secondsPerLoop = loopMs*Sample(0.001);
|
||||
return dbPerSecond*secondsPerLoop;
|
||||
}
|
||||
|
||||
signalsmith::mix::StereoMultiMixer<Sample, 8> stereoMixer;
|
||||
|
||||
struct MultiDelay {
|
||||
signalsmith::delay::MultiBuffer<Sample> buffer;
|
||||
double delayScale = 1;
|
||||
std::array<int, 8> delayOffsets, delayOffsetsPrev;
|
||||
|
||||
void configure(double maxRangeSamples, double scale) {
|
||||
delayScale = scale;
|
||||
buffer.resize(8, std::ceil(maxRangeSamples*delayScale) + 1);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
buffer.reset();
|
||||
}
|
||||
|
||||
void updateLengths(int seed, double rangeSamples, bool minimise=true) {
|
||||
rangeSamples *= delayScale;
|
||||
delayOffsetsPrev = delayOffsets;
|
||||
std::mt19937 engine(seed);
|
||||
std::uniform_real_distribution<float> unitDist(0, 1);
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
float unit = unitDist(engine);
|
||||
delayOffsets[i] = int(-std::floor(rangeSamples*(unit + i)/8));
|
||||
std::uniform_int_distribution<int> indexDist(0, i);
|
||||
int swapIndex = indexDist(engine);
|
||||
std::swap(delayOffsets[i], delayOffsets[swapIndex]);
|
||||
}
|
||||
if (minimise) { // Moves things along so the shortest delay is always 0
|
||||
int maximumDelay = delayOffsets[0];
|
||||
for (auto &d : delayOffsets) maximumDelay = std::max(d, maximumDelay);
|
||||
for (auto &d : delayOffsets) d -= maximumDelay;
|
||||
}
|
||||
}
|
||||
|
||||
void updateLengthsExponential(int seed, double rangeSamples) {
|
||||
rangeSamples *= delayScale;
|
||||
delayOffsetsPrev = delayOffsets;
|
||||
std::mt19937 engine(seed);
|
||||
constexpr double ratios[8] = {0.125, -0.125, 0.375, -0.375, 0.625, -0.625, 0.875, -0.875};
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
delayOffsets[i] = int(-std::floor(rangeSamples*std::pow(2, ratios[i]/2)));
|
||||
std::uniform_int_distribution<int> indexDist(0, i);
|
||||
int swapIndex = indexDist(engine);
|
||||
std::swap(delayOffsets[i], delayOffsets[swapIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
MultiDelay & write(const Array &arr) {
|
||||
++buffer;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
buffer[i][0] = arr[i];
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Array read() {
|
||||
Array result;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
result[i] = buffer[i][delayOffsets[i]];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Array read(Sample fade) {
|
||||
Array result;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
Sample to = buffer[i][delayOffsets[i]];
|
||||
Sample from = buffer[i][delayOffsetsPrev[i]];
|
||||
result[i] = from + (to - from)*fade;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
MultiDelay delay1, delay2, delay3, delay4, delayFeedback, delayEarly;
|
||||
void updateDelays(double roomSamples) {
|
||||
delay1.updateLengths(0x876753A5, roomSamples, false);
|
||||
delay2.updateLengths(0x876753A5, roomSamples);
|
||||
delay3.updateLengths(0x5974DF44, roomSamples);
|
||||
delay4.updateLengths(0x8CDBF7E6, roomSamples);
|
||||
delayFeedback.updateLengthsExponential(0xC6BF7158, roomSamples);
|
||||
delayEarly.updateLengths(0x0BDDE171, roomSamples);
|
||||
}
|
||||
};
|
||||
|
||||
using Reverb = stfx::LibraryEffect<float, ReverbSTFX>;
|
||||
|
||||
}} // namespace
|
||||
|
||||
#endif // include guard
|
||||
@ -1,6 +1,5 @@
|
||||
/* Copyright 2021 Geraint Luff / Signalsmith Audio Ltd
|
||||
|
||||
This file is released under the MIT License - if you need anything else, let us know. 🙂
|
||||
/* Copyright 2021-2022 Geraint Luff / Signalsmith Audio Ltd
|
||||
Released under the Boost Software License (see LICENSE.txt)
|
||||
|
||||
The main thing you need is `stfx::LibraryEffect<Sample, EffectTemplate>`
|
||||
which produces a simple effect class from an STFX effect template.
|
||||
@ -396,7 +395,7 @@ namespace stfx {
|
||||
Io io{inputs, outputs};
|
||||
Block block(blockLength, fadeRatio, fadeRatioStep, justHadReset);
|
||||
|
||||
((EffectClass *)this)->process(io, (const Config &)config, (const Block &)block);
|
||||
((EffectClass *)this)->processSTFX(io, (const Config &)config, (const Block &)block);
|
||||
|
||||
if (fadeRatioEnd >= 1) {
|
||||
// Fade just finished, so we reset
|
||||
|
||||
24
units.h
Normal file
24
units.h
Normal file
@ -0,0 +1,24 @@
|
||||
/* Copyright 2022 Signalsmith Audio Ltd. / Geraint Luff
|
||||
Released under the Boost Software License (see LICENSE.txt) */
|
||||
#ifndef SIGNALSMITH_UNITS_H
|
||||
#define SIGNALSMITH_UNITS_H
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace signalsmith { namespace units {
|
||||
|
||||
static double dbToGain(double db) {
|
||||
return std::pow(10, db*0.05);
|
||||
}
|
||||
static double gainToDb(double gain) {
|
||||
return std::log10(std::max<double>(gain, 1e-10))*20;
|
||||
}
|
||||
static double pcToRatio(double percent) {
|
||||
return percent*0.01;
|
||||
}
|
||||
static double ratioToPc(double linear) {
|
||||
return linear*100;
|
||||
}
|
||||
|
||||
}} // namespace
|
||||
#endif // include guard
|
||||
Loading…
x
Reference in New Issue
Block a user