Companion to Audio Signal Processing in Faust

A guide to Julius O. Smith III's online textbook Audio Signal Processing in Faust — the functional DSP language invented at Grame by Orlarey, Fober & Letz, and the block-diagram algebra that makes it expressive enough to capture a Karplus-Strong string, an 8-channel feedback delay network, or a compressor in a handful of lines.

Julius O. Smith III Yann Orlarey Dominique Fober Stéphane Letz Max Mathews
Begin the tour ↓

1Prologue — Why Faust

Faust (Functional AUdio STream) is a domain-specific language for real-time digital signal processing. A Faust program is a single algebraic expression that describes a block diagram; the compiler turns that expression into efficient C++, Rust, LLVM IR, JavaScript or WebAssembly. One thirty-line file becomes a VST plug-in, an iOS app, an ESP32 firmware image, or a web-audio node.

The language was designed in 2002 at Grame (Lyon) by Yann Orlarey, Dominique Fober and Stéphane Letz, with an explicit goal: replace the error-prone loops and buffer pointers of C/C++ DSP with a purely functional, sample-accurate, algebraic notation that you could read the way an electronics engineer reads a block diagram. Julius O. Smith III — long associated with the CCRMA lab at Stanford and author of the four-volume Mathematics of the DFT / Introduction to Digital Filters / Physical Audio Signal Processing / Spectral Audio Signal Processing tetralogy — adopted Faust as the lingua franca of his fifth online book, Audio Signal Processing in Faust, which this guide accompanies.

What Faust buys you

PropertyConsequence
Purely functionalno mutable state outside ~; referentially transparent; trivially parallel
Typed signalsevery expression has a signature (n → m); compiler catches channel mismatches at compile time
Block-diagram semanticsfive composition operators (: , <: :> ~) match exactly what you draw on paper
Sample-accurateno block-size assumption in the source; compiler vectorises as it pleases
Multi-target backendone file → C++, Rust, LLVM, WASM, JavaScript, Julia, Cmajor, JSFX, Cuda…
Mathematical transparencythe compiler can print the normalised block diagram and the symbolic signal expression

A first example — a one-pole lowpass in three tokens

// A one-pole smoother with pole at a = 0.99.
process = + ~ *(0.99) : _;

Read left to right: the input feeds an adder; the adder's output feeds a multiplier by 0.99; the multiplier's output feeds back into the adder via ~, which the compiler interprets as a single-sample delay in the feedback path. The trailing : _ routes the result to the output. The difference equation is

$$y[n] \;=\; x[n] + 0.99\,y[n-1]$$

and the transfer function is $H(z) = 1/(1-0.99\,z^{-1})$. Three tokens of Faust, an hour of C, and an entire afternoon of MATLAB.

This guide

We will rebuild Faust's block-diagram algebra from scratch, translate its type system into plain English, then work through the progression laid out in JOS's book: oscillators, delay lines, recursive filters, classical filter topologies (biquad, allpass, comb, Schroeder, FDN), physical models (Karplus-Strong, waveguides), and three worked case studies (channel strip, reverb, nonlinear shelf). A final chapter covers the toolchain — Faust IDE, the faust2… script family, the standard libraries — and Faust's continuing role in research on differentiable DSP and neural audio.

Every Faust listing on this page is copy-pasteable into the Faust IDE. Every JavaScript demo computes the same algorithm in-browser via Web Audio so you can hear the result without compiling anything.

2A History of Audio Programming Languages

Faust did not arrive in a vacuum. It is the youngest surviving member of a sixty-year lineage that began with Max Mathews pointing the first digital audio loudspeaker at a room full of Bell Labs engineers in 1957 and playing them a computer-generated melody. Every language since has answered the same two questions in a different order: how do you describe a signal flow, and how fast do you want it to run?

