A family of distortion effects implemented in plain C++, intended as a learning resource for understanding how digital waveshaping works from first principles. Three algorithms are provided, each demonstrating a different approach to deforming an audio waveform and the harmonic character that results.
Distortion works by passing each audio sample through a non-linear function; one that does not scale the input proportionally. The shape of that function determines whether the result sounds warm and musical, harsh and aggressive, or complex and metallic.
This technique is also the basis of the Sherbert Clipper VST Effect.
| File | Description |
|---|---|
Distortion.h / .cpp |
Abstract base class — shared distortionAmount parameter and ProcessSample() interface |
SoftClipDistortion.h / .cpp |
Soft clipping via tanh — warm, musical overdrive |
HardClipDistortion.h / .cpp |
Hard clipping via clamp — harsh, aggressive fuzz |
FoldbackDistortion.h / .cpp |
Foldback reflection — complex, metallic harmonics |
main.cpp |
Example: generates a 440Hz sine wave and demonstrates all three effects |
Uses the tanh (hyperbolic tangent) function to smoothly round off the peaks of the waveform. The curve is gradual, so harmonics are introduced gently. The result sounds warm and saturated, similar to a valve/tube amplifier being driven hard.
input: /\ /\
output: ⌒ ⌒ (peaks rounded off smoothly)
output = tanh(distortionAmount * input)At low drive the signal passes through almost unchanged. At high drive it approaches a square wave with rounded edges. Because the transition is always smooth, there are no abrupt discontinuities and the distortion never sounds harsh.
Amplifies the signal then cuts it flat at a fixed threshold. Any part of the waveform that exceeds the threshold is simply removed. The abrupt transition introduces strong odd harmonics (3rd, 5th, 7th) — the characteristic buzz of fuzz pedals and transistor clipping circuits.
input: /\ /\
output: ⌐¬ ⌐¬ (peaks cut flat)
output = clamp(distortionAmount * input, -threshold, +threshold)The threshold parameter controls where clipping begins, independently of drive. A lower threshold clips earlier and more aggressively.
When the signal exceeds the threshold, rather than clipping it, the waveform is reflected back inward, like a ball bouncing off a wall. If it folds past the opposite boundary it folds again, and so on until it falls within range.
input: /\ /\
output: /\/\/\ (peaks fold back on themselves)
while (sample > threshold || sample < -threshold)
{
if (sample > threshold) sample = 2 * threshold - sample;
if (sample < -threshold) sample = -2 * threshold - sample;
}Multiple folds per sample are possible at high drive, producing a dense and complex harmonic spectrum - metallic and dissonant in character. This is very different from clipping and is commonly used in synthesis and noise music.
The core distinction is the shape of the transition:
| Soft Clip | Hard Clip | |
|---|---|---|
| Transition | Gradual curve | Abrupt flat cut |
| Character | Warm, musical | Harsh, aggressive |
| Harmonics | Gentle, even | Strong odd harmonics |
| Analogy | Valve amp overdrive | Fuzz pedal |
Both approaches are waveshaping; the difference is entirely in the function applied to each sample.
// Instantiate any subclass directly
Sherbert::SoftClipDistortion softClip(2.0f);
Sherbert::HardClipDistortion hardClip(3.0f, 0.8f);
Sherbert::FoldbackDistortion foldback(2.0f, 1.0f);
// Process one sample at a time in your audio loop
float output = softClip.ProcessSample(input);ProcessSample returns the fully wet signal. Blend with the dry input to control the mix:
const float wetAmount = 0.5f; // 0.0 = fully dry, 1.0 = fully wet
float wet = distortion.ProcessSample(input);
float output = (wetAmount * wet) + ((1.0f - wetAmount) * input);All three subclasses share the Distortion base interface, so you can swap algorithms without changing your audio loop:
void ProcessBuffer(Sherbert::Distortion& effect, std::vector<float>& buffer)
{
for (float& sample : buffer)
sample = effect.ProcessSample(sample);
}
// Works with any subclass
ProcessBuffer(softClip, buffer);
ProcessBuffer(hardClip, buffer);
ProcessBuffer(foldback, buffer);| Method | Description |
|---|---|
ProcessSample(input) |
Pure virtual. Process one sample — override in each subclass. |
setDistortionAmount(value) |
Update drive amount. Must be > 0. |
getDistortionAmount() |
Returns current drive amount. |
| Method | Description |
|---|---|
SoftClipDistortion(distortionAmount) |
Construct with drive amount (typical range: 1.0 – 10.0). |
ProcessSample(input) |
Applies tanh(amount * input). |
| Method | Description |
|---|---|
HardClipDistortion(distortionAmount, threshold) |
Construct with drive and clip threshold (0.0, 1.0]. Default threshold: 1.0. |
ProcessSample(input) |
Applies clamp(amount * input, -threshold, +threshold). |
setThreshold(value) |
Update clip threshold at runtime. |
getThreshold() |
Returns current threshold. |
| Method | Description |
|---|---|
FoldbackDistortion(distortionAmount, threshold) |
Construct with drive and fold boundary. Default threshold: 1.0. |
ProcessSample(input) |
Folds signal back at ±threshold until in range. |
setThreshold(value) |
Update fold boundary at runtime. |
getThreshold() |
Returns current threshold. |
This implementation processes each sample independently with no internal state, which makes it simple to follow but also means it lacks some features found in real-world distortion effects:
No anti-aliasing - non-linear waveshaping introduces harmonics that can exceed the Nyquist frequency, producing aliasing artefacts. Production distortion implementations oversample the signal before processing and downsample after to avoid this.
No tone control - real distortion pedals and plugins almost always include a filter after the waveshaper (typically a low-pass or tilt EQ) to shape the resulting harmonic content. A simple biquad low-pass filter after ProcessSample would be a natural next step.
No DC offset correction - asymmetric input signals can produce a DC offset after nonlinear processing. A high-pass filter at a very low frequency (e.g. 5–10Hz) in the feedback path corrects this.
If you want to explore further, the natural next steps from here are:
- Oversampling (2x or 4x) before the waveshaper to reduce aliasing
- A post-distortion tone filter (low-pass biquad)
- Asymmetric waveshaping (different curves for positive and negative halves)
- Combining soft clip with hard clip for a diode-style clipping simulation