Lab

timeline

An arrangement's timeline from @audio-ui/core: the playhead and the zoom, clips placed, moved, trimmed, split and faded, ranges such as a loop, the navigator, track heights, snapping to a grid, and a wheel zoom around the pointer.

pnpm add @audio-ui/core
import { clipEnd, copyRange, coverClips, duplicateClip, FADE_SHAPES, /* … */ } from "@audio-ui/core/timeline";
Adapters@audio-ui/react/timeline, @audio-ui/elements/piano-roll, @audio-ui/elements/timeline
Used byuse-timeline-cursor
ExportKindWhat it is
ClipLimitsinterfaceWhat a clip edit keeps a clip within: the timeline's length, the shortest it may be, and its source's length.
ClipValueinterfaceA clip: a stretch of a source (a recording, a pattern) placed on the timeline.
clipEndfunctionWhere the clip ends on the timeline.
copyRangefunctionWhat the stretch from from to to holds of items: the part of each clip inside it, moved so the stretch starts at 0.
coverClipsfunctionWhat a clip laid on a lane leaves of the clips already there: they are covered, not stacked.
duplicateClipfunctionA copy of the clip placed right after it, or as late as the timeline allows when there is not room for it.
FADE_SHAPESconstThe fade shapes in the order a control steps through them.
FadeShapetypeHow a fade rises or falls: evenly, quick then slow, slow then quick, or eased at both ends.
fadeGainfunctionThe gain a fade of shape has progress of the way through it, from 0 (silent) to 1 (full): the curve of a fade-in; a fade-out plays it backwards.
insertTimefunctionOpens a stretch length long at at: everything from there moves later, and a clip across at is cut in two around the gap.
moveClipfunctionMoves the clip to begin at start, keeping its length and its place in the source.
nextFadeShapefunctionThe shape after shape, going round, for a control that steps through them.
removeTimefunctionTakes the stretch from from to to out of the timeline: what is in it goes, and everything after it moves back to close the gap.
setClipFadeInfunctionSets the fade-in's length, which cannot run into the fade-out.
setClipFadeOutfunctionSets the fade-out's length, which cannot run into the fade-in.
setClipFadeShapefunctionGives the fade at edge the shape shape.
splitClipfunctionCuts the clip in two at at, a position on the timeline.
trimClipEndfunctionTrims the clip's end to end, as far as the timeline and the source allow.
trimClipStartfunctionTrims the clip's start to start: what is cut off stays in the source, so the clip's content does not move on the timeline.
ClipChangeEventDetailstypeWhat onValueChange receives with each change to a clip: what moved (target), for a move how far and over which lane, whether Alt asks for a copy, the reason ("drag", "keyboard", "input-change" or "double-click"), the event, and cancel() to refuse the change.
ClipCommitEventDetailstypeWhat onValueCommitted receives once a gesture on a clip ends: what moved, the reason and the event behind it.
ClipControllerinterfaceWhat createClipController returns: the snapshot to draw from, and the presses, keys, typed values and fade shapes that move, trim and fade the clip, and select it.
ClipOptionsinterfaceThe options of createClipController: the clip and its selection, each held or controlled, the timeline's length, grid, steps and zoom, the shortest length, the source's length, and the callbacks.
ClipSnapshotinterfaceWhat a view draws a clip from: its value, whether it is selected, and what a drag is moving and by how much.
ClipTargettypeWhat a change to the clip moves.
createClipControllerfunctionA clip's behavior with no view attached: its span, held or controlled, moved, trimmed or faded by the pointer or the keys, and selected.
keyTargetsfunctionThe keys' targets for an edge or a clip, moved by the keyboard.
laneUnderfunctionThe lane the pointer is over, if any: the element that marks itself as one.
normalizeClipfunctionThe clip as the timeline holds it: finite, on the timeline, fades within its length.
sameClipfunctionWhether two clips hold the same values.
wholeClipfunctionThe clip a timeline without one starts with: the whole of it, from the source's start.
adjacentPointfunctionThe nearest of points strictly after from (direction 1) or strictly before it (direction -1), or null when there is none that way: what jumping to the next or the previous locator, or cue, needs.
createNavigatorControllerfunctionThe navigator's behavior: a bar with the stretch the viewport shows inside it, which a drag or a key moves.
movedRangefunctionWhere part lands when moved by delta units from from, within the timeline.
NavigatorControllerinterfaceWhat createNavigatorController returns: the stretch the window shows, and the presses, keys, typed values and double click that move it.
NavigatorMetricsinterfaceHow the viewport is scrolled and zoomed, as last laid out.
NavigatorOptionsinterfaceThe options of createNavigatorController: the timeline's length, how the viewport is laid out, the functions that scroll and zoom it, and the narrowest the window may be.
NavigatorParttypeThe part of the navigator a gesture moves: one of its edges, or the window between them.
rangeOfThumbfunctionThe stretch a window drawn from thumb[0] to thumb[1] px shows: thumbOf read back.
thumbOffunctionThe window as it is drawn on a bar bar px wide, its two edges in px, for the stretch range: never narrower than least, and inside the bar, at the end of the timeline too.
visibleRangefunctionThe stretch of the timeline the viewport shows, in units.
createTimelineControllerfunctionThe timeline's playback position and zoom, with no view attached: held, controlled or live, moved by a press, a drag, a key or an input, and zoomed.
TimelineControllerinterfaceWhat createTimelineController returns: the snapshot to draw from, and the presses, keys, typed values and zooms that move the playhead or the view.
TimelineOptionsinterfaceThe options of createTimelineController: the position and the zoom, each held, controlled or (the position) live, the timeline's length, grid and steps, the zoom's bounds, and the callbacks.
TimelinePositionChangeEventDetailstypeWhat onPositionChange receives with each move of the playhead: the reason ("press", "drag", "keyboard" or "input-change"), the event, and cancel() to refuse the move.
TimelinePositionCommitEventDetailstypeWhat onPositionCommitted receives once a move of the playhead ends: the reason and the event behind it.
TimelineSelectionHandlersinterfaceListens to the stretch a Shift + drag on the ruler selects: set by the ruler.
TimelineSnapshotinterfaceWhat a view draws the timeline from.
TimelineZoomChangeEventDetailstypeWhat onZoomChange receives with each zoom: the reason ("wheel", "drag", "keyboard", "input-change" or "double-click"), the event, and cancel() to refuse it.
valueAtLanefunctionThe timeline's units under a point of lane, clamped to the timeline.
createRangeControllerfunctionA range's behavior with no view attached: its span on the timeline, held or controlled, moved or resized by its body or its edges, drawn across empty lane, and moved by the keys and inputs.
drawnRangeWidthfunctionHow wide a range width px long is drawn, so that it keeps least px of room for its handles: from 2 * least px up, as long as it is, and under that it still grows with its length, at half the rate.
RangeChangeEventDetailstypeWhat onValueChange receives with each change to a range: which edge moved, or "both", the reason ("drag", "draw", "keyboard" or "input-change"), the event, and cancel() to refuse the change.
RangeCommitEventDetailstypeWhat onValueCommitted receives once a gesture on a range ends: which edge moved, the reason and the event behind it.
RangeControllerinterfaceWhat createRangeController returns: the snapshot to draw from, and the presses, draws, keys and typed values that move and resize the range.
RangeOptionsinterfaceThe options of createRangeController: the range, held or controlled, the timeline's length, grid, steps and zoom, the shortest length, whether it can be moved or drawn, and the callbacks.
RangeSnapshotinterfaceWhat a view draws a range from.
rangeWidthOfDrawnfunctionHow long a range is, in px, when it is drawn drawn px wide.
anchoredScrollfunctionWhere the viewport must scroll so anchor.value stays anchor.offset px from its start at the new zoom.
clampZoomfunctionKeeps zoom within its bounds; anything that is not a positive number is refused.
valueAtOffsetfunctionThe value under a point offset px from the viewport's start.
ZoomAnchorinterfaceA point held still through a zoom: a value, and how far from the viewport's start it sits, in px.
bypassesGridfunctionWhether a gesture asks to ignore the grid: Cmd on a Mac, Ctrl elsewhere.
GridinterfaceGrid lines step apart, starting from offset.
nearestSnapPointfunctionThe point of grid nearest value within its radius, or null when none is that close.
SNAP_RADIUS_PXconstHow far from a snap point, in px, a dragged thing is pulled onto it.
snapMovefunctionWhere something dragged from origin should land, given where the pointer would put it.
snapRangeMovefunctionWhere one edge of a range dragged from origin, or the whole range with "both", should land when the pointer has moved it by delta: the dragged edge snaps the way anything dragged does, and the range keeps within the timeline and its minimum length.
snapSpanMovefunctionWhere a span length long, dragged from origin, should start when the pointer would start it at target: either of its edges lands on a snap point within reach (the nearer one wins), or else its start snaps the way anything dragged does.
snapToGridfunctionThe snap point within reach of value, or else the grid line nearest it.
withoutSnapPointsfunctionThe grid without the given points, so something dragged is not pulled onto its own edges.
withSnapRadiusfunctionThe grid with its points pulling from radius units away, so a gesture can set it from its own zoom.
createTrackControllerfunctionA track's height, with no view attached: what a drag, a key, an input or a double click asks of it.
TrackControllerinterfaceWhat createTrackController returns: the snapshot to draw from, and the presses, keys, typed values and reset that ask for a height.
TrackHeightChangeEventDetailstypeWhat onHeightChange receives with each resize of a track: the reason ("drag", "keyboard", "input-change" or "double-click"), the event, and cancel() to refuse it.
TrackHeightCommitEventDetailstypeWhat onHeightCommitted receives once a resize ends: the reason and the event behind it.
TrackOptionsinterfaceThe options of createTrackController: the height its parent holds, its bounds and steps, how to measure it, and the callbacks.
TrackSnapshotinterfaceWhat a view draws a track's resize handle from: whether a resize is under way.
createWheelZoomfunctionA wheel zoom around the pointer.
WHEEL_ZOOM_SENSITIVITYconstHow far one pixel of wheel delta zooms, as an exponent.
WheelZoomControllerinterfaceWhat createWheelZoom returns: the wheel handler, setOptions, and dispose.
WheelZoomOptionsinterfaceThe options of createWheelZoom: the timeline's length, the zoom as drawn, where its start sits, and the function that applies a zoom.

