distortion.h
Distortion as one object.
#include <sonore/distortion.h>- 1type
- Guides: Getting started, MIDI and MPE
On this page
Design notes
"Add some drive" was thirty lines every time: a gain, a curve, an oversampler around the curve, a DC blocker after it because the curve was asymmetric, a gain to undo the level the drive added, a tone control, a mix. Each of the thirty was written before and got right before; the object is the thirty at once, on the SDK's rules -- the curve runs oversampled, the DC comes off, the level is compensated so the drive knob changes the tone and not the volume, and the latency is reported.
The compensation is EQUAL RMS: a sine at -12 dBFS comes out at the RMS it went in with, whatever the curve did to its shape. Matching peaks instead (the first draft) let a hard-driven square read 2 dB louder than the sine it came from, because a square's fundamental is 4/pi of its peak -- and loudness is what the ear compares, not peaks.
The oversampling factor is a template parameter because it is a real trade and the right answer depends on the curve. Measured on a hot (-6 dBFS) 15 kHz tone, worst folded product below the fundamental:
tanh at 20 dB of drive: 8x is under -40 dB, where the generation
harness's aliasing gate sits; 4x reads -38.6;
hard clip at 30 dB: a full square at 15 kHz has 1/n harmonics to
infinity, and no affordable factor makes that
clean -- 4x leaves the 11th in the second
half-band's transition band, 8x reaches the
25th folding into the passband.
So: Stages=3 (8x, 28 samples of latency) by default -- measured, tanh at 20 dB holds its folds under -40 dB there and 4x reads -38.6 -- with Stages=2 (4x, 24 samples) the documented lighter option, and the test file records both numbers.
For a diode-clipper pedal use clipper.h, which is the circuit; this is the family of memoryless shapes.
Included by dsp.h.
Types 1
template<int Stages = 3> class Distortion
A memoryless distortion with everything around the curve done: drive, the curve oversampled by 2 to the power Stages, DC removal, equal-RMS level compensation, a tone lowpass and a dry/wet mix whose dry path waits for the wet one (see the file comment for the measurements behind the default 8x).
One channel, per sample. Call setSampleRate() in prepare() and report latencySamples() to the host: 28 samples at the default 8x, 24 at 4x. Drive is 0..40 dB, bias -0.5..0.5, tone 1..20 kHz, mix 0..1 (fully wet until set). The setters and process() run on the audio thread and can be called every block: the compensation is measured again, over 256 points of the curve, only when the drive, curve, bias or auto gain changed, and drive and makeup glide together in decibels. For a diode-clipper pedal use clipper.h, which is the circuit.