first commit
This commit is contained in:
35
docs/media/audio/audio-timeline/src/engine/clip/clip.md
Normal file
35
docs/media/audio/audio-timeline/src/engine/clip/clip.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Engine Clip Helpers
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
The `clip.ts` module contains the internal bridge between `TimelineEngine` public methods and the pure `clip-operations.ts` helpers. It wraps each operation with OTel span recording, histogram timing, and event emission so the engine class stays focused on orchestration.
|
||||
|
||||
## Features
|
||||
|
||||
- `engineAddClip` appends a clip and emits `clipChange`
|
||||
- `engineRemoveClip` filters a clip and emits `clipChange`
|
||||
- `engineMoveClip` applies constrained drags, skips no-ops, and records duration
|
||||
- `engineTrimClip` trims a boundary and records a `timeline.clip_operation` span
|
||||
- `engineSplitClip` splits a clip and records a `timeline.clip_operation` span
|
||||
|
||||
## Usage
|
||||
|
||||
These helpers are used internally by `TimelineEngine`. Consumers interact with the engine's public methods instead.
|
||||
|
||||
## API Reference
|
||||
|
||||
| Export | Signature | Description |
|
||||
|--------|-----------|-------------|
|
||||
| `engineAddClip` | `(deps, trackId, clip) => void` | Add clip |
|
||||
| `engineRemoveClip` | `(deps, trackId, clipId) => void` | Remove clip |
|
||||
| `engineMoveClip` | `(deps, trackId, clipId, delta) => number` | Move clip |
|
||||
| `engineTrimClip` | `(deps, trackId, clipId, boundary, delta) => void` | Trim clip |
|
||||
| `engineSplitClip` | `(deps, trackId, clipId, atSample) => void` | Split clip |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The `ClipDeps` interface decouples the helpers from the engine class, making them testable in isolation. Histograms record `audio_timeline_clip_operation_duration_ms` for every trim and split. No-op moves (where the constrained delta is zero) skip both the undo push and the event emission, keeping the history clean. OTel spans include the operation name, track id, and clip id so that performance traces can pinpoint slow interactions.
|
||||
52
docs/media/audio/audio-timeline/src/engine/engine.md
Normal file
52
docs/media/audio/audio-timeline/src/engine/engine.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# TimelineEngine
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`TimelineEngine` is the central state container for the `@suaveplan/audio-timeline` package. It owns the multi-track arrangement, playback transport, mixer state, zoom/selection view state, and a full undo/redo history. All rendering is delegated to a pluggable `TimelineAdapter`, keeping the engine headless and suitable for both web and native targets.
|
||||
|
||||
## Features
|
||||
|
||||
- Track CRUD: `setTracks`, `addTrack`, `removeTrack`, `updateTrack`, `selectTrack`
|
||||
- Clip CRUD: `addClip`, `removeClip`, `moveClip`, `trimClip`, `splitClip`
|
||||
- Transport: `play`, `pause`, `stop`, `seek` guarded by an internal FSM
|
||||
- Mixer: `setMasterVolume`, `setTrackVolume`, `setTrackMute`, `setTrackSolo`, `setTrackPan`
|
||||
- View: `zoomIn`, `zoomOut`, `setZoomLevel`, `setSelection`, `setLoopRegion`, `setLoopEnabled`
|
||||
- Undo/redo with transaction support (`beginTransaction`, `commitTransaction`, `abortTransaction`)
|
||||
- Serialization via `toJSON` and static `fromJSON`
|
||||
- Typed events via `on` / `off`
|
||||
- OTel spans and counters for clip operations and transport
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic
|
||||
|
||||
```ts
|
||||
import { TimelineEngine, NullTimelineAdapter } from "@suaveplan/audio-timeline";
|
||||
|
||||
const engine = new TimelineEngine({ sampleRate: 44100 });
|
||||
engine.setAdapter(new NullTimelineAdapter());
|
||||
engine.addTrack({ name: "Drums" });
|
||||
engine.play();
|
||||
```
|
||||
|
||||
### Advanced
|
||||
|
||||
```ts
|
||||
engine.beginTransaction();
|
||||
engine.addClip(trackId, clip);
|
||||
engine.moveClip(trackId, clip.id, 500);
|
||||
engine.commitTransaction();
|
||||
engine.undo();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
`TimelineEngine` exposes the full public surface documented in the spec. Key getters: `getState()` returns an immutable snapshot; `getCurrentTime()` reads from the adapter; `getTempo()` returns current BPM.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The engine is deliberately headless. It does not touch the DOM or Web Audio directly; every output goes through the adapter. State mutations push `Command<Track[]>` instances onto an `UndoStack`. On undo/redo, if the track count is unchanged the engine calls `adapter.updateTrack` per changed track instead of `adapter.setTracks` to avoid playback interruption. Events are emitted through an `EventBus` so UI layers can subscribe to granular changes without polling.
|
||||
35
docs/media/audio/audio-timeline/src/engine/mixer/mixer.md
Normal file
35
docs/media/audio/audio-timeline/src/engine/mixer/mixer.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Engine Mixer Helpers
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
The `mixer.ts` module bridges `TimelineEngine` mixer public methods to the pure `mixer.ts` helpers. It manages undo pushes and adapter forwarding for every mixer mutation, keeping the engine class from growing too large.
|
||||
|
||||
## Features
|
||||
|
||||
- `engineSetMasterVolume` updates master volume and pushes undo
|
||||
- `engineSetTrackVolume` delegates to pure helper and forwards to adapter
|
||||
- `engineSetTrackMute` toggles mute and emits via adapter
|
||||
- `engineSetTrackSolo` toggles solo and emits via adapter
|
||||
- `engineSetTrackPan` clamps pan and emits via adapter
|
||||
|
||||
## Usage
|
||||
|
||||
These helpers are used internally by `TimelineEngine`. Consumers call `engine.setTrackVolume(id, 0.8)` etc.
|
||||
|
||||
## API Reference
|
||||
|
||||
| Export | Signature | Description |
|
||||
|--------|-----------|-------------|
|
||||
| `engineSetMasterVolume` | `(deps, volume) => void` | Master volume |
|
||||
| `engineSetTrackVolume` | `(deps, trackId, volume) => void` | Track volume |
|
||||
| `engineSetTrackMute` | `(deps, trackId, muted) => void` | Track mute |
|
||||
| `engineSetTrackSolo` | `(deps, trackId, soloed) => void` | Track solo |
|
||||
| `engineSetTrackPan` | `(deps, trackId, pan) => void` | Track pan |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The `MixerDeps` interface isolates the helpers from the engine class. Every track mutation goes through `updateTrackInPlace`, which pushes an undo command automatically. The master volume operation is special-cased because it does not live on a track; it pushes its own undo command directly. All other mixer state lives on individual tracks and is therefore covered by the standard track-update undo path. This symmetry makes it easy to reason about what changes are reversible.
|
||||
28
docs/media/audio/audio-timeline/src/engine/state/state.md
Normal file
28
docs/media/audio/audio-timeline/src/engine/state/state.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Engine State Builder
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
The `state.ts` module builds the immutable `TimelineState` snapshot returned by `TimelineEngine.getState()`. It derives computed fields such as `duration`, `samplesPerPixel`, `canZoomIn`, and `canZoomOut` from the engine's internal fields so consumers always see a consistent, derived view.
|
||||
|
||||
## Features
|
||||
|
||||
- `buildTimelineState` assembles a complete `TimelineState` object from internal deps
|
||||
- `computeDuration` scans all clips to find the timeline end
|
||||
|
||||
## Usage
|
||||
|
||||
Used internally by `TimelineEngine.getState()`.
|
||||
|
||||
## API Reference
|
||||
|
||||
| Export | Signature | Description |
|
||||
|--------|-----------|-------------|
|
||||
| `buildTimelineState` | `(deps: StateDeps) => TimelineState` | Build snapshot |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Duration is calculated by iterating every clip in every track and taking the maximum `startSample + durationSamples`. This is `O(clips)` and is only run when `getState()` is called, not on every mutation. The `StateDeps` interface decouples the builder from the engine class, allowing tests to construct snapshots without instantiating a full engine. Because duration is derived rather than stored, it always reflects the current arrangement even after complex undo/redo sequences. Consumers can rely on `duration` being accurate without subscribing to additional events. The snapshot also includes `canZoomIn` and `canZoomOut` booleans so UI buttons can disable themselves at table bounds. All derived fields are recalculated on every `getState()` call, which keeps the internal engine state minimal and avoids stale derived data.
|
||||
@@ -0,0 +1,34 @@
|
||||
# Playback FSM
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
The `transport-fsm.ts` module creates the finite state machine that guards timeline transport transitions. It prevents illegal transitions such as pausing while already stopped, ensuring that the engine and adapter never receive contradictory commands.
|
||||
|
||||
## Features
|
||||
|
||||
- Three states: `stopped`, `playing`, `paused`
|
||||
- Valid transitions: `stopped --play--> playing`, `playing --pause--> paused`, `playing --stop--> stopped`, `paused --play--> playing`, `paused --stop--> stopped`, `stopped --seek--> stopped`, `playing --seek--> playing`, `paused --seek--> paused`
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { createPlaybackFsm } from "./transport-fsm.js";
|
||||
|
||||
const fsm = createPlaybackFsm();
|
||||
fsm.send("play");
|
||||
console.log(fsm.current); // "playing"
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
| Export | Signature | Description |
|
||||
|--------|-----------|-------------|
|
||||
| `createPlaybackFsm` | `() => FSMInterpreter` | Create playback FSM |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The FSM is created with `@suaveplan/fsm`. It uses an empty context object because the timeline engine stores playback state separately. Invalid transitions throw `FSMError`, which the engine catches and ignores. This design keeps transport state management declarative and easy to extend if new states (such as `recording`) are added later. The FSM interpreter is created fresh for each engine instance, so multiple engines can coexist with independent playback states. This is important for applications that may host multiple timeline views or sessions simultaneously. Each engine instance manages its own FSM lifecycle independently. The FSM definition is hard-coded in this module because the playback state machine is universal and does not need customisation for different adapters or rendering backends.
|
||||
@@ -0,0 +1,33 @@
|
||||
# Engine Transport Helpers
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
The `transport.ts` module bridges `TimelineEngine` transport public methods (`play`, `pause`, `stop`, `seek`) to the `TimelineAdapter`. Each method is guarded by the playback FSM and records OTel spans, ensuring that illegal transitions are silently ignored and every transport change is observable.
|
||||
|
||||
## Features
|
||||
|
||||
- `enginePlay` sends `play` to FSM, calls `adapter.play`, and emits event
|
||||
- `enginePause` sends `pause` to FSM, calls `adapter.pause`, and emits event
|
||||
- `engineStop` sends `stop` to FSM, calls `adapter.stop`, resets time, and emits event
|
||||
- `engineSeek` calls `adapter.seek` directly without FSM involvement
|
||||
|
||||
## Usage
|
||||
|
||||
Used internally by `TimelineEngine`. Consumers call `engine.play()` etc.
|
||||
|
||||
## API Reference
|
||||
|
||||
| Export | Signature | Description |
|
||||
|--------|-----------|-------------|
|
||||
| `enginePlay` | `(deps) => void` | Start playback |
|
||||
| `enginePause` | `(deps) => void` | Pause playback |
|
||||
| `engineStop` | `(deps) => void` | Stop and reset |
|
||||
| `engineSeek` | `(deps, time) => void` | Seek to time |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Every transport mutation creates a `timeline.transport` OTel span. The FSM prevents illegal transitions (e.g. `pause` while stopped) by throwing `FSMError`, which the engine silently catches and ignores. Seek does not consult the FSM because it is valid in any state. This allows users to scrub the playhead while playback is running, paused, or stopped without triggering transition errors. The adapter receives the seek command immediately so audio output can stay in sync with the UI.
|
||||
31
docs/media/audio/audio-timeline/src/engine/undo/undo.md
Normal file
31
docs/media/audio/audio-timeline/src/engine/undo/undo.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Engine Undo Helpers
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
The `undo.ts` module provides the command builder and incremental adapter diff logic used by `TimelineEngine` for undo/redo. It converts before/after track snapshots into `Command<Track[]>` instances and detects which individual tracks changed so the engine can forward minimal updates to the adapter.
|
||||
|
||||
## Features
|
||||
|
||||
- `createTracksUndoStack` constructs an `UndoStack<Track[]>` with a 500-entry limit
|
||||
- `buildTracksCommand` creates a `Command<Track[]>` from before/after snapshots
|
||||
- `findChangedTrackIds` compares two track arrays to detect which tracks changed
|
||||
|
||||
## Usage
|
||||
|
||||
Used internally by `TimelineEngine`.
|
||||
|
||||
## API Reference
|
||||
|
||||
| Export | Signature | Description |
|
||||
|--------|-----------|-------------|
|
||||
| `createTracksUndoStack` | `(initial) => UndoStack<Track[]>` | Create undo stack |
|
||||
| `buildTracksCommand` | `(label, prev, next) => TracksCommand` | Build command |
|
||||
| `findChangedTrackIds` | `(prev, next) => string[]` | Diff tracks |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`findChangedTrackIds` uses `JSON.stringify` for a fast deep-equal check. This is sufficient because tracks are plain JSON-compatible objects with no circular references. The function returns ids present in `prev` but not `next` (removed tracks) as well as ids whose contents differ. When the track count is unchanged, the engine uses these ids to call `adapter.updateTrack` individually, avoiding a full `setTracks` reset that could interrupt playback. The 500-entry limit prevents unbounded memory growth in long editing sessions. When the limit is reached, the oldest entries are discarded automatically so the stack stays bounded.
|
||||
36
docs/media/audio/audio-timeline/src/engine/zoom/zoom.md
Normal file
36
docs/media/audio/audio-timeline/src/engine/zoom/zoom.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Engine Zoom Helpers
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
The `zoom.ts` module bridges `TimelineEngine` zoom, selection, and loop public methods to the pure `timeline-operations.ts` and `zoom.ts` helpers. It manages internal state updates and event emission so the engine class stays focused on high-level orchestration.
|
||||
|
||||
## Features
|
||||
|
||||
- `engineZoomIn` / `engineZoomOut` step through the zoom table and emit `zoomChange`
|
||||
- `engineSetZoomLevel` snaps to the nearest table entry
|
||||
- `engineSetSelection` normalises ranges and emits `selectionChange`
|
||||
- `engineSetLoopRegion` updates loop bounds and forwards to adapter
|
||||
- `engineSetLoopEnabled` toggles loop and forwards to adapter
|
||||
|
||||
## Usage
|
||||
|
||||
These helpers are used internally by `TimelineEngine`. Consumers call `engine.zoomIn()` etc.
|
||||
|
||||
## API Reference
|
||||
|
||||
| Export | Signature | Description |
|
||||
|--------|-----------|-------------|
|
||||
| `engineZoomIn` | `(deps) => void` | Zoom in |
|
||||
| `engineZoomOut` | `(deps) => void` | Zoom out |
|
||||
| `engineSetZoomLevel` | `(deps, samplesPerPixel) => void` | Set zoom |
|
||||
| `engineSetSelection` | `(deps, start, end) => void` | Set selection |
|
||||
| `engineSetLoopRegion` | `(deps, start, end) => void` | Set loop region |
|
||||
| `engineSetLoopEnabled` | `(deps, enabled) => void` | Toggle loop |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The `ZoomDeps` interface isolates the helpers from the engine class, making them testable in isolation. Loop changes are forwarded to the adapter immediately so the audio backend can update its loop markers. Selection changes are clamped to the current document duration so invalid ranges cannot be created. Zoom changes only affect the view and do not touch the arrangement, which is why they bypass the undo stack entirely.
|
||||
Reference in New Issue
Block a user