MIDI & polyphony: nvoices and the magic sliders
A mono DSP ignores note events. To make a .dsp a playable
instrument, declare polyphony in the source — the source is the single
source of truth, there is no separate UI toggle:
declare options "[midi:on][nvoices:8]";
import("stdfaust.lib");
freq = hslider("freq", 440, 20, 8000, 0.01);
gate = button("gate");
gain = hslider("gain", 0.8, 0, 1, 0.01);
env = en.adsr(0.005, 0.1, 0.7, 0.3, gate);
process = os.sawtooth(freq) * env * gain <: _, _;
The three magic-name sliders are the contract with the poly runtime:
| Param | Filled per voice with |
|---|---|
freq | The note's frequency (from the MIDI note number) |
gate | 1 on note-on, 0 on note-off — drive your envelope with it |
gain | The velocity, normalized 0..1 |
On every note-on the runtime allocates one of the N voices and writes these
three params into that voice only; your process runs once per voice
and the voices are summed. [nvoices:N] is also what registers the DSP
as a MIDI consumer at all — check system.state { sections: ["midi_consumers"] } when notes seem to go nowhere.
Wiring: a running DSP exposes a <module_id>:midi-in sink in the routing
table (only while running — build the graph, run, THEN connect). Connect
the on-screen keyboard or a sequencer to it in the Patchbay, or via
routing.connect { kind: "midi" }.
Reading params on a poly DSP — a trap
dsp.param.get on freq / gate / gain of an [nvoices] DSP
returns the GLOBAL param — which stays at its init value even while notes
are audibly playing, because the real values live per voice. The result
carries an explicit note about this. Do not conclude "keyOn is broken" from
such a read; verify audibility with your ears or the master meters instead
(see the Hearing is proof section).