Compare commits
10 Commits
6e41b252a7
...
b79063c97b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b79063c97b | ||
|
|
ba22c38a61 | ||
|
|
d22cd189fe | ||
|
|
7896c05a5e | ||
|
|
e3fc8462b5 | ||
|
|
3771df3f1f | ||
|
|
9df6e69313 | ||
|
|
e38dada1d7 | ||
|
|
a5c32ecaf8 | ||
|
|
713f3a309a |
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -1,3 +1,3 @@
|
||||
[submodule "dsp"]
|
||||
path = dsp
|
||||
url = https://signalsmith-audio.co.uk/code/dsp.git/
|
||||
url = https://signalsmith-audio.co.uk/code/dsp.git
|
||||
|
||||
23
LICENSE.txt
23
LICENSE.txt
@ -1,23 +0,0 @@
|
||||
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.
|
||||
32
README.md
Normal file
32
README.md
Normal file
@ -0,0 +1,32 @@
|
||||
# Signalsmith Basics
|
||||
|
||||
A collection of basic effects, available as plugins and re-usable open-source C++ classes.
|
||||
|
||||
## How to use
|
||||
|
||||
The [main project page](https://signalsmith-audio.co.uk/code/basics/) has details about the specific effects (and audio examples), but they are all quite similar to use:
|
||||
|
||||
```cpp
|
||||
// Limiter with maximum attack/lookahead of 100ms
|
||||
signalsmith::basics::Limiter effect(100);
|
||||
|
||||
effect.configure(sampleRate, maxBlockSize, channels);
|
||||
effect.configure(sampleRate, maxBlockSize, inputChannels, outputChannels);
|
||||
```
|
||||
|
||||
Then when processing (all on the audio thread):
|
||||
|
||||
```cpp
|
||||
// clear buffers
|
||||
effect.reset()
|
||||
|
||||
// Change parameters with assignment
|
||||
effect.attackMs = 20;
|
||||
|
||||
// process a block
|
||||
float **inputBuffers, **outputBuffers;
|
||||
int blockSize;
|
||||
effect.process(inputBuffers, outputBuffers, blockSize);
|
||||
```
|
||||
|
||||
You can also inspect latency (`effect.latencySamples()`) and tail length (`effect.tailSamples()`).
|
||||
171
crunch.h
Normal file
171
crunch.h
Normal file
@ -0,0 +1,171 @@
|
||||
/* Copyright 2022 Signalsmith Audio Ltd. / Geraint Luff
|
||||
Released under the Boost Software License (see LICENSE.txt) */
|
||||
#ifndef SIGNALSMITH_BASICS_CRUNCH_H
|
||||
#define SIGNALSMITH_BASICS_CRUNCH_H
|
||||
|
||||
#include "dsp/rates.h"
|
||||
SIGNALSMITH_DSP_VERSION_CHECK(1, 4, 1)
|
||||
|
||||
#include "./stfx-library.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace signalsmith { namespace basics {
|
||||
|
||||
template<class BaseEffect>
|
||||
class CrunchSTFX : public BaseEffect {
|
||||
using typename BaseEffect::Sample;
|
||||
using typename BaseEffect::ParamRange;
|
||||
using typename BaseEffect::ParamStepped;
|
||||
|
||||
int channels = 0;
|
||||
signalsmith::rates::Oversampler2xFIR<Sample> oversampler;
|
||||
struct GainshapeADAA {
|
||||
Sample prevX = 0, prevIntegral = 0;
|
||||
Sample fuzzPositive = 1, fuzzNegative = 1;
|
||||
|
||||
void setFuzzFactor(Sample k) {
|
||||
fuzzPositive = 1 + k - k*k;
|
||||
fuzzNegative = 1 - k - k*k;
|
||||
prevIntegral = integralGain(prevX);
|
||||
}
|
||||
|
||||
Sample gain(Sample x) const {
|
||||
Sample fuzzGain = (x >= 0 ? fuzzPositive : fuzzNegative);
|
||||
return fuzzGain/std::sqrt(1 + x*x);
|
||||
}
|
||||
Sample integralGain(Sample x) const {
|
||||
if (x >= 0) {
|
||||
return fuzzPositive*std::log(std::sqrt(1 + x*x) + x);
|
||||
} else { // more accurate if we flip it
|
||||
return -fuzzNegative*std::log(std::sqrt(1 + x*x) - x);
|
||||
}
|
||||
}
|
||||
Sample averageGain(Sample range) const {
|
||||
// Average gain from 0-range, ignoring fuzz
|
||||
return std::log(std::sqrt(1 + range*range) + range)/range;
|
||||
}
|
||||
static constexpr Sample minDiffX = 1e-4;
|
||||
|
||||
void reset() {
|
||||
prevX = 0;
|
||||
prevIntegral = integralGain(prevX);
|
||||
}
|
||||
|
||||
Sample operator()(Sample x) {
|
||||
Sample diffX = x - prevX;
|
||||
Sample integral = integralGain(x);
|
||||
Sample diffIntegral = integral - prevIntegral;
|
||||
prevX = x;
|
||||
prevIntegral = integral;
|
||||
if (std::abs(diffX) < minDiffX) return gain(x);
|
||||
return diffIntegral/diffX;
|
||||
}
|
||||
};
|
||||
std::vector<GainshapeADAA> gainshapers;
|
||||
using Filter = signalsmith::filters::BiquadStatic<Sample>;
|
||||
std::vector<Filter> toneFilters, outputFilters;
|
||||
|
||||
static constexpr int oversampleHalfLatency = 16;
|
||||
static constexpr Sample autoGainLevel = 0.1;
|
||||
public:
|
||||
ParamRange drive{4};
|
||||
ParamRange fuzz{0};
|
||||
ParamRange toneHz{2000};
|
||||
ParamRange outGain{1};
|
||||
|
||||
bool autoGain;
|
||||
CrunchSTFX(bool autoGain=true) : autoGain(autoGain) {}
|
||||
|
||||
template<class Storage>
|
||||
void state(Storage &storage) {
|
||||
storage.info("[Basics] Crunch", "A simple distortion/saturation");
|
||||
int version = storage.version(0);
|
||||
if (version != 0) return;
|
||||
|
||||
using namespace signalsmith::units;
|
||||
storage.range("drive", drive)
|
||||
.info("drive", "pre-distortion input gain")
|
||||
.range(dbToGain(-12), 4, dbToGain(40))
|
||||
.unit("dB", 1, dbToGain, gainToDb)
|
||||
.unit("");
|
||||
storage.range("fuzz", fuzz)
|
||||
.info("fuzz", "amplitude-independent distortion")
|
||||
.range(0, 0.5, 1)
|
||||
.unit("%", 0, pcToRatio, ratioToPc);
|
||||
storage.range("toneHz", toneHz)
|
||||
.info("tone", "limits the brightness of the distortion")
|
||||
.range(100, 4000, 20000)
|
||||
.unit("Hz", 0, 0, 1000)
|
||||
.unit("kHz", 1, kHzToHz, hzToKHz, 1000, 1e100);
|
||||
|
||||
storage.range("outGain", outGain)
|
||||
.info("out", "output gain")
|
||||
.range(dbToGain(-12), 1, dbToGain(24))
|
||||
.unit("dB", 1, dbToGain, gainToDb)
|
||||
.unit("");
|
||||
}
|
||||
|
||||
template<class Config>
|
||||
void configure(Config &config) {
|
||||
channels = config.outputChannels = config.inputChannels;
|
||||
config.auxInputs.resize(0);
|
||||
config.auxOutputs.resize(0);
|
||||
|
||||
oversampler.resize(channels, config.maxBlockSize, oversampleHalfLatency, std::min(0.45, 21000/config.sampleRate));
|
||||
gainshapers.resize(channels);
|
||||
toneFilters.resize(channels);
|
||||
outputFilters.resize(channels);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
oversampler.reset();
|
||||
for (auto &g : gainshapers) g.reset();
|
||||
for (auto &f : toneFilters) f.reset();
|
||||
for (auto &f : outputFilters) f.reset();
|
||||
}
|
||||
|
||||
int latencySamples() const {
|
||||
return oversampleHalfLatency*2;
|
||||
}
|
||||
|
||||
template <class Io, class Config, class Block>
|
||||
void processSTFX(Io &io, Config &config, Block &block) {
|
||||
auto inputGain = block.smooth(drive);
|
||||
|
||||
double outputGainFrom = outGain.from();
|
||||
double outputGainTo = outGain.to();
|
||||
if (autoGain) {
|
||||
Sample averageGain = gainshapers[0].averageGain(autoGainLevel*drive.from());
|
||||
outputGainFrom /= drive.from()*averageGain;
|
||||
outputGainTo /= drive.to()*gainshapers[0].averageGain(autoGainLevel*drive.to());
|
||||
}
|
||||
auto outputGain = block.smooth(outputGainFrom, outputGainTo);
|
||||
|
||||
for (int c = 0; c < channels; ++c) {
|
||||
auto &gainshaper = gainshapers[c];
|
||||
gainshaper.setFuzzFactor(fuzz);
|
||||
auto &toneFilter = toneFilters[c];
|
||||
toneFilter.lowpass(toneHz/(config.sampleRate*2));
|
||||
auto &outputFilter = outputFilters[c];
|
||||
outputFilter.highpass((10 + 40*fuzz)/(config.sampleRate*2)); // more aggressive when fuzz is enabled, since it's very asymmetrical
|
||||
|
||||
oversampler.upChannel(c, io.input[c], block.length);
|
||||
Sample *samples = oversampler[c];
|
||||
for (int i = 0; i < block.length*2; ++i) {
|
||||
double hi = i*0.5;
|
||||
Sample x = samples[i]*inputGain.at(hi);
|
||||
Sample gain = gainshaper(x)*outputGain.at(hi);
|
||||
Sample y = x*toneFilter(gain);
|
||||
samples[i] = outputFilter(y);
|
||||
}
|
||||
oversampler.downChannel(c, io.output[c], block.length);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using Crunch = stfx::LibraryEffect<float, CrunchSTFX>;
|
||||
|
||||
}} // namespace
|
||||
|
||||
#endif // include guard
|
||||
2
dsp
2
dsp
@ -1 +1 @@
|
||||
Subproject commit 763fd4751da69ce412878a31ffd383811e562f5f
|
||||
Subproject commit 618097bed9e7fb1b87a99592f78c9a8a964eda08
|
||||
59
limiter.h
59
limiter.h
@ -3,34 +3,22 @@ Released under the Boost Software License (see LICENSE.txt) */
|
||||
#ifndef SIGNALSMITH_BASICS_LIMITER_H
|
||||
#define SIGNALSMITH_BASICS_LIMITER_H
|
||||
|
||||
#include "./dsp/delay.h"
|
||||
#include "./dsp/envelopes.h"
|
||||
#include "dsp/delay.h"
|
||||
#include "dsp/envelopes.h"
|
||||
SIGNALSMITH_DSP_VERSION_CHECK(1, 1, 0)
|
||||
|
||||
#include "./units.h"
|
||||
#include "./stfx-library.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace signalsmith { namespace basics {
|
||||
|
||||
template<typename Sample, class BaseEffect>
|
||||
template<class BaseEffect>
|
||||
class LimiterSTFX : public BaseEffect {
|
||||
using typename BaseEffect::Sample;
|
||||
using typename BaseEffect::ParamRange;
|
||||
using typename BaseEffect::ParamSteps;
|
||||
|
||||
// Unit conversions (for display only)
|
||||
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;
|
||||
}
|
||||
using typename BaseEffect::ParamStepped;
|
||||
|
||||
int channels = 0;
|
||||
double maxDelayMs = 0;
|
||||
@ -38,52 +26,53 @@ namespace signalsmith { namespace basics {
|
||||
|
||||
public:
|
||||
ParamRange inputGain{1};
|
||||
ParamRange outputLimit{db_gain(-3)};
|
||||
ParamRange outputLimit{signalsmith::units::dbToGain(-3)};
|
||||
ParamRange attackMs{20}, holdMs{0}, releaseMs{0};
|
||||
|
||||
ParamSteps smoothingStages{1};
|
||||
ParamStepped smoothingStages{1};
|
||||
ParamRange linkChannels{0.5};
|
||||
|
||||
LimiterSTFX(double maxDelayMs=100) : maxDelayMs(maxDelayMs) {}
|
||||
|
||||
template<class Storage>
|
||||
void state(Storage &storage) {
|
||||
using namespace signalsmith::units;
|
||||
storage.info("[Basics] Limiter", "A simple lookahead limiter");
|
||||
int version = storage.version(4);
|
||||
if (version != 4) return;
|
||||
|
||||
storage.param("inputGain", inputGain)
|
||||
storage.range("inputGain", inputGain)
|
||||
.info("pre-gain", "amplifies the input before limiting")
|
||||
.range(db_gain(-12), 1, db_gain(24))
|
||||
.unit("dB", 1, db_gain, gain_db)
|
||||
.range(dbToGain(-12), 1, dbToGain(24))
|
||||
.unit("dB", 1, dbToGain, gainToDb)
|
||||
.unit("");
|
||||
storage.param("outputLimit", outputLimit)
|
||||
storage.range("outputLimit", outputLimit)
|
||||
.info("limit", "maximum output amplitude")
|
||||
.range(db_gain(-24), db_gain(-12), 1)
|
||||
.unit("dB", 1, db_gain, gain_db)
|
||||
.range(dbToGain(-24), dbToGain(-12), 1)
|
||||
.unit("dB", 1, dbToGain, gainToDb)
|
||||
// Extra resolution between -1dB and 0dB
|
||||
.unit("dB", 2, db_gain, gain_db, db_gain(-1), 1)
|
||||
.unit("dB", 2, dbToGain, gainToDb, dbToGain(-1), 1)
|
||||
.unit("");
|
||||
storage.param("attackMs", attackMs)
|
||||
storage.range("attackMs", attackMs)
|
||||
.info("attack", "envelope smoothing time")
|
||||
.range(1, 10, maxDelayMs/2)
|
||||
.unit("ms", 0);
|
||||
storage.param("holdMs", holdMs)
|
||||
storage.range("holdMs", holdMs)
|
||||
.info("hold", "hold constant after peaks")
|
||||
.range(0, 10, maxDelayMs/2)
|
||||
.unit("ms", 0);
|
||||
storage.param("releaseMs", releaseMs)
|
||||
storage.range("releaseMs", releaseMs)
|
||||
.info("release", "extra release time (in addition to attack + hold)")
|
||||
.range(0, 10, 250)
|
||||
.unit("ms", 0);
|
||||
|
||||
storage.param("smoothingStages", smoothingStages)
|
||||
storage.stepped("smoothingStages", smoothingStages)
|
||||
.info("smoothing", "smoothing filter(s) used for attack-smoothing")
|
||||
.names(1, "rect", "double");
|
||||
storage.param("linkChannels", linkChannels)
|
||||
.label(1, "rect", "double");
|
||||
storage.range("linkChannels", linkChannels)
|
||||
.info("link", "link channel gains together")
|
||||
.range(0, 0.5, 1)
|
||||
.unit("%", 0, pc_linear, linear_pc);
|
||||
.unit("%", 0, pcToRatio, ratioToPc);
|
||||
}
|
||||
|
||||
// Gain envelopes are calculated per-channel
|
||||
@ -156,7 +145,7 @@ namespace signalsmith { namespace basics {
|
||||
int attackSamples = delaySamplesTo;
|
||||
int holdSamples = std::ceil(holdMs*0.001*sampleRate);
|
||||
Sample releaseSamples = releaseMs*0.001*sampleRate;
|
||||
int stages = smoothingStages.to();
|
||||
int stages = smoothingStages;
|
||||
|
||||
for (auto &envelope : channelEnvelopes) {
|
||||
envelope.peakHold.set(attackSamples + holdSamples);
|
||||
|
||||
243
reverb.h
243
reverb.h
@ -5,6 +5,7 @@ Released under the Boost Software License (see LICENSE.txt) */
|
||||
|
||||
#include "dsp/delay.h"
|
||||
#include "dsp/mix.h"
|
||||
#include "dsp/filters.h"
|
||||
SIGNALSMITH_DSP_VERSION_CHECK(1, 3, 3)
|
||||
|
||||
#include "./stfx-library.h"
|
||||
@ -15,28 +16,34 @@ SIGNALSMITH_DSP_VERSION_CHECK(1, 3, 3)
|
||||
|
||||
namespace signalsmith { namespace basics {
|
||||
|
||||
template<typename Sample, class BaseEffect>
|
||||
template<class BaseEffect>
|
||||
struct ReverbSTFX : public BaseEffect {
|
||||
using typename BaseEffect::Sample;
|
||||
using typename BaseEffect::ParamRange;
|
||||
using typename BaseEffect::ParamSteps;
|
||||
using typename BaseEffect::ParamStepped;
|
||||
using Array = std::array<Sample, 8>;
|
||||
using Array3 = std::array<Sample, 3>;
|
||||
|
||||
ParamRange dry{1}, wet{0.25};
|
||||
ParamRange roomMs{100};
|
||||
ParamRange rt20{3};
|
||||
ParamRange early{1};
|
||||
|
||||
ReverbSTFX(double maxRoomMs=200) : maxRoomMs(maxRoomMs) {}
|
||||
ParamRange dry{1}, wet{0.5};
|
||||
ParamRange roomMs{80};
|
||||
ParamRange rt20{1};
|
||||
ParamRange early{1.5};
|
||||
ParamRange detune{2};
|
||||
|
||||
ParamRange lowCutHz{80}, highCutHz{12000};
|
||||
ParamRange lowDampRate{1.5}, highDampRate{2.5};
|
||||
|
||||
ReverbSTFX(double maxRoomMs=200, double detuneDepthMs=2) : maxRoomMs(maxRoomMs), detuneDepthMs(detuneDepthMs) {}
|
||||
|
||||
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;
|
||||
int version = storage.version(5);
|
||||
if (version != 5) return;
|
||||
|
||||
storage.param("dry", dry)
|
||||
storage.range("dry", dry)
|
||||
.info("dry", "dry signal gain")
|
||||
.range(0, 1, 4)
|
||||
.unit("dB", 1, dbToGain, gainToDb)
|
||||
@ -44,7 +51,7 @@ namespace signalsmith { namespace basics {
|
||||
.exact(0, "off")
|
||||
.unit("");
|
||||
|
||||
storage.param("wet", wet)
|
||||
storage.range("wet", wet)
|
||||
.info("wet", "reverb tail gain")
|
||||
.range(0, 1, 4)
|
||||
.unit("dB", 1, dbToGain, gainToDb)
|
||||
@ -52,35 +59,77 @@ namespace signalsmith { namespace basics {
|
||||
.exact(0, "off")
|
||||
.unit("");
|
||||
|
||||
storage.param("roomMs", roomMs)
|
||||
storage.range("roomMs", roomMs)
|
||||
.info("room", "room size (1ms ~ 1 foot)")
|
||||
.range(10, 100, maxRoomMs)
|
||||
.unit("ms", 0);
|
||||
|
||||
storage.param("rt20", rt20)
|
||||
storage.range("rt20", rt20)
|
||||
.info("decay", "RT20: decay time to -20dB")
|
||||
.range(0.1, 3, 30)
|
||||
.range(0.01, 2, 30)
|
||||
.unit("seconds", 2, 0, 1)
|
||||
.unit("seconds", 1, 1, 1e100);
|
||||
|
||||
storage.param("early", early)
|
||||
storage.range("early", early)
|
||||
.info("early", "Early reflections")
|
||||
.range(0, 1, 2)
|
||||
.range(0, 1, 2.5)
|
||||
.unit("%", 0, pcToRatio, ratioToPc);
|
||||
|
||||
storage.range("detune", detune)
|
||||
.info("detune", "Detuning rate (inside feedback loop)")
|
||||
.range(0, 5, 50)
|
||||
.unit("", 1);
|
||||
|
||||
storage.range("lowCutHz", lowCutHz)
|
||||
.info("low cut", "Removes low frequencies")
|
||||
.range(10, 80, 500)
|
||||
.unit("Hz", 0);
|
||||
storage.range("lowDampRate", lowDampRate)
|
||||
.info("low damp", "Reduce low frequencies over time")
|
||||
.range(1, 2, 10)
|
||||
.unit("", 1);
|
||||
|
||||
storage.range("highCutHz", highCutHz)
|
||||
.info("high cut", "Removes high frequencies")
|
||||
.range(1000, 5000, 20000)
|
||||
.unit("kHz", 1, kHzToHz, hzToKHz, 1000, 1e100)
|
||||
.unit("Hz", 0);
|
||||
storage.range("highDampRate", highDampRate)
|
||||
.info("high damp", "Reduce high frequencies over time")
|
||||
.range(1, 2, 10)
|
||||
.unit("", 1);
|
||||
}
|
||||
|
||||
|
||||
template<class Preset>
|
||||
void presets(Preset &preset) {
|
||||
if (preset("ambient")) {
|
||||
wet = 0.85;
|
||||
roomMs = 80;
|
||||
rt20 = 8.5;
|
||||
early = 0.55;
|
||||
detune = 8.5;
|
||||
lowCutHz = 50;
|
||||
lowDampRate = 1.5;
|
||||
highCutHz = 7200;
|
||||
highDampRate = 2;
|
||||
}
|
||||
}
|
||||
|
||||
template<class Config>
|
||||
void configure(Config &config) {
|
||||
config.outputChannels = config.inputChannels = 2;
|
||||
sampleRate = config.sampleRate;
|
||||
config.outputChannels = config.inputChannels = 2; // stereo effect only
|
||||
config.auxInputs.resize(0);
|
||||
config.auxOutputs.resize(0);
|
||||
|
||||
|
||||
detuneDepthSamples = detuneDepthMs*0.001*config.sampleRate;
|
||||
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);
|
||||
delayFeedback.configure(maxRoomSamples*1.6 + detuneDepthSamples, 1);
|
||||
delayEarly.configure(maxRoomSamples, 0.25);
|
||||
}
|
||||
|
||||
@ -90,12 +139,23 @@ namespace signalsmith { namespace basics {
|
||||
delay3.reset();
|
||||
delayFeedback.reset();
|
||||
delayEarly.reset();
|
||||
|
||||
for (auto &f : lowCutFilters) f.reset();
|
||||
for (auto &f : highCutFilters) f.reset();
|
||||
for (auto &f : lowDampFilters) f.reset();
|
||||
for (auto &f : highDampFilters) f.reset();
|
||||
|
||||
detuneLfoPhase = 0;
|
||||
}
|
||||
|
||||
int latencySamples() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int tailSamples() {
|
||||
return std::round(sampleRate*rt20*3); // decay to -60dB
|
||||
}
|
||||
|
||||
template<class Io, class Config, class Block>
|
||||
void processSTFX(Io &io, Config &config, Block &block) {
|
||||
using Hadamard = signalsmith::mix::Hadamard<Sample, 8>;
|
||||
@ -105,36 +165,52 @@ namespace signalsmith { namespace basics {
|
||||
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();
|
||||
|
||||
auto smoothedDryGain = block.smooth(dry);
|
||||
Sample scalingFactor = stereoMixer.scalingFactor2()*0.015625; // 4 Hadamard mixes
|
||||
auto smoothedWetGain = block.smooth(wet.from(), wet.to());
|
||||
|
||||
using signalsmith::units::dbToGain;
|
||||
double decayGainFrom = dbToGain(getDecayDb(rt20.from(), roomMs.from()));
|
||||
double decayGainTo = dbToGain(getDecayDb(rt20.to(), roomMs.to()));
|
||||
auto smoothedDecayGain = block.smooth(decayGainFrom, decayGainTo);
|
||||
auto smoothedInputGain = block.smooth( // scale according to the number of expected echoes in the first 100ms
|
||||
2*scalingFactor*std::sqrt((1 - decayGainFrom)/(1 - std::pow(decayGainFrom, 100/roomMs.from()))),
|
||||
2*scalingFactor*std::sqrt((1 - decayGainTo)/(1 - std::pow(decayGainTo, 100/roomMs.to())))
|
||||
);
|
||||
auto smoothedEarlyGain = block.smooth(early, [&](double g) {
|
||||
return g*0.35; // tuned by ear
|
||||
});
|
||||
|
||||
updateFilters(decayGainTo);
|
||||
|
||||
// Detuning LFO rate
|
||||
double detuneCentsPerLoop = detune*std::sqrt(roomMs*0.001);
|
||||
double detuneLfoRate = (detuneCentsPerLoop*0.0004)/detuneDepthSamples; // tuned by ear, assuming 3/8 channels are detuned
|
||||
|
||||
for (int i = 0; i < block.length; ++i) {
|
||||
Sample inputGain = smoothedInputGain.at(i);
|
||||
Sample decayGain = smoothedDecayGain.at(i);
|
||||
Sample earlyGain = smoothedEarlyGain.at(i);
|
||||
|
||||
std::array<Sample, 2> stereoIn = {Sample(inputLeft[i]), Sample(inputRight[i])};
|
||||
|
||||
Array samples;
|
||||
std::array<Sample, 2> stereoIn = {inputLeft[i]*inputGain, inputRight[i]*inputGain};
|
||||
stereoMixer.stereoToMulti(stereoIn, samples);
|
||||
std::array<Sample, 2> stereoInScaled = {stereoIn[0]*inputGain, stereoIn[1]*inputGain};
|
||||
stereoMixer.stereoToMulti(stereoInScaled, samples);
|
||||
|
||||
double lfoCos = std::cos(detuneLfoPhase*2*M_PI), lfoSin = std::sin(detuneLfoPhase*2*M_PI);
|
||||
Array3 lfoArray = {
|
||||
Sample((0.5 + lfoCos*0.5)*detuneDepthSamples),
|
||||
Sample((0.5 + lfoCos*-0.25 + lfoSin*0.43301270189)*detuneDepthSamples),
|
||||
Sample((0.5 + lfoCos*-0.25 + lfoSin*-0.43301270189)*detuneDepthSamples)
|
||||
};
|
||||
detuneLfoPhase += detuneLfoRate;
|
||||
|
||||
if (fading) {
|
||||
Sample fade = block.fade(i);
|
||||
@ -143,10 +219,16 @@ namespace signalsmith { namespace basics {
|
||||
samples = delay2.write(samples).read(fade);
|
||||
Hadamard::unscaledInPlace(samples);
|
||||
|
||||
Array feedback = delayFeedback.read(fade);
|
||||
Array feedback = delayFeedback.readDetuned(lfoArray, fade);
|
||||
Householder::inPlace(feedback);
|
||||
for (int c = 0; c < 8; ++c) {
|
||||
feedback[c] = highDampFilters[c](lowDampFilters[c](feedback[c]));
|
||||
}
|
||||
Array feedbackInput;
|
||||
for (int c = 0; c < 8; ++c) feedbackInput[c] = samples[c] + feedback[c]*decayGain;
|
||||
for (int c = 0; c < 8; ++c) {
|
||||
int c2 = (c + 3)&7;
|
||||
feedbackInput[c2] = samples[c] + feedback[c]*decayGain;
|
||||
}
|
||||
delayFeedback.write(feedbackInput);
|
||||
|
||||
Array earlyReflections = delayEarly.write(samples).read(fade);
|
||||
@ -162,10 +244,16 @@ namespace signalsmith { namespace basics {
|
||||
samples = delay2.write(samples).read();
|
||||
Hadamard::unscaledInPlace(samples);
|
||||
|
||||
Array feedback = delayFeedback.read();
|
||||
Array feedback = delayFeedback.readDetuned(lfoArray);
|
||||
Householder::inPlace(feedback);
|
||||
for (int c = 0; c < 8; ++c) {
|
||||
feedback[c] = highDampFilters[c](lowDampFilters[c](feedback[c]));
|
||||
}
|
||||
Array feedbackInput;
|
||||
for (int c = 0; c < 8; ++c) feedbackInput[c] = samples[c] + feedback[c]*decayGain;
|
||||
for (int c = 0; c < 8; ++c) {
|
||||
int c2 = (c + 3)&7;
|
||||
feedbackInput[c2] = samples[c] + feedback[c]*decayGain;
|
||||
}
|
||||
delayFeedback.write(feedbackInput);
|
||||
|
||||
Array earlyReflections = delayEarly.write(samples).read();
|
||||
@ -179,17 +267,42 @@ namespace signalsmith { namespace basics {
|
||||
|
||||
std::array<Sample, 2> stereoOut;
|
||||
stereoMixer.multiToStereo(samples, stereoOut);
|
||||
|
||||
for (int c = 0; c < 2; ++c) {
|
||||
stereoOut[c] = highCutFilters[c](lowCutFilters[c](stereoOut[c]));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
detuneLfoPhase -= std::floor(detuneLfoPhase);
|
||||
}
|
||||
|
||||
private:
|
||||
int channels = 0;
|
||||
double maxRoomMs;
|
||||
double sampleRate = 1;
|
||||
double maxRoomMs, detuneDepthMs;
|
||||
double detuneLfoPhase = 0;
|
||||
double detuneDepthSamples = 0;
|
||||
|
||||
using Filter = signalsmith::filters::BiquadStatic<Sample>;
|
||||
std::array<Filter, 2> lowCutFilters, highCutFilters;
|
||||
std::array<Filter, 8> lowDampFilters, highDampFilters;
|
||||
|
||||
void updateFilters(double feedbackGain) {
|
||||
for (auto &f : lowCutFilters) f.highpassQ(lowCutHz/sampleRate, 0.5);
|
||||
for (auto &f : highCutFilters) f.lowpassQ(highCutHz/sampleRate, 0.5);
|
||||
|
||||
Sample lowDampHz = lowCutHz + 100;
|
||||
Sample highDampHz = highCutHz*0.5;
|
||||
Sample lowDampGain = std::max(std::pow(feedbackGain, lowDampRate), 1e-3);
|
||||
Sample highDampGain = std::max(std::pow(feedbackGain, highDampRate), 1e-3);
|
||||
for (auto &f : lowDampFilters) f.lowShelfQ(lowDampHz/sampleRate, lowDampGain, 0.5);
|
||||
for (auto &f : highDampFilters) f.highShelf(highDampHz/sampleRate, highDampGain);
|
||||
}
|
||||
|
||||
static Sample getDecayDb(Sample rt20, Sample loopMs) {
|
||||
Sample dbPerSecond = -20/rt20;
|
||||
@ -232,16 +345,12 @@ namespace signalsmith { namespace basics {
|
||||
}
|
||||
}
|
||||
|
||||
void updateLengthsExponential(int seed, double rangeSamples) {
|
||||
void updateLengthsExponential(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};
|
||||
constexpr double ratios[8] = {0.0625, -0.0625, 0.1875, -0.1875, 0.3125, -0.3125, 0.4375, -0.4375};
|
||||
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]);
|
||||
delayOffsets[i] = int(-std::floor(rangeSamples*std::pow(2, ratios[i])));
|
||||
}
|
||||
}
|
||||
|
||||
@ -269,15 +378,41 @@ namespace signalsmith { namespace basics {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
signalsmith::delay::Reader<Sample, signalsmith::delay::InterpolatorLagrange7> fractionalReader;
|
||||
Array readDetuned(Array3 lfoDepths) {
|
||||
Array result;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
result[i] = fractionalReader.read(buffer[i], lfoDepths[i] - delayOffsets[i]);
|
||||
}
|
||||
for (int i = 3; i < 8; ++i) {
|
||||
result[i] = buffer[i][delayOffsets[i]];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Array readDetuned(Array3 lfoDepths, Sample fade) {
|
||||
Array result;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
Sample to = fractionalReader.read(buffer[i], lfoDepths[i] - delayOffsets[i]);
|
||||
Sample from = fractionalReader.read(buffer[i], lfoDepths[i] - delayOffsetsPrev[i]);
|
||||
result[i] = from + (to - from)*fade;
|
||||
}
|
||||
for (int i = 3; 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);
|
||||
delay1.updateLengths(0x6DD09EE5, roomSamples, false);
|
||||
delay2.updateLengths(0x876753A5, roomSamples);
|
||||
delay3.updateLengths(0x5974DF44, roomSamples);
|
||||
delay4.updateLengths(0x8CDBF7E6, roomSamples);
|
||||
delayFeedback.updateLengthsExponential(0xC6BF7158, roomSamples);
|
||||
delayFeedback.updateLengthsExponential(roomSamples);
|
||||
delayEarly.updateLengths(0x0BDDE171, roomSamples);
|
||||
}
|
||||
};
|
||||
|
||||
147
stfx-library.h
147
stfx-library.h
@ -10,8 +10,86 @@
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cmath>
|
||||
|
||||
namespace stfx {
|
||||
// Convenient units for range parameters - not really part of the main STFX API
|
||||
namespace units {
|
||||
static inline double dbToGain(double db) {
|
||||
return std::pow(10, db*0.05);
|
||||
}
|
||||
static inline double gainToDb(double gain) {
|
||||
return std::log10(std::max<double>(gain, 1e-10))*20;
|
||||
}
|
||||
static inline double dbToEnergy(double db) {
|
||||
return std::pow(10, db*0.1);
|
||||
}
|
||||
static inline double energyToDb(double gain) {
|
||||
return std::log10(std::max<double>(gain, 1e-10))*10;
|
||||
}
|
||||
static inline double pcToRatio(double percent) {
|
||||
return percent*0.01;
|
||||
}
|
||||
static inline double ratioToPc(double linear) {
|
||||
return linear*100;
|
||||
}
|
||||
static inline double kHzToHz(double kHz) {
|
||||
return kHz*1000;
|
||||
}
|
||||
static inline double hzToKHz(double hz) {
|
||||
return hz*0.001;
|
||||
}
|
||||
static inline double sToMs(double sec) {
|
||||
return sec*1000;
|
||||
}
|
||||
static inline double msToS(double ms) {
|
||||
return ms*0.001;
|
||||
}
|
||||
|
||||
template<class RangeParam>
|
||||
RangeParam & rangeGain(RangeParam ¶m) {
|
||||
return param
|
||||
.unit("dB", 1, dbToGain, gainToDb) // default display is dB
|
||||
.unit("%", 0, pcToRatio, ratioToPc)
|
||||
.exact(0, "off")
|
||||
.unit("x"); // Allow things like "2x" (~6dB) for text-input
|
||||
}
|
||||
template<class RangeParam>
|
||||
RangeParam & rangeHz(RangeParam ¶m) {
|
||||
return param
|
||||
.unit("Hz", 0, 10, 1000)
|
||||
.unit("Hz", 1, 1, 10)
|
||||
.unit("Hz", 2, 0, 1)
|
||||
.unit("kHz", 1, kHzToHz, hzToKHz, 10000, 1e100)
|
||||
.unit("kHz", 2, kHzToHz, hzToKHz, 1000, 10000);
|
||||
}
|
||||
template<class RangeParam>
|
||||
RangeParam & rangePercent(RangeParam ¶m) {
|
||||
return param
|
||||
.unit("%", 0, pcToRatio, ratioToPc)
|
||||
.unit("x");
|
||||
}
|
||||
template<class RangeParam>
|
||||
RangeParam & rangeMs(RangeParam ¶m) {
|
||||
return param
|
||||
.unit("ms", 2, 0, 1)
|
||||
.unit("ms", 1, 1, 10)
|
||||
.unit("ms", 0, 10, 1000)
|
||||
.unit("seconds", 2, sToMs, msToS, 0, 1)
|
||||
.unit("seconds", 1, sToMs, msToS, 1, 10)
|
||||
.unit("seconds", 0, sToMs, msToS, 10, 1e100);
|
||||
}
|
||||
template<class RangeParam>
|
||||
RangeParam & rangeSec(RangeParam ¶m) {
|
||||
return param
|
||||
.unit("seconds", 2, 0, 1)
|
||||
.unit("seconds", 1, 1, 10)
|
||||
.unit("seconds", 0, 10, 1e100)
|
||||
.unit("ms", 2, msToS, sToMs, 0, 0.001)
|
||||
.unit("ms", 1, msToS, sToMs, 0.001, 0.01)
|
||||
.unit("ms", 0, msToS, sToMs, 0.01, 1);
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
// A value which changes linearly within a given index range (e.g. a block)
|
||||
@ -20,7 +98,7 @@ namespace stfx {
|
||||
public:
|
||||
LinearSegment(double offset, double gradient) : offset(offset), gradient(gradient) {}
|
||||
|
||||
double at(int i) {
|
||||
double at(double i) {
|
||||
return offset + i*gradient;
|
||||
}
|
||||
bool changing() {
|
||||
@ -119,7 +197,7 @@ namespace stfx {
|
||||
}
|
||||
struct DoNothingEvent {
|
||||
int offset = 0;
|
||||
void operator ()() {}
|
||||
void operator()() {}
|
||||
};
|
||||
DoNothingEvent operator [](int) {
|
||||
return DoNothingEvent();
|
||||
@ -142,7 +220,7 @@ namespace stfx {
|
||||
}
|
||||
template<class EventList, class ...Others>
|
||||
const Block & split(EventList &&list, Others &&...others) const {
|
||||
return split(list).split(std::forward<Others>(others)...);
|
||||
return split(list, list.size()).split(std::forward<Others>(others)...);
|
||||
}
|
||||
/// Base-case for templated recursion
|
||||
const Block & split() const {
|
||||
@ -170,7 +248,7 @@ namespace stfx {
|
||||
current = v;
|
||||
return *this;
|
||||
}
|
||||
// Return the
|
||||
// Return the current fade
|
||||
Value from() const {
|
||||
return _from;
|
||||
}
|
||||
@ -178,6 +256,11 @@ namespace stfx {
|
||||
return _to;
|
||||
}
|
||||
|
||||
template<class Storage>
|
||||
void state(Storage &storage) {
|
||||
storage("value", current);
|
||||
}
|
||||
|
||||
// Shuffle the internal values along to start a new fade, return whether it's actually changing
|
||||
bool _libStartFade() {
|
||||
_from = _to;
|
||||
@ -220,7 +303,7 @@ namespace stfx {
|
||||
return *this;
|
||||
}
|
||||
template<typename ...Args>
|
||||
PInfoPlaceholder & names(Args ...) {
|
||||
PInfoPlaceholder & label(Args ...) {
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
@ -228,26 +311,35 @@ namespace stfx {
|
||||
}
|
||||
|
||||
/// Base class for our effect to inherit from. Provides parameter classes and some default config.
|
||||
template<typename SampleType>
|
||||
class LibraryEffectBase {
|
||||
protected:
|
||||
using ParamRange = LibraryParam<double>;
|
||||
using ParamSteps = LibraryParam<int>;
|
||||
using ParamStepped = LibraryParam<int>;
|
||||
public:
|
||||
using Sample = SampleType;
|
||||
|
||||
ParamRange bpm{120};
|
||||
|
||||
double paramFadeMs() {
|
||||
return 20;
|
||||
}
|
||||
int latencySamples() {
|
||||
return 0;
|
||||
}
|
||||
int tailSamples() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<class Presets>
|
||||
void presets(Presets &) {}
|
||||
};
|
||||
|
||||
/// 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...>;
|
||||
/// The effect template takes `EffectTemplate<BaseClass, ...ExtraConfig>`
|
||||
template<typename Sample, template <class, class...> class EffectTemplate, class ...ExtraConfig>
|
||||
class LibraryEffect : public EffectTemplate<stfx::LibraryEffectBase<Sample>, ExtraConfig...> {
|
||||
using EffectClass = EffectTemplate<stfx::LibraryEffectBase<Sample>, ExtraConfig...>;
|
||||
|
||||
// This is passed to the effect's `.state()` method during initialisation, and collects pointers to the effect's parameters
|
||||
class CollectParams {
|
||||
@ -256,12 +348,12 @@ namespace stfx {
|
||||
std::vector<LibraryParam<int> *> stepParams;
|
||||
|
||||
// Add registered parameters to the list
|
||||
PInfoPlaceholder param(const char *, LibraryParam<double> ¶m, const char *codeExpr=nullptr) {
|
||||
PInfoPlaceholder range(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) {
|
||||
PInfoPlaceholder stepped(const char *, LibraryParam<int> ¶m, const char *codeExpr=nullptr) {
|
||||
(void)codeExpr;
|
||||
stepParams.push_back(¶m);
|
||||
return {};
|
||||
@ -269,23 +361,28 @@ namespace stfx {
|
||||
|
||||
// The effect might ask us to store/fetch the serialisation version, we just echo it back
|
||||
static int version(int v) {return v;}
|
||||
// Ignore the UI/synchronisation stuff
|
||||
static bool extra() {return false;}
|
||||
static void invalidate(const char *) {}
|
||||
|
||||
// We ignore any basic type
|
||||
void operator ()(bool) {}
|
||||
void operator ()(int) {}
|
||||
void operator ()(long) {}
|
||||
void operator ()(double) {}
|
||||
void operator ()(float) {}
|
||||
void operator()(const char *, bool) {}
|
||||
void operator()(const char *, int) {}
|
||||
void operator()(const char *, long) {}
|
||||
void operator()(const char *, double) {}
|
||||
void operator()(const char *, float) {}
|
||||
// And strings
|
||||
void operator()(const char *, std::string &) {}
|
||||
// Iterate through vectors
|
||||
template<class Item>
|
||||
void operator()(const char *label, std::vector<Item> &vector) {
|
||||
for (auto &item : vector) (*this)(label, item);
|
||||
}
|
||||
// Assume all other arguments have a `.state()`, and recurse into it
|
||||
template<class OtherObject>
|
||||
void operator ()(OtherObject &obj) {
|
||||
void operator()(const char *, 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);
|
||||
@ -311,7 +408,7 @@ namespace stfx {
|
||||
std::vector<int> auxInputs, auxOutputs;
|
||||
int maxBlockSize = 256;
|
||||
|
||||
bool operator ==(const Config &other) {
|
||||
bool operator ==(const Config &other) const {
|
||||
return sampleRate == other.sampleRate
|
||||
&& inputChannels == other.inputChannels
|
||||
&& outputChannels == other.outputChannels
|
||||
@ -367,7 +464,7 @@ namespace stfx {
|
||||
/// 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) {
|
||||
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
|
||||
|
||||
12
units.h
12
units.h
@ -13,12 +13,24 @@ namespace signalsmith { namespace units {
|
||||
static double gainToDb(double gain) {
|
||||
return std::log10(std::max<double>(gain, 1e-10))*20;
|
||||
}
|
||||
static double dbToEnergy(double db) {
|
||||
return std::pow(10, db*0.1);
|
||||
}
|
||||
static double energyToDb(double gain) {
|
||||
return std::log10(std::max<double>(gain, 1e-10))*10;
|
||||
}
|
||||
static double pcToRatio(double percent) {
|
||||
return percent*0.01;
|
||||
}
|
||||
static double ratioToPc(double linear) {
|
||||
return linear*100;
|
||||
}
|
||||
static double kHzToHz(double kHz) {
|
||||
return kHz*1000;
|
||||
}
|
||||
static double hzToKHz(double hz) {
|
||||
return hz*0.001;
|
||||
}
|
||||
|
||||
}} // namespace
|
||||
#endif // include guard
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user