Why FAUST for Synthesizer Development?
FAUST (Functional Audio Stream) is a domain-specific language designed for real-time audio signal processing. Unlike traditional imperative languages, FAUST uses a functional approach that lets you think in terms of signal flow diagrams rather than loops and buffers.
Key advantages for synth engineers:
- Write once, deploy everywhere: Compile to C++, WebAssembly, VST, Max/MSP externals, even embedded ARM/STM32
- Mathematical clarity: Code reads like block diagrams and transfer functions
- Automatic optimization: Compiler generates highly efficient DSP code with SIMD vectorization
- Rapid prototyping: Online IDE with instant audio feedback and visual block diagrams
- Hardware deployment: Direct path to Daisy Seed, Teensy, and custom Eurorack modules
Getting Started: FAUST Fundamentals
1. Installation & Setup
Start with the FAUST Web IDE at faustide.grame.fr for immediate experimentation with no installation required. For local development:
# macOS
brew install faust
# Linux
sudo apt-get install faust
# Windows
# Download installer from faust.grame.fr
2. Core Syntax: Signal Flow
FAUST programs describe signal processing as compositions of primitive operations:
// Basic gain control
process = _ : *(0.5);
// Stereo gain
process = _ , _ : *(0.5) , *(0.5);
// Parallel composition
process = _ <: *(0.5) , *(0.3);
Key operators:
:— Sequential composition (connect output to input),— Parallel composition (side-by-side signals)<:— Split (duplicate signal):>— Merge (sum signals)~— Recursive composition (feedback)
Building Synth Components
One-Pole Lowpass Filter
Start with the simplest analog-style filter — essential for envelope followers and smoothing:
import("stdfaust.lib");
// One-pole lowpass: y[n] = a*x[n] + (1-a)*y[n-1]
onepole(a) = _ : + ~ *(1-a) : *(a);
// Control via cutoff frequency
onepole_fc(fc) = onepole(1 - exp(-2*ma.PI*fc/ma.SR));
process = os.osc(440) : onepole_fc(hslider("cutoff", 1000, 20, 20000, 1));
State Variable Filter (SVF)
The workhorse filter for synths — simultaneous LP, HP, BP, and notch outputs:
import("stdfaust.lib");
svf(freq, q) = _ : svf_block
with {
g = tan(ma.PI * freq / ma.SR);
k = 1.0 / q;
a1 = 1.0 / (1.0 + g * (g + k));
a2 = g * a1;
a3 = g * a2;
svf_block(in) = lp, hp, bp
with {
v3 = in - ic2eq;
v1 = a1 * ic1eq + a2 * v3;
v2 = ic2eq + a2 * ic1eq + a3 * v3;
ic1eq = 2*v1 - ic1eq : mem;
ic2eq = 2*v2 - ic2eq : mem;
lp = v2;
hp = in - k*v1 - v2;
bp = v1;
};
};
freq = hslider("freq", 1000, 20, 20000, 1);
q = hslider("Q", 1, 0.5, 20, 0.1);
mode = nentry("mode[style:menu{'LP':0;'HP':1;'BP':2}]", 0, 0, 2, 1);
process = os.sawtooth(110) : svf(freq, q) : ba.selectn(3, mode);
BLEP Antialiased Sawtooth
Naive waveforms alias badly. Use BLEP (Band-Limited Step) for clean harmonics:
import("stdfaust.lib");
// Integrated BLEP correction
polyblep(phase, dt) = ba.if(phase < dt,
phase/dt - 0.5 * (phase/dt) * (phase/dt),
ba.if(phase > (1.0 - dt),
0.5 * ((phase - 1.0)/dt) * ((phase - 1.0)/dt),
0));
saw_blep(freq) = phase : polyblep_saw
with {
dt = freq / ma.SR;
phase = os.phasor(1, freq);
polyblep_saw(p) = 2.0*p - 1.0 - polyblep(p, dt);
};
process = saw_blep(hslider("freq", 110, 20, 2000, 0.1));
ADSR Envelope Generator
import("stdfaust.lib");
adsr(a, d, s, r, gate) = env
with {
attack = a : max(ma.EPSILON);
decay = d : max(ma.EPSILON);
release = r : max(ma.EPSILON);
env = gate : trigger : + ~ decay_release : min(1.0)
with {
trigger(g) = g : ba.impulsify;
decay_release(prev) = ba.if(gate > 0,
// Attack or decay phase
ba.if(prev < 1.0,
prev + 1.0/ma.SR/attack, // Attack
prev + (s - prev)/ma.SR/decay // Decay
),
// Release phase
prev * exp(-1.0/ma.SR/release)
);
};
};
gate = button("gate");
a = hslider("attack", 0.01, 0.001, 2, 0.001);
d = hslider("decay", 0.1, 0.001, 2, 0.001);
s = hslider("sustain", 0.7, 0, 1, 0.01);
r = hslider("release", 0.2, 0.001, 5, 0.001);
process = os.sawtooth(220) * adsr(a, d, s, r, gate);
Deployment Targets
Web Audio (WebAssembly)
Deploy your synth to the browser with zero plugins:
# Compile to WebAssembly module
faust2wasm -worklet mysynth.dsp
# Generates:
# - mysynth.wasm (DSP code)
# - mysynth.js (glue code)
# - mysynth.html (test page)
Perfect for interactive documentation, educational tools, or full web-based DAWs. See the Software Resources page for Web Audio API integration patterns.
VST/AU Plugins
# macOS Audio Unit
faust2au mysynth.dsp
# VST2/VST3
faust2vst mysynth.dsp
# JUCE-based VST3
faust2juce mysynth.dsp
Eurorack Hardware: Daisy Seed
The Electrosmith Daisy Seed is an ARM Cortex-M7 platform designed for Eurorack modules. FAUST compiles directly to Daisy-compatible C++:
# Install Daisy toolchain first
git clone https://github.com/electro-smith/DaisyExamples
cd DaisyExamples
# Compile FAUST to Daisy
faust2daisy -nvoices 4 mysynth.dsp
# Flash to hardware
make program-dfu
Hardware specs: 480MHz, 64MB SDRAM, stereo 24-bit audio, CV inputs — enough for complex polyphonic synths or effects. See Modular Resources for Daisy module designs.
Teensy 4.x with Audio Shield
For DIY Eurorack modules on a budget:
# Generate Teensy C++ code
faust2teensy mysynth.dsp
# Import into Arduino IDE / PlatformIO
# Uses Teensy Audio Library backend
Advanced Techniques
Polyphonic Voice Architecture
FAUST handles polyphony with the nvoices metadata:
import("stdfaust.lib");
// Declare polyphonic parameters
freq = hslider("freq[style:knob]", 440, 20, 8000, 1);
gain = hslider("gain[style:knob]", 0.5, 0, 1, 0.01);
gate = button("gate");
// Voice design
voice = os.sawtooth(freq) * en.adsr(0.01, 0.1, 0.7, 0.2, gate) * gain;
// Polyphonic wrapper (4 voices)
process = voice : ef.cubicnl(0.5, 0) : fi.lowpass(2, 8000)
with {
// Metadata for polyphony
declare nvoices "4";
};
Compile-Time Optimizations
Enable maximum performance for embedded targets:
# Fast math, vectorization, and inlining
faust -vec -lv 1 -vs 32 -dfs -mcd 16 mysynth.dsp -o mysynth.cpp
# Architecture-specific
faust -vec -lv 1 -vs 32 -march=native mysynth.dsp -o mysynth.cpp
Integrating C++ Libraries
Access external DSP code via foreign functions:
declare name "HybridSynth";
// Foreign function declaration
my_external_filter = ffunction(float my_filter(float), <mylib.h>, "");
// Use in FAUST graph
process = os.osc(440) : my_external_filter;
Essential FAUST Libraries
FAUST ships with comprehensive standard libraries for synth development:
stdfaust.lib— Imports all standard librariesoscillators.lib— Waveform generators (saw, pulse, sine, triangle, noise)filters.lib— Butterworth, Chebyshev, Moog ladder, SVF, comb filtersenvelopes.lib— ADSR, AR, ASR, gate, trigger utilitieseffects.lib— Reverb, delay, chorus, flanger, phaser, distortionmaths.lib— DSP math functions, constants, conversionssignals.lib— Bus routing, mixing, level utilities
import("stdfaust.lib");
// Use library oscillators
process = os.sawtooth(220) + os.square(220) : fi.resonlp(1000, 5, 1);
Learning Resources & Examples
- Official FAUST Documentation — Complete language reference and tutorials
- FAUST Standard Libraries — Browse source code for hundreds of DSP building blocks
- FAUST Web IDE — Interactive online editor with examples and instant audio preview
- GitHub Examples — Synths, effects, physical models, and advanced techniques
- Julius O. Smith's Books — Theory foundation (many FAUST library algorithms based on JOS work)
Building a Complete Eurorack Module
Putting it all together — a multimode filter module with CV control for Daisy Seed:
import("stdfaust.lib");
// Hardware mappings for Daisy patch
freq_knob = hslider("freq[scale:log]", 1000, 20, 20000, 1);
freq_cv = hslider("freq_cv[scale:log]", 0, -5, 5, 0.01) * 1000;
q_knob = hslider("resonance", 1, 0.5, 20, 0.1);
mode_switch = nentry("mode", 0, 0, 3, 1);
// Voltage to frequency (1V/oct)
v_to_freq(v) = 440 * pow(2, v - 4.75);
// Combined frequency control
total_freq = freq_knob + v_to_freq(freq_cv) : min(20000) : max(20);
// SVF core (from earlier example)
svf(freq, q) = /* ... SVF implementation ... */;
// Output selector
filter_bank = svf(total_freq, q_knob);
mode_select = filter_bank : ba.selectn(4, mode_switch);
// Main process with soft clipping
process = _ : mode_select : ef.cubicnl(0.5, 0);
// Daisy metadata
declare options "[nvoices:1]";
declare daisy_patch "1";
This generates production-ready firmware for a professional Eurorack filter. Pair with the Modular Design guide for PCB layout and panel design.
Next Steps
Now that you understand FAUST fundamentals:
- Experiment in the Web IDE — Start with simple filters and build up complexity
- Study the standard libraries — Learn idiomatic FAUST patterns from expert code
- Deploy to hardware — Order a Daisy Seed and build your first Eurorack prototype
- Explore compilation targets — Try VST, Max/MSP, Pure Data, SuperCollider exports
- Read the DSP theory — Julius O. Smith's books provide the mathematical foundation