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 libraries
  • oscillators.lib — Waveform generators (saw, pulse, sine, triangle, noise)
  • filters.lib — Butterworth, Chebyshev, Moog ladder, SVF, comb filters
  • envelopes.lib — ADSR, AR, ASR, gate, trigger utilities
  • effects.lib — Reverb, delay, chorus, flanger, phaser, distortion
  • maths.lib — DSP math functions, constants, conversions
  • signals.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

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:

  1. Experiment in the Web IDE — Start with simple filters and build up complexity
  2. Study the standard libraries — Learn idiomatic FAUST patterns from expert code
  3. Deploy to hardware — Order a Daisy Seed and build your first Eurorack prototype
  4. Explore compilation targets — Try VST, Max/MSP, Pure Data, SuperCollider exports
  5. Read the DSP theory — Julius O. Smith's books provide the mathematical foundation