Components
Time and sequencing
Blocks
A saturator as a device you can plug in: a curve, a drive, a mix and an output as a state, a panel that draws its transfer curve, a Web Audio engine and the tools an agent drives it with.
Audio UI
"use client";
import { useState } from "react";Adds warmth or dirt. The drive pushes the signal into one of four curves, a soft clip, a hard clip, a wavefold or an asymmetric tube, and the panel draws the transfer curve against the straight line of a clean sound. It is a device like the compressor: a state, a panel that draws it, an engine that plays it and tools an agent calls, each its own file.
import { SaturatorPanel } from "@/components/audio-ui/devices/saturator/panel";
import { saturatorDevice } from "@/components/audio-ui/devices/saturator/device";
const [state, setState] = useState(() => saturatorDevice.defaults());
<SaturatorPanel onValueChange={setState} value={state} />;The state is a plain object keyed by parameter id, so a host stores it, saves it with its project and sends it where it likes. saturate(curve, input) is one curve and saturatorCurve(state) the whole transfer curve with the mix and the output.
import { createSaturatorEngine } from "@/components/audio-ui/devices/saturator/engine";
const engine = createSaturatorEngine(ctx, state);
source.connect(engine.input);
engine.output.connect(ctx.destination);
engine.update(nextState); // parameters ramp: no click
engine.dispose();The drive is a gain before the table of the curve, so turning it never rebuilds the table; only picking another curve does. The shaper oversamples 4x to keep the clip from aliasing.
The shaper oversamples 4x, which delays the signal by 4 ms in Chrome (192 samples). The dry path waits as long, so a partial mix does not comb; the device then has a latency of 4 ms to report to a host. The number was measured in Chrome and the specification does not give it, so measure it in another browser with measureLatency of device-measure.
import { deviceTools } from "@audio-ui/core/device";
import { saturatorDevice } from "@/components/audio-ui/devices/saturator/device";
const [set, reset] = deviceTools(saturatorDevice);set and reset change the state, with the ranges the device enforces: a value out of range is brought in and reported, a wrong one is refused by name, and a call never throws. A choice such as the curve or the mode takes only the values of its options.
Every control is a native input: the knobs are sliders named after their parameter, with the value and unit as the text value, the choices are toggle buttons, and everything takes the keyboard.