Skip to content

JDSherbert/Audio-DSP-Distortion

Repository files navigation

Audio DSP Algorithms: Distortion

Stars Badge

Forks Badge

Watchers Badge

Issues Badge


Audio Processing License




Overview

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.


Files

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

Algorithms

Soft Clip - SoftClipDistortion

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.


Hard Clip - HardClipDistortion

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.


Foldback - FoldbackDistortion

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.


Soft Clip vs Hard Clip - Key Difference

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.


Usage

Basic

// 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);

Wet/Dry Mix

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);

Polymorphic Usage

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);

API Reference

Distortion (base)

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.

SoftClipDistortion

Method Description
SoftClipDistortion(distortionAmount) Construct with drive amount (typical range: 1.0 – 10.0).
ProcessSample(input) Applies tanh(amount * input).

HardClipDistortion

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.

FoldbackDistortion

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.

Limitations & Next Steps

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

About

Simple C++ implementations of some basic Audio DSP Distortion techniques. Includes an example usage case with a simulated sinusoidal signal.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages