@waveform-playlist/core 5.0.0-alpha.6 → 5.0.0-alpha.8
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/dist/index.d.mts +50 -1
- package/dist/index.d.ts +50 -1
- package/dist/index.js +8 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +8 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -9,6 +9,44 @@
|
|
|
9
9
|
* - Clips can overlap (for crossfades)
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* WaveformData object from waveform-data.js library.
|
|
14
|
+
* Supports resample() and slice() for dynamic zoom levels.
|
|
15
|
+
* See: https://github.com/bbc/waveform-data.js
|
|
16
|
+
*/
|
|
17
|
+
interface WaveformDataObject {
|
|
18
|
+
/** Sample rate of the original audio */
|
|
19
|
+
readonly sample_rate: number;
|
|
20
|
+
/** Number of audio samples per pixel */
|
|
21
|
+
readonly scale: number;
|
|
22
|
+
/** Length of waveform data in pixels */
|
|
23
|
+
readonly length: number;
|
|
24
|
+
/** Bit depth (8 or 16) */
|
|
25
|
+
readonly bits: number;
|
|
26
|
+
/** Duration in seconds */
|
|
27
|
+
readonly duration: number;
|
|
28
|
+
/** Number of channels */
|
|
29
|
+
readonly channels: number;
|
|
30
|
+
/** Get channel data */
|
|
31
|
+
channel: (index: number) => {
|
|
32
|
+
min_array: () => number[];
|
|
33
|
+
max_array: () => number[];
|
|
34
|
+
};
|
|
35
|
+
/** Resample to different scale */
|
|
36
|
+
resample: (options: {
|
|
37
|
+
scale: number;
|
|
38
|
+
} | {
|
|
39
|
+
width: number;
|
|
40
|
+
}) => WaveformDataObject;
|
|
41
|
+
/** Slice a portion of the waveform */
|
|
42
|
+
slice: (options: {
|
|
43
|
+
startTime: number;
|
|
44
|
+
endTime: number;
|
|
45
|
+
} | {
|
|
46
|
+
startIndex: number;
|
|
47
|
+
endIndex: number;
|
|
48
|
+
}) => WaveformDataObject;
|
|
49
|
+
}
|
|
12
50
|
/**
|
|
13
51
|
* Generic effects function type for track-level audio processing.
|
|
14
52
|
*
|
|
@@ -62,6 +100,13 @@ interface AudioClip {
|
|
|
62
100
|
name?: string;
|
|
63
101
|
/** Optional color for visual distinction */
|
|
64
102
|
color?: string;
|
|
103
|
+
/**
|
|
104
|
+
* Pre-computed waveform data from waveform-data.js library.
|
|
105
|
+
* When provided, the library will use this instead of computing peaks from the audioBuffer.
|
|
106
|
+
* Supports resampling to different zoom levels and slicing for clip trimming.
|
|
107
|
+
* Load with: `const waveformData = await loadWaveformData('/path/to/peaks.dat')`
|
|
108
|
+
*/
|
|
109
|
+
waveformData?: WaveformDataObject;
|
|
65
110
|
}
|
|
66
111
|
/**
|
|
67
112
|
* Represents a track containing multiple audio clips
|
|
@@ -121,6 +166,8 @@ interface CreateClipOptions {
|
|
|
121
166
|
color?: string;
|
|
122
167
|
fadeIn?: Fade;
|
|
123
168
|
fadeOut?: Fade;
|
|
169
|
+
/** Pre-computed waveform data from waveform-data.js (e.g., from BBC audiowaveform) */
|
|
170
|
+
waveformData?: WaveformDataObject;
|
|
124
171
|
}
|
|
125
172
|
/**
|
|
126
173
|
* Options for creating a new audio clip (using seconds for convenience)
|
|
@@ -135,6 +182,8 @@ interface CreateClipOptionsSeconds {
|
|
|
135
182
|
color?: string;
|
|
136
183
|
fadeIn?: Fade;
|
|
137
184
|
fadeOut?: Fade;
|
|
185
|
+
/** Pre-computed waveform data from waveform-data.js (e.g., from BBC audiowaveform) */
|
|
186
|
+
waveformData?: WaveformDataObject;
|
|
138
187
|
}
|
|
139
188
|
/**
|
|
140
189
|
* Options for creating a new track
|
|
@@ -281,4 +330,4 @@ declare function pixelsToSamples(pixels: number, samplesPerPixel: number): numbe
|
|
|
281
330
|
declare function pixelsToSeconds(pixels: number, samplesPerPixel: number, sampleRate: number): number;
|
|
282
331
|
declare function secondsToPixels(seconds: number, samplesPerPixel: number, sampleRate: number): number;
|
|
283
332
|
|
|
284
|
-
export { type AudioBuffer$1 as AudioBuffer, type AudioClip, type ClipTrack, type CreateClipOptions, type CreateClipOptionsSeconds, type CreateTrackOptions, type Fade, type FadeType, type Gap, InteractionState, type PlaylistConfig, type PlayoutState, type TimeSelection, type Timeline, type Track, type TrackEffectsFunction, type WaveformConfig, clipsOverlap, createClip, createClipFromSeconds, createTimeline, createTrack, findGaps, getClipsAtSample, getClipsInRange, pixelsToSamples, pixelsToSeconds, samplesToPixels, samplesToSeconds, secondsToPixels, secondsToSamples, sortClipsByTime };
|
|
333
|
+
export { type AudioBuffer$1 as AudioBuffer, type AudioClip, type ClipTrack, type CreateClipOptions, type CreateClipOptionsSeconds, type CreateTrackOptions, type Fade, type FadeType, type Gap, InteractionState, type PlaylistConfig, type PlayoutState, type TimeSelection, type Timeline, type Track, type TrackEffectsFunction, type WaveformConfig, type WaveformDataObject, clipsOverlap, createClip, createClipFromSeconds, createTimeline, createTrack, findGaps, getClipsAtSample, getClipsInRange, pixelsToSamples, pixelsToSeconds, samplesToPixels, samplesToSeconds, secondsToPixels, secondsToSamples, sortClipsByTime };
|
package/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,44 @@
|
|
|
9
9
|
* - Clips can overlap (for crossfades)
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* WaveformData object from waveform-data.js library.
|
|
14
|
+
* Supports resample() and slice() for dynamic zoom levels.
|
|
15
|
+
* See: https://github.com/bbc/waveform-data.js
|
|
16
|
+
*/
|
|
17
|
+
interface WaveformDataObject {
|
|
18
|
+
/** Sample rate of the original audio */
|
|
19
|
+
readonly sample_rate: number;
|
|
20
|
+
/** Number of audio samples per pixel */
|
|
21
|
+
readonly scale: number;
|
|
22
|
+
/** Length of waveform data in pixels */
|
|
23
|
+
readonly length: number;
|
|
24
|
+
/** Bit depth (8 or 16) */
|
|
25
|
+
readonly bits: number;
|
|
26
|
+
/** Duration in seconds */
|
|
27
|
+
readonly duration: number;
|
|
28
|
+
/** Number of channels */
|
|
29
|
+
readonly channels: number;
|
|
30
|
+
/** Get channel data */
|
|
31
|
+
channel: (index: number) => {
|
|
32
|
+
min_array: () => number[];
|
|
33
|
+
max_array: () => number[];
|
|
34
|
+
};
|
|
35
|
+
/** Resample to different scale */
|
|
36
|
+
resample: (options: {
|
|
37
|
+
scale: number;
|
|
38
|
+
} | {
|
|
39
|
+
width: number;
|
|
40
|
+
}) => WaveformDataObject;
|
|
41
|
+
/** Slice a portion of the waveform */
|
|
42
|
+
slice: (options: {
|
|
43
|
+
startTime: number;
|
|
44
|
+
endTime: number;
|
|
45
|
+
} | {
|
|
46
|
+
startIndex: number;
|
|
47
|
+
endIndex: number;
|
|
48
|
+
}) => WaveformDataObject;
|
|
49
|
+
}
|
|
12
50
|
/**
|
|
13
51
|
* Generic effects function type for track-level audio processing.
|
|
14
52
|
*
|
|
@@ -62,6 +100,13 @@ interface AudioClip {
|
|
|
62
100
|
name?: string;
|
|
63
101
|
/** Optional color for visual distinction */
|
|
64
102
|
color?: string;
|
|
103
|
+
/**
|
|
104
|
+
* Pre-computed waveform data from waveform-data.js library.
|
|
105
|
+
* When provided, the library will use this instead of computing peaks from the audioBuffer.
|
|
106
|
+
* Supports resampling to different zoom levels and slicing for clip trimming.
|
|
107
|
+
* Load with: `const waveformData = await loadWaveformData('/path/to/peaks.dat')`
|
|
108
|
+
*/
|
|
109
|
+
waveformData?: WaveformDataObject;
|
|
65
110
|
}
|
|
66
111
|
/**
|
|
67
112
|
* Represents a track containing multiple audio clips
|
|
@@ -121,6 +166,8 @@ interface CreateClipOptions {
|
|
|
121
166
|
color?: string;
|
|
122
167
|
fadeIn?: Fade;
|
|
123
168
|
fadeOut?: Fade;
|
|
169
|
+
/** Pre-computed waveform data from waveform-data.js (e.g., from BBC audiowaveform) */
|
|
170
|
+
waveformData?: WaveformDataObject;
|
|
124
171
|
}
|
|
125
172
|
/**
|
|
126
173
|
* Options for creating a new audio clip (using seconds for convenience)
|
|
@@ -135,6 +182,8 @@ interface CreateClipOptionsSeconds {
|
|
|
135
182
|
color?: string;
|
|
136
183
|
fadeIn?: Fade;
|
|
137
184
|
fadeOut?: Fade;
|
|
185
|
+
/** Pre-computed waveform data from waveform-data.js (e.g., from BBC audiowaveform) */
|
|
186
|
+
waveformData?: WaveformDataObject;
|
|
138
187
|
}
|
|
139
188
|
/**
|
|
140
189
|
* Options for creating a new track
|
|
@@ -281,4 +330,4 @@ declare function pixelsToSamples(pixels: number, samplesPerPixel: number): numbe
|
|
|
281
330
|
declare function pixelsToSeconds(pixels: number, samplesPerPixel: number, sampleRate: number): number;
|
|
282
331
|
declare function secondsToPixels(seconds: number, samplesPerPixel: number, sampleRate: number): number;
|
|
283
332
|
|
|
284
|
-
export { type AudioBuffer$1 as AudioBuffer, type AudioClip, type ClipTrack, type CreateClipOptions, type CreateClipOptionsSeconds, type CreateTrackOptions, type Fade, type FadeType, type Gap, InteractionState, type PlaylistConfig, type PlayoutState, type TimeSelection, type Timeline, type Track, type TrackEffectsFunction, type WaveformConfig, clipsOverlap, createClip, createClipFromSeconds, createTimeline, createTrack, findGaps, getClipsAtSample, getClipsInRange, pixelsToSamples, pixelsToSeconds, samplesToPixels, samplesToSeconds, secondsToPixels, secondsToSamples, sortClipsByTime };
|
|
333
|
+
export { type AudioBuffer$1 as AudioBuffer, type AudioClip, type ClipTrack, type CreateClipOptions, type CreateClipOptionsSeconds, type CreateTrackOptions, type Fade, type FadeType, type Gap, InteractionState, type PlaylistConfig, type PlayoutState, type TimeSelection, type Timeline, type Track, type TrackEffectsFunction, type WaveformConfig, type WaveformDataObject, clipsOverlap, createClip, createClipFromSeconds, createTimeline, createTrack, findGaps, getClipsAtSample, getClipsInRange, pixelsToSamples, pixelsToSeconds, samplesToPixels, samplesToSeconds, secondsToPixels, secondsToSamples, sortClipsByTime };
|
package/dist/index.js
CHANGED
|
@@ -51,7 +51,8 @@ function createClip(options) {
|
|
|
51
51
|
name,
|
|
52
52
|
color,
|
|
53
53
|
fadeIn,
|
|
54
|
-
fadeOut
|
|
54
|
+
fadeOut,
|
|
55
|
+
waveformData
|
|
55
56
|
} = options;
|
|
56
57
|
return {
|
|
57
58
|
id: generateId(),
|
|
@@ -63,7 +64,8 @@ function createClip(options) {
|
|
|
63
64
|
name,
|
|
64
65
|
color,
|
|
65
66
|
fadeIn,
|
|
66
|
-
fadeOut
|
|
67
|
+
fadeOut,
|
|
68
|
+
waveformData
|
|
67
69
|
};
|
|
68
70
|
}
|
|
69
71
|
function createClipFromSeconds(options) {
|
|
@@ -76,7 +78,8 @@ function createClipFromSeconds(options) {
|
|
|
76
78
|
name,
|
|
77
79
|
color,
|
|
78
80
|
fadeIn,
|
|
79
|
-
fadeOut
|
|
81
|
+
fadeOut,
|
|
82
|
+
waveformData
|
|
80
83
|
} = options;
|
|
81
84
|
const sampleRate = audioBuffer.sampleRate;
|
|
82
85
|
return createClip({
|
|
@@ -88,7 +91,8 @@ function createClipFromSeconds(options) {
|
|
|
88
91
|
name,
|
|
89
92
|
color,
|
|
90
93
|
fadeIn,
|
|
91
|
-
fadeOut
|
|
94
|
+
fadeOut,
|
|
95
|
+
waveformData
|
|
92
96
|
});
|
|
93
97
|
}
|
|
94
98
|
function createTrack(options) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/types/clip.ts","../src/types/index.ts","../src/utils/conversions.ts"],"sourcesContent":["export * from './types';\nexport * from './utils';\n","/**\n * Clip-Based Model Types\n *\n * These types support a professional multi-track editing model where:\n * - Each track can contain multiple audio clips\n * - Clips can be positioned anywhere on the timeline\n * - Clips have independent trim points (offset/duration)\n * - Gaps between clips are silent\n * - Clips can overlap (for crossfades)\n */\n\nimport { Fade } from './index';\n\n/**\n * Generic effects function type for track-level audio processing.\n *\n * The actual implementation receives Tone.js audio nodes. Using generic types\n * here to avoid circular dependencies with the playout package.\n *\n * @param graphEnd - The end of the track's audio graph (Tone.js Gain node)\n * @param destination - Where to connect the effects output (Tone.js ToneAudioNode)\n * @param isOffline - Whether rendering offline (for export)\n * @returns Optional cleanup function called when track is disposed\n *\n * @example\n * ```typescript\n * const trackEffects: TrackEffectsFunction = (graphEnd, destination, isOffline) => {\n * const reverb = new Tone.Reverb({ decay: 1.5 });\n * graphEnd.connect(reverb);\n * reverb.connect(destination);\n *\n * return () => {\n * reverb.dispose();\n * };\n * };\n * ```\n */\nexport type TrackEffectsFunction = (\n graphEnd: unknown,\n destination: unknown,\n isOffline: boolean\n) => void | (() => void);\n\n/**\n * Represents a single audio clip on the timeline\n *\n * IMPORTANT: All positions/durations are stored as SAMPLE COUNTS (integers)\n * to avoid floating-point precision errors. Convert to seconds only when\n * needed for playback using: seconds = samples / sampleRate\n */\nexport interface AudioClip {\n /** Unique identifier for this clip */\n id: string;\n\n /** The audio buffer containing the audio data */\n audioBuffer: AudioBuffer;\n\n /** Position on timeline where this clip starts (in samples at timeline sampleRate) */\n startSample: number;\n\n /** Duration of this clip (in samples) - how much of the audio buffer to play */\n durationSamples: number;\n\n /** Offset into the audio buffer where playback starts (in samples) - the \"trim start\" point */\n offsetSamples: number;\n\n /** Optional fade in effect */\n fadeIn?: Fade;\n\n /** Optional fade out effect */\n fadeOut?: Fade;\n\n /** Clip-specific gain/volume multiplier (0.0 to 1.0+) */\n gain: number;\n\n /** Optional label/name for this clip */\n name?: string;\n\n /** Optional color for visual distinction */\n color?: string;\n}\n\n/**\n * Represents a track containing multiple audio clips\n */\nexport interface ClipTrack {\n /** Unique identifier for this track */\n id: string;\n\n /** Display name for this track */\n name: string;\n\n /** Array of audio clips on this track */\n clips: AudioClip[];\n\n /** Whether this track is muted */\n muted: boolean;\n\n /** Whether this track is soloed */\n soloed: boolean;\n\n /** Track volume (0.0 to 1.0+) */\n volume: number;\n\n /** Stereo pan (-1.0 = left, 0 = center, 1.0 = right) */\n pan: number;\n\n /** Optional track color for visual distinction */\n color?: string;\n\n /** Track height in pixels (for UI) */\n height?: number;\n\n /** Optional effects function for this track */\n effects?: TrackEffectsFunction;\n}\n\n/**\n * Represents the entire timeline/project\n */\nexport interface Timeline {\n /** All tracks in the timeline */\n tracks: ClipTrack[];\n\n /** Total timeline duration in seconds */\n duration: number;\n\n /** Sample rate for all audio (typically 44100 or 48000) */\n sampleRate: number;\n\n /** Optional project name */\n name?: string;\n\n /** Optional tempo (BPM) for grid snapping */\n tempo?: number;\n\n /** Optional time signature for grid snapping */\n timeSignature?: {\n numerator: number;\n denominator: number;\n };\n}\n\n/**\n * Options for creating a new audio clip (using sample counts)\n */\nexport interface CreateClipOptions {\n audioBuffer: AudioBuffer;\n startSample: number; // Position on timeline (in samples)\n durationSamples?: number; // Defaults to full buffer duration (in samples)\n offsetSamples?: number; // Defaults to 0\n gain?: number; // Defaults to 1.0\n name?: string;\n color?: string;\n fadeIn?: Fade;\n fadeOut?: Fade;\n}\n\n/**\n * Options for creating a new audio clip (using seconds for convenience)\n */\nexport interface CreateClipOptionsSeconds {\n audioBuffer: AudioBuffer;\n startTime: number; // Position on timeline (in seconds)\n duration?: number; // Defaults to full buffer duration (in seconds)\n offset?: number; // Defaults to 0 (in seconds)\n gain?: number; // Defaults to 1.0\n name?: string;\n color?: string;\n fadeIn?: Fade;\n fadeOut?: Fade;\n}\n\n/**\n * Options for creating a new track\n */\nexport interface CreateTrackOptions {\n name: string;\n clips?: AudioClip[];\n muted?: boolean;\n soloed?: boolean;\n volume?: number;\n pan?: number;\n color?: string;\n height?: number;\n}\n\n/**\n * Creates a new AudioClip with sensible defaults (using sample counts)\n */\nexport function createClip(options: CreateClipOptions): AudioClip {\n const {\n audioBuffer,\n startSample,\n durationSamples = audioBuffer.length, // Full buffer by default\n offsetSamples = 0,\n gain = 1.0,\n name,\n color,\n fadeIn,\n fadeOut,\n } = options;\n\n return {\n id: generateId(),\n audioBuffer,\n startSample,\n durationSamples,\n offsetSamples,\n gain,\n name,\n color,\n fadeIn,\n fadeOut,\n };\n}\n\n/**\n * Creates a new AudioClip from time-based values (convenience function)\n * Converts seconds to samples using the audioBuffer's sampleRate\n */\nexport function createClipFromSeconds(options: CreateClipOptionsSeconds): AudioClip {\n const {\n audioBuffer,\n startTime,\n duration = audioBuffer.duration,\n offset = 0,\n gain = 1.0,\n name,\n color,\n fadeIn,\n fadeOut,\n } = options;\n\n const sampleRate = audioBuffer.sampleRate;\n\n return createClip({\n audioBuffer,\n startSample: Math.round(startTime * sampleRate),\n durationSamples: Math.round(duration * sampleRate),\n offsetSamples: Math.round(offset * sampleRate),\n gain,\n name,\n color,\n fadeIn,\n fadeOut,\n });\n}\n\n/**\n * Creates a new ClipTrack with sensible defaults\n */\nexport function createTrack(options: CreateTrackOptions): ClipTrack {\n const {\n name,\n clips = [],\n muted = false,\n soloed = false,\n volume = 1.0,\n pan = 0,\n color,\n height,\n } = options;\n\n return {\n id: generateId(),\n name,\n clips,\n muted,\n soloed,\n volume,\n pan,\n color,\n height,\n };\n}\n\n/**\n * Creates a new Timeline with sensible defaults\n */\nexport function createTimeline(\n tracks: ClipTrack[],\n sampleRate: number = 44100,\n options?: {\n name?: string;\n tempo?: number;\n timeSignature?: { numerator: number; denominator: number };\n }\n): Timeline {\n // Calculate total duration from all clips across all tracks (in seconds)\n const durationSamples = tracks.reduce((maxSamples, track) => {\n const trackSamples = track.clips.reduce((max, clip) => {\n return Math.max(max, clip.startSample + clip.durationSamples);\n }, 0);\n return Math.max(maxSamples, trackSamples);\n }, 0);\n\n const duration = durationSamples / sampleRate;\n\n return {\n tracks,\n duration,\n sampleRate,\n name: options?.name,\n tempo: options?.tempo,\n timeSignature: options?.timeSignature,\n };\n}\n\n/**\n * Generates a unique ID for clips and tracks\n */\nfunction generateId(): string {\n return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n}\n\n/**\n * Utility: Get all clips within a sample range\n */\nexport function getClipsInRange(\n track: ClipTrack,\n startSample: number,\n endSample: number\n): AudioClip[] {\n return track.clips.filter((clip) => {\n const clipEnd = clip.startSample + clip.durationSamples;\n // Clip overlaps with range if:\n // - Clip starts before range ends AND\n // - Clip ends after range starts\n return clip.startSample < endSample && clipEnd > startSample;\n });\n}\n\n/**\n * Utility: Get all clips at a specific sample position\n */\nexport function getClipsAtSample(track: ClipTrack, sample: number): AudioClip[] {\n return track.clips.filter((clip) => {\n const clipEnd = clip.startSample + clip.durationSamples;\n return sample >= clip.startSample && sample < clipEnd;\n });\n}\n\n/**\n * Utility: Check if two clips overlap\n */\nexport function clipsOverlap(clip1: AudioClip, clip2: AudioClip): boolean {\n const clip1End = clip1.startSample + clip1.durationSamples;\n const clip2End = clip2.startSample + clip2.durationSamples;\n\n return clip1.startSample < clip2End && clip1End > clip2.startSample;\n}\n\n/**\n * Utility: Sort clips by startSample\n */\nexport function sortClipsByTime(clips: AudioClip[]): AudioClip[] {\n return [...clips].sort((a, b) => a.startSample - b.startSample);\n}\n\n/**\n * Utility: Find gaps between clips (silent regions)\n */\nexport interface Gap {\n startSample: number;\n endSample: number;\n durationSamples: number;\n}\n\nexport function findGaps(track: ClipTrack): Gap[] {\n if (track.clips.length === 0) return [];\n\n const sorted = sortClipsByTime(track.clips);\n const gaps: Gap[] = [];\n\n for (let i = 0; i < sorted.length - 1; i++) {\n const currentClipEnd = sorted[i].startSample + sorted[i].durationSamples;\n const nextClipStart = sorted[i + 1].startSample;\n\n if (nextClipStart > currentClipEnd) {\n gaps.push({\n startSample: currentClipEnd,\n endSample: nextClipStart,\n durationSamples: nextClipStart - currentClipEnd,\n });\n }\n }\n\n return gaps;\n}\n","export interface WaveformConfig {\n sampleRate: number;\n samplesPerPixel: number;\n waveHeight?: number;\n waveOutlineColor?: string;\n waveFillColor?: string;\n waveProgressColor?: string;\n}\n\nexport interface AudioBuffer {\n length: number;\n duration: number;\n numberOfChannels: number;\n sampleRate: number;\n getChannelData(channel: number): Float32Array;\n}\n\nexport interface Track {\n id: string;\n name: string;\n src?: string | AudioBuffer; // Support both URL strings and AudioBuffer objects\n gain: number;\n muted: boolean;\n soloed: boolean;\n stereoPan: number;\n startTime: number;\n endTime?: number;\n fadeIn?: Fade;\n fadeOut?: Fade;\n cueIn?: number;\n cueOut?: number;\n}\n\n/**\n * Simple fade configuration\n */\nexport interface Fade {\n /** Duration of the fade in seconds */\n duration: number;\n /** Type of fade curve (default: 'linear') */\n type?: FadeType;\n}\n\nexport type FadeType = 'logarithmic' | 'linear' | 'sCurve' | 'exponential';\n\nexport interface PlaylistConfig {\n samplesPerPixel?: number;\n waveHeight?: number;\n container?: HTMLElement;\n isAutomaticScroll?: boolean;\n timescale?: boolean;\n colors?: {\n waveOutlineColor?: string;\n waveFillColor?: string;\n waveProgressColor?: string;\n };\n controls?: {\n show?: boolean;\n width?: number;\n };\n zoomLevels?: number[];\n}\n\nexport interface PlayoutState {\n isPlaying: boolean;\n isPaused: boolean;\n cursor: number;\n duration: number;\n}\n\nexport interface TimeSelection {\n start: number;\n end: number;\n}\n\nexport enum InteractionState {\n Cursor = 'cursor',\n Select = 'select',\n Shift = 'shift',\n FadeIn = 'fadein',\n FadeOut = 'fadeout',\n}\n\n// Export clip-based model types\nexport * from './clip';\n","export function samplesToSeconds(samples: number, sampleRate: number): number {\n return samples / sampleRate;\n}\n\nexport function secondsToSamples(seconds: number, sampleRate: number): number {\n return Math.ceil(seconds * sampleRate);\n}\n\nexport function samplesToPixels(samples: number, samplesPerPixel: number): number {\n return Math.floor(samples / samplesPerPixel);\n}\n\nexport function pixelsToSamples(pixels: number, samplesPerPixel: number): number {\n return Math.floor(pixels * samplesPerPixel);\n}\n\nexport function pixelsToSeconds(\n pixels: number,\n samplesPerPixel: number,\n sampleRate: number\n): number {\n return (pixels * samplesPerPixel) / sampleRate;\n}\n\nexport function secondsToPixels(\n seconds: number,\n samplesPerPixel: number,\n sampleRate: number\n): number {\n return Math.ceil((seconds * sampleRate) / samplesPerPixel);\n}\n"],"mappings":"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","names":["InteractionState"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/types/clip.ts","../src/types/index.ts","../src/utils/conversions.ts"],"sourcesContent":["export * from './types';\nexport * from './utils';\n","/**\n * Clip-Based Model Types\n *\n * These types support a professional multi-track editing model where:\n * - Each track can contain multiple audio clips\n * - Clips can be positioned anywhere on the timeline\n * - Clips have independent trim points (offset/duration)\n * - Gaps between clips are silent\n * - Clips can overlap (for crossfades)\n */\n\nimport { Fade } from './index';\n\n/**\n * WaveformData object from waveform-data.js library.\n * Supports resample() and slice() for dynamic zoom levels.\n * See: https://github.com/bbc/waveform-data.js\n */\nexport interface WaveformDataObject {\n /** Sample rate of the original audio */\n readonly sample_rate: number;\n /** Number of audio samples per pixel */\n readonly scale: number;\n /** Length of waveform data in pixels */\n readonly length: number;\n /** Bit depth (8 or 16) */\n readonly bits: number;\n /** Duration in seconds */\n readonly duration: number;\n /** Number of channels */\n readonly channels: number;\n /** Get channel data */\n channel: (index: number) => {\n min_array: () => number[];\n max_array: () => number[];\n };\n /** Resample to different scale */\n resample: (options: { scale: number } | { width: number }) => WaveformDataObject;\n /** Slice a portion of the waveform */\n slice: (options: { startTime: number; endTime: number } | { startIndex: number; endIndex: number }) => WaveformDataObject;\n}\n\n/**\n * Generic effects function type for track-level audio processing.\n *\n * The actual implementation receives Tone.js audio nodes. Using generic types\n * here to avoid circular dependencies with the playout package.\n *\n * @param graphEnd - The end of the track's audio graph (Tone.js Gain node)\n * @param destination - Where to connect the effects output (Tone.js ToneAudioNode)\n * @param isOffline - Whether rendering offline (for export)\n * @returns Optional cleanup function called when track is disposed\n *\n * @example\n * ```typescript\n * const trackEffects: TrackEffectsFunction = (graphEnd, destination, isOffline) => {\n * const reverb = new Tone.Reverb({ decay: 1.5 });\n * graphEnd.connect(reverb);\n * reverb.connect(destination);\n *\n * return () => {\n * reverb.dispose();\n * };\n * };\n * ```\n */\nexport type TrackEffectsFunction = (\n graphEnd: unknown,\n destination: unknown,\n isOffline: boolean\n) => void | (() => void);\n\n/**\n * Represents a single audio clip on the timeline\n *\n * IMPORTANT: All positions/durations are stored as SAMPLE COUNTS (integers)\n * to avoid floating-point precision errors. Convert to seconds only when\n * needed for playback using: seconds = samples / sampleRate\n */\nexport interface AudioClip {\n /** Unique identifier for this clip */\n id: string;\n\n /** The audio buffer containing the audio data */\n audioBuffer: AudioBuffer;\n\n /** Position on timeline where this clip starts (in samples at timeline sampleRate) */\n startSample: number;\n\n /** Duration of this clip (in samples) - how much of the audio buffer to play */\n durationSamples: number;\n\n /** Offset into the audio buffer where playback starts (in samples) - the \"trim start\" point */\n offsetSamples: number;\n\n /** Optional fade in effect */\n fadeIn?: Fade;\n\n /** Optional fade out effect */\n fadeOut?: Fade;\n\n /** Clip-specific gain/volume multiplier (0.0 to 1.0+) */\n gain: number;\n\n /** Optional label/name for this clip */\n name?: string;\n\n /** Optional color for visual distinction */\n color?: string;\n\n /**\n * Pre-computed waveform data from waveform-data.js library.\n * When provided, the library will use this instead of computing peaks from the audioBuffer.\n * Supports resampling to different zoom levels and slicing for clip trimming.\n * Load with: `const waveformData = await loadWaveformData('/path/to/peaks.dat')`\n */\n waveformData?: WaveformDataObject;\n}\n\n/**\n * Represents a track containing multiple audio clips\n */\nexport interface ClipTrack {\n /** Unique identifier for this track */\n id: string;\n\n /** Display name for this track */\n name: string;\n\n /** Array of audio clips on this track */\n clips: AudioClip[];\n\n /** Whether this track is muted */\n muted: boolean;\n\n /** Whether this track is soloed */\n soloed: boolean;\n\n /** Track volume (0.0 to 1.0+) */\n volume: number;\n\n /** Stereo pan (-1.0 = left, 0 = center, 1.0 = right) */\n pan: number;\n\n /** Optional track color for visual distinction */\n color?: string;\n\n /** Track height in pixels (for UI) */\n height?: number;\n\n /** Optional effects function for this track */\n effects?: TrackEffectsFunction;\n}\n\n/**\n * Represents the entire timeline/project\n */\nexport interface Timeline {\n /** All tracks in the timeline */\n tracks: ClipTrack[];\n\n /** Total timeline duration in seconds */\n duration: number;\n\n /** Sample rate for all audio (typically 44100 or 48000) */\n sampleRate: number;\n\n /** Optional project name */\n name?: string;\n\n /** Optional tempo (BPM) for grid snapping */\n tempo?: number;\n\n /** Optional time signature for grid snapping */\n timeSignature?: {\n numerator: number;\n denominator: number;\n };\n}\n\n/**\n * Options for creating a new audio clip (using sample counts)\n */\nexport interface CreateClipOptions {\n audioBuffer: AudioBuffer;\n startSample: number; // Position on timeline (in samples)\n durationSamples?: number; // Defaults to full buffer duration (in samples)\n offsetSamples?: number; // Defaults to 0\n gain?: number; // Defaults to 1.0\n name?: string;\n color?: string;\n fadeIn?: Fade;\n fadeOut?: Fade;\n /** Pre-computed waveform data from waveform-data.js (e.g., from BBC audiowaveform) */\n waveformData?: WaveformDataObject;\n}\n\n/**\n * Options for creating a new audio clip (using seconds for convenience)\n */\nexport interface CreateClipOptionsSeconds {\n audioBuffer: AudioBuffer;\n startTime: number; // Position on timeline (in seconds)\n duration?: number; // Defaults to full buffer duration (in seconds)\n offset?: number; // Defaults to 0 (in seconds)\n gain?: number; // Defaults to 1.0\n name?: string;\n color?: string;\n fadeIn?: Fade;\n fadeOut?: Fade;\n /** Pre-computed waveform data from waveform-data.js (e.g., from BBC audiowaveform) */\n waveformData?: WaveformDataObject;\n}\n\n/**\n * Options for creating a new track\n */\nexport interface CreateTrackOptions {\n name: string;\n clips?: AudioClip[];\n muted?: boolean;\n soloed?: boolean;\n volume?: number;\n pan?: number;\n color?: string;\n height?: number;\n}\n\n/**\n * Creates a new AudioClip with sensible defaults (using sample counts)\n */\nexport function createClip(options: CreateClipOptions): AudioClip {\n const {\n audioBuffer,\n startSample,\n durationSamples = audioBuffer.length, // Full buffer by default\n offsetSamples = 0,\n gain = 1.0,\n name,\n color,\n fadeIn,\n fadeOut,\n waveformData,\n } = options;\n\n return {\n id: generateId(),\n audioBuffer,\n startSample,\n durationSamples,\n offsetSamples,\n gain,\n name,\n color,\n fadeIn,\n fadeOut,\n waveformData,\n };\n}\n\n/**\n * Creates a new AudioClip from time-based values (convenience function)\n * Converts seconds to samples using the audioBuffer's sampleRate\n */\nexport function createClipFromSeconds(options: CreateClipOptionsSeconds): AudioClip {\n const {\n audioBuffer,\n startTime,\n duration = audioBuffer.duration,\n offset = 0,\n gain = 1.0,\n name,\n color,\n fadeIn,\n fadeOut,\n waveformData,\n } = options;\n\n const sampleRate = audioBuffer.sampleRate;\n\n return createClip({\n audioBuffer,\n startSample: Math.round(startTime * sampleRate),\n durationSamples: Math.round(duration * sampleRate),\n offsetSamples: Math.round(offset * sampleRate),\n gain,\n name,\n color,\n fadeIn,\n fadeOut,\n waveformData,\n });\n}\n\n/**\n * Creates a new ClipTrack with sensible defaults\n */\nexport function createTrack(options: CreateTrackOptions): ClipTrack {\n const {\n name,\n clips = [],\n muted = false,\n soloed = false,\n volume = 1.0,\n pan = 0,\n color,\n height,\n } = options;\n\n return {\n id: generateId(),\n name,\n clips,\n muted,\n soloed,\n volume,\n pan,\n color,\n height,\n };\n}\n\n/**\n * Creates a new Timeline with sensible defaults\n */\nexport function createTimeline(\n tracks: ClipTrack[],\n sampleRate: number = 44100,\n options?: {\n name?: string;\n tempo?: number;\n timeSignature?: { numerator: number; denominator: number };\n }\n): Timeline {\n // Calculate total duration from all clips across all tracks (in seconds)\n const durationSamples = tracks.reduce((maxSamples, track) => {\n const trackSamples = track.clips.reduce((max, clip) => {\n return Math.max(max, clip.startSample + clip.durationSamples);\n }, 0);\n return Math.max(maxSamples, trackSamples);\n }, 0);\n\n const duration = durationSamples / sampleRate;\n\n return {\n tracks,\n duration,\n sampleRate,\n name: options?.name,\n tempo: options?.tempo,\n timeSignature: options?.timeSignature,\n };\n}\n\n/**\n * Generates a unique ID for clips and tracks\n */\nfunction generateId(): string {\n return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n}\n\n/**\n * Utility: Get all clips within a sample range\n */\nexport function getClipsInRange(\n track: ClipTrack,\n startSample: number,\n endSample: number\n): AudioClip[] {\n return track.clips.filter((clip) => {\n const clipEnd = clip.startSample + clip.durationSamples;\n // Clip overlaps with range if:\n // - Clip starts before range ends AND\n // - Clip ends after range starts\n return clip.startSample < endSample && clipEnd > startSample;\n });\n}\n\n/**\n * Utility: Get all clips at a specific sample position\n */\nexport function getClipsAtSample(track: ClipTrack, sample: number): AudioClip[] {\n return track.clips.filter((clip) => {\n const clipEnd = clip.startSample + clip.durationSamples;\n return sample >= clip.startSample && sample < clipEnd;\n });\n}\n\n/**\n * Utility: Check if two clips overlap\n */\nexport function clipsOverlap(clip1: AudioClip, clip2: AudioClip): boolean {\n const clip1End = clip1.startSample + clip1.durationSamples;\n const clip2End = clip2.startSample + clip2.durationSamples;\n\n return clip1.startSample < clip2End && clip1End > clip2.startSample;\n}\n\n/**\n * Utility: Sort clips by startSample\n */\nexport function sortClipsByTime(clips: AudioClip[]): AudioClip[] {\n return [...clips].sort((a, b) => a.startSample - b.startSample);\n}\n\n/**\n * Utility: Find gaps between clips (silent regions)\n */\nexport interface Gap {\n startSample: number;\n endSample: number;\n durationSamples: number;\n}\n\nexport function findGaps(track: ClipTrack): Gap[] {\n if (track.clips.length === 0) return [];\n\n const sorted = sortClipsByTime(track.clips);\n const gaps: Gap[] = [];\n\n for (let i = 0; i < sorted.length - 1; i++) {\n const currentClipEnd = sorted[i].startSample + sorted[i].durationSamples;\n const nextClipStart = sorted[i + 1].startSample;\n\n if (nextClipStart > currentClipEnd) {\n gaps.push({\n startSample: currentClipEnd,\n endSample: nextClipStart,\n durationSamples: nextClipStart - currentClipEnd,\n });\n }\n }\n\n return gaps;\n}\n","export interface WaveformConfig {\n sampleRate: number;\n samplesPerPixel: number;\n waveHeight?: number;\n waveOutlineColor?: string;\n waveFillColor?: string;\n waveProgressColor?: string;\n}\n\nexport interface AudioBuffer {\n length: number;\n duration: number;\n numberOfChannels: number;\n sampleRate: number;\n getChannelData(channel: number): Float32Array;\n}\n\nexport interface Track {\n id: string;\n name: string;\n src?: string | AudioBuffer; // Support both URL strings and AudioBuffer objects\n gain: number;\n muted: boolean;\n soloed: boolean;\n stereoPan: number;\n startTime: number;\n endTime?: number;\n fadeIn?: Fade;\n fadeOut?: Fade;\n cueIn?: number;\n cueOut?: number;\n}\n\n/**\n * Simple fade configuration\n */\nexport interface Fade {\n /** Duration of the fade in seconds */\n duration: number;\n /** Type of fade curve (default: 'linear') */\n type?: FadeType;\n}\n\nexport type FadeType = 'logarithmic' | 'linear' | 'sCurve' | 'exponential';\n\nexport interface PlaylistConfig {\n samplesPerPixel?: number;\n waveHeight?: number;\n container?: HTMLElement;\n isAutomaticScroll?: boolean;\n timescale?: boolean;\n colors?: {\n waveOutlineColor?: string;\n waveFillColor?: string;\n waveProgressColor?: string;\n };\n controls?: {\n show?: boolean;\n width?: number;\n };\n zoomLevels?: number[];\n}\n\nexport interface PlayoutState {\n isPlaying: boolean;\n isPaused: boolean;\n cursor: number;\n duration: number;\n}\n\nexport interface TimeSelection {\n start: number;\n end: number;\n}\n\nexport enum InteractionState {\n Cursor = 'cursor',\n Select = 'select',\n Shift = 'shift',\n FadeIn = 'fadein',\n FadeOut = 'fadeout',\n}\n\n// Export clip-based model types\nexport * from './clip';\n","export function samplesToSeconds(samples: number, sampleRate: number): number {\n return samples / sampleRate;\n}\n\nexport function secondsToSamples(seconds: number, sampleRate: number): number {\n return Math.ceil(seconds * sampleRate);\n}\n\nexport function samplesToPixels(samples: number, samplesPerPixel: number): number {\n return Math.floor(samples / samplesPerPixel);\n}\n\nexport function pixelsToSamples(pixels: number, samplesPerPixel: number): number {\n return Math.floor(pixels * samplesPerPixel);\n}\n\nexport function pixelsToSeconds(\n pixels: number,\n samplesPerPixel: number,\n sampleRate: number\n): number {\n return (pixels * samplesPerPixel) / sampleRate;\n}\n\nexport function secondsToPixels(\n seconds: number,\n samplesPerPixel: number,\n sampleRate: number\n): number {\n return Math.ceil((seconds * sampleRate) / samplesPerPixel);\n}\n"],"mappings":"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","names":["InteractionState"]}
|
package/dist/index.mjs
CHANGED
|
@@ -10,7 +10,8 @@ function createClip(options) {
|
|
|
10
10
|
name,
|
|
11
11
|
color,
|
|
12
12
|
fadeIn,
|
|
13
|
-
fadeOut
|
|
13
|
+
fadeOut,
|
|
14
|
+
waveformData
|
|
14
15
|
} = options;
|
|
15
16
|
return {
|
|
16
17
|
id: generateId(),
|
|
@@ -22,7 +23,8 @@ function createClip(options) {
|
|
|
22
23
|
name,
|
|
23
24
|
color,
|
|
24
25
|
fadeIn,
|
|
25
|
-
fadeOut
|
|
26
|
+
fadeOut,
|
|
27
|
+
waveformData
|
|
26
28
|
};
|
|
27
29
|
}
|
|
28
30
|
function createClipFromSeconds(options) {
|
|
@@ -35,7 +37,8 @@ function createClipFromSeconds(options) {
|
|
|
35
37
|
name,
|
|
36
38
|
color,
|
|
37
39
|
fadeIn,
|
|
38
|
-
fadeOut
|
|
40
|
+
fadeOut,
|
|
41
|
+
waveformData
|
|
39
42
|
} = options;
|
|
40
43
|
const sampleRate = audioBuffer.sampleRate;
|
|
41
44
|
return createClip({
|
|
@@ -47,7 +50,8 @@ function createClipFromSeconds(options) {
|
|
|
47
50
|
name,
|
|
48
51
|
color,
|
|
49
52
|
fadeIn,
|
|
50
|
-
fadeOut
|
|
53
|
+
fadeOut,
|
|
54
|
+
waveformData
|
|
51
55
|
});
|
|
52
56
|
}
|
|
53
57
|
function createTrack(options) {
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types/clip.ts","../src/types/index.ts","../src/utils/conversions.ts"],"sourcesContent":["/**\n * Clip-Based Model Types\n *\n * These types support a professional multi-track editing model where:\n * - Each track can contain multiple audio clips\n * - Clips can be positioned anywhere on the timeline\n * - Clips have independent trim points (offset/duration)\n * - Gaps between clips are silent\n * - Clips can overlap (for crossfades)\n */\n\nimport { Fade } from './index';\n\n/**\n * Generic effects function type for track-level audio processing.\n *\n * The actual implementation receives Tone.js audio nodes. Using generic types\n * here to avoid circular dependencies with the playout package.\n *\n * @param graphEnd - The end of the track's audio graph (Tone.js Gain node)\n * @param destination - Where to connect the effects output (Tone.js ToneAudioNode)\n * @param isOffline - Whether rendering offline (for export)\n * @returns Optional cleanup function called when track is disposed\n *\n * @example\n * ```typescript\n * const trackEffects: TrackEffectsFunction = (graphEnd, destination, isOffline) => {\n * const reverb = new Tone.Reverb({ decay: 1.5 });\n * graphEnd.connect(reverb);\n * reverb.connect(destination);\n *\n * return () => {\n * reverb.dispose();\n * };\n * };\n * ```\n */\nexport type TrackEffectsFunction = (\n graphEnd: unknown,\n destination: unknown,\n isOffline: boolean\n) => void | (() => void);\n\n/**\n * Represents a single audio clip on the timeline\n *\n * IMPORTANT: All positions/durations are stored as SAMPLE COUNTS (integers)\n * to avoid floating-point precision errors. Convert to seconds only when\n * needed for playback using: seconds = samples / sampleRate\n */\nexport interface AudioClip {\n /** Unique identifier for this clip */\n id: string;\n\n /** The audio buffer containing the audio data */\n audioBuffer: AudioBuffer;\n\n /** Position on timeline where this clip starts (in samples at timeline sampleRate) */\n startSample: number;\n\n /** Duration of this clip (in samples) - how much of the audio buffer to play */\n durationSamples: number;\n\n /** Offset into the audio buffer where playback starts (in samples) - the \"trim start\" point */\n offsetSamples: number;\n\n /** Optional fade in effect */\n fadeIn?: Fade;\n\n /** Optional fade out effect */\n fadeOut?: Fade;\n\n /** Clip-specific gain/volume multiplier (0.0 to 1.0+) */\n gain: number;\n\n /** Optional label/name for this clip */\n name?: string;\n\n /** Optional color for visual distinction */\n color?: string;\n}\n\n/**\n * Represents a track containing multiple audio clips\n */\nexport interface ClipTrack {\n /** Unique identifier for this track */\n id: string;\n\n /** Display name for this track */\n name: string;\n\n /** Array of audio clips on this track */\n clips: AudioClip[];\n\n /** Whether this track is muted */\n muted: boolean;\n\n /** Whether this track is soloed */\n soloed: boolean;\n\n /** Track volume (0.0 to 1.0+) */\n volume: number;\n\n /** Stereo pan (-1.0 = left, 0 = center, 1.0 = right) */\n pan: number;\n\n /** Optional track color for visual distinction */\n color?: string;\n\n /** Track height in pixels (for UI) */\n height?: number;\n\n /** Optional effects function for this track */\n effects?: TrackEffectsFunction;\n}\n\n/**\n * Represents the entire timeline/project\n */\nexport interface Timeline {\n /** All tracks in the timeline */\n tracks: ClipTrack[];\n\n /** Total timeline duration in seconds */\n duration: number;\n\n /** Sample rate for all audio (typically 44100 or 48000) */\n sampleRate: number;\n\n /** Optional project name */\n name?: string;\n\n /** Optional tempo (BPM) for grid snapping */\n tempo?: number;\n\n /** Optional time signature for grid snapping */\n timeSignature?: {\n numerator: number;\n denominator: number;\n };\n}\n\n/**\n * Options for creating a new audio clip (using sample counts)\n */\nexport interface CreateClipOptions {\n audioBuffer: AudioBuffer;\n startSample: number; // Position on timeline (in samples)\n durationSamples?: number; // Defaults to full buffer duration (in samples)\n offsetSamples?: number; // Defaults to 0\n gain?: number; // Defaults to 1.0\n name?: string;\n color?: string;\n fadeIn?: Fade;\n fadeOut?: Fade;\n}\n\n/**\n * Options for creating a new audio clip (using seconds for convenience)\n */\nexport interface CreateClipOptionsSeconds {\n audioBuffer: AudioBuffer;\n startTime: number; // Position on timeline (in seconds)\n duration?: number; // Defaults to full buffer duration (in seconds)\n offset?: number; // Defaults to 0 (in seconds)\n gain?: number; // Defaults to 1.0\n name?: string;\n color?: string;\n fadeIn?: Fade;\n fadeOut?: Fade;\n}\n\n/**\n * Options for creating a new track\n */\nexport interface CreateTrackOptions {\n name: string;\n clips?: AudioClip[];\n muted?: boolean;\n soloed?: boolean;\n volume?: number;\n pan?: number;\n color?: string;\n height?: number;\n}\n\n/**\n * Creates a new AudioClip with sensible defaults (using sample counts)\n */\nexport function createClip(options: CreateClipOptions): AudioClip {\n const {\n audioBuffer,\n startSample,\n durationSamples = audioBuffer.length, // Full buffer by default\n offsetSamples = 0,\n gain = 1.0,\n name,\n color,\n fadeIn,\n fadeOut,\n } = options;\n\n return {\n id: generateId(),\n audioBuffer,\n startSample,\n durationSamples,\n offsetSamples,\n gain,\n name,\n color,\n fadeIn,\n fadeOut,\n };\n}\n\n/**\n * Creates a new AudioClip from time-based values (convenience function)\n * Converts seconds to samples using the audioBuffer's sampleRate\n */\nexport function createClipFromSeconds(options: CreateClipOptionsSeconds): AudioClip {\n const {\n audioBuffer,\n startTime,\n duration = audioBuffer.duration,\n offset = 0,\n gain = 1.0,\n name,\n color,\n fadeIn,\n fadeOut,\n } = options;\n\n const sampleRate = audioBuffer.sampleRate;\n\n return createClip({\n audioBuffer,\n startSample: Math.round(startTime * sampleRate),\n durationSamples: Math.round(duration * sampleRate),\n offsetSamples: Math.round(offset * sampleRate),\n gain,\n name,\n color,\n fadeIn,\n fadeOut,\n });\n}\n\n/**\n * Creates a new ClipTrack with sensible defaults\n */\nexport function createTrack(options: CreateTrackOptions): ClipTrack {\n const {\n name,\n clips = [],\n muted = false,\n soloed = false,\n volume = 1.0,\n pan = 0,\n color,\n height,\n } = options;\n\n return {\n id: generateId(),\n name,\n clips,\n muted,\n soloed,\n volume,\n pan,\n color,\n height,\n };\n}\n\n/**\n * Creates a new Timeline with sensible defaults\n */\nexport function createTimeline(\n tracks: ClipTrack[],\n sampleRate: number = 44100,\n options?: {\n name?: string;\n tempo?: number;\n timeSignature?: { numerator: number; denominator: number };\n }\n): Timeline {\n // Calculate total duration from all clips across all tracks (in seconds)\n const durationSamples = tracks.reduce((maxSamples, track) => {\n const trackSamples = track.clips.reduce((max, clip) => {\n return Math.max(max, clip.startSample + clip.durationSamples);\n }, 0);\n return Math.max(maxSamples, trackSamples);\n }, 0);\n\n const duration = durationSamples / sampleRate;\n\n return {\n tracks,\n duration,\n sampleRate,\n name: options?.name,\n tempo: options?.tempo,\n timeSignature: options?.timeSignature,\n };\n}\n\n/**\n * Generates a unique ID for clips and tracks\n */\nfunction generateId(): string {\n return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n}\n\n/**\n * Utility: Get all clips within a sample range\n */\nexport function getClipsInRange(\n track: ClipTrack,\n startSample: number,\n endSample: number\n): AudioClip[] {\n return track.clips.filter((clip) => {\n const clipEnd = clip.startSample + clip.durationSamples;\n // Clip overlaps with range if:\n // - Clip starts before range ends AND\n // - Clip ends after range starts\n return clip.startSample < endSample && clipEnd > startSample;\n });\n}\n\n/**\n * Utility: Get all clips at a specific sample position\n */\nexport function getClipsAtSample(track: ClipTrack, sample: number): AudioClip[] {\n return track.clips.filter((clip) => {\n const clipEnd = clip.startSample + clip.durationSamples;\n return sample >= clip.startSample && sample < clipEnd;\n });\n}\n\n/**\n * Utility: Check if two clips overlap\n */\nexport function clipsOverlap(clip1: AudioClip, clip2: AudioClip): boolean {\n const clip1End = clip1.startSample + clip1.durationSamples;\n const clip2End = clip2.startSample + clip2.durationSamples;\n\n return clip1.startSample < clip2End && clip1End > clip2.startSample;\n}\n\n/**\n * Utility: Sort clips by startSample\n */\nexport function sortClipsByTime(clips: AudioClip[]): AudioClip[] {\n return [...clips].sort((a, b) => a.startSample - b.startSample);\n}\n\n/**\n * Utility: Find gaps between clips (silent regions)\n */\nexport interface Gap {\n startSample: number;\n endSample: number;\n durationSamples: number;\n}\n\nexport function findGaps(track: ClipTrack): Gap[] {\n if (track.clips.length === 0) return [];\n\n const sorted = sortClipsByTime(track.clips);\n const gaps: Gap[] = [];\n\n for (let i = 0; i < sorted.length - 1; i++) {\n const currentClipEnd = sorted[i].startSample + sorted[i].durationSamples;\n const nextClipStart = sorted[i + 1].startSample;\n\n if (nextClipStart > currentClipEnd) {\n gaps.push({\n startSample: currentClipEnd,\n endSample: nextClipStart,\n durationSamples: nextClipStart - currentClipEnd,\n });\n }\n }\n\n return gaps;\n}\n","export interface WaveformConfig {\n sampleRate: number;\n samplesPerPixel: number;\n waveHeight?: number;\n waveOutlineColor?: string;\n waveFillColor?: string;\n waveProgressColor?: string;\n}\n\nexport interface AudioBuffer {\n length: number;\n duration: number;\n numberOfChannels: number;\n sampleRate: number;\n getChannelData(channel: number): Float32Array;\n}\n\nexport interface Track {\n id: string;\n name: string;\n src?: string | AudioBuffer; // Support both URL strings and AudioBuffer objects\n gain: number;\n muted: boolean;\n soloed: boolean;\n stereoPan: number;\n startTime: number;\n endTime?: number;\n fadeIn?: Fade;\n fadeOut?: Fade;\n cueIn?: number;\n cueOut?: number;\n}\n\n/**\n * Simple fade configuration\n */\nexport interface Fade {\n /** Duration of the fade in seconds */\n duration: number;\n /** Type of fade curve (default: 'linear') */\n type?: FadeType;\n}\n\nexport type FadeType = 'logarithmic' | 'linear' | 'sCurve' | 'exponential';\n\nexport interface PlaylistConfig {\n samplesPerPixel?: number;\n waveHeight?: number;\n container?: HTMLElement;\n isAutomaticScroll?: boolean;\n timescale?: boolean;\n colors?: {\n waveOutlineColor?: string;\n waveFillColor?: string;\n waveProgressColor?: string;\n };\n controls?: {\n show?: boolean;\n width?: number;\n };\n zoomLevels?: number[];\n}\n\nexport interface PlayoutState {\n isPlaying: boolean;\n isPaused: boolean;\n cursor: number;\n duration: number;\n}\n\nexport interface TimeSelection {\n start: number;\n end: number;\n}\n\nexport enum InteractionState {\n Cursor = 'cursor',\n Select = 'select',\n Shift = 'shift',\n FadeIn = 'fadein',\n FadeOut = 'fadeout',\n}\n\n// Export clip-based model types\nexport * from './clip';\n","export function samplesToSeconds(samples: number, sampleRate: number): number {\n return samples / sampleRate;\n}\n\nexport function secondsToSamples(seconds: number, sampleRate: number): number {\n return Math.ceil(seconds * sampleRate);\n}\n\nexport function samplesToPixels(samples: number, samplesPerPixel: number): number {\n return Math.floor(samples / samplesPerPixel);\n}\n\nexport function pixelsToSamples(pixels: number, samplesPerPixel: number): number {\n return Math.floor(pixels * samplesPerPixel);\n}\n\nexport function pixelsToSeconds(\n pixels: number,\n samplesPerPixel: number,\n sampleRate: number\n): number {\n return (pixels * samplesPerPixel) / sampleRate;\n}\n\nexport function secondsToPixels(\n seconds: number,\n samplesPerPixel: number,\n sampleRate: number\n): number {\n return Math.ceil((seconds * sampleRate) / samplesPerPixel);\n}\n"],"mappings":"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","names":["InteractionState"]}
|
|
1
|
+
{"version":3,"sources":["../src/types/clip.ts","../src/types/index.ts","../src/utils/conversions.ts"],"sourcesContent":["/**\n * Clip-Based Model Types\n *\n * These types support a professional multi-track editing model where:\n * - Each track can contain multiple audio clips\n * - Clips can be positioned anywhere on the timeline\n * - Clips have independent trim points (offset/duration)\n * - Gaps between clips are silent\n * - Clips can overlap (for crossfades)\n */\n\nimport { Fade } from './index';\n\n/**\n * WaveformData object from waveform-data.js library.\n * Supports resample() and slice() for dynamic zoom levels.\n * See: https://github.com/bbc/waveform-data.js\n */\nexport interface WaveformDataObject {\n /** Sample rate of the original audio */\n readonly sample_rate: number;\n /** Number of audio samples per pixel */\n readonly scale: number;\n /** Length of waveform data in pixels */\n readonly length: number;\n /** Bit depth (8 or 16) */\n readonly bits: number;\n /** Duration in seconds */\n readonly duration: number;\n /** Number of channels */\n readonly channels: number;\n /** Get channel data */\n channel: (index: number) => {\n min_array: () => number[];\n max_array: () => number[];\n };\n /** Resample to different scale */\n resample: (options: { scale: number } | { width: number }) => WaveformDataObject;\n /** Slice a portion of the waveform */\n slice: (options: { startTime: number; endTime: number } | { startIndex: number; endIndex: number }) => WaveformDataObject;\n}\n\n/**\n * Generic effects function type for track-level audio processing.\n *\n * The actual implementation receives Tone.js audio nodes. Using generic types\n * here to avoid circular dependencies with the playout package.\n *\n * @param graphEnd - The end of the track's audio graph (Tone.js Gain node)\n * @param destination - Where to connect the effects output (Tone.js ToneAudioNode)\n * @param isOffline - Whether rendering offline (for export)\n * @returns Optional cleanup function called when track is disposed\n *\n * @example\n * ```typescript\n * const trackEffects: TrackEffectsFunction = (graphEnd, destination, isOffline) => {\n * const reverb = new Tone.Reverb({ decay: 1.5 });\n * graphEnd.connect(reverb);\n * reverb.connect(destination);\n *\n * return () => {\n * reverb.dispose();\n * };\n * };\n * ```\n */\nexport type TrackEffectsFunction = (\n graphEnd: unknown,\n destination: unknown,\n isOffline: boolean\n) => void | (() => void);\n\n/**\n * Represents a single audio clip on the timeline\n *\n * IMPORTANT: All positions/durations are stored as SAMPLE COUNTS (integers)\n * to avoid floating-point precision errors. Convert to seconds only when\n * needed for playback using: seconds = samples / sampleRate\n */\nexport interface AudioClip {\n /** Unique identifier for this clip */\n id: string;\n\n /** The audio buffer containing the audio data */\n audioBuffer: AudioBuffer;\n\n /** Position on timeline where this clip starts (in samples at timeline sampleRate) */\n startSample: number;\n\n /** Duration of this clip (in samples) - how much of the audio buffer to play */\n durationSamples: number;\n\n /** Offset into the audio buffer where playback starts (in samples) - the \"trim start\" point */\n offsetSamples: number;\n\n /** Optional fade in effect */\n fadeIn?: Fade;\n\n /** Optional fade out effect */\n fadeOut?: Fade;\n\n /** Clip-specific gain/volume multiplier (0.0 to 1.0+) */\n gain: number;\n\n /** Optional label/name for this clip */\n name?: string;\n\n /** Optional color for visual distinction */\n color?: string;\n\n /**\n * Pre-computed waveform data from waveform-data.js library.\n * When provided, the library will use this instead of computing peaks from the audioBuffer.\n * Supports resampling to different zoom levels and slicing for clip trimming.\n * Load with: `const waveformData = await loadWaveformData('/path/to/peaks.dat')`\n */\n waveformData?: WaveformDataObject;\n}\n\n/**\n * Represents a track containing multiple audio clips\n */\nexport interface ClipTrack {\n /** Unique identifier for this track */\n id: string;\n\n /** Display name for this track */\n name: string;\n\n /** Array of audio clips on this track */\n clips: AudioClip[];\n\n /** Whether this track is muted */\n muted: boolean;\n\n /** Whether this track is soloed */\n soloed: boolean;\n\n /** Track volume (0.0 to 1.0+) */\n volume: number;\n\n /** Stereo pan (-1.0 = left, 0 = center, 1.0 = right) */\n pan: number;\n\n /** Optional track color for visual distinction */\n color?: string;\n\n /** Track height in pixels (for UI) */\n height?: number;\n\n /** Optional effects function for this track */\n effects?: TrackEffectsFunction;\n}\n\n/**\n * Represents the entire timeline/project\n */\nexport interface Timeline {\n /** All tracks in the timeline */\n tracks: ClipTrack[];\n\n /** Total timeline duration in seconds */\n duration: number;\n\n /** Sample rate for all audio (typically 44100 or 48000) */\n sampleRate: number;\n\n /** Optional project name */\n name?: string;\n\n /** Optional tempo (BPM) for grid snapping */\n tempo?: number;\n\n /** Optional time signature for grid snapping */\n timeSignature?: {\n numerator: number;\n denominator: number;\n };\n}\n\n/**\n * Options for creating a new audio clip (using sample counts)\n */\nexport interface CreateClipOptions {\n audioBuffer: AudioBuffer;\n startSample: number; // Position on timeline (in samples)\n durationSamples?: number; // Defaults to full buffer duration (in samples)\n offsetSamples?: number; // Defaults to 0\n gain?: number; // Defaults to 1.0\n name?: string;\n color?: string;\n fadeIn?: Fade;\n fadeOut?: Fade;\n /** Pre-computed waveform data from waveform-data.js (e.g., from BBC audiowaveform) */\n waveformData?: WaveformDataObject;\n}\n\n/**\n * Options for creating a new audio clip (using seconds for convenience)\n */\nexport interface CreateClipOptionsSeconds {\n audioBuffer: AudioBuffer;\n startTime: number; // Position on timeline (in seconds)\n duration?: number; // Defaults to full buffer duration (in seconds)\n offset?: number; // Defaults to 0 (in seconds)\n gain?: number; // Defaults to 1.0\n name?: string;\n color?: string;\n fadeIn?: Fade;\n fadeOut?: Fade;\n /** Pre-computed waveform data from waveform-data.js (e.g., from BBC audiowaveform) */\n waveformData?: WaveformDataObject;\n}\n\n/**\n * Options for creating a new track\n */\nexport interface CreateTrackOptions {\n name: string;\n clips?: AudioClip[];\n muted?: boolean;\n soloed?: boolean;\n volume?: number;\n pan?: number;\n color?: string;\n height?: number;\n}\n\n/**\n * Creates a new AudioClip with sensible defaults (using sample counts)\n */\nexport function createClip(options: CreateClipOptions): AudioClip {\n const {\n audioBuffer,\n startSample,\n durationSamples = audioBuffer.length, // Full buffer by default\n offsetSamples = 0,\n gain = 1.0,\n name,\n color,\n fadeIn,\n fadeOut,\n waveformData,\n } = options;\n\n return {\n id: generateId(),\n audioBuffer,\n startSample,\n durationSamples,\n offsetSamples,\n gain,\n name,\n color,\n fadeIn,\n fadeOut,\n waveformData,\n };\n}\n\n/**\n * Creates a new AudioClip from time-based values (convenience function)\n * Converts seconds to samples using the audioBuffer's sampleRate\n */\nexport function createClipFromSeconds(options: CreateClipOptionsSeconds): AudioClip {\n const {\n audioBuffer,\n startTime,\n duration = audioBuffer.duration,\n offset = 0,\n gain = 1.0,\n name,\n color,\n fadeIn,\n fadeOut,\n waveformData,\n } = options;\n\n const sampleRate = audioBuffer.sampleRate;\n\n return createClip({\n audioBuffer,\n startSample: Math.round(startTime * sampleRate),\n durationSamples: Math.round(duration * sampleRate),\n offsetSamples: Math.round(offset * sampleRate),\n gain,\n name,\n color,\n fadeIn,\n fadeOut,\n waveformData,\n });\n}\n\n/**\n * Creates a new ClipTrack with sensible defaults\n */\nexport function createTrack(options: CreateTrackOptions): ClipTrack {\n const {\n name,\n clips = [],\n muted = false,\n soloed = false,\n volume = 1.0,\n pan = 0,\n color,\n height,\n } = options;\n\n return {\n id: generateId(),\n name,\n clips,\n muted,\n soloed,\n volume,\n pan,\n color,\n height,\n };\n}\n\n/**\n * Creates a new Timeline with sensible defaults\n */\nexport function createTimeline(\n tracks: ClipTrack[],\n sampleRate: number = 44100,\n options?: {\n name?: string;\n tempo?: number;\n timeSignature?: { numerator: number; denominator: number };\n }\n): Timeline {\n // Calculate total duration from all clips across all tracks (in seconds)\n const durationSamples = tracks.reduce((maxSamples, track) => {\n const trackSamples = track.clips.reduce((max, clip) => {\n return Math.max(max, clip.startSample + clip.durationSamples);\n }, 0);\n return Math.max(maxSamples, trackSamples);\n }, 0);\n\n const duration = durationSamples / sampleRate;\n\n return {\n tracks,\n duration,\n sampleRate,\n name: options?.name,\n tempo: options?.tempo,\n timeSignature: options?.timeSignature,\n };\n}\n\n/**\n * Generates a unique ID for clips and tracks\n */\nfunction generateId(): string {\n return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n}\n\n/**\n * Utility: Get all clips within a sample range\n */\nexport function getClipsInRange(\n track: ClipTrack,\n startSample: number,\n endSample: number\n): AudioClip[] {\n return track.clips.filter((clip) => {\n const clipEnd = clip.startSample + clip.durationSamples;\n // Clip overlaps with range if:\n // - Clip starts before range ends AND\n // - Clip ends after range starts\n return clip.startSample < endSample && clipEnd > startSample;\n });\n}\n\n/**\n * Utility: Get all clips at a specific sample position\n */\nexport function getClipsAtSample(track: ClipTrack, sample: number): AudioClip[] {\n return track.clips.filter((clip) => {\n const clipEnd = clip.startSample + clip.durationSamples;\n return sample >= clip.startSample && sample < clipEnd;\n });\n}\n\n/**\n * Utility: Check if two clips overlap\n */\nexport function clipsOverlap(clip1: AudioClip, clip2: AudioClip): boolean {\n const clip1End = clip1.startSample + clip1.durationSamples;\n const clip2End = clip2.startSample + clip2.durationSamples;\n\n return clip1.startSample < clip2End && clip1End > clip2.startSample;\n}\n\n/**\n * Utility: Sort clips by startSample\n */\nexport function sortClipsByTime(clips: AudioClip[]): AudioClip[] {\n return [...clips].sort((a, b) => a.startSample - b.startSample);\n}\n\n/**\n * Utility: Find gaps between clips (silent regions)\n */\nexport interface Gap {\n startSample: number;\n endSample: number;\n durationSamples: number;\n}\n\nexport function findGaps(track: ClipTrack): Gap[] {\n if (track.clips.length === 0) return [];\n\n const sorted = sortClipsByTime(track.clips);\n const gaps: Gap[] = [];\n\n for (let i = 0; i < sorted.length - 1; i++) {\n const currentClipEnd = sorted[i].startSample + sorted[i].durationSamples;\n const nextClipStart = sorted[i + 1].startSample;\n\n if (nextClipStart > currentClipEnd) {\n gaps.push({\n startSample: currentClipEnd,\n endSample: nextClipStart,\n durationSamples: nextClipStart - currentClipEnd,\n });\n }\n }\n\n return gaps;\n}\n","export interface WaveformConfig {\n sampleRate: number;\n samplesPerPixel: number;\n waveHeight?: number;\n waveOutlineColor?: string;\n waveFillColor?: string;\n waveProgressColor?: string;\n}\n\nexport interface AudioBuffer {\n length: number;\n duration: number;\n numberOfChannels: number;\n sampleRate: number;\n getChannelData(channel: number): Float32Array;\n}\n\nexport interface Track {\n id: string;\n name: string;\n src?: string | AudioBuffer; // Support both URL strings and AudioBuffer objects\n gain: number;\n muted: boolean;\n soloed: boolean;\n stereoPan: number;\n startTime: number;\n endTime?: number;\n fadeIn?: Fade;\n fadeOut?: Fade;\n cueIn?: number;\n cueOut?: number;\n}\n\n/**\n * Simple fade configuration\n */\nexport interface Fade {\n /** Duration of the fade in seconds */\n duration: number;\n /** Type of fade curve (default: 'linear') */\n type?: FadeType;\n}\n\nexport type FadeType = 'logarithmic' | 'linear' | 'sCurve' | 'exponential';\n\nexport interface PlaylistConfig {\n samplesPerPixel?: number;\n waveHeight?: number;\n container?: HTMLElement;\n isAutomaticScroll?: boolean;\n timescale?: boolean;\n colors?: {\n waveOutlineColor?: string;\n waveFillColor?: string;\n waveProgressColor?: string;\n };\n controls?: {\n show?: boolean;\n width?: number;\n };\n zoomLevels?: number[];\n}\n\nexport interface PlayoutState {\n isPlaying: boolean;\n isPaused: boolean;\n cursor: number;\n duration: number;\n}\n\nexport interface TimeSelection {\n start: number;\n end: number;\n}\n\nexport enum InteractionState {\n Cursor = 'cursor',\n Select = 'select',\n Shift = 'shift',\n FadeIn = 'fadein',\n FadeOut = 'fadeout',\n}\n\n// Export clip-based model types\nexport * from './clip';\n","export function samplesToSeconds(samples: number, sampleRate: number): number {\n return samples / sampleRate;\n}\n\nexport function secondsToSamples(seconds: number, sampleRate: number): number {\n return Math.ceil(seconds * sampleRate);\n}\n\nexport function samplesToPixels(samples: number, samplesPerPixel: number): number {\n return Math.floor(samples / samplesPerPixel);\n}\n\nexport function pixelsToSamples(pixels: number, samplesPerPixel: number): number {\n return Math.floor(pixels * samplesPerPixel);\n}\n\nexport function pixelsToSeconds(\n pixels: number,\n samplesPerPixel: number,\n sampleRate: number\n): number {\n return (pixels * samplesPerPixel) / sampleRate;\n}\n\nexport function secondsToPixels(\n seconds: number,\n samplesPerPixel: number,\n sampleRate: number\n): number {\n return Math.ceil((seconds * sampleRate) / samplesPerPixel);\n}\n"],"mappings":"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","names":["InteractionState"]}
|