Output

The page's one audio context, and a master bus with a safety limiter that every sound of a screen goes through.

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 server

Browsers 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

Built by Ouest Labs, the source code is available on GitHub.
RSS