1
0

Add Crunch

This commit is contained in:
Geraint 2023-01-05 13:24:37 +00:00
parent e3fc8462b5
commit 7896c05a5e
5 changed files with 215 additions and 25 deletions

32
README.md Normal file
View 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
View 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

@ -1 +1 @@
Subproject commit bb5c8115ca40e6966c6421d06135ff84624f1d5e
Subproject commit 618097bed9e7fb1b87a99592f78c9a8a964eda08

View File

@ -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

View File

@ -104,7 +104,7 @@ namespace signalsmith { namespace basics {
if (preset("ambient")) {
wet = 0.85;
roomMs = 80;
rt20 = 11.5;
rt20 = 8.5;
early = 0.55;
detune = 8.5;
lowCutHz = 50;