Add Crunch
This commit is contained in:
parent
e3fc8462b5
commit
7896c05a5e
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()`).
|
||||
170
crunch.h
Normal file
170
crunch.h
Normal file
@ -0,0 +1,170 @@
|
||||
/* 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<typename Sample, class BaseEffect>
|
||||
class CrunchSTFX : public BaseEffect {
|
||||
using typename BaseEffect::ParamRange;
|
||||
using typename BaseEffect::ParamSteps;
|
||||
|
||||
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.param("drive", drive)
|
||||
.info("drive", "pre-distortion input gain")
|
||||
.range(dbToGain(-12), 4, dbToGain(40))
|
||||
.unit("dB", 1, dbToGain, gainToDb)
|
||||
.unit("");
|
||||
storage.param("fuzz", fuzz)
|
||||
.info("fuzz", "amplitude-independent distortion")
|
||||
.range(0, 0.5, 1)
|
||||
.unit("%", 0, pcToRatio, ratioToPc);
|
||||
storage.param("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.param("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 bb5c8115ca40e6966c6421d06135ff84624f1d5e
|
||||
Subproject commit 618097bed9e7fb1b87a99592f78c9a8a964eda08
|
||||
34
limiter.h
34
limiter.h
@ -3,10 +3,11 @@ 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>
|
||||
@ -18,27 +19,13 @@ namespace signalsmith { namespace basics {
|
||||
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;
|
||||
}
|
||||
|
||||
int channels = 0;
|
||||
double maxDelayMs = 0;
|
||||
signalsmith::delay::MultiBuffer<Sample> multiBuffer;
|
||||
|
||||
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};
|
||||
@ -48,21 +35,22 @@ namespace signalsmith { namespace basics {
|
||||
|
||||
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)
|
||||
.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)
|
||||
.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)
|
||||
.info("attack", "envelope smoothing time")
|
||||
@ -83,7 +71,7 @@ namespace signalsmith { namespace basics {
|
||||
storage.param("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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user