# Timeline Operations _(no submodules)_ ## Purpose The `timeline-operations.ts` module handles view-state mutations that do **not** affect the audio arrangement and therefore do not push undo commands. These helpers operate on `TimelineState` snapshots and return partial updates that the engine merges into its internal state. Keeping view logic separate from arrangement logic makes the engine easier to reason about and test. ## Features - `setSelection` normalises reversed ranges and clamps to document duration - `setLoopRegion` updates loop boundaries without clamping - `zoomIn` / `zoomOut` step through a discrete zoom table - `setZoomLevel` snaps arbitrary `samplesPerPixel` values to the table ## Usage ### Basic ```ts import { setSelection, setLoopRegion } from "@suaveplan/audio-timeline/operations"; const sel = setSelection(state, 100, 50); // normalises to 50..100 const loop = setLoopRegion(state, 0, 5000); ``` ### Advanced ```ts import { zoomIn, zoomOut, setZoomLevel } from "@suaveplan/audio-timeline/operations"; const z1 = zoomIn(zoomTable, 3); // { zoomIndex: 4, samplesPerPixel: 16 } const z2 = setZoomLevel(zoomTable, 150); // snaps to 128 ``` ## API Reference | Export | Signature | Description | |--------|-----------|-------------| | `setSelection` | `(state, start, end) => { selectionStart, selectionEnd }` | Normalise and clamp | | `setLoopRegion` | `(state, start, end) => { loopStart, loopEnd }` | Set loop bounds | | `zoomIn` | `(table, index) => { zoomIndex, samplesPerPixel }` | Step in | | `zoomOut` | `(table, index) => { zoomIndex, samplesPerPixel }` | Step out | | `setZoomLevel` | `(table, spp) => { zoomIndex, samplesPerPixel }` | Snap to table | ## Implementation Notes Zoom and selection are purely view state; they are not part of the undo history because users expect zoom gestures to be ephemeral. Keeping these helpers pure makes them trivial to unit-test without instantiating a full `TimelineEngine`. The clamping logic in `setSelection` guarantees that consumers always read `selectionStart <= selectionEnd`, even when the user drags the selection backwards. Loop boundaries are not clamped because the engine may allow loops that extend beyond the current document length for live expansion workflows.