Audio UI
Repeats the sound. At the start of every interval it catches a slice, then plays it again and again until the interval ends, each repeat quieter by the decay. The lengths are note values of the host's tempo, so the repeats fall in time. Turning it on catches a slice at once, a stutter to play live. It is a device: a state, a panel, an engine and tools, each its own file.
Installation
pnpm dlx shadcn@latest add @audio-ui/stutter
It installs the device's state, its engine and its panel to components/audio-ui/devices/stutter/. The demo above, which wires them to a loop, is @audio-ui/block-stutter.
Controls
Controls · 5
Panel
The panel draws one interval, in beats: the slice it catches, the sound as it plays, then each repeat at the level the decay leaves it. stutterCycle(state) is what it draws, and stutterSettings(state, tempo) the lengths in seconds.
import { StutterPanel } from "@/components/audio-ui/devices/stutter/panel";
import { stutterDevice } from "@/components/audio-ui/devices/stutter/device";
const [state, setState] = useState(() => stutterDevice.defaults());
<StutterPanel onValueChange={setState} value={state} />;Engine
Create it, connect it and update it as for any device: see The engine.
The stutter runs on the audio thread: the stutter of @audio/effect-stutter in an AudioWorklet loaded from a blob (STUTTER_WORKLET is its source), 128 frames at a time, with short fades at the edges of each repeat so it does not click. Its cycle runs from when it starts, not from the bar: the device knows the tempo, not where the transport is. Turning it on starts a fresh cycle, catching a slice at once, and new lengths, from a control or a change of tempo, wait for them to rest, then fade in on a fresh cycle. A worklet cannot import, so the device carries the kernel as published (STUTTER_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.