Arrangement
Transport and mixer
EQ and filters
Modulation
Utility
Instruments
A transport clock that schedules notes ahead on the audio clock, with a loop, seeking and tempo changes, and the musical time it counts in.
A timer in JavaScript drifts and stalls when the page is busy or in the background; the audio clock does not. useTransportClock looks a little ahead on the audio clock and calls you back with the exact audio time of each step, so you schedule the sound at that time and it plays on the beat whatever the page is doing.
import { useTransportClock } from "@/hooks/use-transport-clock";
const clock = useTransportClock({
bpm: 120,
stepsPerBeat: 4, // a step is a sixteenth note
totalSteps: 64, // four bars
region: loop, // { start, end } in steps, or null for all of it
onStep: (step, time) => {
for (const note of notesAt(step)) {
voices.triggerNote(note.pitch, envelope, note.velocity, time);
}
},
onReset: () => voices.releaseAll(),
});
<Button onClick={clock.isPlaying ? clock.stop : clock.start}>Play</Button>;The clock knows when, never what: onStep decides what plays. onReset runs on every start, stop, seek and loop change, to drop what was already scheduled ahead. For a playhead, read getPosition() in an animation frame and push it to a source: it gives fractional steps, so the playhead glides between them.
The piano roll plays its notes with this clock through usePianoRollPlayback, which is a good example to copy.
Options · 6
time.Returns · 5
step now (the start of the region when left out): seeking, or hearing a moved loop at once. No-op while stopped.The scheduling itself is plain functions in @audio-ui/core/time (advance, anchoredCursor, positionAt, SCHEDULE_AHEAD_SECONDS): the hook only connects them to React and the audio context, so an engine outside React uses them directly.
@audio-ui/core/time turns seconds into bars and beats and back. A tempo is { bpm, beatsPerBar, beatUnit }: bpm counts quarter notes, and beatsPerBar and beatUnit are the time signature, 6/8 being { beatsPerBar: 6, beatUnit: 8 }.
import {
barsBeatsAt,
describeBarsBeats,
secondsPerBar,
secondsPerBeat,
} from "@audio-ui/core/time";
const tempo = { beatsPerBar: 4, beatUnit: 4, bpm: 120 };
secondsPerBeat(tempo); // 0.5
secondsPerBar(tempo); // 2
barsBeatsAt(5, tempo); // { bar: 3, beat: 3, sixteenth: 1 }
describeBarsBeats(5, tempo); // "Bar 3, beat 3", for aria-valuetextsecondsPerQuarter, secondsPerBeat, secondsPerBar: How long a quarter note, a beat and a bar last at a tempo.barsBeatsAt, describeBarsBeats: Where a time falls, in bars, beats and sixteenths, as numbers or as words.barsOf, barsBeatsWith: The same along a song whose time signature changes.gridInterval, gridLabel, GRID_DIVISIONS: The grid a timeline snaps to at a zoom: bars, beats, triplets, an adaptive grid.ticksToSeconds, secondsToTicks: MIDI ticks and seconds, along a map of tempo changes.warpedTime, warpMarkers: Time stretched by warp markers, as a clip that follows the tempo.syncedSeconds, SYNCED_DIVISIONS: The length of a note value, such as 1/8 or 1/4T, for a delay or an LFO in sync.The Transport and the Timeline count in this time, so a bar means the same everywhere.