Audio DSP
Open-source audio moves fast and announces almost nothing. Below is what changed recently across the tools plugin developers work with, updated daily, plus a short list of free reading if you want to understand the DSP behind it.
Open-source projects
Updated daily. Last release and last activity, newest first.
| Project | Kind | What it is | Latest | Last activity |
|---|---|---|---|---|
| Cardinal | Modular | VCV Rack packaged as a plugin, self-contained and GPL. | 26.02 | 28 Jul 2026 |
| Surge XT | Synth | Open-source hybrid synth, and a readable codebase to learn from. | 1.3.4 | 28 Jul 2026 |
| Faust | Language | A DSP language that compiles to plugins in most formats. | 2.85.9 | 28 Jul 2026 |
| Shortcircuit XT | Sampler | Open-source sampler from the Surge team. | no tagged release | 28 Jul 2026 |
| DPF | Framework | Minimal framework aimed at small, dependency-light builds. | no tagged release | 26 Jul 2026 |
| CLAP Validator | Validator | Automated tests for common CLAP bugs, invalid behaviour and unsafe parameter combinations. | 0.4.1 | 24 Jul 2026 |
| JUCE | Framework | Widely used C++ framework for cross-platform plugins and standalone audio applications. | 9.0.0 | 23 Jul 2026 |
| CLAP Wrapper | Tool | Packages a CLAP-first plugin as VST3, AUv2, AUv3, AAX or a standalone application. | 0.15.1 | 21 Jul 2026 |
| pluginval | Validator | Cross-platform tester for plugin stability and host compatibility, interactive or in CI. | 1.0.4 | 19 Jul 2026 |
| iPlug2 | Framework | C++ framework targeting VST3, AUv2/3, AAX, CLAP and Web Audio Modules. | 0.3.0 | 15 Jul 2026 |
| CLAP | Format | Open plugin format, an alternative to VST3 with no licence agreement. | 1.2.10 | 13 Jul 2026 |
| Cmajor | Language | C-family language for fast, portable DSP code. GPL and commercial dual-licensed. | 1.0.3175 | 10 Jul 2026 |
| chowdsp_wdf | Library | Header-only C++ library for real-time Wave Digital Filter circuit models. | Version 1.0.0 | 3 Jul 2026 |
| NIH-plug | Framework | Plugin framework for Rust, targeting CLAP and VST3. | no tagged release | 10 May 2026 |
| LV2 | Format | The extensible plugin format used across Linux audio. | 1.18.10 | 10 Apr 2026 |
| Signalsmith Stretch | Library | Single-header pitch shifting and time stretching, with unusually readable design notes. | 1.1.0 | 24 Jan 2026 |
| VCV Rack | Modular | Virtual Eurorack, and the module SDK behind it. | 2.6.6 | 4 Nov 2025 |
| sfizz | Sampler | SFZ sample player, usable as a library or a plugin. | 1.2.3 | 17 Mar 2025 |
Reading
- The Art of VA Filter Design Vadim Zavalishin
The reference on turning analogue filter circuits into digital ones. Free PDF, and the source most virtual-analogue filters trace back to.
- Physical Audio Signal Processing Julius O. Smith, Stanford
Four full books on filters, spectral audio and physical modelling, readable free online.
- DAFx conference papers DAFx
Two decades of audio effects research, open archive. Where most new effect ideas appear first.
- Audio EQ Cookbook Robert Bristow-Johnson
The standard collection of biquad equations for low-pass, high-pass, shelving, peaking and notch filters.
- musicdsp.org archive community
The old snippet archive. Rough and inconsistent, still the fastest way to see how an algorithm is usually written.
Following
- Signalsmith DSP writing Geraint Luff
Implementation-led articles on reverbs, limiters, waveshaping, interpolation and pitch processing.
- Audio Developer Conference archive ADC
Years of practical talks on plugin formats, real-time systems, filters, synthesis, testing and optimisation.
- Airwindows Chris Johnson
Hundreds of small open-source plugins, each with a write-up of what it does and why.
- Cytomic technical papers Andy Simper
Short, precise papers on filter topologies from the developer of The Glue.
- KVR DSP and plugin development forum KVR
Where working plugin developers argue about implementation details in public.
Code
The arithmetic only. Persistent state is kept per channel, and coefficients are worked out when a parameter changes rather than per sample.
Decibels and linear amplitude C++
Converts between the decibel values used by meters, compressors and gain controls and the linear values the audio maths works in.
Use 20 for amplitude. The equivalent for power values uses 10.
The lower limit prevents log10(0). At 1e-12 the floor sits at -240 dB.
gain = pow(10.0, gainDb / 20.0);
levelDb = 20.0 * log10(max(level, 1e-12)); Soft-saturating preamp C++
Saturation with level compensation, so raising the amount does not just make it louder.
x = input sample, p = amount (0 to 1).
The x * x term leaves a DC offset behind, so follow it with a DC blocker.
drive = 1.0 + p * 3.0;
warmth = p * 0.25;
sat = tanh(x * drive * 0.9); // symmetric, odd harmonics
harm = x * x * warmth; // asymmetric, even harmonics
output = (sat + harm) * (1.0 / (1.0 + p * 0.8)); Nonlinear processing creates harmonics above the input frequency. When those cross Nyquist they fold back as aliasing, usually heard as a brittle high end. Oversample around the shaper when strong drive or bright material makes it audible.
DC blocker C++
Removes the offset any asymmetric shaper leaves behind.
cornerHz around 5 is a safe choice. Work R out when the sample rate changes, not per sample.
x1 and y1 are the previous input and output. One set per channel.
R = exp(-2.0 * pi * cornerHz / sampleRate);
y = x - x1 + R * y1;
x1 = x;
y1 = y;
output = y; Biquad EQ coefficients C++
One set of formulas covering every band in an EQ. The standard reference, from Robert Bristow-Johnson's cookbook.
f0 = frequency, Q = resonance or bandwidth, dBgain for peaking and shelving only.
Work the coefficients out when a parameter changes, then run the last two lines per sample.
w0 = 2.0 * pi * f0 / sampleRate;
cosw = cos(w0);
alpha = sin(w0) / (2.0 * Q);
A = pow(10.0, dBgain / 40.0); // peaking and shelving only
t = 2.0 * sqrt(A) * alpha; // shelving only
// low-pass
b0 = (1.0 - cosw) * 0.5; b1 = 1.0 - cosw; b2 = b0;
a0 = 1.0 + alpha; a1 = -2.0 * cosw; a2 = 1.0 - alpha;
// high-pass
b0 = (1.0 + cosw) * 0.5; b1 = -(1.0 + cosw); b2 = b0;
a0 = 1.0 + alpha; a1 = -2.0 * cosw; a2 = 1.0 - alpha;
// band-pass, 0 dB peak
b0 = alpha; b1 = 0.0; b2 = -alpha;
a0 = 1.0 + alpha; a1 = -2.0 * cosw; a2 = 1.0 - alpha;
// notch
b0 = 1.0; b1 = -2.0 * cosw; b2 = 1.0;
a0 = 1.0 + alpha; a1 = -2.0 * cosw; a2 = 1.0 - alpha;
// peaking
b0 = 1.0 + alpha * A; b1 = -2.0 * cosw; b2 = 1.0 - alpha * A;
a0 = 1.0 + alpha / A; a1 = -2.0 * cosw; a2 = 1.0 - alpha / A;
// low shelf
b0 = A * ((A+1) - (A-1)*cosw + t);
b1 = 2.0*A * ((A-1) - (A+1)*cosw);
b2 = A * ((A+1) - (A-1)*cosw - t);
a0 = (A+1) + (A-1)*cosw + t;
a1 = -2.0 * ((A-1) + (A+1)*cosw);
a2 = (A+1) + (A-1)*cosw - t;
// high shelf
b0 = A * ((A+1) + (A-1)*cosw + t);
b1 = -2.0*A * ((A-1) + (A+1)*cosw);
b2 = A * ((A+1) + (A-1)*cosw - t);
a0 = (A+1) - (A-1)*cosw + t;
a1 = 2.0 * ((A-1) - (A+1)*cosw);
a2 = (A+1) - (A-1)*cosw - t;
// per sample, direct form 1
y = (b0/a0)*x + (b1/a0)*x1 + (b2/a0)*x2 - (a1/a0)*y1 - (a2/a0)*y2;
x2 = x1; x1 = x; y2 = y1; y1 = y; Q above about 10 at a low f0 puts the poles very close to the unit circle, where the filter rings and becomes sensitive to rounding. Direct form 1 holds up better there than the transposed forms.
One-pole low-pass and high-pass C++
The cheapest useful filter: 6 dB per octave, no resonance, one sample of state. Good for tone controls, smoothing and taming a feedback path.
Both share the same coefficient, so a pair costs one exp. Work it out when the cutoff changes.
Measured -3 dB at the cutoff, as expected.
x = exp(-2.0 * pi * cutoffHz / sampleRate);
// low-pass
y1 = (1.0 - x) * in + x * y1;
lowOut = y1;
// high-pass, same x
b0 = 0.5 * (1.0 + x);
hpY = b0 * in - b0 * hpX1 + x * hpY;
hpX1 = in;
highOut = hpY; LFO shapes from a phase C++
Four waveforms plus sample and hold, all read from one phase running 0 to 1.
Advance with phase += freqHz / sampleRate, and wrap by subtracting 1.0 when it passes 1.
Trigger the sample and hold on that wrap. Use your own random source, not rand(), inside the audio callback.
sine = sin(2.0 * pi * phase);
triangle = 2.0 * fabs(2.0 * (phase - floor(phase + 0.5))) - 1.0;
square = (phase < 0.5) ? 1.0 : -1.0;
saw = 2.0 * phase - 1.0;
// sample and hold: refresh only when the phase wraps
if (wrapped) shValue = nextRandom(); // -1 to +1
sampleHold = shValue; Envelope follower C++
Tracks the level of a signal. The core of every compressor, gate, ducker and envelope-driven filter.
Rises fast and falls slow by using a different coefficient in each direction.
coeff = 1 - exp(-1 / (timeMs * sampleRate / 1000)), worked out once per time value.
level = fabs(x);
if (level > env)
env += attackCoeff * (level - env);
else
env += releaseCoeff * (level - env);
output = env; This defines the stated time as one time constant, the point where 63.2% of the movement is done. Some processors instead quote the time to settle within -60 dB, which is about seven time constants, so coefficients copied from elsewhere may look different without being wrong.
Parameter smoothing C++
Stops a knob move or automation step from clicking. Apply to any parameter read per block.
coeff = exp(-1 / (timeMs * sampleRate / 1000)). Around 20 ms suits most controls.
Smooth the value, not the audio: run this on the parameter before it reaches the maths.
current = current * coeff + target * (1.0 - coeff);
output = current; Same time-constant convention as the envelope follower: the stated time is 63.2% of the way there, not fully settled.
Soft knee compression curve C++
Maps an input level in dB to an output level in dB, bending gradually into the ratio instead of switching at the threshold.
x, threshold and knee in dB, ratio as N to 1. Feed it the envelope, not the sample.
Continuous at both knee edges, so there is no step where the regions meet.
if (x <= threshold - knee * 0.5)
y = x; // below the knee
else if (x >= threshold + knee * 0.5)
y = threshold + (x - threshold) / ratio; // fully compressed
else {
d = x - threshold + knee * 0.5;
t = d / knee;
y = x + (1.0 / ratio - 1.0) * d * t * 0.5;
}
gainDb = y - x; // apply this, not y Exponential envelope segment C++
One stage of an ADSR. Curves towards the target instead of sliding linearly, which is what makes it sound natural.
N = length of the segment in samples. reach = how close to the target it gets by the end, e.g. 0.01.
mul is computed once when the time changes, not per sample.
mul = pow(reach, 1.0 / N); // once, when the time changes
value = target + (value - target) * mul; // per sample Mid/side encode and decode C++
Turns stereo into a centre channel and a difference channel. The basis of mid/side EQ, width controls and processing centred and wide material separately.
This halves while encoding and uses unity gain while decoding. Others use 1 / sqrt(2) in both directions; either works, but do not mix them.
Width above 1 widens the result and can raise the decoded peak, so watch the output meter.
mid = (left + right) * 0.5;
side = (left - right) * 0.5;
side *= width; // 0 = mono, 1 = unchanged
left = mid + side;
right = mid - side; Constant-power pan C++
Moves a mono source across the stereo field without it dipping in the middle.
pan runs -1 (hard left) to +1 (hard right). Centre gives 0.707 on both sides, not 0.5.
The two gains satisfy L squared plus R squared equal to 1 at every position, which is what keeps the perceived level steady.
theta = ((pan + 1.0) * 0.5) * (pi / 2.0);
leftGain = cos(theta);
rightGain = sin(theta);
outL = in * leftGain;
outR = in * rightGain; Pink noise C++
Noise falling at 3 dB per octave, which is closer to how the ear weights a spectrum than white noise.
Paul Kellet's economy method. b0 to b6 are persistent state, white is a fresh random value in -1 to +1.
The final multiply keeps it roughly inside -1 to +1.
b0 = 0.99886 * b0 + white * 0.0555179;
b1 = 0.99332 * b1 + white * 0.0750759;
b2 = 0.96900 * b2 + white * 0.1538520;
b3 = 0.86650 * b3 + white * 0.3104856;
b4 = 0.55000 * b4 + white * 0.5329522;
b5 = -0.7616 * b5 - white * 0.0168980;
pink = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362;
b6 = white * 0.115926;
output = pink * 0.11; Tempo to milliseconds C++
Delay and LFO times that land on the grid.
noteValue: 1.0 = quarter, 0.5 = eighth, 0.25 = sixteenth.
Multiply by 1.5 for dotted, by 2/3 for a triplet. At 120 BPM a dotted eighth is 375 ms.
ms = (60000.0 / bpm) * noteValue; MIDI note to frequency C++
Note 69 is A4 at 440 Hz. Change the 440.0 to retune the whole instrument.
Pitch bend arrives as 14 bits, 0 to 16383, centred at 8192.
range is the bend width in semitones, usually 2 or 12.
freq = 440.0 * pow(2.0, (note - 69) / 12.0);
// with pitch bend
semitones = ((bend - 8192.0) / 8192.0) * range;
freq = freq * pow(2.0, semitones / 12.0); Silence cutoff for persistent state C++
Clears inaudibly small persistent state before it eventually reaches the subnormal range, where some processors fall into a slow path.
Run it on state that decays towards silence, such as filter memory or a feedback buffer, not on every sample of the audio.
1e-15 is roughly -300 dB, so this is a practical silence cutoff rather than the actual IEEE denormal boundary, which is far lower.
if (fabs(x) < 1e-15)
x = 0.0;