Usage

A timeline is made of several controllers, each with no view attached and each following the contract every controller shares, and of plain functions that edit clips. Positions are in the timeline's own units, seconds or beats, and the zoom is in pixels per unit.

The playhead and the zoom

createTimelineController holds the playback position and the zoom: held, controlled with position and zoom, or live with source, which it follows without a render. A press or a drag on the ruler or a lane moves the position, onto a snap point of the grid within a few pixels, the keys step it by step and largeStep, and a Shift + drag on the ruler selects a stretch, reported to the handlers given to it. A zoom of null fits the timeline to its width.

createWheelZoom zooms with the wheel around the pointer, holding the value under it still, once per frame. A trackpad's pinch zooms by the square of the fingers' spread, so a few pinches go from the whole timeline to its finest zoom, and no frame zooms more than twice in or half out, so a mouse's notch never jumps further; anchoredScroll is where to scroll so a value stays put through a zoom, and clampZoom keeps a zoom within its bounds.

Clips

A clip is a stretch of a source, a recording or a pattern, placed on the timeline: where it starts, how long it is, where in the source it begins (offset), and its fades. Every edit is a plain function that returns a new clip:

import { moveClip, setClipFadeIn, splitClip, trimClipStart } from "@audio-ui/core/timeline";

const clip = { start: 4, length: 8, offset: 0, fadeIn: 0, fadeOut: 0 };
const limits = { duration: 64, sourceLength: 8 };

