Audio UI
A four-operator FM synth. Each operator is a sine at a ratio of the pitch, with its own level and its own envelope. The algorithm wires them: a carrier is heard, and a modulator bends the pitch of the operator under it many times a second, which grows the harmonics. The deeper a modulator goes, the brighter the sound, so a sound can start bright and mellow as it rings, as an electric piano or a bell does. Operator 4 can feed back on itself, from a sine towards a saw. Start from one of its eight presets (E.Piano, Tubular, Bass, Brass, Marimba, Clav, Organ, Glass Pad), then shape it. It is a device: a state, a panel, an engine and tools, each its own file.
Installation
pnpm dlx shadcn@latest add @audio-ui/fm-synth
It installs the device's state, its engine and its panel to components/audio-ui/devices/fm-synth/. The demo above, which wires them to a loop, is @audio-ui/block-fm-synth.
Controls
The controls read 0 to 10, the ratios in steps of a list and the algorithm by its number, and show the value they set in the unit the sound takes.
Controls · 22
Panel
The panel loads a preset from the menu in its title bar, lays out what every operator shares, then each operator's ratio, level, decay and sustain, and has a keyboard of two octaves that plays the synth. fmSynthPreset(name) gives a preset's whole state, FM_SYNTH_PRESETS lists them, and FM_ALGORITHMS holds each algorithm's carriers and links, to draw its wiring. With activeNotes, the notes held now, such as on a MIDI keyboard, light up their keys. The panel sends notes out and does not make the sound, so it plays through any engine.
import { FmSynthPanel } from "@/components/audio-ui/devices/fm-synth/panel";
import { fmSynthDevice } from "@/components/audio-ui/devices/fm-synth/device";
const [state, setState] = useState(() => fmSynthDevice.defaults());
<FmSynthPanel onValueChange={setState} value={state} />;Engine
Create it, connect it and update it as for any device: see The engine.
engine.noteOn({ frequency: 261.63, velocity: 0.9 }); // a harder key is louder and brighter
engine.noteOff(); // every operator fades over the release
// Scheduled ahead on the audio clock, by a host that plays notes from a song:
engine.noteOn({ frequency: 440 }, { at: time, id: "a4" });
engine.noteOff({ at: time + length, id: "a4" });An engine plays one voice. For chords, make as many as the notes you hold and give each note its own: the arrangement's engine does it for a MIDI track, and the block above keeps eight. A harder key is louder and modulates deeper, and the treble is modulated less, so its harmonics never fold back into noise. Let go, every operator falls back over the release from where it had got to. A note while one is held cuts it and strikes anew. A level, a ratio, the feedback and the vibrato move a held note; the algorithm and the envelopes take the next one. An agent plays the synth with the play tool.