Audio UI
Places a sound all around the head, for headphones. A pan only sets a level between two speakers; this one makes the cues the ear uses: the far ear hears the sound a fraction of a millisecond later, and darker, in the head's shadow. Drag the sound around the head, behind it, beside it, farther out. It is a device: a state, a panel, an engine and tools, each its own file.
Installation
pnpm dlx shadcn@latest add @audio-ui/binaural-panner
It installs the device's state, its engine and its panel to components/audio-ui/devices/binaural-panner/. The demo above, which wires them to a loop, is @audio-ui/block-binaural-panner.
Controls
Controls · 5
The cues are of direction around the head, not of height, and a sound behind sounds much like one in front: the model has no ears' folds to tell them apart.
Panel
The panel shows the head seen from above, in front up, and a handle for the sound: its angle is the direction and how far out it is the distance. binauralPoint(state) is where the handle sits, and binauralPlace(point) the direction and distance of a point.
import { BinauralPannerPanel } from "@/components/audio-ui/devices/binaural-panner/panel";
import { binauralPannerDevice } from "@/components/audio-ui/devices/binaural-panner/device";
const [state, setState] = useState(() => binauralPannerDevice.defaults());
<BinauralPannerPanel onValueChange={setState} value={state} />;Engine
Create it, connect it and update it as for any device: see The engine.
The panner runs on the audio thread: the binaural panner of @audio/spatial-binaural, the structural model of Brown and Duda, in an AudioWorklet loaded from a blob (BINAURAL_PANNER_WORKLET is its source), 128 frames at a time. It folds the sound to mono, delays the far ear as a round head would, and shades it with a one-pole filter, smoothing a change of direction so it does not click. Its delay line is made for the largest head, so the head can change while it plays, and a direction is sent as the angle nearest the last, so a turn across the back goes the short way round. A worklet cannot import, so the device carries the kernel as published (BINAURAL_PANNER_KERNELS, in its kernels.ts). Until it is ready, and in a context with no worklets, the sound passes clean: ready resolves once it is in the graph.