trimClipStart(clip, 5, limits); // { start: 5, length: 7, offset: 1, … }: the content stays put
splitClip(clip, 6); // two clips, the second reading the source from 2
moveClip(clip, 10, limits); // { start: 10, … }
setClipFadeIn(clip, 2); // a fade-in that cannot run into the fade-out

duplicateClip, trimClipEnd, setClipFadeOut and setClipFadeShape complete them; a fade's shape is one of FADE_SHAPES (linear, fast, slow, smooth), and fadeGain is its gain partway through. On a lane, coverClips lays a clip over others the way an arrangement does, covering rather than stacking them, and insertTime, removeTime and copyRange edit a stretch of time across every clip in it.

createClipController is a clip's gestures: moved by its body, trimmed by its edges, faded by its fade handles, from the pointer or the keys, and selected.

Ranges, the navigator and tracks

  • createRangeController is a span on the timeline, such as the loop or a selection: moved or resized by its body or its edges, drawn across empty lane, and moved from the keys and inputs.
  • createNavigatorController is the overview bar with the visible stretch inside it: moving the window scrolls, moving an edge zooms and holds the other edge still. visibleRange, thumbOf and rangeOfThumb convert between them.
  • createTrackController reports what a drag, a key, an input or a double click asks of a track's height; the parent holds the height and sets it.

Snapping

A Grid is lines step apart from an offset, with snap points such as clip edges that pull from SNAP_RADIUS_PX away. snapToGrid(value, grid) gives the snap point within reach or the nearest line, snapMove, snapSpanMove and snapRangeMove place something dragged, and bypassesGrid(event) says when a gesture asks to ignore the grid: Cmd on a Mac, Ctrl elsewhere.

The Arrangement draws these in React.

Built by Ouest Labs, the source code is available on GitHub.
RSS