# 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.

```bash
npm install @audio-ui/core
```

```ts
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 by: `use-timeline-cursor`

| Export | Kind | What it is |
| --- | --- | --- |
| `ClipLimits` | interface | What a clip edit keeps a clip within: the timeline's length, the shortest it may be, and its source's length. |
| `ClipValue` | interface | A clip: a stretch of a source (a recording, a pattern) placed on the timeline. |
| `clipEnd` | function | Where the clip ends on the timeline. |
| `copyRange` | function | What the stretch from `from` to `to` holds of `items`: the part of each clip inside it, moved so the stretch starts at 0. |
| `coverClips` | function | What a clip laid on a lane leaves of the clips already there: they are covered, not stacked. |
| `duplicateClip` | function | A copy of the clip placed right after it, or as late as the timeline allows when there is not room for it. |
| `FADE_SHAPES` | const | The fade shapes in the order a control steps through them. |
| `FadeShape` | type | How a fade rises or falls: evenly, quick then slow, slow then quick, or eased at both ends. |
| `fadeGain` | function | The 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. |
| `insertTime` | function | Opens a stretch `length` long at `at`: everything from there moves later, and a clip across `at` is cut in two around the gap. |
| `moveClip` | function | Moves the clip to begin at `start`, keeping its length and its place in the source. |
| `nextFadeShape` | function | The shape after `shape`, going round, for a control that steps through them. |
| `removeTime` | function | Takes 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. |
| `setClipFadeIn` | function | Sets the fade-in's length, which cannot run into the fade-out. |
| `setClipFadeOut` | function | Sets the fade-out's length, which cannot run into the fade-in. |
| `setClipFadeShape` | function | Gives the fade at `edge` the shape `shape`. |
| `splitClip` | function | Cuts the clip in two at `at`, a position on the timeline. |
| `trimClipEnd` | function | Trims the clip's end to `end`, as far as the timeline and the source allow. |
| `trimClipStart` | function | Trims 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. |
| `ClipChangeEventDetails` | type | What `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. |
| `ClipCommitEventDetails` | type | What `onValueCommitted` receives once a gesture on a clip ends: what moved, the reason and the event behind it. |
| `ClipController` | interface | What `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. |
| `ClipOptions` | interface | The 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. |
| `ClipSnapshot` | interface | What a view draws a clip from: its value, whether it is selected, and what a drag is moving and by how much. |
| `ClipTarget` | type | What a change to the clip moves. |
| `createClipController` | function | A clip's behavior with no view attached: its span, held or controlled, moved, trimmed or faded by the pointer or the keys, and selected. |
| `keyTargets` | function | The keys' targets for an edge or a clip, moved by the keyboard. |
| `laneUnder` | function | The lane the pointer is over, if any: the element that marks itself as one. |
| `normalizeClip` | function | The clip as the timeline holds it: finite, on the timeline, fades within its length. |
| `sameClip` | function | Whether two clips hold the same values. |
| `wholeClip` | function | The clip a timeline without one starts with: the whole of it, from the source's start. |
| `adjacentPoint` | function | The 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. |
| `createNavigatorController` | function | The navigator's behavior: a bar with the stretch the viewport shows inside it, which a drag or a key moves. |
| `movedRange` | function | Where `part` lands when moved by `delta` units from `from`, within the timeline. |
| `NavigatorController` | interface | What `createNavigatorController` returns: the stretch the window shows, and the presses, keys, typed values and double click that move it. |
| `NavigatorMetrics` | interface | How the viewport is scrolled and zoomed, as last laid out. |
| `NavigatorOptions` | interface | The 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. |
| `NavigatorPart` | type | The part of the navigator a gesture moves: one of its edges, or the window between them. |
| `rangeOfThumb` | function | The stretch a window drawn from `thumb[0]` to `thumb[1]` px shows: `thumbOf` read back. |
| `thumbOf` | function | The 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. |
| `visibleRange` | function | The stretch of the timeline the viewport shows, in units. |
| `createTimelineController` | function | The 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. |
| `TimelineController` | interface | What `createTimelineController` returns: the snapshot to draw from, and the presses, keys, typed values and zooms that move the playhead or the view. |
| `TimelineOptions` | interface | The 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. |
| `TimelinePositionChangeEventDetails` | type | What `onPositionChange` receives with each move of the playhead: the reason (`"press"`, `"drag"`, `"keyboard"` or `"input-change"`), the event, and `cancel()` to refuse the move. |
| `TimelinePositionCommitEventDetails` | type | What `onPositionCommitted` receives once a move of the playhead ends: the reason and the event behind it. |
| `TimelineSelectionHandlers` | interface | Listens to the stretch a Shift + drag on the ruler selects: set by the ruler. |
| `TimelineSnapshot` | interface | What a view draws the timeline from. |
| `TimelineZoomChangeEventDetails` | type | What `onZoomChange` receives with each zoom: the reason (`"wheel"`, `"drag"`, `"keyboard"`, `"input-change"` or `"double-click"`), the event, and `cancel()` to refuse it. |
| `valueAtLane` | function | The timeline's units under a point of `lane`, clamped to the timeline. |
| `createRangeController` | function | A 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. |
| `drawnRangeWidth` | function | How 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. |
| `RangeChangeEventDetails` | type | What `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. |
| `RangeCommitEventDetails` | type | What `onValueCommitted` receives once a gesture on a range ends: which edge moved, the reason and the event behind it. |
| `RangeController` | interface | What `createRangeController` returns: the snapshot to draw from, and the presses, draws, keys and typed values that move and resize the range. |
| `RangeOptions` | interface | The 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. |
| `RangeSnapshot` | interface | What a view draws a range from. |
| `rangeWidthOfDrawn` | function | How long a range is, in px, when it is drawn `drawn` px wide. |
| `anchoredScroll` | function | Where the viewport must scroll so `anchor.value` stays `anchor.offset` px from its start at the new zoom. |
| `clampZoom` | function | Keeps `zoom` within its bounds; anything that is not a positive number is refused. |
| `valueAtOffset` | function | The value under a point `offset` px from the viewport's start. |
| `ZoomAnchor` | interface | A point held still through a zoom: a value, and how far from the viewport's start it sits, in px. |
| `bypassesGrid` | function | Whether a gesture asks to ignore the grid: Cmd on a Mac, Ctrl elsewhere. |
| `Grid` | interface | Grid lines `step` apart, starting from `offset`. |
| `nearestSnapPoint` | function | The point of `grid` nearest `value` within its radius, or null when none is that close. |
| `SNAP_RADIUS_PX` | const | How far from a snap point, in px, a dragged thing is pulled onto it. |
| `snapMove` | function | Where something dragged from `origin` should land, given where the pointer would put it. |
| `snapRangeMove` | function | Where 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. |
| `snapSpanMove` | function | Where 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. |
| `snapToGrid` | function | The snap point within reach of `value`, or else the grid line nearest it. |
| `withoutSnapPoints` | function | The grid without the given points, so something dragged is not pulled onto its own edges. |
| `withSnapRadius` | function | The grid with its points pulling from `radius` units away, so a gesture can set it from its own zoom. |
| `createTrackController` | function | A track's height, with no view attached: what a drag, a key, an input or a double click asks of it. |
| `TrackController` | interface | What `createTrackController` returns: the snapshot to draw from, and the presses, keys, typed values and reset that ask for a height. |
| `TrackHeightChangeEventDetails` | type | What `onHeightChange` receives with each resize of a track: the reason (`"drag"`, `"keyboard"`, `"input-change"` or `"double-click"`), the event, and `cancel()` to refuse it. |
| `TrackHeightCommitEventDetails` | type | What `onHeightCommitted` receives once a resize ends: the reason and the event behind it. |
| `TrackOptions` | interface | The options of `createTrackController`: the height its parent holds, its bounds and steps, how to measure it, and the callbacks. |
| `TrackSnapshot` | interface | What a view draws a track's resize handle from: whether a resize is under way. |
| `createWheelZoom` | function | A wheel zoom around the pointer. |
| `WHEEL_ZOOM_SENSITIVITY` | const | How far one pixel of wheel delta zooms, as an exponent. |
| `WheelZoomController` | interface | What `createWheelZoom` returns: the wheel handler, `setOptions`, and `dispose`. |
| `WheelZoomOptions` | interface | The 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](/docs/core/controllers) 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:

```ts
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.

**In React**

The [Arrangement](/docs/components/base/arrangement) draws these in React.

**In Vue, Svelte and Web**

[`<audio-ui-timeline>`](/docs/elements/timeline) draws these in your framework.

