A modulator is a file
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.dspin 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:
| Template | What it is | Tempo sync |
|---|---|---|
lfo | Four shapes (sine, triangle, saw, square) from one resettable phasor, rate in Hz | yes — the sync menu (1/1 … 1/16) |
sample-hold | A new random value on every clock tick, deliberately unsmoothed | yes |
random | Band-limited drift | no |
macro | One 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.