Arrangement
Transport and mixer
EQ and filters
Modulation
Utility
Instruments
The audio context
pnpm dlx shadcn@latest add @audio-ui/web-audio
A page has one AudioContext, shared by every hook and engine on it, made the first time something asks for it. Ask for it where you need it:
import { webAudio } from "@/lib/web-audio";
const ctx = webAudio.getContext(); // null on the serverBrowsers start a context suspended until the user does something on the page. Resume it from the gesture that starts the sound, such as a play button:
const play = () => {
const ctx = webAudio.getContext();
if (ctx?.state === "suspended") {
ctx.resume();
}
transport.play();
};The master bus
pnpm dlx shadcn@latest add @audio-ui/use-master-bus
Connect every sound of a screen into one master bus, never straight into ctx.destination. One fader then sets the level of everything, and a limiter at the end keeps a loud mix from clipping.
import { useMasterBus } from "@/hooks/use-master-bus";
const [volume, setVolume] = useState(0.7);
const output = useMasterBus(volume);
const voices = useSynthVoices({ destination: output });
<Fader aria-label="Volume" max={1} min={0} onValueChange={setVolume} value={volume} />;The bus is a gain, a limiter and a trim that cancels the limiter's make-up gain, so below its threshold (-3 dBFS) the sound passes unchanged. A change of volume ramps over 10 ms, so dragging the fader never clicks. The limiter adds a few milliseconds of latency.
Props · 2
null on the server and in the first render, before the context exists.