1957 — Music I
Max Mathews at Bell Labs writes Music I on an IBM 704 — a single triangle oscillator, offline, non-real-time, the first digital synthesiser. By Music III (1960) the language introduces unit generators, the enduring abstraction. Music V (1969) is portable Fortran.
1986 — CSound
Barry Vercoe at MIT Media Lab releases CSound, a direct C descendant of Music-N. Separates an orchestra file of unit-generator instruments from a score file of notes. Still in active use forty years later.
1988 — Max
Miller Puckette at IRCAM writes Max (named after Mathews) — graphical patching of MIDI and control-rate objects. David Zicarelli ports it to the Macintosh; MSP (1997) adds audio-rate signal objects and it becomes Max/MSP. Commercial, closed-source.
1996 — Pure Data
Puckette's open-source response to Max/MSP. Same dataflow patching, free, cross-platform. Still the vehicle of choice for intermedia artists and installation work.
1996 — SuperCollider
James McCartney writes SuperCollider — a Smalltalk-inspired object-oriented language with a scsynth audio server. Text-based, just-in-time compiled, live-codable. Went open source in 2002 and is the backbone of the live-coding scene (TidalCycles runs on it).
2002 — Faust
Yann Orlarey, Dominique Fober and Stéphane Letz at Grame introduce Faust at the International Computer Music Conference. Functional, statically typed, block-diagram-algebraic, compiled ahead-of-time to C++. Open source from day one.
2003 — ChucK
Ge Wang and Perry Cook at Princeton release ChucK — a strongly-timed concurrent language where time is a first-class type. Aimed at live coding and teaching.
2010s — The WASM era
Grame releases faust2webaudio and the browser-based Faust IDE; SuperCollider gains scsynth.js; JUCE embeds a Faust-JIT compiler. Real-time audio DSP runs in a browser tab.
2020s — Differentiable
The functional-pure, single-expression nature of Faust makes it an unusually clean target for automatic differentiation. Research projects (DDSP, neural Faust, faust-torch) back-propagate through Faust programs to learn DSP parameters from audio data.

Where Faust fits

LanguageParadigmExecutionGraphical
CSoundunit-generator / imperativeinterpreted C engineno
Max/MSP, Pddataflowinterpretedyes
SuperColliderobject-oriented / client-serverJIT + C engineno
ChucKstrongly-timed concurrentinterpreted VMno
Faustpure functional, block-diagram algebraahead-of-time compiled to C++/Rust/WASM/LLVMno (but prints diagrams)
Max Mathews
1926 – 2011
American electrical engineer at Bell Labs; father of computer music. Wrote Music I-V between 1957 and 1969. Late in life at CCRMA championed the radio-baton controller.
Miller S. Puckette
b. 1959
Wrote Max at IRCAM (1988) and Pure Data (1996). Professor at UCSD. Long collaborator with Philippe Manoury on real-time score following.
Roger Dannenberg
b. 1955
Carnegie Mellon; author of Nyquist (Lisp-based music language) and the CMU MIDI Toolkit. Co-developed score-following in the 1980s independently of Vercoe.
Perry R. Cook
b. 1955
Princeton; wrote the Synthesis ToolKit (STK) and co-invented ChucK with Ge Wang. Long collaborator with JOS on physical modelling.
Yann Orlarey
b. 1959
Scientific director of Grame (Lyon); principal designer of Faust. Background in functional programming & lambda calculus; Faust is a direct descendant of that tradition.
Dominique Fober
Grame research engineer
Co-designer of Faust; works on music representation, INScore (animated scores) and the Guido score notation system.
Stéphane Letz
Grame research engineer
Co-designer of Faust; wrote JACK for OS X and much of Faust's JIT-LLVM and WASM backend infrastructure.
Julius O. Smith III
b. 1953
CCRMA (Stanford). Doctoral work on digital waveguides; authored the four-volume online textbook series and the Faust companion Audio Signal Processing in Faust. His courses Music 220 and 320C are the canonical academic introduction to Faust.

3Faust Block-Diagram Algebra

The whole of Faust is five composition operators acting on primitive boxes. Every program — a oscillator, a reverb, a compressor — is a single algebraic expression built from these. Once you see them as block-diagram constructors, Faust syntax vanishes and you are just reading wiring diagrams.

The five operators

OperatorNameBlock diagram meaningType rule
A : Bsequentialoutputs of A wired to inputs of B(a→b) : (b→c) ⇒ (a→c)
A , BparallelA and B placed side-by-side, disjoint buses(a→b) , (c→d) ⇒ (a+c → b+d)
A <: Bspliteach output of A fan-out-broadcast to inputs of B(a→b) <: (kb→c) ⇒ (a→c), k ∈ ℕ
A :> Bmergeoutputs of A summed k-at-a-time to inputs of B(a→kb) :> (b→c) ⇒ (a→c)
A ~ Brecursiveoutputs of A fed back through B, via a 1-sample delay(a+c→b) ~ (b→c) ⇒ (a→b)

Operator precedence (tightest first): ~ , , , : , <: , :>. Parentheses disambiguate. The recursion operator ~ is magic: Faust inserts a one-sample delay in the feedback path so the expression is well-defined (otherwise the compiler would need to predict the future).

Primitive boxes you will meet everywhere

BoxSignatureMeaning
_1 → 1identity (through-wire)
!1 → 0cut (drops the signal on the floor)
+, -, *, /2 → 1sample-wise arithmetic
0.50 → 1constant 0.5
@(d)1 → 1delay of d samples
mem1 → 11-sample delay (= @(1))
+(0.5)1 → 1partial application: add 0.5
Partial application is a functional-programming trick: + takes two signals, but +(0.5) has already been given one of them (the constant 0.5) and therefore takes only one signal. This is the reason process = _ : +(0.5) compiles to "add 0.5 to the input".

Worked example — the five operators on paper

// A: sequential  ─ two gains in series
A = *(0.7) : *(1.2);      // (1 → 1) : (1 → 1)  ⇒  1 → 1

// B: parallel ─ stereo pair of independent gains
B = *(0.7) , *(1.2);      // (1→1) , (1→1)     ⇒  2 → 2

// C: split  ─ one input fed to both channels of a stereo pair
C = _ <: *(0.7) , *(1.2); // (1→1) <: (2→2)    ⇒  1 → 2  (mono → stereo)

// D: merge ─ stereo summed to mono then gain
D = _ , _ :> *(0.5);    // (2→2) :> (1→1)    ⇒  2 → 1  (stereo → mono)

// E: recursive ─ the one-pole smoother we have already met
E = + ~ *(0.99);          // (1+1→1) ~ (1→1)   ⇒  1 → 1

Block-diagram construction kit

Pick two primitive boxes and an operator. The panel below prints the resulting Faust expression, draws the block diagram, and reports the signal-type signature.

Top: block diagram drawn from the two boxes and operator you chose. Bottom: Faust expression and the inferred (ins → outs) type.

4The Type System

Faust's types are deliberately small. There are scalar types for individual samples, and there is one signal type: an ordered tuple of scalar streams with a known cardinality and a known sample rate.

Scalar types

NameWhat it isExample literal
int32-bit signed integer42, -3
floatsingle-precision IEEE 754 by default (compile flags select double or quad)0.5, 3.14e-2
boolrepresented as 0 / 1 float, returned by comparison operatorsx < 0.5

Signal types — the (n → m) signature

Every Faust expression has a type of the form $(n \rightarrow m)$, read n inputs, m outputs. The compiler infers this by structural induction over the five operators and the primitive boxes. Type errors are reported as mismatches in cardinality — for example _ : (_,_) fails because the left side produces one signal but the right side consumes two.

Sample rates

Most Faust boxes run at the full audio sample rate. Some — notably UI controls (hslider, vslider, button, checkbox) — run at control rate: the compiler is free to evaluate them once per block rather than once per sample. The si.smoo and si.polySmooth helpers in signals.lib exist precisely to interpolate a control-rate slider up to audio rate without zipper noise.

A complete minimal program

import("stdfaust.lib");

// Two sliders at control rate...
gain  = hslider("gain", 0.5, 0, 1, 0.01) : si.smoo;
bias  = hslider("bias", 0.0, -1, 1, 0.01) : si.smoo;

// ...applied to a single mono input.
process = _ : +(bias) : *(gain) : *(0.8);

The type of process is $(1 \rightarrow 1)$: one input channel, one output. If the input were stereo we would write process = (_:+ (bias):*(gain)) , (_:+ (bias):*(gain)) or, more idiomatically, process = par(i, 2, +(bias) : *(gain));.

The par(i, N, expr) iterator, together with seq, sum and prod, lets you write a chain of any length in constant source size. A 32-channel limiter is the same three lines as a 2-channel one.

5Basic Oscillators

The simplest useful Faust programs are oscillators. They also make a nice tour of the language: a phasor exposes recursion; a sawtooth exposes aliasing; a wavetable sine exposes table lookup; a polyBLEP exposes the library ecosystem.

5.1 The ramp phasor

A phasor is a 0 Hz-to-Nyquist sawtooth that ramps from 0 to 1 at a controlled frequency. In Faust:

import("stdfaust.lib");

// ramp = fractional counter that wraps at 1.0
phasor(f) = f/ma.SR : (+ : ma.frac) ~ _;

process = phasor(220);   // 220 Hz ramp 0…1

The ~ _ part is Faust shorthand for "feed the output back into the adder through a one-sample delay", exactly as in Ch 3. The ma.frac box (from maths.lib) keeps the counter in [0, 1). Divide a sawtooth by 0.5 and subtract 1 and you have a symmetric bipolar ramp.

5.2 A sine by table lookup

Faust's standard library defines os.osc(f), an anti-aliased wavetable sine. Internally it is a phasor driving a 1024-sample cosine table with linear interpolation:

// Idealised – the real implementation lives in oscillators.lib.
osc(f) = phasor(f) * N : rdtable(N, sineTable, _)
  with {
    N = 1024;
    sineTable = ba.time : *(2.0*ma.PI/N) : sin;
  };

5.3 Aliasing and the polyBLEP fix

A raw sawtooth has a discontinuity every period. Its Fourier series $\sum_{n} (-1)^{n+1}/n$ does not decay fast enough, so at sample rate 44.1 kHz a 5 kHz sawtooth contains energy above Nyquist that folds back as audible aliasing. The standard fix is the polyBLEP (polynomial band-limited step) correction of Välimäki & Huovilainen: subtract a pre-computed residual around each discontinuity. Faust's os.sawtooth(f) and os.square(f) implement this out of the box.

Interactive: hear the oscillators

Left: time-domain waveform for one period. Right: magnitude spectrum via a 1024-point FFT. Try saw-naive vs saw-polyBLEP at 2 kHz to see aliases fold back.

6Delay Lines and Filters

Every linear time-invariant system can be built from delays, adders and multipliers. Faust gives you @(d) for integer delays, de.delay(maxN, n, x) for variable-length delays, and de.fdelay(maxN, n, x) for fractional delays. The filter library filters.lib sits on top.

The one-pole lowpass, revisited

import("stdfaust.lib");

// y[n] = (1-a)·x[n] + a·y[n-1]
lp1(a) = *(1.0-a) : + ~ *(a);

// Cutoff f_c at -3 dB  ⇒  a = exp(-2π f_c / SR)
process = lp1(exp(-2.0*ma.PI*1000.0/ma.SR));

The biquad — direct-form II transposed

A biquad implements any second-order transfer function:

$$H(z) = \frac{b_0 + b_1 z^{-1} + b_2 z^{-2}}{1 + a_1 z^{-1} + a_2 z^{-2}}$$

Faust's fi.tf2 and fi.iir wrap this for you:

import("stdfaust.lib");

// fi.tf2(b0, b1, b2, a1, a2) — normalised biquad.
biquad(b0,b1,b2,a1,a2) = fi.tf2(b0,b1,b2,a1,a2);

// Resonant lowpass (RBJ cookbook) at cutoff fc, Q.
resLP(fc,q) = fi.resonlp(fc, q, 1.0);

process = resLP(1200, 4.0);

Interactive: biquad frequency response

Left: pole-zero plot on the unit circle. Right: magnitude response in dB, computed by evaluating H(e^{jω}) at 512 frequencies. Audio test: pink noise through the filter.

7Recursion — the Core of Feedback Filters

The ~ operator is the single feature that separates Faust from a glorified pipeline language. Understanding it precisely is the key to reading any non-trivial Faust program.

Semantics in one paragraph

The expression A ~ B requires A to have type $(n_a + k \rightarrow n_b)$ and B to have type $(n_b \rightarrow k)$. The compiler wires the $n_b$ outputs of A into B; B's $k$ outputs feed back into A's last $k$ inputs, but through a one-sample delay. The result has type $(n_a \rightarrow n_b)$. The mandatory delay is what makes the circular equation well-defined.

Worked derivation: + ~ *(a)

Take A = + (the 2→1 adder) and B = *(a) (a 1→1 gain). Then A ~ B has type $(1 \rightarrow 1)$. The difference equation is

$$y[n] \;=\; x[n] + a\,y[n-1]$$

Take the z-transform of both sides:

$$Y(z) \;=\; X(z) + a\,z^{-1}\,Y(z) \quad\Longrightarrow\quad H(z) \;=\; \frac{Y(z)}{X(z)} \;=\; \frac{1}{1 - a\,z^{-1}}$$

One pole at $z = a$. For $|a| < 1$ the filter is stable; at $a \to 1$ it becomes an (ideal) integrator; for $|a| > 1$ it explodes. The impulse response is $h[n] = a^n \cdot u[n]$ (a geometric decay), and the magnitude response is

$$|H(e^{j\omega})|^2 \;=\; \frac{1}{1 - 2a\cos\omega + a^2}.$$

Interactive: vary a, see the pole and the impulse response

Left: z-plane with unit circle and pole at z=a. Middle: impulse response $h[n]=a^n$. Right: magnitude response in dB.

Why Faust inserts the delay

Consider y = y. As a mathematical equation this is satisfied by any $y$; as a causal real-time program it specifies nothing. Faust avoids the ambiguity by defining A ~ B to mean A's output at sample n is computed from B's output at sample n−1. Equivalently: the compiler inserts a mem in the feedback path. The cost is one sample of group delay inside every feedback loop — which is exactly right for a digital filter.

Multi-dimensional feedback

~ generalises beyond scalar feedback. A state-space block that consumes $n$ inputs and produces $n$ outputs can be closed on itself by A ~ si.bus(n), giving a self-recurrent MIMO system with $n$ one-sample feedback wires. This is how an 8x8 feedback delay network (Ch 8) is written.

8Composition Patterns for Standard Filters

Once you are fluent in the five operators and ~ you can build classical filter structures from first principles — the library versions are then obvious re-uses.

8.1 Resonant biquad from scratch

The canonical RBJ resonant lowpass (Robert Bristow-Johnson 1994) is a biquad with coefficients

$$\omega_0 = 2\pi f_c / f_s,\quad \alpha = \sin\omega_0 / (2Q)$$
import("stdfaust.lib");

rbjLP(fc, q) = fi.tf2(b0/a0, b1/a0, b2/a0, a1/a0, a2/a0)
  with {
    w0    = 2.0*ma.PI*fc/ma.SR;
    cs    = cos(w0);
    alpha = sin(w0)/(2.0*q);
    b0 = (1.0-cs)/2.0;  b1 = 1.0-cs;  b2 = (1.0-cs)/2.0;
    a0 = 1.0+alpha;  a1 = -2.0*cs;  a2 = 1.0-alpha;
  };

8.2 Allpass, comb, Schroeder chain

// 1st-order allpass
ap1(a) = _ <: (*(-a), _) : + ~ *(a);

// Feedback comb
fbcomb(N, g) = + ~ (@(N) : *(g));

// Schroeder allpass (Manfred Schroeder 1962, JASA 34)
schroederAP(N, g) = (+ <: @(N), *(g)) ~ *(-g) : (+ : _);

// A chain of four Schroeder allpasses — the core of many vintage reverbs
process = seq(i, 4, schroederAP(225+347*i, 0.7));

8.3 An 8x8 feedback delay network in a dozen lines

A feedback delay network (Jot & Chaigne 1991; Stautner & Puckette 1982) is a bank of $N$ delay lines whose outputs are mixed by an orthogonal $N\times N$ matrix and fed back to the inputs. Choosing a unitary mixing matrix makes the network lossless — energy is preserved until per-loop attenuation is added. The simplest stable choice is the Householder matrix $U = I - \frac{2}{N}\mathbf{1}\mathbf{1}^T$, which is orthogonal with a single multiply-and-sum per output:

$$U\mathbf{y} \;=\; \mathbf{y} - \frac{2}{N}\left(\sum_i y_i\right)\mathbf{1}.$$
import("stdfaust.lib");

// Prime-number delay lengths avoid flutter echoes
DL = (601, 691, 773, 853, 907, 1049, 1109, 1151);

N  = 8;
g  = 0.93;                                // per-delay gain; controls T60

// 8 parallel delays with per-loop attenuation
delays   = par(i, N, @(ba.take(i+1, DL)) : *(g));

// Householder mixing matrix I - (2/N) 1·1^T
mix     = (si.bus(N) <: par(i, N, _), (si.bus(N) :> *(2.0/N)))
          : ro.interleave(N, 2)
          : par(i, N, -);

// Input distributor: mono in → 8 parallel taps
distrib = _ <: si.bus(N);

// Close the loop with ~ ; sum 8 outputs and scale.
fdn8    = distrib : (si.bus(N) :> si.bus(N)) : (delays : mix) ~ si.bus(N)
          : (si.bus(N) :> *(1.0/N));

process = fdn8;

Interactive: FDN reverb tail

Top: time-domain response of a 4-delay miniature FDN (scaled down from 8 for visual clarity). Bottom: log-envelope showing exponential decay governed by g.

9Physical Modelling in Faust

Karplus-Strong (1983) and Julius Smith's digital waveguide formalism (1987) are compact enough to compile in a browser and expressive enough to make convincing plucked-string and bowed-string sounds. Faust's physmodels.lib contains dozens of these models; here we rebuild the canonical three.

9.1 Karplus-Strong, three lines

import("stdfaust.lib");

// Excite once, then a delay line with in-line 2-tap lowpass in feedback.
ks(freq, decay) = noise : + ~ (@(N) : avg : *(decay))
  with {
    N     = int(ma.SR/freq) - 1;
    avg   = _ <: _, mem :> *(0.5);
    noise = no.noise * (ba.time < N);  // one-period burst only
  };

process = ks(220, 0.998);

The two-tap average _ <: _, mem :> *(0.5) is a symmetric FIR lowpass with zero at Nyquist, which is both a simple frequency-dependent loss and a half-sample delay — the original trick of Karplus & Strong's 1983 paper.

9.2 Bowed-string core loop

A digital waveguide consists of two bidirectional delay lines (the two travelling waves on an ideal string) plus junction boxes at each end. Julius Smith's bowed-string model (PASP, Ch. 9) adds a friction nonlinearity at the bow contact point:

import("stdfaust.lib");

// 10-line bowed string, heavily simplified from pm.bow in physmodels.lib
bow(force, vel) = deltaV : frictionCurve(force) : *(force)
  with {
    deltaV         = vel - _;               // string velocity subtracted later
    frictionCurve(f) = *(5.0) : +(0.75) : pow(-4.0);  // stick-slip μ(Δv)
  };

string(f) = + ~ (de.fdelay(4096, ma.SR/f - 1) : *(-0.995) : fi.lowpass(1, 4000));

process(force, vel, f) = bow(force, vel) : string(f);

Interactive: Karplus-Strong pluck

Top: first 2048 samples of the pluck. Bottom: log-magnitude envelope on a dB scale — the straight-line decay is the characteristic audible signature of KS.

For the full collection — bowed violin, clarinet reed, marimba bar, vocal tract — see the sibling Companion to Physical Audio Signal Processing and Faust's own physmodels.lib.

10The faust2… Toolchain

Faust is a compiler front-end. It produces a language-agnostic internal representation, then hands it to a backend which emits C++, Rust, LLVM IR, WebAssembly, JavaScript, Julia, JSFX, Cmajor or CUDA. The faust2… shell-script family wraps that compiler with platform-specific glue so one source file can become a standalone GUI app, a VST plug-in, a JUCE project, a Pure Data external, or a web-audio node.

The useful subset of the faust2… scripts

ScriptWhat it producesTypical use
faust2webaudioan ES-module WebAudio node (WASM + glue JS)deploy a plug-in to a web page
faust2jaqta standalone Qt app with JACK I/Oprototype on Linux
faust2vsta VST2 or VST3 plug-in binaryrun inside a DAW
faust2jucea full JUCE project directoryship a cross-platform plug-in
faust2pda Pure Data external (.pd_linux, .dll, .pd_darwin)embed in a Pd patch
faust2androidan .apk with a generated Android UIrun on a phone
faust2iosan Xcode project for iOSrun on iPad / iPhone
faust2sndfilea command-line .wav → .wav offline rendererbatch-process audio

The Faust IDE

Since 2018 the entire compiler has run in the browser via faustide.grame.fr. You paste a .dsp file into the left pane; the IDE compiles to WASM, spawns a Web Audio node, draws the block diagram, and gives you sliders that are live-linked to the hslider declarations in the source. Every listing in this guide runs there unmodified.

The standard libraries

Faust ships with a collection of .lib files under libraries/. They are themselves Faust programs — open filters.lib and you can read exactly how fi.resonlp is implemented. Every listing below uses the prefix namespace style: import stdfaust.lib once and refer to definitions as os.osc, fi.lowpass, de.fdelay, etc.

LibraryPrefixHighlights
maths.libma.SR, PI, frac, copysign
basics.libba.time, take, countdown, sAndH
signals.libsi.bus, smoo, block, polySmooth
oscillators.libos.phasor, osc, sawtooth, square, triangle, polyBLEP variants
filters.libfi.lowpass, highpass, resonlp, tf2, iir, svf, zita_shelf
delays.libde.delay, fdelay, sdelay, Lagrange, Thiran
reverbs.libre.freeverb, zita_rev1, jpverb, fdnRev
compressors.libco.compressor_mono, limiter_1176, peak_expander
envelopes.liben.adsr, asr, ar, amFollower
noises.libno.noise, pink_noise, multirandom
physmodels.libpm.brassModel, clarinetModel, marimbaBarModel, bowedString
analyzers.liban.rms_envelope, amp_follower, pitchTracker
routes.libro.interleave, cross, hadamard, butterfly

A concrete command-line session

