pnpm add @audio-ui/engine
import { adjacentDivision, barLabelInterval, DOTTED_FACTOR, divisionAt, divisionSeconds, /* … */ } from "@audio-ui/engine/time";| Used by | arrangement, arrangement-engine, dawproject, demo-song, demo-sound, synced-rate, use-arrangement, use-piano-roll-playback, use-timeline-grid, use-transport-clock |
|---|
| Export | Kind | What it is |
|---|---|---|
adjacentDivision | function | The fixed division next to division: a finer one (direction 1, twice as dense) or a coarser one (-1), held at the ends of the list. |
barLabelInterval | function | Seconds between labelled bars on a ruler at zoom px per second: always a whole number of bars, doubled until labels are at least minSpacing px apart, so they never collide or fall mid-bar. |
DOTTED_FACTOR | const | Three halves: a dotted note is its straight one and half again. |
divisionAt | function | The fixed division whose step is nearest seconds at tempo: what a grid that follows the zoom becomes when it is fixed where it is. |
divisionSeconds | function | How long one fixed division lasts at tempo, in seconds. |
FixedGridDivision | type | The id of a fixed grid division: "4-bars" to "1/32". |
GRID_DIVISIONS | const | The fixed grid divisions, coarsest first: a number of bars, or a note value. |
GridDivision | type | adaptive follows the zoom; a fixed division stays put however far you zoom. |
GridIntervalOptions | interface | How close adaptive grid lines may be, and whether the grid is in triplets. |
gridInterval | function | Seconds between grid lines at zoom px per second: the lines a timeline draws and the step it snaps to, so what you see is where you land. |
gridLabel | function | Names an interval the way a grid readout does: "1 bar", "4 bars", "1/16", "1/8T". |
METRONOME_RHYTHMS | const | How often a metronome clicks: with the beat, or a note value, straight or triplet. |
MetronomeRhythm | type | The id of a metronome rhythm: "auto" (the beat), a bar, or a note value, straight or triplet. |
metronomeRhythmSeconds | function | Seconds between clicks for a rhythm, or null for auto, which has no interval of its own: it is the beat, whatever the signature makes one. |
SYNCED_DIVISIONS | const | The note values a time parameter that follows the tempo can take, shortest first: 1/16 is a sixteenth note, 1/8t a triplet eighth (two thirds of it) and 1/8d a dotted eighth (one and a half times it). |
SyncedDivision | type | A note value as syncedSeconds reads it: "1/8", its triplet "1/8t" or its dotted "1/8d". |
syncedSeconds | function | How long a synced note value lasts at tempo, in seconds: 1/8 an eighth, 1/8t its triplet, 1/8d its dotted one. |
TRIPLET_FACTOR | const | Three in the space of two: a triplet division is 2/3 of its straight one. |
Bar | interface | A bar as a ruler draws it. |
barsBeatsWith | function | Where seconds falls in bars, beats and sixteenths through the signature changes, each counted from one. |
barsOf | function | The bars from 0 to until seconds, through the signature changes: whole bars of each signature, and a partial bar where a change cuts one short. |
isSignature | function | Whether a signature is one a bar can have: 1 to 99 beats of a whole, half, quarter, eighth or sixteenth note. |
MetronomeClick | interface | A click of a metronome. |
metronomeClicks | function | The clicks of a metronome from 0 to until seconds, through the signature changes. |
SignatureChange | interface | A new signature from at on, in seconds: beatsPerBar beats of a beatUnit note. |
advance | function | Advances cursor, emitting every step boundary now within SCHEDULE_AHEAD_SECONDS of now. |
anchoredCursor | function | A cursor that emits step at now, the seed for start() and for every re-anchor (seek, region commit). |
anchorForTempoChange | function | The anchor to carry on with when the tempo changes while playing. |
MAX_CATCH_UP_SECONDS | const | How late a boundary may be and still be played, half a second. |
positionAt | function | The continuous playhead position, in fractional full-grid steps, at now. |
SCHEDULE_AHEAD_SECONDS | const | How far ahead of now boundaries are committed, in seconds: the article's 100 ms. |
SCHEDULER_POLL_MS | const | How often to poll the scheduler, in milliseconds: the article's 25 ms. |
SchedulerAnchor | interface | The audio-time / step pair a running clock is anchored to, for deriving the display position. |
SchedulerConfig | interface | What the scheduler steps through: the window of steps it loops over and how long a step lasts, read on every call. |
SchedulerCursor | interface | Where the scheduler is: the next step it will emit and when. |
StepBoundary | interface | A step to play and the audio time it starts at. |
BarsBeats | interface | A position in bars, beats and sixteenths, each counted from one. |
barsBeatsAt | function | Where seconds falls in bars, beats and sixteenths, each counted from one. |
bpmToMicrosecondsPerQuarterNote | function | A tempo in BPM as microseconds per quarter note, the unit a MIDI file stores: 120 BPM is 500000. |
DEFAULT_MICROSECONDS_PER_QUARTER_NOTE | const | What a file with no Set Tempo event plays at: 120 BPM, 500,000 microseconds a quarter note. |
DEFAULT_TEMPO | const | 120 BPM in 4/4. |
describeBarsBeats | function | "Bar 3, beat 2", the way a position is read out to a screen reader. |
microsecondsPerQuarterNoteToBpm | function | The BPM of a Set Tempo's microseconds per quarter note: 500000 is 120. |
secondsPerBar | function | Seconds per bar: a beat times the beats in a bar. |
secondsPerBeat | function | Seconds per beat, a beat being one beatUnit note: in 6/8, half a quarter. |
secondsPerQuarter | function | Seconds per quarter note: BPM counts quarters whatever the signature says. |
Tempo | interface | Tempo and time signature: how long a quarter note, a beat and a bar last in seconds, and where a position in seconds falls in bars and beats. |
addWarpMarker | function | Adds a marker at a time on the clip, pinning what plays there; nothing changes in how it plays until it moves. |
moveWarpMarker | function | Moves the marker at index to a new time on the clip, stretching the recording on either side of it: it stops short of its neighbours. |
removeWarpMarker | function | Takes the marker at index away, as long as two are left to make a map. |
scaleWarp | function | Plays the whole map factor times as long: 2 is half the speed, an octave down; 0.5 twice the speed. |
unwarpedTime | function | What time in the source plays at a time on the clip. |
WarpMarker | interface | A time in the source, pinned to a time on the clip. |
warpedTime | function | Where on the clip a time in the source plays. |
warpMarkers | function | The markers that make a map: finite ones, in order, each later than the one before in both times. |
warpRate | function | How fast the source plays at a time on the clip: 1 as recorded, 2 twice as fast. |
Usage
A Tempo is a BPM and a signature. Every function that turns seconds into music, or music into seconds, reads one:
import { barsBeatsAt, secondsPerBar, secondsPerBeat, syncedSeconds } from "@audio-ui/engine/time";
const tempo = { bpm: 120, beatsPerBar: 6, beatUnit: 8 };
secondsPerBeat(tempo); // 0.25: an eighth note at 120 BPM
secondsPerBar(tempo); // 1.5
barsBeatsAt(2, tempo); // { bar: 2, beat: 3, sixteenth: 1 }
syncedSeconds("1/8d", tempo); // a dotted eighth, for a delay that follows the tempoBPM counts quarter notes whatever the signature says, so a 6/8 bar at 120 BPM lasts three quarters. describeBarsBeats gives the same position as text for a screen reader, "Bar 2, beat 3".
The grid
gridInterval(division, zoom, tempo) is the step between two grid lines at a zoom in pixels per second, and the step an edit snaps to, so what is drawn is where a drag lands. "adaptive" follows the zoom and keeps lines at least 24 px apart; a fixed division (an id of GRID_DIVISIONS, from "4-bars" to "1/32") stays put. gridLabel names the interval as a readout does, "1 bar" or "1/8T", and barLabelInterval spaces the bar numbers of a ruler.
Signature changes and the metronome
barsOf(tempo, changes, until) lists the bars up to a time through SignatureChanges, with a partial bar where a change cuts one short, and barsBeatsWith(seconds, tempo, changes) reads a position through them. metronomeClicks(tempo, changes, until, rhythm) lists the clicks over the same changes, each with accent on the first of its bar; METRONOME_RHYTHMS holds the rhythms it can click at, "auto" (the beat) by default.
The scheduler
A transport clock that plays sound ahead of time calls advance on a timer: it returns every step boundary within the next 100 ms, each with the audio time to give to a node's start(), and the cursor to call it with next time.
import { advance, anchoredCursor, SCHEDULER_POLL_MS } from "@audio-ui/engine/time";
const config = { regionStart: 0, regionEnd: 16, secondsPerStep: 60 / 120 / 4 };
let cursor = anchoredCursor(ctx.currentTime, 0);
setInterval(() => {
const next = advance(cursor, ctx.currentTime, config);
for (const { step, time } of next.boundaries) {
playStep(step, time);
}
cursor = next.cursor;
}, SCHEDULER_POLL_MS);config is read fresh on every call, so a tempo change takes effect at the next boundary. positionAt(now, anchor, config) is the playhead between boundaries, in fractional steps, and anchorForTempoChange keeps it continuous when the tempo moves while playing. A boundary later than half a second is skipped rather than played late.
The registry's useTransportClock runs this loop for a React page.
Warp markers
A WarpMarker pins a time in a recording to a time on a clip. warpMarkers keeps the markers that make a valid map, warpedTime and unwarpedTime read it both ways, warpRate is how fast the recording plays at a point, and addWarpMarker, moveWarpMarker, removeWarpMarker and scaleWarp edit it.
MIDI tempos
A MIDI file stores a tempo as microseconds per quarter note: bpmToMicrosecondsPerQuarterNote(120) is 500000, and microsecondsPerQuarterNoteToBpm goes back.