All books

A modulator is a file

~2 min read · updated 2026-08-29 · markdown

FaustWave has no "LFO feature". A modulator is an ordinary .dsp document with a bargraph tagged [modout]; while it runs, that output is a modulation source in the Patchbay, and every live parameter of every running instrument, Builder and FX slot is a sink. Wiring is a cable with an amount, like audio is a cable with a gain.

This has three consequences you will feel immediately:

  • You can read it. Open Mod LFO Breath.dsp in the demo and the whole modulator is twenty lines of Faust. Change the shape, add a second output, tempo-sync it — it is your code.
  • It persists like a file. The modulator is in assets/, its edges are in the project's routing table, the values it swings around are in the project. Reopen the project and the breathing is back.
  • It is a source like any other. An envelope follower on the drum bus, a random walk, a macro knob — anything with [modout] can drive anything with a slider.

The corpus templates

You rarely write one from scratch. modulation.source.add { type } creates a modulator document from a template and runs it:

TemplateWhat it isTempo sync
lfoFour shapes (sine, triangle, saw, square) from one resettable phasor, rate in Hzyes — the sync menu (1/1 … 1/16)
sample-holdA new random value on every clock tick, deliberately unsmoothedyes
randomBand-limited driftno
macroOne knob, no life of its own — the stage performer's source—
actions:
  - action_id: modulation.source.add
    input: { type: lfo }
    save_as: lfo
    label: "▶ Create an LFO document and run it"
  - action_id: modulation.source.list
    input: {}
    label: "▶ Every source that is running, with its ports and edge count"

Tempo sync works through two metadata tags a modulator (or any DSP) can carry: a slider tagged [bpm] receives the live transport tempo, a button tagged [transportreset] is pulsed on stop → play so the phase re-aligns with the downbeat. The demo's Mod SH Bell steps on every eighth because of exactly those two lines.

Writing your own

declare name "Breath";
import("stdfaust.lib");
rate  = hslider("rate[unit:Hz][scale:log]", 0.135, 0.01, 20, 0.001);
depth = hslider("depth", 1, 0, 1, 0.001);
process = attach(0, os.osc(rate) * depth : hbargraph("out[modout]", -1, 1));

Three rules: the output range is what the bargraph declares (-1..1 or 0..1 — the amount maths below uses it); attach(0, …) keeps the DSP audio-silent while forcing the bargraph to compute; the modulator must run (dsp.run) — a source registers only while its patch runs, which is also why routing.list shows modulation sinks only for running instruments.

Where you see it

The Modulators pane (shipped in Perform) lists every source with its live value and edges. The Inspector colours every modulatable widget: arm a source with modulation.assign.set (or the panel's assign toggle) and a drag on any widget creates the edge and sets its depth — the gesture; the next chapter is the arithmetic behind it.

  • FaustWave — Faust DSP § Live params, modulation and observability — the [modout] tag from the instrument's side.
  • FaustWave — Patchbay & Routing § Cable kinds — modulation is the third cable kind beside audio and MIDI.
Try it yourself — the IDE runs in your browser. Open the IDE → Get the desktop app

Text licensed under CC BY 4.0 — Mani Weber / FaustWave. For language models: llms.txt · llms-full.txt

AUDIO · 48k · 48.0ms FAUST · 3 KB · 1171 docs CPU · 8.4% BPM · 120.0 UTF-8 BETA· v0.90.0