$ cat lp1.dsp
import("stdfaust.lib");
fc      = hslider("fc", 1000, 20, 20000, 1) : si.smoo;
process = fi.lowpass(1, fc);

$ faust -svg lp1.dsp -o lp1.cpp              # C++ + SVG block diagram
$ faust2jaqt -osc lp1.dsp                    # standalone Qt app, OSC-controllable
$ faust2webaudio -poly 8 lp1.dsp             # 8-voice WASM WebAudio node
$ faust2vst -voices 16 lp1.dsp               # 16-voice polyphonic VST

11Case Studies

Three complete Faust programs, each with a JavaScript audio approximation so you can hear the effect inline without compiling anything.

11.1 A channel strip — gain, three-band EQ, compressor

import("stdfaust.lib");

// --- Controls ---
inGain  = hslider("[0]Input gain [unit:dB]", 0, -24, 24, 0.1) : ba.db2linear : si.smoo;
lowG    = hslider("[1]Low   [unit:dB]", 0, -18, 18, 0.1) : si.smoo;
midG    = hslider("[2]Mid   [unit:dB]", 0, -18, 18, 0.1) : si.smoo;
highG   = hslider("[3]High  [unit:dB]", 0, -18, 18, 0.1) : si.smoo;
thresh  = hslider("[4]Thresh[unit:dB]", -18, -60, 0, 0.5);
ratio   = hslider("[5]Ratio", 4, 1, 20, 0.1);
att     = hslider("[6]Attack [unit:ms]", 5, 0.1, 100, 0.1) : *(0.001);
rel     = hslider("[7]Release[unit:ms]", 80, 1, 500, 1) : *(0.001);

// --- Three-band EQ via RBJ shelves + mid peak ---
eq = fi.lowshelf(2, lowG,  250)
   : fi.peak_eq(midG, 1200, 1.0)
   : fi.highshelf(2, highG, 4000);

// --- Feed-forward compressor with soft-knee (co.compressor_mono) ---
comp = co.compressor_mono(ratio, thresh, att, rel);

process = *(inGain) : eq : comp;
Left: compressor static curve (input dB vs output dB) with soft-knee transition at the threshold. Right: three-band EQ magnitude response computed live from the slider values.

11.2 A complete FreeVerb in Faust

FreeVerb (Jezar 2000) is a Schroeder-style reverb: eight lowpass-feedback comb filters in parallel driving four Schroeder allpasses in series. It sits in reverbs.lib as re.mono_freeverb:

import("stdfaust.lib");

fb1      = hslider("Room size", 0.5, 0, 1, 0.01);
damp     = hslider("Damping",  0.5, 0, 1, 0.01);
spread   = 0;

process  = re.mono_freeverb(fb1, damp, 0.5, spread);

11.3 A nonlinear high-shelf — tube-style brightness

Classic tube preamps brighten the signal more at high input levels. We emulate this by a soft-clipper after a high-shelf, with the shelf gain modulated by the envelope follower.

import("stdfaust.lib");

drive    = hslider("Drive [unit:dB]", 6, 0, 24, 0.1) : ba.db2linear : si.smoo;

soft(x)  = x / (1.0 + abs(x));

// env-modulated high-shelf: up to +8 dB at full drive
envShelf = _ <: an.amp_follower(0.01) : *(8.0) , _ : fi.highshelf(2, _, 3500);

process  = *(drive) : envShelf : soft;
The soft function above is Schlichting's $x/(1+|x|)$ soft clipper — odd-order only, so perfectly symmetric. Replace by $\tanh$ for a slightly warmer curve or $x - x^3/3$ for a polynomial one that is cheap to differentiate (useful for neural-Faust work in Ch 12).

12Legacy and Continuing Impact

Twenty-plus years after Orlarey, Fober and Letz's first ICMC paper, Faust has become the lingua franca of research-grade audio DSP. Its role in the modern ecosystem is threefold: the browser-audio bridge, the reference plug-in generator, and the teaching language of choice at Stanford, IRCAM, KAIST and a dozen other labs.

Real-time WASM audio

The earliest demonstration that Faust could target the browser was a 2015 Grame paper by Letz, Denoual and Orlarey; today faust2webaudio produces a drop-in ES-module Audio Worklet that runs inside the browser's real-time audio thread. Most major interactive DSP demonstrations on the modern web — including faust-playground, mephisto.js, and several of the Cycling '74 RNBO examples — are built on this pipeline.

Plug-in generation

