@waveform-playlist/engine 7.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Naomi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,272 @@
1
+ import { AudioClip, ClipTrack } from '@waveform-playlist/core';
2
+
3
+ /**
4
+ * Clip Operations
5
+ *
6
+ * Pure functions for constraining clip movement, boundary trimming,
7
+ * and splitting clips on a timeline. All positions are in samples (integers).
8
+ */
9
+
10
+ /**
11
+ * Constrain clip movement delta to prevent overlaps with adjacent clips
12
+ * and going before sample 0.
13
+ *
14
+ * @param clip - The clip being dragged
15
+ * @param deltaSamples - Requested movement in samples (negative = left, positive = right)
16
+ * @param sortedClips - All clips on the track, sorted by startSample
17
+ * @param clipIndex - Index of the dragged clip in sortedClips
18
+ * @returns Constrained delta that prevents overlaps
19
+ */
20
+ declare function constrainClipDrag(clip: AudioClip, deltaSamples: number, sortedClips: AudioClip[], clipIndex: number): number;
21
+ /**
22
+ * Constrain boundary trim delta for left or right edge of a clip.
23
+ *
24
+ * LEFT boundary: delta moves the left edge (positive = shrink, negative = expand)
25
+ * - startSample += delta, offsetSamples += delta, durationSamples -= delta
26
+ *
27
+ * RIGHT boundary: delta applied to durationSamples (positive = expand, negative = shrink)
28
+ * - durationSamples += delta
29
+ *
30
+ * @param clip - The clip being trimmed
31
+ * @param deltaSamples - Requested trim delta in samples
32
+ * @param boundary - Which edge is being trimmed: 'left' or 'right'
33
+ * @param sortedClips - All clips on the track, sorted by startSample
34
+ * @param clipIndex - Index of the trimmed clip in sortedClips
35
+ * @param minDurationSamples - Minimum allowed clip duration in samples
36
+ * @returns Constrained delta
37
+ */
38
+ declare function constrainBoundaryTrim(clip: AudioClip, deltaSamples: number, boundary: 'left' | 'right', sortedClips: AudioClip[], clipIndex: number, minDurationSamples: number): number;
39
+ /**
40
+ * Snap a split sample position to the nearest pixel boundary.
41
+ *
42
+ * @param splitSample - The sample position to snap
43
+ * @param samplesPerPixel - Current zoom level (samples per pixel)
44
+ * @returns Snapped sample position
45
+ */
46
+ declare function calculateSplitPoint(splitSample: number, samplesPerPixel: number): number;
47
+ /**
48
+ * Split a clip into two clips at the given sample position.
49
+ *
50
+ * The left clip retains the original fadeIn; the right clip retains the original fadeOut.
51
+ * Both clips share the same waveformData reference.
52
+ * If the clip has a name, suffixes " (1)" and " (2)" are appended.
53
+ *
54
+ * @param clip - The clip to split
55
+ * @param splitSample - The timeline sample position where the split occurs
56
+ * @returns Object with `left` and `right` AudioClip
57
+ */
58
+ declare function splitClip(clip: AudioClip, splitSample: number): {
59
+ left: AudioClip;
60
+ right: AudioClip;
61
+ };
62
+ /**
63
+ * Check whether a clip can be split at the given sample position.
64
+ *
65
+ * The split point must be strictly inside the clip (not at start or end),
66
+ * and both resulting clips must meet the minimum duration requirement.
67
+ *
68
+ * @param clip - The clip to check
69
+ * @param sample - The timeline sample position to test
70
+ * @param minDurationSamples - Minimum allowed clip duration in samples
71
+ * @returns true if the split is valid
72
+ */
73
+ declare function canSplitAt(clip: AudioClip, sample: number, minDurationSamples: number): boolean;
74
+
75
+ /**
76
+ * Viewport operations for virtual scrolling.
77
+ *
78
+ * Pure math helpers that determine which portion of the timeline
79
+ * is visible and which canvas chunks need to be mounted.
80
+ */
81
+ /**
82
+ * Calculate the visible region with an overscan buffer for virtual scrolling.
83
+ *
84
+ * The buffer extends the visible range on both sides so that chunks are
85
+ * mounted slightly before they scroll into view, preventing flicker.
86
+ *
87
+ * @param scrollLeft - Current horizontal scroll position in pixels
88
+ * @param containerWidth - Width of the scroll container in pixels
89
+ * @param bufferRatio - Multiplier for buffer size (default 1.5x container width)
90
+ * @returns Object with visibleStart and visibleEnd in pixels
91
+ */
92
+ declare function calculateViewportBounds(scrollLeft: number, containerWidth: number, bufferRatio?: number): {
93
+ visibleStart: number;
94
+ visibleEnd: number;
95
+ };
96
+ /**
97
+ * Get an array of chunk indices that overlap the visible viewport.
98
+ *
99
+ * Chunks are fixed-width segments of the total timeline width. Only chunks
100
+ * that intersect [visibleStart, visibleEnd) are included. The last chunk
101
+ * may be narrower than chunkWidth if totalWidth is not evenly divisible.
102
+ *
103
+ * @param totalWidth - Total width of the timeline in pixels
104
+ * @param chunkWidth - Width of each chunk in pixels
105
+ * @param visibleStart - Left edge of the visible region in pixels
106
+ * @param visibleEnd - Right edge of the visible region in pixels
107
+ * @returns Array of chunk indices (0-based) that are visible
108
+ */
109
+ declare function getVisibleChunkIndices(totalWidth: number, chunkWidth: number, visibleStart: number, visibleEnd: number): number[];
110
+ /**
111
+ * Determine whether a scroll change is large enough to warrant
112
+ * recalculating the viewport and re-rendering chunks.
113
+ *
114
+ * Small scroll movements are ignored to avoid excessive recomputation
115
+ * during smooth scrolling.
116
+ *
117
+ * @param oldScrollLeft - Previous scroll position in pixels
118
+ * @param newScrollLeft - Current scroll position in pixels
119
+ * @param threshold - Minimum pixel delta to trigger an update (default 100)
120
+ * @returns true if the scroll delta meets or exceeds the threshold
121
+ */
122
+ declare function shouldUpdateViewport(oldScrollLeft: number, newScrollLeft: number, threshold?: number): boolean;
123
+
124
+ /**
125
+ * Calculate total timeline duration in seconds from all tracks/clips.
126
+ * Iterates all clips, finds the furthest clip end (startSample + durationSamples),
127
+ * converts to seconds using each clip's sampleRate.
128
+ *
129
+ * @param tracks - Array of clip tracks
130
+ * @returns Duration in seconds
131
+ */
132
+ declare function calculateDuration(tracks: ClipTrack[]): number;
133
+ /**
134
+ * Find the zoom level index closest to a given samplesPerPixel.
135
+ * Returns exact match if found, otherwise the index whose value is
136
+ * nearest to the target (by absolute difference).
137
+ *
138
+ * @param targetSamplesPerPixel - The samplesPerPixel value to find
139
+ * @param zoomLevels - Array of available zoom levels (samplesPerPixel values)
140
+ * @returns Index into the zoomLevels array
141
+ */
142
+ declare function findClosestZoomIndex(targetSamplesPerPixel: number, zoomLevels: number[]): number;
143
+ /**
144
+ * Keep viewport centered during zoom changes.
145
+ * Calculates center time from old zoom, computes new pixel position at new zoom,
146
+ * and returns new scrollLeft clamped to >= 0.
147
+ *
148
+ * @param oldSamplesPerPixel - Previous zoom level
149
+ * @param newSamplesPerPixel - New zoom level
150
+ * @param scrollLeft - Current horizontal scroll position
151
+ * @param containerWidth - Viewport width in pixels
152
+ * @param sampleRate - Audio sample rate
153
+ * @param controlWidth - Width of track controls panel (defaults to 0)
154
+ * @returns New scrollLeft value
155
+ */
156
+ declare function calculateZoomScrollPosition(oldSamplesPerPixel: number, newSamplesPerPixel: number, scrollLeft: number, containerWidth: number, sampleRate: number, controlWidth?: number): number;
157
+ /**
158
+ * Clamp a seek position to the valid range [0, duration].
159
+ *
160
+ * @param time - Requested seek time in seconds
161
+ * @param duration - Maximum duration in seconds
162
+ * @returns Clamped time value
163
+ */
164
+ declare function clampSeekPosition(time: number, duration: number): number;
165
+
166
+ /**
167
+ * Interface for pluggable audio playback adapters.
168
+ * Implement this to connect PlaylistEngine to any audio backend
169
+ * (Tone.js, openDAW, HTMLAudioElement, etc.)
170
+ */
171
+ interface PlayoutAdapter {
172
+ init(): Promise<void>;
173
+ setTracks(tracks: ClipTrack[]): void;
174
+ play(startTime: number, endTime?: number): Promise<void>;
175
+ pause(): void;
176
+ stop(): void;
177
+ seek(time: number): void;
178
+ getCurrentTime(): number;
179
+ isPlaying(): boolean;
180
+ setMasterVolume(volume: number): void;
181
+ setTrackVolume(trackId: string, volume: number): void;
182
+ setTrackMute(trackId: string, muted: boolean): void;
183
+ setTrackSolo(trackId: string, soloed: boolean): void;
184
+ setTrackPan(trackId: string, pan: number): void;
185
+ dispose(): void;
186
+ }
187
+ /**
188
+ * Snapshot of playlist engine state, emitted on every state change.
189
+ */
190
+ interface EngineState {
191
+ tracks: ClipTrack[];
192
+ duration: number;
193
+ currentTime: number;
194
+ isPlaying: boolean;
195
+ samplesPerPixel: number;
196
+ sampleRate: number;
197
+ selectedTrackId: string | null;
198
+ zoomIndex: number;
199
+ canZoomIn: boolean;
200
+ canZoomOut: boolean;
201
+ }
202
+ /**
203
+ * Configuration options for PlaylistEngine constructor.
204
+ */
205
+ interface PlaylistEngineOptions {
206
+ adapter?: PlayoutAdapter;
207
+ sampleRate?: number;
208
+ samplesPerPixel?: number;
209
+ zoomLevels?: number[];
210
+ }
211
+ /**
212
+ * Events emitted by PlaylistEngine.
213
+ */
214
+ interface EngineEvents {
215
+ statechange: (state: EngineState) => void;
216
+ timeupdate: (time: number) => void;
217
+ play: () => void;
218
+ pause: () => void;
219
+ stop: () => void;
220
+ }
221
+
222
+ /**
223
+ * PlaylistEngine — Stateful, framework-agnostic timeline engine.
224
+ *
225
+ * Composes pure operations from ./operations with an event emitter
226
+ * and optional PlayoutAdapter for audio playback delegation.
227
+ */
228
+
229
+ type EventName = keyof EngineEvents;
230
+ declare class PlaylistEngine {
231
+ private _tracks;
232
+ private _currentTime;
233
+ private _isPlaying;
234
+ private _selectedTrackId;
235
+ private _sampleRate;
236
+ private _zoomLevels;
237
+ private _zoomIndex;
238
+ private _adapter;
239
+ private _animFrameId;
240
+ private _disposed;
241
+ private _listeners;
242
+ constructor(options?: PlaylistEngineOptions);
243
+ getState(): EngineState;
244
+ setTracks(tracks: ClipTrack[]): void;
245
+ addTrack(track: ClipTrack): void;
246
+ removeTrack(trackId: string): void;
247
+ selectTrack(trackId: string | null): void;
248
+ moveClip(trackId: string, clipId: string, deltaSamples: number): void;
249
+ splitClip(trackId: string, clipId: string, atSample: number): void;
250
+ trimClip(trackId: string, clipId: string, boundary: 'left' | 'right', deltaSamples: number): void;
251
+ play(startTime?: number, endTime?: number): Promise<void>;
252
+ pause(): void;
253
+ stop(): void;
254
+ seek(time: number): void;
255
+ setMasterVolume(volume: number): void;
256
+ setTrackVolume(trackId: string, volume: number): void;
257
+ setTrackMute(trackId: string, muted: boolean): void;
258
+ setTrackSolo(trackId: string, soloed: boolean): void;
259
+ setTrackPan(trackId: string, pan: number): void;
260
+ zoomIn(): void;
261
+ zoomOut(): void;
262
+ setZoomLevel(samplesPerPixel: number): void;
263
+ on<K extends EventName>(event: K, listener: EngineEvents[K]): void;
264
+ off<K extends EventName>(event: K, listener: EngineEvents[K]): void;
265
+ dispose(): void;
266
+ private _emit;
267
+ private _emitStateChange;
268
+ private _startTimeUpdateLoop;
269
+ private _stopTimeUpdateLoop;
270
+ }
271
+
272
+ export { type EngineEvents, type EngineState, PlaylistEngine, type PlaylistEngineOptions, type PlayoutAdapter, calculateDuration, calculateSplitPoint, calculateViewportBounds, calculateZoomScrollPosition, canSplitAt, clampSeekPosition, constrainBoundaryTrim, constrainClipDrag, findClosestZoomIndex, getVisibleChunkIndices, shouldUpdateViewport, splitClip };
@@ -0,0 +1,272 @@
1
+ import { AudioClip, ClipTrack } from '@waveform-playlist/core';
2
+
3
+ /**
4
+ * Clip Operations
5
+ *
6
+ * Pure functions for constraining clip movement, boundary trimming,
7
+ * and splitting clips on a timeline. All positions are in samples (integers).
8
+ */
9
+
10
+ /**
11
+ * Constrain clip movement delta to prevent overlaps with adjacent clips
12
+ * and going before sample 0.
13
+ *
14
+ * @param clip - The clip being dragged
15
+ * @param deltaSamples - Requested movement in samples (negative = left, positive = right)
16
+ * @param sortedClips - All clips on the track, sorted by startSample
17
+ * @param clipIndex - Index of the dragged clip in sortedClips
18
+ * @returns Constrained delta that prevents overlaps
19
+ */
20
+ declare function constrainClipDrag(clip: AudioClip, deltaSamples: number, sortedClips: AudioClip[], clipIndex: number): number;
21
+ /**
22
+ * Constrain boundary trim delta for left or right edge of a clip.
23
+ *
24
+ * LEFT boundary: delta moves the left edge (positive = shrink, negative = expand)
25
+ * - startSample += delta, offsetSamples += delta, durationSamples -= delta
26
+ *
27
+ * RIGHT boundary: delta applied to durationSamples (positive = expand, negative = shrink)
28
+ * - durationSamples += delta
29
+ *
30
+ * @param clip - The clip being trimmed
31
+ * @param deltaSamples - Requested trim delta in samples
32
+ * @param boundary - Which edge is being trimmed: 'left' or 'right'
33
+ * @param sortedClips - All clips on the track, sorted by startSample
34
+ * @param clipIndex - Index of the trimmed clip in sortedClips
35
+ * @param minDurationSamples - Minimum allowed clip duration in samples
36
+ * @returns Constrained delta
37
+ */
38
+ declare function constrainBoundaryTrim(clip: AudioClip, deltaSamples: number, boundary: 'left' | 'right', sortedClips: AudioClip[], clipIndex: number, minDurationSamples: number): number;
39
+ /**
40
+ * Snap a split sample position to the nearest pixel boundary.
41
+ *
42
+ * @param splitSample - The sample position to snap
43
+ * @param samplesPerPixel - Current zoom level (samples per pixel)
44
+ * @returns Snapped sample position
45
+ */
46
+ declare function calculateSplitPoint(splitSample: number, samplesPerPixel: number): number;
47
+ /**
48
+ * Split a clip into two clips at the given sample position.
49
+ *
50
+ * The left clip retains the original fadeIn; the right clip retains the original fadeOut.
51
+ * Both clips share the same waveformData reference.
52
+ * If the clip has a name, suffixes " (1)" and " (2)" are appended.
53
+ *
54
+ * @param clip - The clip to split
55
+ * @param splitSample - The timeline sample position where the split occurs
56
+ * @returns Object with `left` and `right` AudioClip
57
+ */
58
+ declare function splitClip(clip: AudioClip, splitSample: number): {
59
+ left: AudioClip;
60
+ right: AudioClip;
61
+ };
62
+ /**
63
+ * Check whether a clip can be split at the given sample position.
64
+ *
65
+ * The split point must be strictly inside the clip (not at start or end),
66
+ * and both resulting clips must meet the minimum duration requirement.
67
+ *
68
+ * @param clip - The clip to check
69
+ * @param sample - The timeline sample position to test
70
+ * @param minDurationSamples - Minimum allowed clip duration in samples
71
+ * @returns true if the split is valid
72
+ */
73
+ declare function canSplitAt(clip: AudioClip, sample: number, minDurationSamples: number): boolean;
74
+
75
+ /**
76
+ * Viewport operations for virtual scrolling.
77
+ *
78
+ * Pure math helpers that determine which portion of the timeline
79
+ * is visible and which canvas chunks need to be mounted.
80
+ */
81
+ /**
82
+ * Calculate the visible region with an overscan buffer for virtual scrolling.
83
+ *
84
+ * The buffer extends the visible range on both sides so that chunks are
85
+ * mounted slightly before they scroll into view, preventing flicker.
86
+ *
87
+ * @param scrollLeft - Current horizontal scroll position in pixels
88
+ * @param containerWidth - Width of the scroll container in pixels
89
+ * @param bufferRatio - Multiplier for buffer size (default 1.5x container width)
90
+ * @returns Object with visibleStart and visibleEnd in pixels
91
+ */
92
+ declare function calculateViewportBounds(scrollLeft: number, containerWidth: number, bufferRatio?: number): {
93
+ visibleStart: number;
94
+ visibleEnd: number;
95
+ };
96
+ /**
97
+ * Get an array of chunk indices that overlap the visible viewport.
98
+ *
99
+ * Chunks are fixed-width segments of the total timeline width. Only chunks
100
+ * that intersect [visibleStart, visibleEnd) are included. The last chunk
101
+ * may be narrower than chunkWidth if totalWidth is not evenly divisible.
102
+ *
103
+ * @param totalWidth - Total width of the timeline in pixels
104
+ * @param chunkWidth - Width of each chunk in pixels
105
+ * @param visibleStart - Left edge of the visible region in pixels
106
+ * @param visibleEnd - Right edge of the visible region in pixels
107
+ * @returns Array of chunk indices (0-based) that are visible
108
+ */
109
+ declare function getVisibleChunkIndices(totalWidth: number, chunkWidth: number, visibleStart: number, visibleEnd: number): number[];
110
+ /**
111
+ * Determine whether a scroll change is large enough to warrant
112
+ * recalculating the viewport and re-rendering chunks.
113
+ *
114
+ * Small scroll movements are ignored to avoid excessive recomputation
115
+ * during smooth scrolling.
116
+ *
117
+ * @param oldScrollLeft - Previous scroll position in pixels
118
+ * @param newScrollLeft - Current scroll position in pixels
119
+ * @param threshold - Minimum pixel delta to trigger an update (default 100)
120
+ * @returns true if the scroll delta meets or exceeds the threshold
121
+ */
122
+ declare function shouldUpdateViewport(oldScrollLeft: number, newScrollLeft: number, threshold?: number): boolean;
123
+
124
+ /**
125
+ * Calculate total timeline duration in seconds from all tracks/clips.
126
+ * Iterates all clips, finds the furthest clip end (startSample + durationSamples),
127
+ * converts to seconds using each clip's sampleRate.
128
+ *
129
+ * @param tracks - Array of clip tracks
130
+ * @returns Duration in seconds
131
+ */
132
+ declare function calculateDuration(tracks: ClipTrack[]): number;
133
+ /**
134
+ * Find the zoom level index closest to a given samplesPerPixel.
135
+ * Returns exact match if found, otherwise the index whose value is
136
+ * nearest to the target (by absolute difference).
137
+ *
138
+ * @param targetSamplesPerPixel - The samplesPerPixel value to find
139
+ * @param zoomLevels - Array of available zoom levels (samplesPerPixel values)
140
+ * @returns Index into the zoomLevels array
141
+ */
142
+ declare function findClosestZoomIndex(targetSamplesPerPixel: number, zoomLevels: number[]): number;
143
+ /**
144
+ * Keep viewport centered during zoom changes.
145
+ * Calculates center time from old zoom, computes new pixel position at new zoom,
146
+ * and returns new scrollLeft clamped to >= 0.
147
+ *
148
+ * @param oldSamplesPerPixel - Previous zoom level
149
+ * @param newSamplesPerPixel - New zoom level
150
+ * @param scrollLeft - Current horizontal scroll position
151
+ * @param containerWidth - Viewport width in pixels
152
+ * @param sampleRate - Audio sample rate
153
+ * @param controlWidth - Width of track controls panel (defaults to 0)
154
+ * @returns New scrollLeft value
155
+ */
156
+ declare function calculateZoomScrollPosition(oldSamplesPerPixel: number, newSamplesPerPixel: number, scrollLeft: number, containerWidth: number, sampleRate: number, controlWidth?: number): number;
157
+ /**
158
+ * Clamp a seek position to the valid range [0, duration].
159
+ *
160
+ * @param time - Requested seek time in seconds
161
+ * @param duration - Maximum duration in seconds
162
+ * @returns Clamped time value
163
+ */
164
+ declare function clampSeekPosition(time: number, duration: number): number;
165
+
166
+ /**
167
+ * Interface for pluggable audio playback adapters.
168
+ * Implement this to connect PlaylistEngine to any audio backend
169
+ * (Tone.js, openDAW, HTMLAudioElement, etc.)
170
+ */
171
+ interface PlayoutAdapter {
172
+ init(): Promise<void>;
173
+ setTracks(tracks: ClipTrack[]): void;
174
+ play(startTime: number, endTime?: number): Promise<void>;
175
+ pause(): void;
176
+ stop(): void;
177
+ seek(time: number): void;
178
+ getCurrentTime(): number;
179
+ isPlaying(): boolean;
180
+ setMasterVolume(volume: number): void;
181
+ setTrackVolume(trackId: string, volume: number): void;
182
+ setTrackMute(trackId: string, muted: boolean): void;
183
+ setTrackSolo(trackId: string, soloed: boolean): void;
184
+ setTrackPan(trackId: string, pan: number): void;
185
+ dispose(): void;
186
+ }
187
+ /**
188
+ * Snapshot of playlist engine state, emitted on every state change.
189
+ */
190
+ interface EngineState {
191
+ tracks: ClipTrack[];
192
+ duration: number;
193
+ currentTime: number;
194
+ isPlaying: boolean;
195
+ samplesPerPixel: number;
196
+ sampleRate: number;
197
+ selectedTrackId: string | null;
198
+ zoomIndex: number;
199
+ canZoomIn: boolean;
200
+ canZoomOut: boolean;
201
+ }
202
+ /**
203
+ * Configuration options for PlaylistEngine constructor.
204
+ */
205
+ interface PlaylistEngineOptions {
206
+ adapter?: PlayoutAdapter;
207
+ sampleRate?: number;
208
+ samplesPerPixel?: number;
209
+ zoomLevels?: number[];
210
+ }
211
+ /**
212
+ * Events emitted by PlaylistEngine.
213
+ */
214
+ interface EngineEvents {
215
+ statechange: (state: EngineState) => void;
216
+ timeupdate: (time: number) => void;
217
+ play: () => void;
218
+ pause: () => void;
219
+ stop: () => void;
220
+ }
221
+
222
+ /**
223
+ * PlaylistEngine — Stateful, framework-agnostic timeline engine.
224
+ *
225
+ * Composes pure operations from ./operations with an event emitter
226
+ * and optional PlayoutAdapter for audio playback delegation.
227
+ */
228
+
229
+ type EventName = keyof EngineEvents;
230
+ declare class PlaylistEngine {
231
+ private _tracks;
232
+ private _currentTime;
233
+ private _isPlaying;
234
+ private _selectedTrackId;
235
+ private _sampleRate;
236
+ private _zoomLevels;
237
+ private _zoomIndex;
238
+ private _adapter;
239
+ private _animFrameId;
240
+ private _disposed;
241
+ private _listeners;
242
+ constructor(options?: PlaylistEngineOptions);
243
+ getState(): EngineState;
244
+ setTracks(tracks: ClipTrack[]): void;
245
+ addTrack(track: ClipTrack): void;
246
+ removeTrack(trackId: string): void;
247
+ selectTrack(trackId: string | null): void;
248
+ moveClip(trackId: string, clipId: string, deltaSamples: number): void;
249
+ splitClip(trackId: string, clipId: string, atSample: number): void;
250
+ trimClip(trackId: string, clipId: string, boundary: 'left' | 'right', deltaSamples: number): void;
251
+ play(startTime?: number, endTime?: number): Promise<void>;
252
+ pause(): void;
253
+ stop(): void;
254
+ seek(time: number): void;
255
+ setMasterVolume(volume: number): void;
256
+ setTrackVolume(trackId: string, volume: number): void;
257
+ setTrackMute(trackId: string, muted: boolean): void;
258
+ setTrackSolo(trackId: string, soloed: boolean): void;
259
+ setTrackPan(trackId: string, pan: number): void;
260
+ zoomIn(): void;
261
+ zoomOut(): void;
262
+ setZoomLevel(samplesPerPixel: number): void;
263
+ on<K extends EventName>(event: K, listener: EngineEvents[K]): void;
264
+ off<K extends EventName>(event: K, listener: EngineEvents[K]): void;
265
+ dispose(): void;
266
+ private _emit;
267
+ private _emitStateChange;
268
+ private _startTimeUpdateLoop;
269
+ private _stopTimeUpdateLoop;
270
+ }
271
+
272
+ export { type EngineEvents, type EngineState, PlaylistEngine, type PlaylistEngineOptions, type PlayoutAdapter, calculateDuration, calculateSplitPoint, calculateViewportBounds, calculateZoomScrollPosition, canSplitAt, clampSeekPosition, constrainBoundaryTrim, constrainClipDrag, findClosestZoomIndex, getVisibleChunkIndices, shouldUpdateViewport, splitClip };