Commercial plug-in houses (e.g. Kadenze, Kilohearts, several smaller companies) use faust2juce as a prototyping backend: a DSP engineer writes the algorithm in Faust, generates a JUCE project, then iterates on the GUI and host integration in C++. The Faust source becomes the canonical specification of the algorithm.

Teaching

Julius Smith's Audio Signal Processing in Faust is the formal companion textbook to Stanford's Music 220 and 320C. Romain Michon's Master's-level course at CCRMA is entirely Faust-based; Grame itself runs annual summer schools. The low syntactic overhead of Faust means an entire biquad appears on one slide, which is an educational property no other DSP language shares.

Differentiable audio-DSP

Because every Faust program is a single expression with no mutable state, automatic differentiation is comparatively straightforward. Recent work (Chowdhury & Clarke 2022; Hayes et al. 2023) compiles Faust programs to PyTorch or JAX back-ends for gradient-based optimisation — so you can fit the parameters of a hand-designed Faust reverb or compressor to audio data in the same way you train a neural network. The DDSP line of research (Engel et al. 2020) took this idea to a full generative model; several follow-ups have re-expressed their backbones in Faust to recover interpretability.

Where to look next

Key References

  1. Orlarey, Y., Fober, D. & Letz, S. (2002). An Algebra for Block Diagram Languages. Proc. ICMC 2002, Gothenburg, 542–547.
  2. Orlarey, Y., Fober, D. & Letz, S. (2004). Syntactical and Semantical Aspects of Faust. Soft Computing 8(9), 623–632.
  3. Smith, J. O. (2020). Audio Signal Processing in Faust. Online book, Stanford CCRMA. ccrma.stanford.edu/~jos/aspf.
  4. Smith, J. O. (2007). Introduction to Digital Filters with Audio Applications. W3K Publishing.
  5. Smith, J. O. (2010). Physical Audio Signal Processing. W3K Publishing.
  6. Smith, J. O. (2011). Spectral Audio Signal Processing. W3K Publishing.
  7. Mathews, M. V. (1969). The Technology of Computer Music. MIT Press.
  8. Vercoe, B. (1986). Csound: A Manual for the Audio Processing System. MIT Media Lab.
  9. Puckette, M. (1988). The Patcher. Proc. ICMC 1988.
  10. Puckette, M. (1996). Pure Data. Proc. ICMC 1996.
  11. McCartney, J. (1996). SuperCollider: A New Real Time Synthesis Language. Proc. ICMC 1996.
  12. Wang, G. & Cook, P. R. (2003). ChucK: A Concurrent, On-the-fly Audio Programming Language. Proc. ICMC 2003.
  13. Karplus, K. & Strong, A. (1983). Digital Synthesis of Plucked-String and Drum Timbres. Computer Music Journal 7(2), 43–55.
  14. Smith, J. O. (1992). Physical Modeling Using Digital Waveguides. Computer Music Journal 16(4), 74–91.
  15. Jot, J.-M. & Chaigne, A. (1991). Digital Delay Networks for Designing Artificial Reverberators. Proc. 90th AES Convention.
  16. Stautner, J. & Puckette, M. (1982). Designing Multi-Channel Reverberators. Computer Music Journal 6(1), 52–65.
  17. Schroeder, M. R. (1962). Natural Sounding Artificial Reverberation. J. Audio Eng. Soc. 10(3), 219–223.
  18. Välimäki, V. & Huovilainen, A. (2007). Antialiasing Oscillators in Subtractive Synthesis. IEEE Signal Processing Magazine 24(2), 116–125.
  19. Bristow-Johnson, R. (1994). Audio EQ Cookbook. Matrix Engineering.
  20. Jezar at Dreampoint (2000). FreeVerb: Schroeder-style reverb in public domain.
  21. Boll, S. F. (1979). Suppression of Acoustic Noise in Speech Using Spectral Subtraction. IEEE Trans. ASSP 27(2), 113–120.
  22. Michon, R., Orlarey, Y., Letz, S. & Fober, D. (2020). Real Time Audio Digital Signal Processing with Faust and the Teensy. Proc. SMC 2020.
  23. Letz, S., Denoual, S. & Orlarey, Y. (2015). Faust Audio DSP Language in the Web. Proc. Linux Audio Conference 2015.
  24. Engel, J. et al. (2020). DDSP: Differentiable Digital Signal Processing. ICLR 2020.
  25. Hayes, B., Saitis, C. & Fazekas, G. (2023). Sinusoidal Frequency Estimation by Gradient Descent. ICASSP 2023.