@waveform-playlist/playout 5.0.0-alpha.7 → 5.0.0-alpha.9
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.js +24 -9
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +24 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -265,10 +265,24 @@ var ToneTrack = class {
|
|
|
265
265
|
this.track.soloed = soloed;
|
|
266
266
|
}
|
|
267
267
|
play(when, offset = 0, duration) {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
268
|
+
this.clips.forEach((clipPlayer) => {
|
|
269
|
+
clipPlayer.player.stop();
|
|
270
|
+
clipPlayer.player.disconnect();
|
|
271
|
+
clipPlayer.player.dispose();
|
|
272
|
+
const newPlayer = new import_tone.Player({
|
|
273
|
+
url: clipPlayer.clipInfo.buffer,
|
|
274
|
+
loop: false,
|
|
275
|
+
onstop: () => {
|
|
276
|
+
this.activePlayers--;
|
|
277
|
+
if (this.activePlayers === 0 && this.onStopCallback) {
|
|
278
|
+
this.onStopCallback();
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
newPlayer.connect(clipPlayer.fadeGain);
|
|
283
|
+
clipPlayer.player = newPlayer;
|
|
284
|
+
clipPlayer.pausedPosition = 0;
|
|
285
|
+
});
|
|
272
286
|
this.activePlayers = 0;
|
|
273
287
|
this.clips.forEach((clipPlayer) => {
|
|
274
288
|
const { player, clipInfo } = clipPlayer;
|
|
@@ -277,21 +291,22 @@ var ToneTrack = class {
|
|
|
277
291
|
const clipEnd = clipInfo.startTime + clipInfo.duration;
|
|
278
292
|
if (playbackPosition < clipEnd) {
|
|
279
293
|
this.activePlayers++;
|
|
280
|
-
|
|
294
|
+
const currentTime = when ?? (0, import_tone.now)();
|
|
295
|
+
clipPlayer.playStartTime = currentTime;
|
|
281
296
|
if (playbackPosition >= clipStart) {
|
|
282
297
|
const clipOffset = playbackPosition - clipStart + clipInfo.offset;
|
|
283
298
|
const remainingDuration = clipInfo.duration - (playbackPosition - clipStart);
|
|
284
299
|
const clipDuration = duration ? Math.min(duration, remainingDuration) : remainingDuration;
|
|
285
300
|
clipPlayer.pausedPosition = clipOffset;
|
|
286
|
-
this.scheduleFades(clipPlayer,
|
|
287
|
-
player.start(
|
|
301
|
+
this.scheduleFades(clipPlayer, currentTime, clipOffset);
|
|
302
|
+
player.start(currentTime, clipOffset, clipDuration);
|
|
288
303
|
} else {
|
|
289
304
|
const delay = clipStart - playbackPosition;
|
|
290
305
|
const clipDuration = duration ? Math.min(duration - delay, clipInfo.duration) : clipInfo.duration;
|
|
291
306
|
if (delay < (duration ?? Infinity)) {
|
|
292
307
|
clipPlayer.pausedPosition = clipInfo.offset;
|
|
293
|
-
this.scheduleFades(clipPlayer,
|
|
294
|
-
player.start(
|
|
308
|
+
this.scheduleFades(clipPlayer, currentTime + delay, clipInfo.offset);
|
|
309
|
+
player.start(currentTime + delay, clipInfo.offset, clipDuration);
|
|
295
310
|
} else {
|
|
296
311
|
this.activePlayers--;
|
|
297
312
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/TonePlayout.ts","../src/ToneTrack.ts","../src/fades.ts","../src/audioContext.ts","../src/mediaStreamSourceManager.ts"],"sourcesContent":["export { TonePlayout } from './TonePlayout';\nexport { ToneTrack } from './ToneTrack';\nexport type { TonePlayoutOptions, EffectsFunction } from './TonePlayout';\nexport type { ToneTrackOptions, TrackEffectsFunction } from './ToneTrack';\n\n// Export global AudioContext manager\nexport {\n getGlobalContext,\n getGlobalAudioContext,\n getGlobalToneContext,\n resumeGlobalAudioContext,\n getGlobalAudioContextState,\n closeGlobalAudioContext,\n} from './audioContext';\n\n// Export MediaStreamSource manager\nexport {\n getMediaStreamSource,\n releaseMediaStreamSource,\n hasMediaStreamSource,\n} from './mediaStreamSourceManager';\n\n// Export fade utilities\nexport {\n applyFadeIn,\n applyFadeOut,\n type FadeConfig,\n type FadeType,\n} from './fades';\n","// Named imports for tree-shaking\nimport {\n Volume,\n ToneAudioNode,\n getDestination,\n start,\n now,\n getTransport,\n getContext,\n BaseContext,\n} from 'tone';\nimport { ToneTrack, ToneTrackOptions } from './ToneTrack';\n\n// Effects function no longer receives ToneLib - effects should import Tone themselves\nexport type EffectsFunction = (masterGainNode: Volume, destination: ToneAudioNode, isOffline: boolean) => void | (() => void);\n\nexport interface TonePlayoutOptions {\n tracks?: ToneTrack[];\n masterGain?: number;\n effects?: EffectsFunction;\n}\n\nexport class TonePlayout {\n private tracks: Map<string, ToneTrack> = new Map();\n private masterVolume: Volume;\n private isInitialized = false;\n private soloedTracks: Set<string> = new Set();\n private manualMuteState: Map<string, boolean> = new Map();\n private effectsCleanup?: () => void;\n private onPlaybackCompleteCallback?: () => void;\n private activeTracks: Map<string, number> = new Map(); // Map track ID to session ID\n private playbackSessionId: number = 0;\n\n constructor(options: TonePlayoutOptions = {}) {\n this.masterVolume = new Volume(this.gainToDb(options.masterGain ?? 1));\n\n // Setup effects chain if provided, otherwise connect directly to destination\n if (options.effects) {\n const cleanup = options.effects(this.masterVolume, getDestination(), false);\n if (cleanup) {\n this.effectsCleanup = cleanup;\n }\n } else {\n this.masterVolume.toDestination();\n }\n\n if (options.tracks) {\n options.tracks.forEach(track => {\n this.tracks.set(track.id, track);\n // Initialize manual mute state for constructor-provided tracks\n this.manualMuteState.set(track.id, track.muted);\n });\n }\n }\n\n private gainToDb(gain: number): number {\n return 20 * Math.log10(gain);\n }\n\n async init(): Promise<void> {\n if (this.isInitialized) return;\n\n await start();\n this.isInitialized = true;\n }\n\n addTrack(trackOptions: ToneTrackOptions): ToneTrack {\n // Ensure tracks connect to master volume instead of destination\n const optionsWithDestination = {\n ...trackOptions,\n destination: this.masterVolume,\n };\n const toneTrack = new ToneTrack(optionsWithDestination);\n this.tracks.set(toneTrack.id, toneTrack);\n // Initialize manual mute state from track options\n this.manualMuteState.set(toneTrack.id, trackOptions.track.muted ?? false);\n // Initialize solo state from track options\n if (trackOptions.track.soloed) {\n this.soloedTracks.add(toneTrack.id);\n }\n return toneTrack;\n }\n\n /**\n * Apply solo muting after all tracks have been added.\n * Call this after adding all tracks to ensure solo logic is applied correctly.\n */\n applyInitialSoloState(): void {\n this.updateSoloMuting();\n }\n\n removeTrack(trackId: string): void {\n const track = this.tracks.get(trackId);\n if (track) {\n track.dispose();\n this.tracks.delete(trackId);\n this.manualMuteState.delete(trackId);\n this.soloedTracks.delete(trackId);\n }\n }\n\n getTrack(trackId: string): ToneTrack | undefined {\n return this.tracks.get(trackId);\n }\n\n play(when?: number, offset?: number, duration?: number): void {\n if (!this.isInitialized) {\n console.warn('TonePlayout not initialized. Call init() first.');\n return;\n }\n\n // Use now() as default, but call it here after init check (not in function signature)\n const startTime = when ?? now();\n const playbackPosition = offset ?? 0;\n\n // Increment session ID to invalidate old callbacks\n this.playbackSessionId++;\n const currentSessionId = this.playbackSessionId;\n\n // Clear active tracks and set up stop callbacks if duration is specified\n this.activeTracks.clear();\n\n // Play tracks based on their individual start times\n this.tracks.forEach((toneTrack) => {\n const trackStartTime = toneTrack.startTime;\n\n if (playbackPosition >= trackStartTime) {\n // Track should be playing - calculate buffer offset and start immediately\n const bufferOffset = playbackPosition - trackStartTime;\n\n if (duration !== undefined) {\n this.activeTracks.set(toneTrack.id, currentSessionId);\n toneTrack.setOnStopCallback(() => {\n // Only process if this track is still in activeTracks with matching session ID\n if (this.activeTracks.get(toneTrack.id) === currentSessionId) {\n this.activeTracks.delete(toneTrack.id);\n if (this.activeTracks.size === 0 && this.onPlaybackCompleteCallback) {\n this.onPlaybackCompleteCallback();\n }\n }\n });\n }\n\n toneTrack.play(startTime, bufferOffset, duration);\n } else {\n // Track should start later - schedule it to start when playback reaches its start time\n const delay = trackStartTime - playbackPosition;\n\n if (duration !== undefined) {\n this.activeTracks.set(toneTrack.id, currentSessionId);\n toneTrack.setOnStopCallback(() => {\n // Only process if this track is still in activeTracks with matching session ID\n if (this.activeTracks.get(toneTrack.id) === currentSessionId) {\n this.activeTracks.delete(toneTrack.id);\n if (this.activeTracks.size === 0 && this.onPlaybackCompleteCallback) {\n this.onPlaybackCompleteCallback();\n }\n }\n });\n }\n\n toneTrack.play(startTime + delay, 0, duration);\n }\n });\n\n // Start transport\n if (offset !== undefined) {\n // Explicit offset provided - seek to that position\n getTransport().start(startTime, offset);\n } else {\n // No offset - resume from pause (Transport resumes from current position)\n getTransport().start(startTime);\n }\n }\n\n pause(): void {\n getTransport().pause();\n this.tracks.forEach(track => {\n track.pause();\n });\n }\n\n stop(): void {\n getTransport().stop();\n this.tracks.forEach(track => {\n track.stop();\n });\n }\n\n setMasterGain(gain: number): void {\n this.masterVolume.volume.value = this.gainToDb(gain);\n }\n\n setSolo(trackId: string, soloed: boolean): void {\n const track = this.tracks.get(trackId);\n if (track) {\n track.setSolo(soloed);\n if (soloed) {\n this.soloedTracks.add(trackId);\n } else {\n this.soloedTracks.delete(trackId);\n }\n\n // Update mute state of all tracks based on solo logic\n this.updateSoloMuting();\n }\n }\n\n private updateSoloMuting(): void {\n const hasSoloedTracks = this.soloedTracks.size > 0;\n\n this.tracks.forEach((track, id) => {\n if (hasSoloedTracks) {\n // If there are soloed tracks, mute all non-soloed tracks\n if (!this.soloedTracks.has(id)) {\n track.setMute(true);\n } else {\n // Restore manual mute state for soloed tracks\n const manuallyMuted = this.manualMuteState.get(id) ?? false;\n track.setMute(manuallyMuted);\n }\n } else {\n // No soloed tracks, restore original manual mute state for all tracks\n const manuallyMuted = this.manualMuteState.get(id) ?? false;\n track.setMute(manuallyMuted);\n }\n });\n }\n\n setMute(trackId: string, muted: boolean): void {\n const track = this.tracks.get(trackId);\n if (track) {\n // Store the manual mute state\n this.manualMuteState.set(trackId, muted);\n track.setMute(muted);\n }\n }\n\n getCurrentTime(): number {\n return getTransport().seconds;\n }\n\n seekTo(time: number): void {\n getTransport().seconds = time;\n }\n\n dispose(): void {\n this.tracks.forEach(track => {\n track.dispose();\n });\n this.tracks.clear();\n\n // Clean up effects if cleanup function was provided\n if (this.effectsCleanup) {\n this.effectsCleanup();\n }\n\n this.masterVolume.dispose();\n }\n\n get context(): BaseContext {\n return getContext();\n }\n\n get sampleRate(): number {\n return getContext().sampleRate;\n }\n\n setOnPlaybackComplete(callback: () => void): void {\n this.onPlaybackCompleteCallback = callback;\n }\n}\n","// Named imports for tree-shaking\nimport {\n Player,\n Volume,\n Gain,\n Panner,\n ToneAudioNode,\n getDestination,\n now,\n} from 'tone';\nimport { Track, type Fade } from '@waveform-playlist/core';\nimport { applyFadeIn, applyFadeOut } from './fades';\n\n// Effects function no longer receives ToneLib - effects should import Tone themselves\nexport type TrackEffectsFunction = (graphEnd: Gain, masterGainNode: ToneAudioNode, isOffline: boolean) => void | (() => void);\n\nexport interface ClipInfo {\n buffer: AudioBuffer;\n startTime: number; // When this clip starts in the track timeline (seconds)\n duration: number; // How long this clip plays (seconds)\n offset: number; // Where to start playing within the buffer (seconds)\n fadeIn?: Fade;\n fadeOut?: Fade;\n gain: number; // Clip-level gain\n}\n\nexport interface ToneTrackOptions {\n buffer?: AudioBuffer; // Legacy: single buffer (deprecated, use clips instead)\n clips?: ClipInfo[]; // Modern: array of clips\n track: Track;\n effects?: TrackEffectsFunction;\n destination?: ToneAudioNode;\n}\n\ninterface ClipPlayer {\n player: Player;\n clipInfo: ClipInfo;\n fadeGain: Gain;\n pausedPosition: number;\n playStartTime: number;\n}\n\nexport class ToneTrack {\n private clips: ClipPlayer[]; // Array of clip players\n private volumeNode: Volume;\n private panNode: Panner;\n private muteGain: Gain;\n private track: Track;\n private effectsCleanup?: () => void;\n private onStopCallback?: () => void;\n private activePlayers: number = 0; // Count of currently playing clips\n\n constructor(options: ToneTrackOptions) {\n this.track = options.track;\n\n // Create shared track-level nodes\n this.volumeNode = new Volume(this.gainToDb(options.track.gain));\n this.panNode = new Panner(options.track.stereoPan);\n this.muteGain = new Gain(options.track.muted ? 0 : 1);\n\n // Connect to destination or apply effects chain\n const destination = options.destination || getDestination();\n if (options.effects) {\n const cleanup = options.effects(this.muteGain, destination, false);\n if (cleanup) {\n this.effectsCleanup = cleanup;\n }\n } else {\n this.muteGain.connect(destination);\n }\n\n // Create clips array - support both legacy single buffer and modern clips array\n const clipInfos: ClipInfo[] = options.clips || (options.buffer ? [{\n buffer: options.buffer,\n startTime: 0, // Legacy: single buffer starts at timeline position 0\n duration: options.buffer.duration, // Legacy: play full buffer duration\n offset: 0,\n fadeIn: options.track.fadeIn,\n fadeOut: options.track.fadeOut,\n gain: 1,\n }] : []);\n\n // Create ClipPlayer for each clip\n this.clips = clipInfos.map(clipInfo => {\n const player = new Player({\n url: clipInfo.buffer,\n loop: false,\n onstop: () => {\n this.activePlayers--;\n if (this.activePlayers === 0 && this.onStopCallback) {\n this.onStopCallback();\n }\n },\n });\n\n const fadeGain = new Gain(clipInfo.gain);\n\n // Chain: Player -> FadeGain -> Volume -> Pan -> MuteGain\n player.connect(fadeGain);\n fadeGain.chain(this.volumeNode, this.panNode, this.muteGain);\n\n // Note: Fades are scheduled in play() method, not here in constructor,\n // because AudioParam automation requires absolute AudioContext time\n\n return {\n player,\n clipInfo,\n fadeGain,\n pausedPosition: 0,\n playStartTime: 0,\n };\n });\n }\n\n /**\n * Schedule fade envelopes for a clip at the given start time\n */\n private scheduleFades(clipPlayer: ClipPlayer, clipStartTime: number, clipOffset: number = 0): void {\n const { clipInfo, fadeGain } = clipPlayer;\n const audioParam = (fadeGain.gain as any)._param as AudioParam;\n\n // Cancel any previous automation\n audioParam.cancelScheduledValues(0);\n\n // Calculate how much of the clip we're skipping (for seeking)\n const skipTime = clipOffset - clipInfo.offset;\n\n // Apply fade in if it exists and we haven't skipped past it\n if (clipInfo.fadeIn && skipTime < clipInfo.fadeIn.duration) {\n const fadeInDuration = clipInfo.fadeIn.duration;\n\n if (skipTime <= 0) {\n // Starting from the beginning - full fade in\n applyFadeIn(\n audioParam,\n clipStartTime,\n fadeInDuration,\n clipInfo.fadeIn.type || 'linear',\n 0,\n clipInfo.gain\n );\n } else {\n // Starting partway through fade in - calculate partial fade\n const remainingFadeDuration = fadeInDuration - skipTime;\n const fadeProgress = skipTime / fadeInDuration;\n const startValue = clipInfo.gain * fadeProgress; // Approximate current fade value\n applyFadeIn(\n audioParam,\n clipStartTime,\n remainingFadeDuration,\n clipInfo.fadeIn.type || 'linear',\n startValue,\n clipInfo.gain\n );\n }\n } else {\n // No fade in or skipped past it - set to full gain\n audioParam.setValueAtTime(clipInfo.gain, clipStartTime);\n }\n\n // Apply fade out if it exists\n if (clipInfo.fadeOut) {\n const fadeOutStart = clipInfo.duration - clipInfo.fadeOut.duration;\n const fadeOutStartInClip = fadeOutStart - skipTime; // Relative to where we're starting\n\n if (fadeOutStartInClip > 0) {\n // Fade out hasn't started yet\n const absoluteFadeOutStart = clipStartTime + fadeOutStartInClip;\n applyFadeOut(\n audioParam,\n absoluteFadeOutStart,\n clipInfo.fadeOut.duration,\n clipInfo.fadeOut.type || 'linear',\n clipInfo.gain,\n 0\n );\n } else if (fadeOutStartInClip > -clipInfo.fadeOut.duration) {\n // We're starting partway through the fade out\n const elapsedFadeOut = -fadeOutStartInClip;\n const remainingFadeDuration = clipInfo.fadeOut.duration - elapsedFadeOut;\n const fadeProgress = elapsedFadeOut / clipInfo.fadeOut.duration;\n const startValue = clipInfo.gain * (1 - fadeProgress); // Approximate current fade value\n applyFadeOut(\n audioParam,\n clipStartTime,\n remainingFadeDuration,\n clipInfo.fadeOut.type || 'linear',\n startValue,\n 0\n );\n }\n // If fadeOutStartInClip <= -duration, we've skipped past the entire fade out\n }\n }\n\n private gainToDb(gain: number): number {\n return 20 * Math.log10(gain);\n }\n\n setVolume(gain: number): void {\n this.track.gain = gain;\n this.volumeNode.volume.value = this.gainToDb(gain);\n }\n\n setPan(pan: number): void {\n this.track.stereoPan = pan;\n this.panNode.pan.value = pan;\n }\n\n setMute(muted: boolean): void {\n this.track.muted = muted;\n this.muteGain.gain.value = muted ? 0 : 1;\n }\n\n setSolo(soloed: boolean): void {\n this.track.soloed = soloed;\n }\n\n play(when?: number, offset: number = 0, duration?: number): void {\n // Evaluate now() inside function body, not in parameter default (which is evaluated at module load time)\n const startWhen = when ?? now();\n\n // Stop any existing playback before starting new playback\n // This handles cases where play is called while still playing (e.g., selection replay)\n if (this.isPlaying) {\n this.stop();\n }\n\n this.activePlayers = 0;\n\n // Play each clip that should be active at this offset\n this.clips.forEach(clipPlayer => {\n const { player, clipInfo } = clipPlayer;\n\n // Calculate absolute timeline position we're starting from\n const playbackPosition = offset;\n\n // Check if this clip should be playing at this position\n const clipStart = clipInfo.startTime;\n const clipEnd = clipInfo.startTime + clipInfo.duration;\n\n if (playbackPosition < clipEnd) {\n // This clip should play\n this.activePlayers++;\n clipPlayer.playStartTime = now();\n\n if (playbackPosition >= clipStart) {\n // We're starting in the middle of this clip\n const clipOffset = playbackPosition - clipStart + clipInfo.offset;\n const remainingDuration = clipInfo.duration - (playbackPosition - clipStart);\n const clipDuration = duration ? Math.min(duration, remainingDuration) : remainingDuration;\n\n clipPlayer.pausedPosition = clipOffset;\n // Schedule fades at the actual playback start time\n this.scheduleFades(clipPlayer, startWhen, clipOffset);\n player.start(startWhen, clipOffset, clipDuration);\n } else {\n // This clip starts later - schedule it\n const delay = clipStart - playbackPosition;\n const clipDuration = duration ? Math.min(duration - delay, clipInfo.duration) : clipInfo.duration;\n\n if (delay < (duration ?? Infinity)) {\n clipPlayer.pausedPosition = clipInfo.offset;\n // Schedule fades at the delayed start time\n this.scheduleFades(clipPlayer, startWhen + delay, clipInfo.offset);\n player.start(startWhen + delay, clipInfo.offset, clipDuration);\n } else {\n this.activePlayers--;\n }\n }\n }\n });\n }\n\n pause(): void {\n // Stop all clips - both started and scheduled\n // Scheduled clips have state 'stopped' but still need to be cancelled\n this.clips.forEach(clipPlayer => {\n if (clipPlayer.player.state === 'started') {\n const elapsed = (now() - clipPlayer.playStartTime) * clipPlayer.player.playbackRate;\n clipPlayer.pausedPosition = clipPlayer.pausedPosition + elapsed;\n }\n // Always call stop() to cancel any scheduled playback\n clipPlayer.player.stop();\n });\n\n this.activePlayers = 0;\n }\n\n stop(when?: number): void {\n // Evaluate now() inside function body, not in parameter default (which is evaluated at module load time)\n const stopWhen = when ?? now();\n this.clips.forEach(clipPlayer => {\n clipPlayer.player.stop(stopWhen);\n clipPlayer.pausedPosition = 0;\n });\n this.activePlayers = 0;\n }\n\n dispose(): void {\n // Clean up effects if cleanup function was provided\n if (this.effectsCleanup) {\n this.effectsCleanup();\n }\n\n // Dispose all clip players\n this.clips.forEach(clipPlayer => {\n clipPlayer.player.dispose();\n clipPlayer.fadeGain.dispose();\n });\n\n // Dispose shared track nodes\n this.volumeNode.dispose();\n this.panNode.dispose();\n this.muteGain.dispose();\n }\n\n get id(): string {\n return this.track.id;\n }\n\n get duration(): number {\n // Return the end time of the last clip\n if (this.clips.length === 0) return 0;\n const lastClip = this.clips[this.clips.length - 1];\n return lastClip.clipInfo.startTime + lastClip.clipInfo.duration;\n }\n\n get buffer(): AudioBuffer {\n // For backward compatibility, return the first clip's buffer\n return this.clips[0]?.clipInfo.buffer;\n }\n\n get isPlaying(): boolean {\n // Track is playing if any clip is playing\n return this.clips.some(clipPlayer => clipPlayer.player.state === 'started');\n }\n\n get muted(): boolean {\n return this.track.muted;\n }\n\n get startTime(): number {\n // Return the track's start time from the Track object\n // This is the absolute timeline position where the track starts\n return this.track.startTime;\n }\n\n setOnStopCallback(callback: () => void): void {\n this.onStopCallback = callback;\n }\n}\n","/**\n * Fade utilities for Web Audio API\n *\n * Applies fade in/out envelopes to AudioParam (typically gain)\n * using various curve types.\n */\n\nexport type FadeType = 'linear' | 'logarithmic' | 'exponential' | 'sCurve';\n\n/**\n * Simple fade configuration - just duration and type\n */\nexport interface FadeConfig {\n /** Duration of the fade in seconds */\n duration: number;\n /** Type of fade curve (default: 'linear') */\n type?: FadeType;\n}\n\n/**\n * Generate a linear fade curve\n */\nfunction linearCurve(length: number, fadeIn: boolean): Float32Array {\n const curve = new Float32Array(length);\n const scale = length - 1;\n\n for (let i = 0; i < length; i++) {\n const x = i / scale;\n curve[i] = fadeIn ? x : 1 - x;\n }\n\n return curve;\n}\n\n/**\n * Generate an exponential fade curve\n */\nfunction exponentialCurve(length: number, fadeIn: boolean): Float32Array {\n const curve = new Float32Array(length);\n const scale = length - 1;\n\n for (let i = 0; i < length; i++) {\n const x = i / scale;\n const index = fadeIn ? i : length - 1 - i;\n curve[index] = Math.exp(2 * x - 1) / Math.E;\n }\n\n return curve;\n}\n\n/**\n * Generate an S-curve (sine-based smooth curve)\n */\nfunction sCurveCurve(length: number, fadeIn: boolean): Float32Array {\n const curve = new Float32Array(length);\n const phase = fadeIn ? Math.PI / 2 : -Math.PI / 2;\n\n for (let i = 0; i < length; i++) {\n curve[i] = Math.sin((Math.PI * i) / length - phase) / 2 + 0.5;\n }\n\n return curve;\n}\n\n/**\n * Generate a logarithmic fade curve\n */\nfunction logarithmicCurve(length: number, fadeIn: boolean, base: number = 10): Float32Array {\n const curve = new Float32Array(length);\n\n for (let i = 0; i < length; i++) {\n const index = fadeIn ? i : length - 1 - i;\n const x = i / length;\n curve[index] = Math.log(1 + base * x) / Math.log(1 + base);\n }\n\n return curve;\n}\n\n/**\n * Generate a fade curve of the specified type\n */\nfunction generateCurve(type: FadeType, length: number, fadeIn: boolean): Float32Array {\n switch (type) {\n case 'linear':\n return linearCurve(length, fadeIn);\n case 'exponential':\n return exponentialCurve(length, fadeIn);\n case 'sCurve':\n return sCurveCurve(length, fadeIn);\n case 'logarithmic':\n return logarithmicCurve(length, fadeIn);\n default:\n return linearCurve(length, fadeIn);\n }\n}\n\n/**\n * Apply a fade in to an AudioParam\n *\n * @param param - The AudioParam to apply the fade to (usually gain)\n * @param startTime - When the fade starts (in seconds, AudioContext time)\n * @param duration - Duration of the fade in seconds\n * @param type - Type of fade curve\n * @param startValue - Starting value (default: 0)\n * @param endValue - Ending value (default: 1)\n */\nexport function applyFadeIn(\n param: AudioParam,\n startTime: number,\n duration: number,\n type: FadeType = 'linear',\n startValue: number = 0,\n endValue: number = 1\n): void {\n if (duration <= 0) return;\n\n if (type === 'linear') {\n // Use native linear ramp for better performance\n param.setValueAtTime(startValue, startTime);\n param.linearRampToValueAtTime(endValue, startTime + duration);\n } else if (type === 'exponential') {\n // Exponential ramp can't start/end at 0, use small value\n param.setValueAtTime(Math.max(startValue, 0.001), startTime);\n param.exponentialRampToValueAtTime(Math.max(endValue, 0.001), startTime + duration);\n } else {\n // Use curve for sCurve and logarithmic\n const curve = generateCurve(type, 10000, true);\n // Scale curve to value range\n const scaledCurve = new Float32Array(curve.length);\n const range = endValue - startValue;\n for (let i = 0; i < curve.length; i++) {\n scaledCurve[i] = startValue + curve[i] * range;\n }\n param.setValueCurveAtTime(scaledCurve, startTime, duration);\n }\n}\n\n/**\n * Apply a fade out to an AudioParam\n *\n * @param param - The AudioParam to apply the fade to (usually gain)\n * @param startTime - When the fade starts (in seconds, AudioContext time)\n * @param duration - Duration of the fade in seconds\n * @param type - Type of fade curve\n * @param startValue - Starting value (default: 1)\n * @param endValue - Ending value (default: 0)\n */\nexport function applyFadeOut(\n param: AudioParam,\n startTime: number,\n duration: number,\n type: FadeType = 'linear',\n startValue: number = 1,\n endValue: number = 0\n): void {\n if (duration <= 0) return;\n\n if (type === 'linear') {\n // Use native linear ramp for better performance\n param.setValueAtTime(startValue, startTime);\n param.linearRampToValueAtTime(endValue, startTime + duration);\n } else if (type === 'exponential') {\n // Exponential ramp can't start/end at 0, use small value\n param.setValueAtTime(Math.max(startValue, 0.001), startTime);\n param.exponentialRampToValueAtTime(Math.max(endValue, 0.001), startTime + duration);\n } else {\n // Use curve for sCurve and logarithmic\n const curve = generateCurve(type, 10000, false);\n // Scale curve to value range\n const scaledCurve = new Float32Array(curve.length);\n const range = startValue - endValue;\n for (let i = 0; i < curve.length; i++) {\n scaledCurve[i] = endValue + curve[i] * range;\n }\n param.setValueCurveAtTime(scaledCurve, startTime, duration);\n }\n}\n","/**\n * Global AudioContext Manager\n *\n * Provides a single AudioContext shared across the entire application.\n * This context is used by Tone.js for playback and by all recording/monitoring hooks.\n *\n * Uses Tone.js's Context class which wraps standardized-audio-context for\n * cross-browser compatibility (fixes Firefox AudioListener issues).\n */\n\nimport { Context, setContext } from 'tone';\n\nlet globalToneContext: Context | null = null;\n\n/**\n * Get the global Tone.js Context\n * This is the main context for cross-browser audio operations.\n * Use context.createAudioWorkletNode(), context.createMediaStreamSource(), etc.\n * @returns The Tone.js Context instance\n */\nexport function getGlobalContext(): Context {\n if (!globalToneContext) {\n globalToneContext = new Context();\n setContext(globalToneContext);\n }\n return globalToneContext;\n}\n\n/**\n * Get or create the global AudioContext\n * Uses Tone.js Context for cross-browser compatibility\n * @returns The global AudioContext instance (rawContext from Tone.Context)\n */\nexport function getGlobalAudioContext(): AudioContext {\n return getGlobalContext().rawContext as AudioContext;\n}\n\n/**\n * @deprecated Use getGlobalContext() instead\n * Get the Tone.js Context's rawContext typed as IAudioContext\n * @returns The rawContext cast as IAudioContext\n */\nexport function getGlobalToneContext(): Context {\n return getGlobalContext();\n}\n\n/**\n * Resume the global AudioContext if it's suspended\n * Should be called in response to a user gesture (e.g., button click)\n * @returns Promise that resolves when context is running\n */\nexport async function resumeGlobalAudioContext(): Promise<void> {\n const context = getGlobalContext();\n if (context.state !== 'running') {\n await context.resume();\n }\n}\n\n/**\n * Get the current state of the global AudioContext\n * @returns The AudioContext state ('suspended', 'running', or 'closed')\n */\nexport function getGlobalAudioContextState(): AudioContextState {\n return globalToneContext?.rawContext.state || 'suspended';\n}\n\n/**\n * Close the global AudioContext\n * Should only be called when the application is shutting down\n */\nexport async function closeGlobalAudioContext(): Promise<void> {\n if (globalToneContext && globalToneContext.rawContext.state !== 'closed') {\n await globalToneContext.close();\n globalToneContext = null;\n }\n}\n","/**\n * MediaStreamSource Manager\n *\n * Manages MediaStreamAudioSourceNode instances to ensure only one source\n * is created per MediaStream per AudioContext.\n *\n * Web Audio API constraint: You can only create one MediaStreamAudioSourceNode\n * per MediaStream per AudioContext. Multiple attempts will fail or disconnect\n * previous sources.\n *\n * This manager ensures a single source is shared across multiple consumers\n * (e.g., AnalyserNode for VU meter, AudioWorkletNode for recording).\n *\n * NOTE: With Tone.js Context, you can also use context.createMediaStreamSource()\n * directly, which handles cross-browser compatibility internally.\n */\n\nimport { getContext } from 'tone';\n\n// Map of MediaStream -> MediaStreamAudioSourceNode\nconst streamSources = new Map<MediaStream, MediaStreamAudioSourceNode>();\n\n// Map of MediaStream -> cleanup handlers\nconst streamCleanupHandlers = new Map<MediaStream, () => void>();\n\n/**\n * Get or create a MediaStreamAudioSourceNode for the given stream\n *\n * @param stream - The MediaStream to create a source for\n * @returns MediaStreamAudioSourceNode that can be connected to multiple nodes\n *\n * @example\n * ```typescript\n * const source = getMediaStreamSource(stream);\n *\n * // Multiple consumers can connect to the same source\n * source.connect(analyserNode); // For VU meter\n * source.connect(workletNode); // For recording\n * ```\n */\nexport function getMediaStreamSource(\n stream: MediaStream\n): MediaStreamAudioSourceNode {\n // Return existing source if we have one for this stream\n if (streamSources.has(stream)) {\n return streamSources.get(stream)!;\n }\n\n // Create new source using Tone.js's shared context for cross-browser compatibility\n const context = getContext();\n const source = context.createMediaStreamSource(stream);\n streamSources.set(stream, source);\n\n // Set up cleanup when stream ends\n const cleanup = () => {\n source.disconnect();\n streamSources.delete(stream);\n streamCleanupHandlers.delete(stream);\n\n // Remove event listener\n stream.removeEventListener('ended', cleanup);\n stream.removeEventListener('inactive', cleanup);\n };\n\n streamCleanupHandlers.set(stream, cleanup);\n\n // Clean up when stream ends or becomes inactive\n stream.addEventListener('ended', cleanup);\n stream.addEventListener('inactive', cleanup);\n\n return source;\n}\n\n/**\n * Manually release a MediaStreamSource\n *\n * Normally you don't need to call this - cleanup happens automatically\n * when the stream ends. Only call this if you need to force cleanup.\n *\n * @param stream - The MediaStream to release the source for\n */\nexport function releaseMediaStreamSource(stream: MediaStream): void {\n const cleanup = streamCleanupHandlers.get(stream);\n if (cleanup) {\n cleanup();\n }\n}\n\n/**\n * Check if a MediaStreamSource exists for the given stream\n *\n * @param stream - The MediaStream to check\n * @returns true if a source exists for this stream\n */\nexport function hasMediaStreamSource(stream: MediaStream): boolean {\n return streamSources.has(stream);\n}\n"],"mappings":"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","names":["import_tone","import_tone","import_tone"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/TonePlayout.ts","../src/ToneTrack.ts","../src/fades.ts","../src/audioContext.ts","../src/mediaStreamSourceManager.ts"],"sourcesContent":["export { TonePlayout } from './TonePlayout';\nexport { ToneTrack } from './ToneTrack';\nexport type { TonePlayoutOptions, EffectsFunction } from './TonePlayout';\nexport type { ToneTrackOptions, TrackEffectsFunction } from './ToneTrack';\n\n// Export global AudioContext manager\nexport {\n getGlobalContext,\n getGlobalAudioContext,\n getGlobalToneContext,\n resumeGlobalAudioContext,\n getGlobalAudioContextState,\n closeGlobalAudioContext,\n} from './audioContext';\n\n// Export MediaStreamSource manager\nexport {\n getMediaStreamSource,\n releaseMediaStreamSource,\n hasMediaStreamSource,\n} from './mediaStreamSourceManager';\n\n// Export fade utilities\nexport {\n applyFadeIn,\n applyFadeOut,\n type FadeConfig,\n type FadeType,\n} from './fades';\n","// Named imports for tree-shaking\nimport {\n Volume,\n ToneAudioNode,\n getDestination,\n start,\n now,\n getTransport,\n getContext,\n BaseContext,\n} from 'tone';\nimport { ToneTrack, ToneTrackOptions } from './ToneTrack';\n\n// Effects function no longer receives ToneLib - effects should import Tone themselves\nexport type EffectsFunction = (masterGainNode: Volume, destination: ToneAudioNode, isOffline: boolean) => void | (() => void);\n\nexport interface TonePlayoutOptions {\n tracks?: ToneTrack[];\n masterGain?: number;\n effects?: EffectsFunction;\n}\n\nexport class TonePlayout {\n private tracks: Map<string, ToneTrack> = new Map();\n private masterVolume: Volume;\n private isInitialized = false;\n private soloedTracks: Set<string> = new Set();\n private manualMuteState: Map<string, boolean> = new Map();\n private effectsCleanup?: () => void;\n private onPlaybackCompleteCallback?: () => void;\n private activeTracks: Map<string, number> = new Map(); // Map track ID to session ID\n private playbackSessionId: number = 0;\n\n constructor(options: TonePlayoutOptions = {}) {\n this.masterVolume = new Volume(this.gainToDb(options.masterGain ?? 1));\n\n // Setup effects chain if provided, otherwise connect directly to destination\n if (options.effects) {\n const cleanup = options.effects(this.masterVolume, getDestination(), false);\n if (cleanup) {\n this.effectsCleanup = cleanup;\n }\n } else {\n this.masterVolume.toDestination();\n }\n\n if (options.tracks) {\n options.tracks.forEach(track => {\n this.tracks.set(track.id, track);\n // Initialize manual mute state for constructor-provided tracks\n this.manualMuteState.set(track.id, track.muted);\n });\n }\n }\n\n private gainToDb(gain: number): number {\n return 20 * Math.log10(gain);\n }\n\n async init(): Promise<void> {\n if (this.isInitialized) return;\n\n await start();\n this.isInitialized = true;\n }\n\n addTrack(trackOptions: ToneTrackOptions): ToneTrack {\n // Ensure tracks connect to master volume instead of destination\n const optionsWithDestination = {\n ...trackOptions,\n destination: this.masterVolume,\n };\n const toneTrack = new ToneTrack(optionsWithDestination);\n this.tracks.set(toneTrack.id, toneTrack);\n // Initialize manual mute state from track options\n this.manualMuteState.set(toneTrack.id, trackOptions.track.muted ?? false);\n // Initialize solo state from track options\n if (trackOptions.track.soloed) {\n this.soloedTracks.add(toneTrack.id);\n }\n return toneTrack;\n }\n\n /**\n * Apply solo muting after all tracks have been added.\n * Call this after adding all tracks to ensure solo logic is applied correctly.\n */\n applyInitialSoloState(): void {\n this.updateSoloMuting();\n }\n\n removeTrack(trackId: string): void {\n const track = this.tracks.get(trackId);\n if (track) {\n track.dispose();\n this.tracks.delete(trackId);\n this.manualMuteState.delete(trackId);\n this.soloedTracks.delete(trackId);\n }\n }\n\n getTrack(trackId: string): ToneTrack | undefined {\n return this.tracks.get(trackId);\n }\n\n play(when?: number, offset?: number, duration?: number): void {\n if (!this.isInitialized) {\n console.warn('TonePlayout not initialized. Call init() first.');\n return;\n }\n\n // Use now() as default, but call it here after init check (not in function signature)\n const startTime = when ?? now();\n const playbackPosition = offset ?? 0;\n\n // Increment session ID to invalidate old callbacks\n this.playbackSessionId++;\n const currentSessionId = this.playbackSessionId;\n\n // Clear active tracks and set up stop callbacks if duration is specified\n this.activeTracks.clear();\n\n // Play tracks based on their individual start times\n this.tracks.forEach((toneTrack) => {\n const trackStartTime = toneTrack.startTime;\n\n if (playbackPosition >= trackStartTime) {\n // Track should be playing - calculate buffer offset and start immediately\n const bufferOffset = playbackPosition - trackStartTime;\n\n if (duration !== undefined) {\n this.activeTracks.set(toneTrack.id, currentSessionId);\n toneTrack.setOnStopCallback(() => {\n // Only process if this track is still in activeTracks with matching session ID\n if (this.activeTracks.get(toneTrack.id) === currentSessionId) {\n this.activeTracks.delete(toneTrack.id);\n if (this.activeTracks.size === 0 && this.onPlaybackCompleteCallback) {\n this.onPlaybackCompleteCallback();\n }\n }\n });\n }\n\n toneTrack.play(startTime, bufferOffset, duration);\n } else {\n // Track should start later - schedule it to start when playback reaches its start time\n const delay = trackStartTime - playbackPosition;\n\n if (duration !== undefined) {\n this.activeTracks.set(toneTrack.id, currentSessionId);\n toneTrack.setOnStopCallback(() => {\n // Only process if this track is still in activeTracks with matching session ID\n if (this.activeTracks.get(toneTrack.id) === currentSessionId) {\n this.activeTracks.delete(toneTrack.id);\n if (this.activeTracks.size === 0 && this.onPlaybackCompleteCallback) {\n this.onPlaybackCompleteCallback();\n }\n }\n });\n }\n\n toneTrack.play(startTime + delay, 0, duration);\n }\n });\n\n // Start transport\n if (offset !== undefined) {\n // Explicit offset provided - seek to that position\n getTransport().start(startTime, offset);\n } else {\n // No offset - resume from pause (Transport resumes from current position)\n getTransport().start(startTime);\n }\n }\n\n pause(): void {\n getTransport().pause();\n this.tracks.forEach(track => {\n track.pause();\n });\n }\n\n stop(): void {\n getTransport().stop();\n this.tracks.forEach(track => {\n track.stop();\n });\n }\n\n setMasterGain(gain: number): void {\n this.masterVolume.volume.value = this.gainToDb(gain);\n }\n\n setSolo(trackId: string, soloed: boolean): void {\n const track = this.tracks.get(trackId);\n if (track) {\n track.setSolo(soloed);\n if (soloed) {\n this.soloedTracks.add(trackId);\n } else {\n this.soloedTracks.delete(trackId);\n }\n\n // Update mute state of all tracks based on solo logic\n this.updateSoloMuting();\n }\n }\n\n private updateSoloMuting(): void {\n const hasSoloedTracks = this.soloedTracks.size > 0;\n\n this.tracks.forEach((track, id) => {\n if (hasSoloedTracks) {\n // If there are soloed tracks, mute all non-soloed tracks\n if (!this.soloedTracks.has(id)) {\n track.setMute(true);\n } else {\n // Restore manual mute state for soloed tracks\n const manuallyMuted = this.manualMuteState.get(id) ?? false;\n track.setMute(manuallyMuted);\n }\n } else {\n // No soloed tracks, restore original manual mute state for all tracks\n const manuallyMuted = this.manualMuteState.get(id) ?? false;\n track.setMute(manuallyMuted);\n }\n });\n }\n\n setMute(trackId: string, muted: boolean): void {\n const track = this.tracks.get(trackId);\n if (track) {\n // Store the manual mute state\n this.manualMuteState.set(trackId, muted);\n track.setMute(muted);\n }\n }\n\n getCurrentTime(): number {\n return getTransport().seconds;\n }\n\n seekTo(time: number): void {\n getTransport().seconds = time;\n }\n\n dispose(): void {\n this.tracks.forEach(track => {\n track.dispose();\n });\n this.tracks.clear();\n\n // Clean up effects if cleanup function was provided\n if (this.effectsCleanup) {\n this.effectsCleanup();\n }\n\n this.masterVolume.dispose();\n }\n\n get context(): BaseContext {\n return getContext();\n }\n\n get sampleRate(): number {\n return getContext().sampleRate;\n }\n\n setOnPlaybackComplete(callback: () => void): void {\n this.onPlaybackCompleteCallback = callback;\n }\n}\n","// Named imports for tree-shaking\nimport {\n Player,\n Volume,\n Gain,\n Panner,\n ToneAudioNode,\n getDestination,\n now,\n} from 'tone';\nimport { Track, type Fade } from '@waveform-playlist/core';\nimport { applyFadeIn, applyFadeOut } from './fades';\n\n// Effects function no longer receives ToneLib - effects should import Tone themselves\nexport type TrackEffectsFunction = (graphEnd: Gain, masterGainNode: ToneAudioNode, isOffline: boolean) => void | (() => void);\n\nexport interface ClipInfo {\n buffer: AudioBuffer;\n startTime: number; // When this clip starts in the track timeline (seconds)\n duration: number; // How long this clip plays (seconds)\n offset: number; // Where to start playing within the buffer (seconds)\n fadeIn?: Fade;\n fadeOut?: Fade;\n gain: number; // Clip-level gain\n}\n\nexport interface ToneTrackOptions {\n buffer?: AudioBuffer; // Legacy: single buffer (deprecated, use clips instead)\n clips?: ClipInfo[]; // Modern: array of clips\n track: Track;\n effects?: TrackEffectsFunction;\n destination?: ToneAudioNode;\n}\n\ninterface ClipPlayer {\n player: Player;\n clipInfo: ClipInfo;\n fadeGain: Gain;\n pausedPosition: number;\n playStartTime: number;\n}\n\nexport class ToneTrack {\n private clips: ClipPlayer[]; // Array of clip players\n private volumeNode: Volume;\n private panNode: Panner;\n private muteGain: Gain;\n private track: Track;\n private effectsCleanup?: () => void;\n private onStopCallback?: () => void;\n private activePlayers: number = 0; // Count of currently playing clips\n\n constructor(options: ToneTrackOptions) {\n this.track = options.track;\n\n // Create shared track-level nodes\n this.volumeNode = new Volume(this.gainToDb(options.track.gain));\n this.panNode = new Panner(options.track.stereoPan);\n this.muteGain = new Gain(options.track.muted ? 0 : 1);\n\n // Connect to destination or apply effects chain\n const destination = options.destination || getDestination();\n if (options.effects) {\n const cleanup = options.effects(this.muteGain, destination, false);\n if (cleanup) {\n this.effectsCleanup = cleanup;\n }\n } else {\n this.muteGain.connect(destination);\n }\n\n // Create clips array - support both legacy single buffer and modern clips array\n const clipInfos: ClipInfo[] = options.clips || (options.buffer ? [{\n buffer: options.buffer,\n startTime: 0, // Legacy: single buffer starts at timeline position 0\n duration: options.buffer.duration, // Legacy: play full buffer duration\n offset: 0,\n fadeIn: options.track.fadeIn,\n fadeOut: options.track.fadeOut,\n gain: 1,\n }] : []);\n\n // Create ClipPlayer for each clip\n this.clips = clipInfos.map(clipInfo => {\n const player = new Player({\n url: clipInfo.buffer,\n loop: false,\n onstop: () => {\n this.activePlayers--;\n if (this.activePlayers === 0 && this.onStopCallback) {\n this.onStopCallback();\n }\n },\n });\n\n const fadeGain = new Gain(clipInfo.gain);\n\n // Chain: Player -> FadeGain -> Volume -> Pan -> MuteGain\n player.connect(fadeGain);\n fadeGain.chain(this.volumeNode, this.panNode, this.muteGain);\n\n // Note: Fades are scheduled in play() method, not here in constructor,\n // because AudioParam automation requires absolute AudioContext time\n\n return {\n player,\n clipInfo,\n fadeGain,\n pausedPosition: 0,\n playStartTime: 0,\n };\n });\n }\n\n /**\n * Schedule fade envelopes for a clip at the given start time\n */\n private scheduleFades(clipPlayer: ClipPlayer, clipStartTime: number, clipOffset: number = 0): void {\n const { clipInfo, fadeGain } = clipPlayer;\n const audioParam = (fadeGain.gain as any)._param as AudioParam;\n\n // Cancel any previous automation\n audioParam.cancelScheduledValues(0);\n\n // Calculate how much of the clip we're skipping (for seeking)\n const skipTime = clipOffset - clipInfo.offset;\n\n // Apply fade in if it exists and we haven't skipped past it\n if (clipInfo.fadeIn && skipTime < clipInfo.fadeIn.duration) {\n const fadeInDuration = clipInfo.fadeIn.duration;\n\n if (skipTime <= 0) {\n // Starting from the beginning - full fade in\n applyFadeIn(\n audioParam,\n clipStartTime,\n fadeInDuration,\n clipInfo.fadeIn.type || 'linear',\n 0,\n clipInfo.gain\n );\n } else {\n // Starting partway through fade in - calculate partial fade\n const remainingFadeDuration = fadeInDuration - skipTime;\n const fadeProgress = skipTime / fadeInDuration;\n const startValue = clipInfo.gain * fadeProgress; // Approximate current fade value\n applyFadeIn(\n audioParam,\n clipStartTime,\n remainingFadeDuration,\n clipInfo.fadeIn.type || 'linear',\n startValue,\n clipInfo.gain\n );\n }\n } else {\n // No fade in or skipped past it - set to full gain\n audioParam.setValueAtTime(clipInfo.gain, clipStartTime);\n }\n\n // Apply fade out if it exists\n if (clipInfo.fadeOut) {\n const fadeOutStart = clipInfo.duration - clipInfo.fadeOut.duration;\n const fadeOutStartInClip = fadeOutStart - skipTime; // Relative to where we're starting\n\n if (fadeOutStartInClip > 0) {\n // Fade out hasn't started yet\n const absoluteFadeOutStart = clipStartTime + fadeOutStartInClip;\n applyFadeOut(\n audioParam,\n absoluteFadeOutStart,\n clipInfo.fadeOut.duration,\n clipInfo.fadeOut.type || 'linear',\n clipInfo.gain,\n 0\n );\n } else if (fadeOutStartInClip > -clipInfo.fadeOut.duration) {\n // We're starting partway through the fade out\n const elapsedFadeOut = -fadeOutStartInClip;\n const remainingFadeDuration = clipInfo.fadeOut.duration - elapsedFadeOut;\n const fadeProgress = elapsedFadeOut / clipInfo.fadeOut.duration;\n const startValue = clipInfo.gain * (1 - fadeProgress); // Approximate current fade value\n applyFadeOut(\n audioParam,\n clipStartTime,\n remainingFadeDuration,\n clipInfo.fadeOut.type || 'linear',\n startValue,\n 0\n );\n }\n // If fadeOutStartInClip <= -duration, we've skipped past the entire fade out\n }\n }\n\n private gainToDb(gain: number): number {\n return 20 * Math.log10(gain);\n }\n\n setVolume(gain: number): void {\n this.track.gain = gain;\n this.volumeNode.volume.value = this.gainToDb(gain);\n }\n\n setPan(pan: number): void {\n this.track.stereoPan = pan;\n this.panNode.pan.value = pan;\n }\n\n setMute(muted: boolean): void {\n this.track.muted = muted;\n this.muteGain.gain.value = muted ? 0 : 1;\n }\n\n setSolo(soloed: boolean): void {\n this.track.soloed = soloed;\n }\n\n play(when?: number, offset: number = 0, duration?: number): void {\n // Recreate all players to avoid Tone.js StateTimeline issues when seeking\n // See: https://github.com/Tonejs/Tone.js/issues/1076\n // The Player's internal StateTimeline doesn't properly clear on stop(),\n // so we need fresh Player instances when rescheduling\n this.clips.forEach(clipPlayer => {\n // Dispose old player\n clipPlayer.player.stop();\n clipPlayer.player.disconnect();\n clipPlayer.player.dispose();\n\n // Create new player with same buffer\n const newPlayer = new Player({\n url: clipPlayer.clipInfo.buffer,\n loop: false,\n onstop: () => {\n this.activePlayers--;\n if (this.activePlayers === 0 && this.onStopCallback) {\n this.onStopCallback();\n }\n },\n });\n\n // Reconnect to audio graph\n newPlayer.connect(clipPlayer.fadeGain);\n\n // Update reference\n clipPlayer.player = newPlayer;\n clipPlayer.pausedPosition = 0;\n });\n\n this.activePlayers = 0;\n\n // Play each clip that should be active at this offset\n this.clips.forEach(clipPlayer => {\n const { player, clipInfo } = clipPlayer;\n\n // Calculate absolute timeline position we're starting from\n const playbackPosition = offset;\n\n // Check if this clip should be playing at this position\n const clipStart = clipInfo.startTime;\n const clipEnd = clipInfo.startTime + clipInfo.duration;\n\n if (playbackPosition < clipEnd) {\n // This clip should play\n this.activePlayers++;\n\n // Get fresh now() for each clip to avoid \"time in the past\" errors\n // This is important when seeking during playback - time passes between scheduling clips\n const currentTime = when ?? now();\n clipPlayer.playStartTime = currentTime;\n\n if (playbackPosition >= clipStart) {\n // We're starting in the middle of this clip\n const clipOffset = playbackPosition - clipStart + clipInfo.offset;\n const remainingDuration = clipInfo.duration - (playbackPosition - clipStart);\n const clipDuration = duration ? Math.min(duration, remainingDuration) : remainingDuration;\n\n clipPlayer.pausedPosition = clipOffset;\n // Schedule fades at the actual playback start time\n this.scheduleFades(clipPlayer, currentTime, clipOffset);\n player.start(currentTime, clipOffset, clipDuration);\n } else {\n // This clip starts later - schedule it\n const delay = clipStart - playbackPosition;\n const clipDuration = duration ? Math.min(duration - delay, clipInfo.duration) : clipInfo.duration;\n\n if (delay < (duration ?? Infinity)) {\n clipPlayer.pausedPosition = clipInfo.offset;\n // Schedule fades at the delayed start time\n this.scheduleFades(clipPlayer, currentTime + delay, clipInfo.offset);\n player.start(currentTime + delay, clipInfo.offset, clipDuration);\n } else {\n this.activePlayers--;\n }\n }\n }\n });\n }\n\n pause(): void {\n // Stop all clips - both started and scheduled\n // Scheduled clips have state 'stopped' but still need to be cancelled\n this.clips.forEach(clipPlayer => {\n if (clipPlayer.player.state === 'started') {\n const elapsed = (now() - clipPlayer.playStartTime) * clipPlayer.player.playbackRate;\n clipPlayer.pausedPosition = clipPlayer.pausedPosition + elapsed;\n }\n // Always call stop() to cancel any scheduled playback\n clipPlayer.player.stop();\n });\n\n this.activePlayers = 0;\n }\n\n stop(when?: number): void {\n // Evaluate now() inside function body, not in parameter default (which is evaluated at module load time)\n const stopWhen = when ?? now();\n this.clips.forEach(clipPlayer => {\n clipPlayer.player.stop(stopWhen);\n clipPlayer.pausedPosition = 0;\n });\n this.activePlayers = 0;\n }\n\n dispose(): void {\n // Clean up effects if cleanup function was provided\n if (this.effectsCleanup) {\n this.effectsCleanup();\n }\n\n // Dispose all clip players\n this.clips.forEach(clipPlayer => {\n clipPlayer.player.dispose();\n clipPlayer.fadeGain.dispose();\n });\n\n // Dispose shared track nodes\n this.volumeNode.dispose();\n this.panNode.dispose();\n this.muteGain.dispose();\n }\n\n get id(): string {\n return this.track.id;\n }\n\n get duration(): number {\n // Return the end time of the last clip\n if (this.clips.length === 0) return 0;\n const lastClip = this.clips[this.clips.length - 1];\n return lastClip.clipInfo.startTime + lastClip.clipInfo.duration;\n }\n\n get buffer(): AudioBuffer {\n // For backward compatibility, return the first clip's buffer\n return this.clips[0]?.clipInfo.buffer;\n }\n\n get isPlaying(): boolean {\n // Track is playing if any clip is playing\n return this.clips.some(clipPlayer => clipPlayer.player.state === 'started');\n }\n\n get muted(): boolean {\n return this.track.muted;\n }\n\n get startTime(): number {\n // Return the track's start time from the Track object\n // This is the absolute timeline position where the track starts\n return this.track.startTime;\n }\n\n setOnStopCallback(callback: () => void): void {\n this.onStopCallback = callback;\n }\n}\n","/**\n * Fade utilities for Web Audio API\n *\n * Applies fade in/out envelopes to AudioParam (typically gain)\n * using various curve types.\n */\n\nexport type FadeType = 'linear' | 'logarithmic' | 'exponential' | 'sCurve';\n\n/**\n * Simple fade configuration - just duration and type\n */\nexport interface FadeConfig {\n /** Duration of the fade in seconds */\n duration: number;\n /** Type of fade curve (default: 'linear') */\n type?: FadeType;\n}\n\n/**\n * Generate a linear fade curve\n */\nfunction linearCurve(length: number, fadeIn: boolean): Float32Array {\n const curve = new Float32Array(length);\n const scale = length - 1;\n\n for (let i = 0; i < length; i++) {\n const x = i / scale;\n curve[i] = fadeIn ? x : 1 - x;\n }\n\n return curve;\n}\n\n/**\n * Generate an exponential fade curve\n */\nfunction exponentialCurve(length: number, fadeIn: boolean): Float32Array {\n const curve = new Float32Array(length);\n const scale = length - 1;\n\n for (let i = 0; i < length; i++) {\n const x = i / scale;\n const index = fadeIn ? i : length - 1 - i;\n curve[index] = Math.exp(2 * x - 1) / Math.E;\n }\n\n return curve;\n}\n\n/**\n * Generate an S-curve (sine-based smooth curve)\n */\nfunction sCurveCurve(length: number, fadeIn: boolean): Float32Array {\n const curve = new Float32Array(length);\n const phase = fadeIn ? Math.PI / 2 : -Math.PI / 2;\n\n for (let i = 0; i < length; i++) {\n curve[i] = Math.sin((Math.PI * i) / length - phase) / 2 + 0.5;\n }\n\n return curve;\n}\n\n/**\n * Generate a logarithmic fade curve\n */\nfunction logarithmicCurve(length: number, fadeIn: boolean, base: number = 10): Float32Array {\n const curve = new Float32Array(length);\n\n for (let i = 0; i < length; i++) {\n const index = fadeIn ? i : length - 1 - i;\n const x = i / length;\n curve[index] = Math.log(1 + base * x) / Math.log(1 + base);\n }\n\n return curve;\n}\n\n/**\n * Generate a fade curve of the specified type\n */\nfunction generateCurve(type: FadeType, length: number, fadeIn: boolean): Float32Array {\n switch (type) {\n case 'linear':\n return linearCurve(length, fadeIn);\n case 'exponential':\n return exponentialCurve(length, fadeIn);\n case 'sCurve':\n return sCurveCurve(length, fadeIn);\n case 'logarithmic':\n return logarithmicCurve(length, fadeIn);\n default:\n return linearCurve(length, fadeIn);\n }\n}\n\n/**\n * Apply a fade in to an AudioParam\n *\n * @param param - The AudioParam to apply the fade to (usually gain)\n * @param startTime - When the fade starts (in seconds, AudioContext time)\n * @param duration - Duration of the fade in seconds\n * @param type - Type of fade curve\n * @param startValue - Starting value (default: 0)\n * @param endValue - Ending value (default: 1)\n */\nexport function applyFadeIn(\n param: AudioParam,\n startTime: number,\n duration: number,\n type: FadeType = 'linear',\n startValue: number = 0,\n endValue: number = 1\n): void {\n if (duration <= 0) return;\n\n if (type === 'linear') {\n // Use native linear ramp for better performance\n param.setValueAtTime(startValue, startTime);\n param.linearRampToValueAtTime(endValue, startTime + duration);\n } else if (type === 'exponential') {\n // Exponential ramp can't start/end at 0, use small value\n param.setValueAtTime(Math.max(startValue, 0.001), startTime);\n param.exponentialRampToValueAtTime(Math.max(endValue, 0.001), startTime + duration);\n } else {\n // Use curve for sCurve and logarithmic\n const curve = generateCurve(type, 10000, true);\n // Scale curve to value range\n const scaledCurve = new Float32Array(curve.length);\n const range = endValue - startValue;\n for (let i = 0; i < curve.length; i++) {\n scaledCurve[i] = startValue + curve[i] * range;\n }\n param.setValueCurveAtTime(scaledCurve, startTime, duration);\n }\n}\n\n/**\n * Apply a fade out to an AudioParam\n *\n * @param param - The AudioParam to apply the fade to (usually gain)\n * @param startTime - When the fade starts (in seconds, AudioContext time)\n * @param duration - Duration of the fade in seconds\n * @param type - Type of fade curve\n * @param startValue - Starting value (default: 1)\n * @param endValue - Ending value (default: 0)\n */\nexport function applyFadeOut(\n param: AudioParam,\n startTime: number,\n duration: number,\n type: FadeType = 'linear',\n startValue: number = 1,\n endValue: number = 0\n): void {\n if (duration <= 0) return;\n\n if (type === 'linear') {\n // Use native linear ramp for better performance\n param.setValueAtTime(startValue, startTime);\n param.linearRampToValueAtTime(endValue, startTime + duration);\n } else if (type === 'exponential') {\n // Exponential ramp can't start/end at 0, use small value\n param.setValueAtTime(Math.max(startValue, 0.001), startTime);\n param.exponentialRampToValueAtTime(Math.max(endValue, 0.001), startTime + duration);\n } else {\n // Use curve for sCurve and logarithmic\n const curve = generateCurve(type, 10000, false);\n // Scale curve to value range\n const scaledCurve = new Float32Array(curve.length);\n const range = startValue - endValue;\n for (let i = 0; i < curve.length; i++) {\n scaledCurve[i] = endValue + curve[i] * range;\n }\n param.setValueCurveAtTime(scaledCurve, startTime, duration);\n }\n}\n","/**\n * Global AudioContext Manager\n *\n * Provides a single AudioContext shared across the entire application.\n * This context is used by Tone.js for playback and by all recording/monitoring hooks.\n *\n * Uses Tone.js's Context class which wraps standardized-audio-context for\n * cross-browser compatibility (fixes Firefox AudioListener issues).\n */\n\nimport { Context, setContext } from 'tone';\n\nlet globalToneContext: Context | null = null;\n\n/**\n * Get the global Tone.js Context\n * This is the main context for cross-browser audio operations.\n * Use context.createAudioWorkletNode(), context.createMediaStreamSource(), etc.\n * @returns The Tone.js Context instance\n */\nexport function getGlobalContext(): Context {\n if (!globalToneContext) {\n globalToneContext = new Context();\n setContext(globalToneContext);\n }\n return globalToneContext;\n}\n\n/**\n * Get or create the global AudioContext\n * Uses Tone.js Context for cross-browser compatibility\n * @returns The global AudioContext instance (rawContext from Tone.Context)\n */\nexport function getGlobalAudioContext(): AudioContext {\n return getGlobalContext().rawContext as AudioContext;\n}\n\n/**\n * @deprecated Use getGlobalContext() instead\n * Get the Tone.js Context's rawContext typed as IAudioContext\n * @returns The rawContext cast as IAudioContext\n */\nexport function getGlobalToneContext(): Context {\n return getGlobalContext();\n}\n\n/**\n * Resume the global AudioContext if it's suspended\n * Should be called in response to a user gesture (e.g., button click)\n * @returns Promise that resolves when context is running\n */\nexport async function resumeGlobalAudioContext(): Promise<void> {\n const context = getGlobalContext();\n if (context.state !== 'running') {\n await context.resume();\n }\n}\n\n/**\n * Get the current state of the global AudioContext\n * @returns The AudioContext state ('suspended', 'running', or 'closed')\n */\nexport function getGlobalAudioContextState(): AudioContextState {\n return globalToneContext?.rawContext.state || 'suspended';\n}\n\n/**\n * Close the global AudioContext\n * Should only be called when the application is shutting down\n */\nexport async function closeGlobalAudioContext(): Promise<void> {\n if (globalToneContext && globalToneContext.rawContext.state !== 'closed') {\n await globalToneContext.close();\n globalToneContext = null;\n }\n}\n","/**\n * MediaStreamSource Manager\n *\n * Manages MediaStreamAudioSourceNode instances to ensure only one source\n * is created per MediaStream per AudioContext.\n *\n * Web Audio API constraint: You can only create one MediaStreamAudioSourceNode\n * per MediaStream per AudioContext. Multiple attempts will fail or disconnect\n * previous sources.\n *\n * This manager ensures a single source is shared across multiple consumers\n * (e.g., AnalyserNode for VU meter, AudioWorkletNode for recording).\n *\n * NOTE: With Tone.js Context, you can also use context.createMediaStreamSource()\n * directly, which handles cross-browser compatibility internally.\n */\n\nimport { getContext } from 'tone';\n\n// Map of MediaStream -> MediaStreamAudioSourceNode\nconst streamSources = new Map<MediaStream, MediaStreamAudioSourceNode>();\n\n// Map of MediaStream -> cleanup handlers\nconst streamCleanupHandlers = new Map<MediaStream, () => void>();\n\n/**\n * Get or create a MediaStreamAudioSourceNode for the given stream\n *\n * @param stream - The MediaStream to create a source for\n * @returns MediaStreamAudioSourceNode that can be connected to multiple nodes\n *\n * @example\n * ```typescript\n * const source = getMediaStreamSource(stream);\n *\n * // Multiple consumers can connect to the same source\n * source.connect(analyserNode); // For VU meter\n * source.connect(workletNode); // For recording\n * ```\n */\nexport function getMediaStreamSource(\n stream: MediaStream\n): MediaStreamAudioSourceNode {\n // Return existing source if we have one for this stream\n if (streamSources.has(stream)) {\n return streamSources.get(stream)!;\n }\n\n // Create new source using Tone.js's shared context for cross-browser compatibility\n const context = getContext();\n const source = context.createMediaStreamSource(stream);\n streamSources.set(stream, source);\n\n // Set up cleanup when stream ends\n const cleanup = () => {\n source.disconnect();\n streamSources.delete(stream);\n streamCleanupHandlers.delete(stream);\n\n // Remove event listener\n stream.removeEventListener('ended', cleanup);\n stream.removeEventListener('inactive', cleanup);\n };\n\n streamCleanupHandlers.set(stream, cleanup);\n\n // Clean up when stream ends or becomes inactive\n stream.addEventListener('ended', cleanup);\n stream.addEventListener('inactive', cleanup);\n\n return source;\n}\n\n/**\n * Manually release a MediaStreamSource\n *\n * Normally you don't need to call this - cleanup happens automatically\n * when the stream ends. Only call this if you need to force cleanup.\n *\n * @param stream - The MediaStream to release the source for\n */\nexport function releaseMediaStreamSource(stream: MediaStream): void {\n const cleanup = streamCleanupHandlers.get(stream);\n if (cleanup) {\n cleanup();\n }\n}\n\n/**\n * Check if a MediaStreamSource exists for the given stream\n *\n * @param stream - The MediaStream to check\n * @returns true if a source exists for this stream\n */\nexport function hasMediaStreamSource(stream: MediaStream): boolean {\n return streamSources.has(stream);\n}\n"],"mappings":"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","names":["import_tone","import_tone","import_tone"]}
|
package/dist/index.mjs
CHANGED
|
@@ -241,10 +241,24 @@ var ToneTrack = class {
|
|
|
241
241
|
this.track.soloed = soloed;
|
|
242
242
|
}
|
|
243
243
|
play(when, offset = 0, duration) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
244
|
+
this.clips.forEach((clipPlayer) => {
|
|
245
|
+
clipPlayer.player.stop();
|
|
246
|
+
clipPlayer.player.disconnect();
|
|
247
|
+
clipPlayer.player.dispose();
|
|
248
|
+
const newPlayer = new Player({
|
|
249
|
+
url: clipPlayer.clipInfo.buffer,
|
|
250
|
+
loop: false,
|
|
251
|
+
onstop: () => {
|
|
252
|
+
this.activePlayers--;
|
|
253
|
+
if (this.activePlayers === 0 && this.onStopCallback) {
|
|
254
|
+
this.onStopCallback();
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
newPlayer.connect(clipPlayer.fadeGain);
|
|
259
|
+
clipPlayer.player = newPlayer;
|
|
260
|
+
clipPlayer.pausedPosition = 0;
|
|
261
|
+
});
|
|
248
262
|
this.activePlayers = 0;
|
|
249
263
|
this.clips.forEach((clipPlayer) => {
|
|
250
264
|
const { player, clipInfo } = clipPlayer;
|
|
@@ -253,21 +267,22 @@ var ToneTrack = class {
|
|
|
253
267
|
const clipEnd = clipInfo.startTime + clipInfo.duration;
|
|
254
268
|
if (playbackPosition < clipEnd) {
|
|
255
269
|
this.activePlayers++;
|
|
256
|
-
|
|
270
|
+
const currentTime = when ?? now();
|
|
271
|
+
clipPlayer.playStartTime = currentTime;
|
|
257
272
|
if (playbackPosition >= clipStart) {
|
|
258
273
|
const clipOffset = playbackPosition - clipStart + clipInfo.offset;
|
|
259
274
|
const remainingDuration = clipInfo.duration - (playbackPosition - clipStart);
|
|
260
275
|
const clipDuration = duration ? Math.min(duration, remainingDuration) : remainingDuration;
|
|
261
276
|
clipPlayer.pausedPosition = clipOffset;
|
|
262
|
-
this.scheduleFades(clipPlayer,
|
|
263
|
-
player.start(
|
|
277
|
+
this.scheduleFades(clipPlayer, currentTime, clipOffset);
|
|
278
|
+
player.start(currentTime, clipOffset, clipDuration);
|
|
264
279
|
} else {
|
|
265
280
|
const delay = clipStart - playbackPosition;
|
|
266
281
|
const clipDuration = duration ? Math.min(duration - delay, clipInfo.duration) : clipInfo.duration;
|
|
267
282
|
if (delay < (duration ?? Infinity)) {
|
|
268
283
|
clipPlayer.pausedPosition = clipInfo.offset;
|
|
269
|
-
this.scheduleFades(clipPlayer,
|
|
270
|
-
player.start(
|
|
284
|
+
this.scheduleFades(clipPlayer, currentTime + delay, clipInfo.offset);
|
|
285
|
+
player.start(currentTime + delay, clipInfo.offset, clipDuration);
|
|
271
286
|
} else {
|
|
272
287
|
this.activePlayers--;
|
|
273
288
|
}
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/TonePlayout.ts","../src/ToneTrack.ts","../src/fades.ts","../src/audioContext.ts","../src/mediaStreamSourceManager.ts"],"sourcesContent":["// Named imports for tree-shaking\nimport {\n Volume,\n ToneAudioNode,\n getDestination,\n start,\n now,\n getTransport,\n getContext,\n BaseContext,\n} from 'tone';\nimport { ToneTrack, ToneTrackOptions } from './ToneTrack';\n\n// Effects function no longer receives ToneLib - effects should import Tone themselves\nexport type EffectsFunction = (masterGainNode: Volume, destination: ToneAudioNode, isOffline: boolean) => void | (() => void);\n\nexport interface TonePlayoutOptions {\n tracks?: ToneTrack[];\n masterGain?: number;\n effects?: EffectsFunction;\n}\n\nexport class TonePlayout {\n private tracks: Map<string, ToneTrack> = new Map();\n private masterVolume: Volume;\n private isInitialized = false;\n private soloedTracks: Set<string> = new Set();\n private manualMuteState: Map<string, boolean> = new Map();\n private effectsCleanup?: () => void;\n private onPlaybackCompleteCallback?: () => void;\n private activeTracks: Map<string, number> = new Map(); // Map track ID to session ID\n private playbackSessionId: number = 0;\n\n constructor(options: TonePlayoutOptions = {}) {\n this.masterVolume = new Volume(this.gainToDb(options.masterGain ?? 1));\n\n // Setup effects chain if provided, otherwise connect directly to destination\n if (options.effects) {\n const cleanup = options.effects(this.masterVolume, getDestination(), false);\n if (cleanup) {\n this.effectsCleanup = cleanup;\n }\n } else {\n this.masterVolume.toDestination();\n }\n\n if (options.tracks) {\n options.tracks.forEach(track => {\n this.tracks.set(track.id, track);\n // Initialize manual mute state for constructor-provided tracks\n this.manualMuteState.set(track.id, track.muted);\n });\n }\n }\n\n private gainToDb(gain: number): number {\n return 20 * Math.log10(gain);\n }\n\n async init(): Promise<void> {\n if (this.isInitialized) return;\n\n await start();\n this.isInitialized = true;\n }\n\n addTrack(trackOptions: ToneTrackOptions): ToneTrack {\n // Ensure tracks connect to master volume instead of destination\n const optionsWithDestination = {\n ...trackOptions,\n destination: this.masterVolume,\n };\n const toneTrack = new ToneTrack(optionsWithDestination);\n this.tracks.set(toneTrack.id, toneTrack);\n // Initialize manual mute state from track options\n this.manualMuteState.set(toneTrack.id, trackOptions.track.muted ?? false);\n // Initialize solo state from track options\n if (trackOptions.track.soloed) {\n this.soloedTracks.add(toneTrack.id);\n }\n return toneTrack;\n }\n\n /**\n * Apply solo muting after all tracks have been added.\n * Call this after adding all tracks to ensure solo logic is applied correctly.\n */\n applyInitialSoloState(): void {\n this.updateSoloMuting();\n }\n\n removeTrack(trackId: string): void {\n const track = this.tracks.get(trackId);\n if (track) {\n track.dispose();\n this.tracks.delete(trackId);\n this.manualMuteState.delete(trackId);\n this.soloedTracks.delete(trackId);\n }\n }\n\n getTrack(trackId: string): ToneTrack | undefined {\n return this.tracks.get(trackId);\n }\n\n play(when?: number, offset?: number, duration?: number): void {\n if (!this.isInitialized) {\n console.warn('TonePlayout not initialized. Call init() first.');\n return;\n }\n\n // Use now() as default, but call it here after init check (not in function signature)\n const startTime = when ?? now();\n const playbackPosition = offset ?? 0;\n\n // Increment session ID to invalidate old callbacks\n this.playbackSessionId++;\n const currentSessionId = this.playbackSessionId;\n\n // Clear active tracks and set up stop callbacks if duration is specified\n this.activeTracks.clear();\n\n // Play tracks based on their individual start times\n this.tracks.forEach((toneTrack) => {\n const trackStartTime = toneTrack.startTime;\n\n if (playbackPosition >= trackStartTime) {\n // Track should be playing - calculate buffer offset and start immediately\n const bufferOffset = playbackPosition - trackStartTime;\n\n if (duration !== undefined) {\n this.activeTracks.set(toneTrack.id, currentSessionId);\n toneTrack.setOnStopCallback(() => {\n // Only process if this track is still in activeTracks with matching session ID\n if (this.activeTracks.get(toneTrack.id) === currentSessionId) {\n this.activeTracks.delete(toneTrack.id);\n if (this.activeTracks.size === 0 && this.onPlaybackCompleteCallback) {\n this.onPlaybackCompleteCallback();\n }\n }\n });\n }\n\n toneTrack.play(startTime, bufferOffset, duration);\n } else {\n // Track should start later - schedule it to start when playback reaches its start time\n const delay = trackStartTime - playbackPosition;\n\n if (duration !== undefined) {\n this.activeTracks.set(toneTrack.id, currentSessionId);\n toneTrack.setOnStopCallback(() => {\n // Only process if this track is still in activeTracks with matching session ID\n if (this.activeTracks.get(toneTrack.id) === currentSessionId) {\n this.activeTracks.delete(toneTrack.id);\n if (this.activeTracks.size === 0 && this.onPlaybackCompleteCallback) {\n this.onPlaybackCompleteCallback();\n }\n }\n });\n }\n\n toneTrack.play(startTime + delay, 0, duration);\n }\n });\n\n // Start transport\n if (offset !== undefined) {\n // Explicit offset provided - seek to that position\n getTransport().start(startTime, offset);\n } else {\n // No offset - resume from pause (Transport resumes from current position)\n getTransport().start(startTime);\n }\n }\n\n pause(): void {\n getTransport().pause();\n this.tracks.forEach(track => {\n track.pause();\n });\n }\n\n stop(): void {\n getTransport().stop();\n this.tracks.forEach(track => {\n track.stop();\n });\n }\n\n setMasterGain(gain: number): void {\n this.masterVolume.volume.value = this.gainToDb(gain);\n }\n\n setSolo(trackId: string, soloed: boolean): void {\n const track = this.tracks.get(trackId);\n if (track) {\n track.setSolo(soloed);\n if (soloed) {\n this.soloedTracks.add(trackId);\n } else {\n this.soloedTracks.delete(trackId);\n }\n\n // Update mute state of all tracks based on solo logic\n this.updateSoloMuting();\n }\n }\n\n private updateSoloMuting(): void {\n const hasSoloedTracks = this.soloedTracks.size > 0;\n\n this.tracks.forEach((track, id) => {\n if (hasSoloedTracks) {\n // If there are soloed tracks, mute all non-soloed tracks\n if (!this.soloedTracks.has(id)) {\n track.setMute(true);\n } else {\n // Restore manual mute state for soloed tracks\n const manuallyMuted = this.manualMuteState.get(id) ?? false;\n track.setMute(manuallyMuted);\n }\n } else {\n // No soloed tracks, restore original manual mute state for all tracks\n const manuallyMuted = this.manualMuteState.get(id) ?? false;\n track.setMute(manuallyMuted);\n }\n });\n }\n\n setMute(trackId: string, muted: boolean): void {\n const track = this.tracks.get(trackId);\n if (track) {\n // Store the manual mute state\n this.manualMuteState.set(trackId, muted);\n track.setMute(muted);\n }\n }\n\n getCurrentTime(): number {\n return getTransport().seconds;\n }\n\n seekTo(time: number): void {\n getTransport().seconds = time;\n }\n\n dispose(): void {\n this.tracks.forEach(track => {\n track.dispose();\n });\n this.tracks.clear();\n\n // Clean up effects if cleanup function was provided\n if (this.effectsCleanup) {\n this.effectsCleanup();\n }\n\n this.masterVolume.dispose();\n }\n\n get context(): BaseContext {\n return getContext();\n }\n\n get sampleRate(): number {\n return getContext().sampleRate;\n }\n\n setOnPlaybackComplete(callback: () => void): void {\n this.onPlaybackCompleteCallback = callback;\n }\n}\n","// Named imports for tree-shaking\nimport {\n Player,\n Volume,\n Gain,\n Panner,\n ToneAudioNode,\n getDestination,\n now,\n} from 'tone';\nimport { Track, type Fade } from '@waveform-playlist/core';\nimport { applyFadeIn, applyFadeOut } from './fades';\n\n// Effects function no longer receives ToneLib - effects should import Tone themselves\nexport type TrackEffectsFunction = (graphEnd: Gain, masterGainNode: ToneAudioNode, isOffline: boolean) => void | (() => void);\n\nexport interface ClipInfo {\n buffer: AudioBuffer;\n startTime: number; // When this clip starts in the track timeline (seconds)\n duration: number; // How long this clip plays (seconds)\n offset: number; // Where to start playing within the buffer (seconds)\n fadeIn?: Fade;\n fadeOut?: Fade;\n gain: number; // Clip-level gain\n}\n\nexport interface ToneTrackOptions {\n buffer?: AudioBuffer; // Legacy: single buffer (deprecated, use clips instead)\n clips?: ClipInfo[]; // Modern: array of clips\n track: Track;\n effects?: TrackEffectsFunction;\n destination?: ToneAudioNode;\n}\n\ninterface ClipPlayer {\n player: Player;\n clipInfo: ClipInfo;\n fadeGain: Gain;\n pausedPosition: number;\n playStartTime: number;\n}\n\nexport class ToneTrack {\n private clips: ClipPlayer[]; // Array of clip players\n private volumeNode: Volume;\n private panNode: Panner;\n private muteGain: Gain;\n private track: Track;\n private effectsCleanup?: () => void;\n private onStopCallback?: () => void;\n private activePlayers: number = 0; // Count of currently playing clips\n\n constructor(options: ToneTrackOptions) {\n this.track = options.track;\n\n // Create shared track-level nodes\n this.volumeNode = new Volume(this.gainToDb(options.track.gain));\n this.panNode = new Panner(options.track.stereoPan);\n this.muteGain = new Gain(options.track.muted ? 0 : 1);\n\n // Connect to destination or apply effects chain\n const destination = options.destination || getDestination();\n if (options.effects) {\n const cleanup = options.effects(this.muteGain, destination, false);\n if (cleanup) {\n this.effectsCleanup = cleanup;\n }\n } else {\n this.muteGain.connect(destination);\n }\n\n // Create clips array - support both legacy single buffer and modern clips array\n const clipInfos: ClipInfo[] = options.clips || (options.buffer ? [{\n buffer: options.buffer,\n startTime: 0, // Legacy: single buffer starts at timeline position 0\n duration: options.buffer.duration, // Legacy: play full buffer duration\n offset: 0,\n fadeIn: options.track.fadeIn,\n fadeOut: options.track.fadeOut,\n gain: 1,\n }] : []);\n\n // Create ClipPlayer for each clip\n this.clips = clipInfos.map(clipInfo => {\n const player = new Player({\n url: clipInfo.buffer,\n loop: false,\n onstop: () => {\n this.activePlayers--;\n if (this.activePlayers === 0 && this.onStopCallback) {\n this.onStopCallback();\n }\n },\n });\n\n const fadeGain = new Gain(clipInfo.gain);\n\n // Chain: Player -> FadeGain -> Volume -> Pan -> MuteGain\n player.connect(fadeGain);\n fadeGain.chain(this.volumeNode, this.panNode, this.muteGain);\n\n // Note: Fades are scheduled in play() method, not here in constructor,\n // because AudioParam automation requires absolute AudioContext time\n\n return {\n player,\n clipInfo,\n fadeGain,\n pausedPosition: 0,\n playStartTime: 0,\n };\n });\n }\n\n /**\n * Schedule fade envelopes for a clip at the given start time\n */\n private scheduleFades(clipPlayer: ClipPlayer, clipStartTime: number, clipOffset: number = 0): void {\n const { clipInfo, fadeGain } = clipPlayer;\n const audioParam = (fadeGain.gain as any)._param as AudioParam;\n\n // Cancel any previous automation\n audioParam.cancelScheduledValues(0);\n\n // Calculate how much of the clip we're skipping (for seeking)\n const skipTime = clipOffset - clipInfo.offset;\n\n // Apply fade in if it exists and we haven't skipped past it\n if (clipInfo.fadeIn && skipTime < clipInfo.fadeIn.duration) {\n const fadeInDuration = clipInfo.fadeIn.duration;\n\n if (skipTime <= 0) {\n // Starting from the beginning - full fade in\n applyFadeIn(\n audioParam,\n clipStartTime,\n fadeInDuration,\n clipInfo.fadeIn.type || 'linear',\n 0,\n clipInfo.gain\n );\n } else {\n // Starting partway through fade in - calculate partial fade\n const remainingFadeDuration = fadeInDuration - skipTime;\n const fadeProgress = skipTime / fadeInDuration;\n const startValue = clipInfo.gain * fadeProgress; // Approximate current fade value\n applyFadeIn(\n audioParam,\n clipStartTime,\n remainingFadeDuration,\n clipInfo.fadeIn.type || 'linear',\n startValue,\n clipInfo.gain\n );\n }\n } else {\n // No fade in or skipped past it - set to full gain\n audioParam.setValueAtTime(clipInfo.gain, clipStartTime);\n }\n\n // Apply fade out if it exists\n if (clipInfo.fadeOut) {\n const fadeOutStart = clipInfo.duration - clipInfo.fadeOut.duration;\n const fadeOutStartInClip = fadeOutStart - skipTime; // Relative to where we're starting\n\n if (fadeOutStartInClip > 0) {\n // Fade out hasn't started yet\n const absoluteFadeOutStart = clipStartTime + fadeOutStartInClip;\n applyFadeOut(\n audioParam,\n absoluteFadeOutStart,\n clipInfo.fadeOut.duration,\n clipInfo.fadeOut.type || 'linear',\n clipInfo.gain,\n 0\n );\n } else if (fadeOutStartInClip > -clipInfo.fadeOut.duration) {\n // We're starting partway through the fade out\n const elapsedFadeOut = -fadeOutStartInClip;\n const remainingFadeDuration = clipInfo.fadeOut.duration - elapsedFadeOut;\n const fadeProgress = elapsedFadeOut / clipInfo.fadeOut.duration;\n const startValue = clipInfo.gain * (1 - fadeProgress); // Approximate current fade value\n applyFadeOut(\n audioParam,\n clipStartTime,\n remainingFadeDuration,\n clipInfo.fadeOut.type || 'linear',\n startValue,\n 0\n );\n }\n // If fadeOutStartInClip <= -duration, we've skipped past the entire fade out\n }\n }\n\n private gainToDb(gain: number): number {\n return 20 * Math.log10(gain);\n }\n\n setVolume(gain: number): void {\n this.track.gain = gain;\n this.volumeNode.volume.value = this.gainToDb(gain);\n }\n\n setPan(pan: number): void {\n this.track.stereoPan = pan;\n this.panNode.pan.value = pan;\n }\n\n setMute(muted: boolean): void {\n this.track.muted = muted;\n this.muteGain.gain.value = muted ? 0 : 1;\n }\n\n setSolo(soloed: boolean): void {\n this.track.soloed = soloed;\n }\n\n play(when?: number, offset: number = 0, duration?: number): void {\n // Evaluate now() inside function body, not in parameter default (which is evaluated at module load time)\n const startWhen = when ?? now();\n\n // Stop any existing playback before starting new playback\n // This handles cases where play is called while still playing (e.g., selection replay)\n if (this.isPlaying) {\n this.stop();\n }\n\n this.activePlayers = 0;\n\n // Play each clip that should be active at this offset\n this.clips.forEach(clipPlayer => {\n const { player, clipInfo } = clipPlayer;\n\n // Calculate absolute timeline position we're starting from\n const playbackPosition = offset;\n\n // Check if this clip should be playing at this position\n const clipStart = clipInfo.startTime;\n const clipEnd = clipInfo.startTime + clipInfo.duration;\n\n if (playbackPosition < clipEnd) {\n // This clip should play\n this.activePlayers++;\n clipPlayer.playStartTime = now();\n\n if (playbackPosition >= clipStart) {\n // We're starting in the middle of this clip\n const clipOffset = playbackPosition - clipStart + clipInfo.offset;\n const remainingDuration = clipInfo.duration - (playbackPosition - clipStart);\n const clipDuration = duration ? Math.min(duration, remainingDuration) : remainingDuration;\n\n clipPlayer.pausedPosition = clipOffset;\n // Schedule fades at the actual playback start time\n this.scheduleFades(clipPlayer, startWhen, clipOffset);\n player.start(startWhen, clipOffset, clipDuration);\n } else {\n // This clip starts later - schedule it\n const delay = clipStart - playbackPosition;\n const clipDuration = duration ? Math.min(duration - delay, clipInfo.duration) : clipInfo.duration;\n\n if (delay < (duration ?? Infinity)) {\n clipPlayer.pausedPosition = clipInfo.offset;\n // Schedule fades at the delayed start time\n this.scheduleFades(clipPlayer, startWhen + delay, clipInfo.offset);\n player.start(startWhen + delay, clipInfo.offset, clipDuration);\n } else {\n this.activePlayers--;\n }\n }\n }\n });\n }\n\n pause(): void {\n // Stop all clips - both started and scheduled\n // Scheduled clips have state 'stopped' but still need to be cancelled\n this.clips.forEach(clipPlayer => {\n if (clipPlayer.player.state === 'started') {\n const elapsed = (now() - clipPlayer.playStartTime) * clipPlayer.player.playbackRate;\n clipPlayer.pausedPosition = clipPlayer.pausedPosition + elapsed;\n }\n // Always call stop() to cancel any scheduled playback\n clipPlayer.player.stop();\n });\n\n this.activePlayers = 0;\n }\n\n stop(when?: number): void {\n // Evaluate now() inside function body, not in parameter default (which is evaluated at module load time)\n const stopWhen = when ?? now();\n this.clips.forEach(clipPlayer => {\n clipPlayer.player.stop(stopWhen);\n clipPlayer.pausedPosition = 0;\n });\n this.activePlayers = 0;\n }\n\n dispose(): void {\n // Clean up effects if cleanup function was provided\n if (this.effectsCleanup) {\n this.effectsCleanup();\n }\n\n // Dispose all clip players\n this.clips.forEach(clipPlayer => {\n clipPlayer.player.dispose();\n clipPlayer.fadeGain.dispose();\n });\n\n // Dispose shared track nodes\n this.volumeNode.dispose();\n this.panNode.dispose();\n this.muteGain.dispose();\n }\n\n get id(): string {\n return this.track.id;\n }\n\n get duration(): number {\n // Return the end time of the last clip\n if (this.clips.length === 0) return 0;\n const lastClip = this.clips[this.clips.length - 1];\n return lastClip.clipInfo.startTime + lastClip.clipInfo.duration;\n }\n\n get buffer(): AudioBuffer {\n // For backward compatibility, return the first clip's buffer\n return this.clips[0]?.clipInfo.buffer;\n }\n\n get isPlaying(): boolean {\n // Track is playing if any clip is playing\n return this.clips.some(clipPlayer => clipPlayer.player.state === 'started');\n }\n\n get muted(): boolean {\n return this.track.muted;\n }\n\n get startTime(): number {\n // Return the track's start time from the Track object\n // This is the absolute timeline position where the track starts\n return this.track.startTime;\n }\n\n setOnStopCallback(callback: () => void): void {\n this.onStopCallback = callback;\n }\n}\n","/**\n * Fade utilities for Web Audio API\n *\n * Applies fade in/out envelopes to AudioParam (typically gain)\n * using various curve types.\n */\n\nexport type FadeType = 'linear' | 'logarithmic' | 'exponential' | 'sCurve';\n\n/**\n * Simple fade configuration - just duration and type\n */\nexport interface FadeConfig {\n /** Duration of the fade in seconds */\n duration: number;\n /** Type of fade curve (default: 'linear') */\n type?: FadeType;\n}\n\n/**\n * Generate a linear fade curve\n */\nfunction linearCurve(length: number, fadeIn: boolean): Float32Array {\n const curve = new Float32Array(length);\n const scale = length - 1;\n\n for (let i = 0; i < length; i++) {\n const x = i / scale;\n curve[i] = fadeIn ? x : 1 - x;\n }\n\n return curve;\n}\n\n/**\n * Generate an exponential fade curve\n */\nfunction exponentialCurve(length: number, fadeIn: boolean): Float32Array {\n const curve = new Float32Array(length);\n const scale = length - 1;\n\n for (let i = 0; i < length; i++) {\n const x = i / scale;\n const index = fadeIn ? i : length - 1 - i;\n curve[index] = Math.exp(2 * x - 1) / Math.E;\n }\n\n return curve;\n}\n\n/**\n * Generate an S-curve (sine-based smooth curve)\n */\nfunction sCurveCurve(length: number, fadeIn: boolean): Float32Array {\n const curve = new Float32Array(length);\n const phase = fadeIn ? Math.PI / 2 : -Math.PI / 2;\n\n for (let i = 0; i < length; i++) {\n curve[i] = Math.sin((Math.PI * i) / length - phase) / 2 + 0.5;\n }\n\n return curve;\n}\n\n/**\n * Generate a logarithmic fade curve\n */\nfunction logarithmicCurve(length: number, fadeIn: boolean, base: number = 10): Float32Array {\n const curve = new Float32Array(length);\n\n for (let i = 0; i < length; i++) {\n const index = fadeIn ? i : length - 1 - i;\n const x = i / length;\n curve[index] = Math.log(1 + base * x) / Math.log(1 + base);\n }\n\n return curve;\n}\n\n/**\n * Generate a fade curve of the specified type\n */\nfunction generateCurve(type: FadeType, length: number, fadeIn: boolean): Float32Array {\n switch (type) {\n case 'linear':\n return linearCurve(length, fadeIn);\n case 'exponential':\n return exponentialCurve(length, fadeIn);\n case 'sCurve':\n return sCurveCurve(length, fadeIn);\n case 'logarithmic':\n return logarithmicCurve(length, fadeIn);\n default:\n return linearCurve(length, fadeIn);\n }\n}\n\n/**\n * Apply a fade in to an AudioParam\n *\n * @param param - The AudioParam to apply the fade to (usually gain)\n * @param startTime - When the fade starts (in seconds, AudioContext time)\n * @param duration - Duration of the fade in seconds\n * @param type - Type of fade curve\n * @param startValue - Starting value (default: 0)\n * @param endValue - Ending value (default: 1)\n */\nexport function applyFadeIn(\n param: AudioParam,\n startTime: number,\n duration: number,\n type: FadeType = 'linear',\n startValue: number = 0,\n endValue: number = 1\n): void {\n if (duration <= 0) return;\n\n if (type === 'linear') {\n // Use native linear ramp for better performance\n param.setValueAtTime(startValue, startTime);\n param.linearRampToValueAtTime(endValue, startTime + duration);\n } else if (type === 'exponential') {\n // Exponential ramp can't start/end at 0, use small value\n param.setValueAtTime(Math.max(startValue, 0.001), startTime);\n param.exponentialRampToValueAtTime(Math.max(endValue, 0.001), startTime + duration);\n } else {\n // Use curve for sCurve and logarithmic\n const curve = generateCurve(type, 10000, true);\n // Scale curve to value range\n const scaledCurve = new Float32Array(curve.length);\n const range = endValue - startValue;\n for (let i = 0; i < curve.length; i++) {\n scaledCurve[i] = startValue + curve[i] * range;\n }\n param.setValueCurveAtTime(scaledCurve, startTime, duration);\n }\n}\n\n/**\n * Apply a fade out to an AudioParam\n *\n * @param param - The AudioParam to apply the fade to (usually gain)\n * @param startTime - When the fade starts (in seconds, AudioContext time)\n * @param duration - Duration of the fade in seconds\n * @param type - Type of fade curve\n * @param startValue - Starting value (default: 1)\n * @param endValue - Ending value (default: 0)\n */\nexport function applyFadeOut(\n param: AudioParam,\n startTime: number,\n duration: number,\n type: FadeType = 'linear',\n startValue: number = 1,\n endValue: number = 0\n): void {\n if (duration <= 0) return;\n\n if (type === 'linear') {\n // Use native linear ramp for better performance\n param.setValueAtTime(startValue, startTime);\n param.linearRampToValueAtTime(endValue, startTime + duration);\n } else if (type === 'exponential') {\n // Exponential ramp can't start/end at 0, use small value\n param.setValueAtTime(Math.max(startValue, 0.001), startTime);\n param.exponentialRampToValueAtTime(Math.max(endValue, 0.001), startTime + duration);\n } else {\n // Use curve for sCurve and logarithmic\n const curve = generateCurve(type, 10000, false);\n // Scale curve to value range\n const scaledCurve = new Float32Array(curve.length);\n const range = startValue - endValue;\n for (let i = 0; i < curve.length; i++) {\n scaledCurve[i] = endValue + curve[i] * range;\n }\n param.setValueCurveAtTime(scaledCurve, startTime, duration);\n }\n}\n","/**\n * Global AudioContext Manager\n *\n * Provides a single AudioContext shared across the entire application.\n * This context is used by Tone.js for playback and by all recording/monitoring hooks.\n *\n * Uses Tone.js's Context class which wraps standardized-audio-context for\n * cross-browser compatibility (fixes Firefox AudioListener issues).\n */\n\nimport { Context, setContext } from 'tone';\n\nlet globalToneContext: Context | null = null;\n\n/**\n * Get the global Tone.js Context\n * This is the main context for cross-browser audio operations.\n * Use context.createAudioWorkletNode(), context.createMediaStreamSource(), etc.\n * @returns The Tone.js Context instance\n */\nexport function getGlobalContext(): Context {\n if (!globalToneContext) {\n globalToneContext = new Context();\n setContext(globalToneContext);\n }\n return globalToneContext;\n}\n\n/**\n * Get or create the global AudioContext\n * Uses Tone.js Context for cross-browser compatibility\n * @returns The global AudioContext instance (rawContext from Tone.Context)\n */\nexport function getGlobalAudioContext(): AudioContext {\n return getGlobalContext().rawContext as AudioContext;\n}\n\n/**\n * @deprecated Use getGlobalContext() instead\n * Get the Tone.js Context's rawContext typed as IAudioContext\n * @returns The rawContext cast as IAudioContext\n */\nexport function getGlobalToneContext(): Context {\n return getGlobalContext();\n}\n\n/**\n * Resume the global AudioContext if it's suspended\n * Should be called in response to a user gesture (e.g., button click)\n * @returns Promise that resolves when context is running\n */\nexport async function resumeGlobalAudioContext(): Promise<void> {\n const context = getGlobalContext();\n if (context.state !== 'running') {\n await context.resume();\n }\n}\n\n/**\n * Get the current state of the global AudioContext\n * @returns The AudioContext state ('suspended', 'running', or 'closed')\n */\nexport function getGlobalAudioContextState(): AudioContextState {\n return globalToneContext?.rawContext.state || 'suspended';\n}\n\n/**\n * Close the global AudioContext\n * Should only be called when the application is shutting down\n */\nexport async function closeGlobalAudioContext(): Promise<void> {\n if (globalToneContext && globalToneContext.rawContext.state !== 'closed') {\n await globalToneContext.close();\n globalToneContext = null;\n }\n}\n","/**\n * MediaStreamSource Manager\n *\n * Manages MediaStreamAudioSourceNode instances to ensure only one source\n * is created per MediaStream per AudioContext.\n *\n * Web Audio API constraint: You can only create one MediaStreamAudioSourceNode\n * per MediaStream per AudioContext. Multiple attempts will fail or disconnect\n * previous sources.\n *\n * This manager ensures a single source is shared across multiple consumers\n * (e.g., AnalyserNode for VU meter, AudioWorkletNode for recording).\n *\n * NOTE: With Tone.js Context, you can also use context.createMediaStreamSource()\n * directly, which handles cross-browser compatibility internally.\n */\n\nimport { getContext } from 'tone';\n\n// Map of MediaStream -> MediaStreamAudioSourceNode\nconst streamSources = new Map<MediaStream, MediaStreamAudioSourceNode>();\n\n// Map of MediaStream -> cleanup handlers\nconst streamCleanupHandlers = new Map<MediaStream, () => void>();\n\n/**\n * Get or create a MediaStreamAudioSourceNode for the given stream\n *\n * @param stream - The MediaStream to create a source for\n * @returns MediaStreamAudioSourceNode that can be connected to multiple nodes\n *\n * @example\n * ```typescript\n * const source = getMediaStreamSource(stream);\n *\n * // Multiple consumers can connect to the same source\n * source.connect(analyserNode); // For VU meter\n * source.connect(workletNode); // For recording\n * ```\n */\nexport function getMediaStreamSource(\n stream: MediaStream\n): MediaStreamAudioSourceNode {\n // Return existing source if we have one for this stream\n if (streamSources.has(stream)) {\n return streamSources.get(stream)!;\n }\n\n // Create new source using Tone.js's shared context for cross-browser compatibility\n const context = getContext();\n const source = context.createMediaStreamSource(stream);\n streamSources.set(stream, source);\n\n // Set up cleanup when stream ends\n const cleanup = () => {\n source.disconnect();\n streamSources.delete(stream);\n streamCleanupHandlers.delete(stream);\n\n // Remove event listener\n stream.removeEventListener('ended', cleanup);\n stream.removeEventListener('inactive', cleanup);\n };\n\n streamCleanupHandlers.set(stream, cleanup);\n\n // Clean up when stream ends or becomes inactive\n stream.addEventListener('ended', cleanup);\n stream.addEventListener('inactive', cleanup);\n\n return source;\n}\n\n/**\n * Manually release a MediaStreamSource\n *\n * Normally you don't need to call this - cleanup happens automatically\n * when the stream ends. Only call this if you need to force cleanup.\n *\n * @param stream - The MediaStream to release the source for\n */\nexport function releaseMediaStreamSource(stream: MediaStream): void {\n const cleanup = streamCleanupHandlers.get(stream);\n if (cleanup) {\n cleanup();\n }\n}\n\n/**\n * Check if a MediaStreamSource exists for the given stream\n *\n * @param stream - The MediaStream to check\n * @returns true if a source exists for this stream\n */\nexport function hasMediaStreamSource(stream: MediaStream): boolean {\n return streamSources.has(stream);\n}\n"],"mappings":";AACA;AAAA,EACE,UAAAA;AAAA,EAEA,kBAAAC;AAAA,EACA;AAAA,EACA,OAAAC;AAAA,EACA;AAAA,EACA;AAAA,OAEK;;;ACTP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,OACK;;;ACaP,SAAS,YAAY,QAAgB,QAA+B;AAClE,QAAM,QAAQ,IAAI,aAAa,MAAM;AACrC,QAAM,QAAQ,SAAS;AAEvB,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,UAAM,IAAI,IAAI;AACd,UAAM,CAAC,IAAI,SAAS,IAAI,IAAI;AAAA,EAC9B;AAEA,SAAO;AACT;AAKA,SAAS,iBAAiB,QAAgB,QAA+B;AACvE,QAAM,QAAQ,IAAI,aAAa,MAAM;AACrC,QAAM,QAAQ,SAAS;AAEvB,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,UAAM,IAAI,IAAI;AACd,UAAM,QAAQ,SAAS,IAAI,SAAS,IAAI;AACxC,UAAM,KAAK,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK;AAAA,EAC5C;AAEA,SAAO;AACT;AAKA,SAAS,YAAY,QAAgB,QAA+B;AAClE,QAAM,QAAQ,IAAI,aAAa,MAAM;AACrC,QAAM,QAAQ,SAAS,KAAK,KAAK,IAAI,CAAC,KAAK,KAAK;AAEhD,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,UAAM,CAAC,IAAI,KAAK,IAAK,KAAK,KAAK,IAAK,SAAS,KAAK,IAAI,IAAI;AAAA,EAC5D;AAEA,SAAO;AACT;AAKA,SAAS,iBAAiB,QAAgB,QAAiB,OAAe,IAAkB;AAC1F,QAAM,QAAQ,IAAI,aAAa,MAAM;AAErC,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,UAAM,QAAQ,SAAS,IAAI,SAAS,IAAI;AACxC,UAAM,IAAI,IAAI;AACd,UAAM,KAAK,IAAI,KAAK,IAAI,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,IAAI,IAAI;AAAA,EAC3D;AAEA,SAAO;AACT;AAKA,SAAS,cAAc,MAAgB,QAAgB,QAA+B;AACpF,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO,YAAY,QAAQ,MAAM;AAAA,IACnC,KAAK;AACH,aAAO,iBAAiB,QAAQ,MAAM;AAAA,IACxC,KAAK;AACH,aAAO,YAAY,QAAQ,MAAM;AAAA,IACnC,KAAK;AACH,aAAO,iBAAiB,QAAQ,MAAM;AAAA,IACxC;AACE,aAAO,YAAY,QAAQ,MAAM;AAAA,EACrC;AACF;AAYO,SAAS,YACd,OACA,WACA,UACA,OAAiB,UACjB,aAAqB,GACrB,WAAmB,GACb;AACN,MAAI,YAAY,EAAG;AAEnB,MAAI,SAAS,UAAU;AAErB,UAAM,eAAe,YAAY,SAAS;AAC1C,UAAM,wBAAwB,UAAU,YAAY,QAAQ;AAAA,EAC9D,WAAW,SAAS,eAAe;AAEjC,UAAM,eAAe,KAAK,IAAI,YAAY,IAAK,GAAG,SAAS;AAC3D,UAAM,6BAA6B,KAAK,IAAI,UAAU,IAAK,GAAG,YAAY,QAAQ;AAAA,EACpF,OAAO;AAEL,UAAM,QAAQ,cAAc,MAAM,KAAO,IAAI;AAE7C,UAAM,cAAc,IAAI,aAAa,MAAM,MAAM;AACjD,UAAM,QAAQ,WAAW;AACzB,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,kBAAY,CAAC,IAAI,aAAa,MAAM,CAAC,IAAI;AAAA,IAC3C;AACA,UAAM,oBAAoB,aAAa,WAAW,QAAQ;AAAA,EAC5D;AACF;AAYO,SAAS,aACd,OACA,WACA,UACA,OAAiB,UACjB,aAAqB,GACrB,WAAmB,GACb;AACN,MAAI,YAAY,EAAG;AAEnB,MAAI,SAAS,UAAU;AAErB,UAAM,eAAe,YAAY,SAAS;AAC1C,UAAM,wBAAwB,UAAU,YAAY,QAAQ;AAAA,EAC9D,WAAW,SAAS,eAAe;AAEjC,UAAM,eAAe,KAAK,IAAI,YAAY,IAAK,GAAG,SAAS;AAC3D,UAAM,6BAA6B,KAAK,IAAI,UAAU,IAAK,GAAG,YAAY,QAAQ;AAAA,EACpF,OAAO;AAEL,UAAM,QAAQ,cAAc,MAAM,KAAO,KAAK;AAE9C,UAAM,cAAc,IAAI,aAAa,MAAM,MAAM;AACjD,UAAM,QAAQ,aAAa;AAC3B,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,kBAAY,CAAC,IAAI,WAAW,MAAM,CAAC,IAAI;AAAA,IACzC;AACA,UAAM,oBAAoB,aAAa,WAAW,QAAQ;AAAA,EAC5D;AACF;;;ADvIO,IAAM,YAAN,MAAgB;AAAA;AAAA,EAUrB,YAAY,SAA2B;AAFvC,SAAQ,gBAAwB;AAG9B,SAAK,QAAQ,QAAQ;AAGrB,SAAK,aAAa,IAAI,OAAO,KAAK,SAAS,QAAQ,MAAM,IAAI,CAAC;AAC9D,SAAK,UAAU,IAAI,OAAO,QAAQ,MAAM,SAAS;AACjD,SAAK,WAAW,IAAI,KAAK,QAAQ,MAAM,QAAQ,IAAI,CAAC;AAGpD,UAAM,cAAc,QAAQ,eAAe,eAAe;AAC1D,QAAI,QAAQ,SAAS;AACnB,YAAM,UAAU,QAAQ,QAAQ,KAAK,UAAU,aAAa,KAAK;AACjE,UAAI,SAAS;AACX,aAAK,iBAAiB;AAAA,MACxB;AAAA,IACF,OAAO;AACL,WAAK,SAAS,QAAQ,WAAW;AAAA,IACnC;AAGA,UAAM,YAAwB,QAAQ,UAAU,QAAQ,SAAS,CAAC;AAAA,MAChE,QAAQ,QAAQ;AAAA,MAChB,WAAW;AAAA;AAAA,MACX,UAAU,QAAQ,OAAO;AAAA;AAAA,MACzB,QAAQ;AAAA,MACR,QAAQ,QAAQ,MAAM;AAAA,MACtB,SAAS,QAAQ,MAAM;AAAA,MACvB,MAAM;AAAA,IACR,CAAC,IAAI,CAAC;AAGN,SAAK,QAAQ,UAAU,IAAI,cAAY;AACrC,YAAM,SAAS,IAAI,OAAO;AAAA,QACxB,KAAK,SAAS;AAAA,QACd,MAAM;AAAA,QACN,QAAQ,MAAM;AACZ,eAAK;AACL,cAAI,KAAK,kBAAkB,KAAK,KAAK,gBAAgB;AACnD,iBAAK,eAAe;AAAA,UACtB;AAAA,QACF;AAAA,MACF,CAAC;AAED,YAAM,WAAW,IAAI,KAAK,SAAS,IAAI;AAGvC,aAAO,QAAQ,QAAQ;AACvB,eAAS,MAAM,KAAK,YAAY,KAAK,SAAS,KAAK,QAAQ;AAK3D,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA,gBAAgB;AAAA,QAChB,eAAe;AAAA,MACjB;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAc,YAAwB,eAAuB,aAAqB,GAAS;AACjG,UAAM,EAAE,UAAU,SAAS,IAAI;AAC/B,UAAM,aAAc,SAAS,KAAa;AAG1C,eAAW,sBAAsB,CAAC;AAGlC,UAAM,WAAW,aAAa,SAAS;AAGvC,QAAI,SAAS,UAAU,WAAW,SAAS,OAAO,UAAU;AAC1D,YAAM,iBAAiB,SAAS,OAAO;AAEvC,UAAI,YAAY,GAAG;AAEjB;AAAA,UACE;AAAA,UACA;AAAA,UACA;AAAA,UACA,SAAS,OAAO,QAAQ;AAAA,UACxB;AAAA,UACA,SAAS;AAAA,QACX;AAAA,MACF,OAAO;AAEL,cAAM,wBAAwB,iBAAiB;AAC/C,cAAM,eAAe,WAAW;AAChC,cAAM,aAAa,SAAS,OAAO;AACnC;AAAA,UACE;AAAA,UACA;AAAA,UACA;AAAA,UACA,SAAS,OAAO,QAAQ;AAAA,UACxB;AAAA,UACA,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF,OAAO;AAEL,iBAAW,eAAe,SAAS,MAAM,aAAa;AAAA,IACxD;AAGA,QAAI,SAAS,SAAS;AACpB,YAAM,eAAe,SAAS,WAAW,SAAS,QAAQ;AAC1D,YAAM,qBAAqB,eAAe;AAE1C,UAAI,qBAAqB,GAAG;AAE1B,cAAM,uBAAuB,gBAAgB;AAC7C;AAAA,UACE;AAAA,UACA;AAAA,UACA,SAAS,QAAQ;AAAA,UACjB,SAAS,QAAQ,QAAQ;AAAA,UACzB,SAAS;AAAA,UACT;AAAA,QACF;AAAA,MACF,WAAW,qBAAqB,CAAC,SAAS,QAAQ,UAAU;AAE1D,cAAM,iBAAiB,CAAC;AACxB,cAAM,wBAAwB,SAAS,QAAQ,WAAW;AAC1D,cAAM,eAAe,iBAAiB,SAAS,QAAQ;AACvD,cAAM,aAAa,SAAS,QAAQ,IAAI;AACxC;AAAA,UACE;AAAA,UACA;AAAA,UACA;AAAA,UACA,SAAS,QAAQ,QAAQ;AAAA,UACzB;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IAEF;AAAA,EACF;AAAA,EAEQ,SAAS,MAAsB;AACrC,WAAO,KAAK,KAAK,MAAM,IAAI;AAAA,EAC7B;AAAA,EAEA,UAAU,MAAoB;AAC5B,SAAK,MAAM,OAAO;AAClB,SAAK,WAAW,OAAO,QAAQ,KAAK,SAAS,IAAI;AAAA,EACnD;AAAA,EAEA,OAAO,KAAmB;AACxB,SAAK,MAAM,YAAY;AACvB,SAAK,QAAQ,IAAI,QAAQ;AAAA,EAC3B;AAAA,EAEA,QAAQ,OAAsB;AAC5B,SAAK,MAAM,QAAQ;AACnB,SAAK,SAAS,KAAK,QAAQ,QAAQ,IAAI;AAAA,EACzC;AAAA,EAEA,QAAQ,QAAuB;AAC7B,SAAK,MAAM,SAAS;AAAA,EACtB;AAAA,EAEA,KAAK,MAAe,SAAiB,GAAG,UAAyB;AAE/D,UAAM,YAAY,QAAQ,IAAI;AAI9B,QAAI,KAAK,WAAW;AAClB,WAAK,KAAK;AAAA,IACZ;AAEA,SAAK,gBAAgB;AAGrB,SAAK,MAAM,QAAQ,gBAAc;AAC/B,YAAM,EAAE,QAAQ,SAAS,IAAI;AAG7B,YAAM,mBAAmB;AAGzB,YAAM,YAAY,SAAS;AAC3B,YAAM,UAAU,SAAS,YAAY,SAAS;AAE9C,UAAI,mBAAmB,SAAS;AAE9B,aAAK;AACL,mBAAW,gBAAgB,IAAI;AAE/B,YAAI,oBAAoB,WAAW;AAEjC,gBAAM,aAAa,mBAAmB,YAAY,SAAS;AAC3D,gBAAM,oBAAoB,SAAS,YAAY,mBAAmB;AAClE,gBAAM,eAAe,WAAW,KAAK,IAAI,UAAU,iBAAiB,IAAI;AAExE,qBAAW,iBAAiB;AAE5B,eAAK,cAAc,YAAY,WAAW,UAAU;AACpD,iBAAO,MAAM,WAAW,YAAY,YAAY;AAAA,QAClD,OAAO;AAEL,gBAAM,QAAQ,YAAY;AAC1B,gBAAM,eAAe,WAAW,KAAK,IAAI,WAAW,OAAO,SAAS,QAAQ,IAAI,SAAS;AAEzF,cAAI,SAAS,YAAY,WAAW;AAClC,uBAAW,iBAAiB,SAAS;AAErC,iBAAK,cAAc,YAAY,YAAY,OAAO,SAAS,MAAM;AACjE,mBAAO,MAAM,YAAY,OAAO,SAAS,QAAQ,YAAY;AAAA,UAC/D,OAAO;AACL,iBAAK;AAAA,UACP;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,QAAc;AAGZ,SAAK,MAAM,QAAQ,gBAAc;AAC/B,UAAI,WAAW,OAAO,UAAU,WAAW;AACzC,cAAM,WAAW,IAAI,IAAI,WAAW,iBAAiB,WAAW,OAAO;AACvE,mBAAW,iBAAiB,WAAW,iBAAiB;AAAA,MAC1D;AAEA,iBAAW,OAAO,KAAK;AAAA,IACzB,CAAC;AAED,SAAK,gBAAgB;AAAA,EACvB;AAAA,EAEA,KAAK,MAAqB;AAExB,UAAM,WAAW,QAAQ,IAAI;AAC7B,SAAK,MAAM,QAAQ,gBAAc;AAC/B,iBAAW,OAAO,KAAK,QAAQ;AAC/B,iBAAW,iBAAiB;AAAA,IAC9B,CAAC;AACD,SAAK,gBAAgB;AAAA,EACvB;AAAA,EAEA,UAAgB;AAEd,QAAI,KAAK,gBAAgB;AACvB,WAAK,eAAe;AAAA,IACtB;AAGA,SAAK,MAAM,QAAQ,gBAAc;AAC/B,iBAAW,OAAO,QAAQ;AAC1B,iBAAW,SAAS,QAAQ;AAAA,IAC9B,CAAC;AAGD,SAAK,WAAW,QAAQ;AACxB,SAAK,QAAQ,QAAQ;AACrB,SAAK,SAAS,QAAQ;AAAA,EACxB;AAAA,EAEA,IAAI,KAAa;AACf,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,IAAI,WAAmB;AAErB,QAAI,KAAK,MAAM,WAAW,EAAG,QAAO;AACpC,UAAM,WAAW,KAAK,MAAM,KAAK,MAAM,SAAS,CAAC;AACjD,WAAO,SAAS,SAAS,YAAY,SAAS,SAAS;AAAA,EACzD;AAAA,EAEA,IAAI,SAAsB;AAExB,WAAO,KAAK,MAAM,CAAC,GAAG,SAAS;AAAA,EACjC;AAAA,EAEA,IAAI,YAAqB;AAEvB,WAAO,KAAK,MAAM,KAAK,gBAAc,WAAW,OAAO,UAAU,SAAS;AAAA,EAC5E;AAAA,EAEA,IAAI,QAAiB;AACnB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,IAAI,YAAoB;AAGtB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,kBAAkB,UAA4B;AAC5C,SAAK,iBAAiB;AAAA,EACxB;AACF;;;ADzUO,IAAM,cAAN,MAAkB;AAAA,EAWvB,YAAY,UAA8B,CAAC,GAAG;AAV9C,SAAQ,SAAiC,oBAAI,IAAI;AAEjD,SAAQ,gBAAgB;AACxB,SAAQ,eAA4B,oBAAI,IAAI;AAC5C,SAAQ,kBAAwC,oBAAI,IAAI;AAGxD,SAAQ,eAAoC,oBAAI,IAAI;AACpD;AAAA,SAAQ,oBAA4B;AAGlC,SAAK,eAAe,IAAIC,QAAO,KAAK,SAAS,QAAQ,cAAc,CAAC,CAAC;AAGrE,QAAI,QAAQ,SAAS;AACnB,YAAM,UAAU,QAAQ,QAAQ,KAAK,cAAcC,gBAAe,GAAG,KAAK;AAC1E,UAAI,SAAS;AACX,aAAK,iBAAiB;AAAA,MACxB;AAAA,IACF,OAAO;AACL,WAAK,aAAa,cAAc;AAAA,IAClC;AAEA,QAAI,QAAQ,QAAQ;AAClB,cAAQ,OAAO,QAAQ,WAAS;AAC9B,aAAK,OAAO,IAAI,MAAM,IAAI,KAAK;AAE/B,aAAK,gBAAgB,IAAI,MAAM,IAAI,MAAM,KAAK;AAAA,MAChD,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEQ,SAAS,MAAsB;AACrC,WAAO,KAAK,KAAK,MAAM,IAAI;AAAA,EAC7B;AAAA,EAEA,MAAM,OAAsB;AAC1B,QAAI,KAAK,cAAe;AAExB,UAAM,MAAM;AACZ,SAAK,gBAAgB;AAAA,EACvB;AAAA,EAEA,SAAS,cAA2C;AAElD,UAAM,yBAAyB;AAAA,MAC7B,GAAG;AAAA,MACH,aAAa,KAAK;AAAA,IACpB;AACA,UAAM,YAAY,IAAI,UAAU,sBAAsB;AACtD,SAAK,OAAO,IAAI,UAAU,IAAI,SAAS;AAEvC,SAAK,gBAAgB,IAAI,UAAU,IAAI,aAAa,MAAM,SAAS,KAAK;AAExE,QAAI,aAAa,MAAM,QAAQ;AAC7B,WAAK,aAAa,IAAI,UAAU,EAAE;AAAA,IACpC;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,wBAA8B;AAC5B,SAAK,iBAAiB;AAAA,EACxB;AAAA,EAEA,YAAY,SAAuB;AACjC,UAAM,QAAQ,KAAK,OAAO,IAAI,OAAO;AACrC,QAAI,OAAO;AACT,YAAM,QAAQ;AACd,WAAK,OAAO,OAAO,OAAO;AAC1B,WAAK,gBAAgB,OAAO,OAAO;AACnC,WAAK,aAAa,OAAO,OAAO;AAAA,IAClC;AAAA,EACF;AAAA,EAEA,SAAS,SAAwC;AAC/C,WAAO,KAAK,OAAO,IAAI,OAAO;AAAA,EAChC;AAAA,EAEA,KAAK,MAAe,QAAiB,UAAyB;AAC5D,QAAI,CAAC,KAAK,eAAe;AACvB,cAAQ,KAAK,iDAAiD;AAC9D;AAAA,IACF;AAGA,UAAM,YAAY,QAAQC,KAAI;AAC9B,UAAM,mBAAmB,UAAU;AAGnC,SAAK;AACL,UAAM,mBAAmB,KAAK;AAG9B,SAAK,aAAa,MAAM;AAGxB,SAAK,OAAO,QAAQ,CAAC,cAAc;AACjC,YAAM,iBAAiB,UAAU;AAEjC,UAAI,oBAAoB,gBAAgB;AAEtC,cAAM,eAAe,mBAAmB;AAExC,YAAI,aAAa,QAAW;AAC1B,eAAK,aAAa,IAAI,UAAU,IAAI,gBAAgB;AACpD,oBAAU,kBAAkB,MAAM;AAEhC,gBAAI,KAAK,aAAa,IAAI,UAAU,EAAE,MAAM,kBAAkB;AAC5D,mBAAK,aAAa,OAAO,UAAU,EAAE;AACrC,kBAAI,KAAK,aAAa,SAAS,KAAK,KAAK,4BAA4B;AACnE,qBAAK,2BAA2B;AAAA,cAClC;AAAA,YACF;AAAA,UACF,CAAC;AAAA,QACH;AAEA,kBAAU,KAAK,WAAW,cAAc,QAAQ;AAAA,MAClD,OAAO;AAEL,cAAM,QAAQ,iBAAiB;AAE/B,YAAI,aAAa,QAAW;AAC1B,eAAK,aAAa,IAAI,UAAU,IAAI,gBAAgB;AACpD,oBAAU,kBAAkB,MAAM;AAEhC,gBAAI,KAAK,aAAa,IAAI,UAAU,EAAE,MAAM,kBAAkB;AAC5D,mBAAK,aAAa,OAAO,UAAU,EAAE;AACrC,kBAAI,KAAK,aAAa,SAAS,KAAK,KAAK,4BAA4B;AACnE,qBAAK,2BAA2B;AAAA,cAClC;AAAA,YACF;AAAA,UACF,CAAC;AAAA,QACH;AAEA,kBAAU,KAAK,YAAY,OAAO,GAAG,QAAQ;AAAA,MAC/C;AAAA,IACF,CAAC;AAGD,QAAI,WAAW,QAAW;AAExB,mBAAa,EAAE,MAAM,WAAW,MAAM;AAAA,IACxC,OAAO;AAEL,mBAAa,EAAE,MAAM,SAAS;AAAA,IAChC;AAAA,EACF;AAAA,EAEA,QAAc;AACZ,iBAAa,EAAE,MAAM;AACrB,SAAK,OAAO,QAAQ,WAAS;AAC3B,YAAM,MAAM;AAAA,IACd,CAAC;AAAA,EACH;AAAA,EAEA,OAAa;AACX,iBAAa,EAAE,KAAK;AACpB,SAAK,OAAO,QAAQ,WAAS;AAC3B,YAAM,KAAK;AAAA,IACb,CAAC;AAAA,EACH;AAAA,EAEA,cAAc,MAAoB;AAChC,SAAK,aAAa,OAAO,QAAQ,KAAK,SAAS,IAAI;AAAA,EACrD;AAAA,EAEA,QAAQ,SAAiB,QAAuB;AAC9C,UAAM,QAAQ,KAAK,OAAO,IAAI,OAAO;AACrC,QAAI,OAAO;AACT,YAAM,QAAQ,MAAM;AACpB,UAAI,QAAQ;AACV,aAAK,aAAa,IAAI,OAAO;AAAA,MAC/B,OAAO;AACL,aAAK,aAAa,OAAO,OAAO;AAAA,MAClC;AAGA,WAAK,iBAAiB;AAAA,IACxB;AAAA,EACF;AAAA,EAEQ,mBAAyB;AAC/B,UAAM,kBAAkB,KAAK,aAAa,OAAO;AAEjD,SAAK,OAAO,QAAQ,CAAC,OAAO,OAAO;AACjC,UAAI,iBAAiB;AAEnB,YAAI,CAAC,KAAK,aAAa,IAAI,EAAE,GAAG;AAC9B,gBAAM,QAAQ,IAAI;AAAA,QACpB,OAAO;AAEL,gBAAM,gBAAgB,KAAK,gBAAgB,IAAI,EAAE,KAAK;AACtD,gBAAM,QAAQ,aAAa;AAAA,QAC7B;AAAA,MACF,OAAO;AAEL,cAAM,gBAAgB,KAAK,gBAAgB,IAAI,EAAE,KAAK;AACtD,cAAM,QAAQ,aAAa;AAAA,MAC7B;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,QAAQ,SAAiB,OAAsB;AAC7C,UAAM,QAAQ,KAAK,OAAO,IAAI,OAAO;AACrC,QAAI,OAAO;AAET,WAAK,gBAAgB,IAAI,SAAS,KAAK;AACvC,YAAM,QAAQ,KAAK;AAAA,IACrB;AAAA,EACF;AAAA,EAEA,iBAAyB;AACvB,WAAO,aAAa,EAAE;AAAA,EACxB;AAAA,EAEA,OAAO,MAAoB;AACzB,iBAAa,EAAE,UAAU;AAAA,EAC3B;AAAA,EAEA,UAAgB;AACd,SAAK,OAAO,QAAQ,WAAS;AAC3B,YAAM,QAAQ;AAAA,IAChB,CAAC;AACD,SAAK,OAAO,MAAM;AAGlB,QAAI,KAAK,gBAAgB;AACvB,WAAK,eAAe;AAAA,IACtB;AAEA,SAAK,aAAa,QAAQ;AAAA,EAC5B;AAAA,EAEA,IAAI,UAAuB;AACzB,WAAO,WAAW;AAAA,EACpB;AAAA,EAEA,IAAI,aAAqB;AACvB,WAAO,WAAW,EAAE;AAAA,EACtB;AAAA,EAEA,sBAAsB,UAA4B;AAChD,SAAK,6BAA6B;AAAA,EACpC;AACF;;;AGrQA,SAAS,SAAS,kBAAkB;AAEpC,IAAI,oBAAoC;AAQjC,SAAS,mBAA4B;AAC1C,MAAI,CAAC,mBAAmB;AACtB,wBAAoB,IAAI,QAAQ;AAChC,eAAW,iBAAiB;AAAA,EAC9B;AACA,SAAO;AACT;AAOO,SAAS,wBAAsC;AACpD,SAAO,iBAAiB,EAAE;AAC5B;AAOO,SAAS,uBAAgC;AAC9C,SAAO,iBAAiB;AAC1B;AAOA,eAAsB,2BAA0C;AAC9D,QAAM,UAAU,iBAAiB;AACjC,MAAI,QAAQ,UAAU,WAAW;AAC/B,UAAM,QAAQ,OAAO;AAAA,EACvB;AACF;AAMO,SAAS,6BAAgD;AAC9D,SAAO,mBAAmB,WAAW,SAAS;AAChD;AAMA,eAAsB,0BAAyC;AAC7D,MAAI,qBAAqB,kBAAkB,WAAW,UAAU,UAAU;AACxE,UAAM,kBAAkB,MAAM;AAC9B,wBAAoB;AAAA,EACtB;AACF;;;AC1DA,SAAS,cAAAC,mBAAkB;AAG3B,IAAM,gBAAgB,oBAAI,IAA6C;AAGvE,IAAM,wBAAwB,oBAAI,IAA6B;AAiBxD,SAAS,qBACd,QAC4B;AAE5B,MAAI,cAAc,IAAI,MAAM,GAAG;AAC7B,WAAO,cAAc,IAAI,MAAM;AAAA,EACjC;AAGA,QAAM,UAAUA,YAAW;AAC3B,QAAM,SAAS,QAAQ,wBAAwB,MAAM;AACrD,gBAAc,IAAI,QAAQ,MAAM;AAGhC,QAAM,UAAU,MAAM;AACpB,WAAO,WAAW;AAClB,kBAAc,OAAO,MAAM;AAC3B,0BAAsB,OAAO,MAAM;AAGnC,WAAO,oBAAoB,SAAS,OAAO;AAC3C,WAAO,oBAAoB,YAAY,OAAO;AAAA,EAChD;AAEA,wBAAsB,IAAI,QAAQ,OAAO;AAGzC,SAAO,iBAAiB,SAAS,OAAO;AACxC,SAAO,iBAAiB,YAAY,OAAO;AAE3C,SAAO;AACT;AAUO,SAAS,yBAAyB,QAA2B;AAClE,QAAM,UAAU,sBAAsB,IAAI,MAAM;AAChD,MAAI,SAAS;AACX,YAAQ;AAAA,EACV;AACF;AAQO,SAAS,qBAAqB,QAA8B;AACjE,SAAO,cAAc,IAAI,MAAM;AACjC;","names":["Volume","getDestination","now","Volume","getDestination","now","getContext"]}
|
|
1
|
+
{"version":3,"sources":["../src/TonePlayout.ts","../src/ToneTrack.ts","../src/fades.ts","../src/audioContext.ts","../src/mediaStreamSourceManager.ts"],"sourcesContent":["// Named imports for tree-shaking\nimport {\n Volume,\n ToneAudioNode,\n getDestination,\n start,\n now,\n getTransport,\n getContext,\n BaseContext,\n} from 'tone';\nimport { ToneTrack, ToneTrackOptions } from './ToneTrack';\n\n// Effects function no longer receives ToneLib - effects should import Tone themselves\nexport type EffectsFunction = (masterGainNode: Volume, destination: ToneAudioNode, isOffline: boolean) => void | (() => void);\n\nexport interface TonePlayoutOptions {\n tracks?: ToneTrack[];\n masterGain?: number;\n effects?: EffectsFunction;\n}\n\nexport class TonePlayout {\n private tracks: Map<string, ToneTrack> = new Map();\n private masterVolume: Volume;\n private isInitialized = false;\n private soloedTracks: Set<string> = new Set();\n private manualMuteState: Map<string, boolean> = new Map();\n private effectsCleanup?: () => void;\n private onPlaybackCompleteCallback?: () => void;\n private activeTracks: Map<string, number> = new Map(); // Map track ID to session ID\n private playbackSessionId: number = 0;\n\n constructor(options: TonePlayoutOptions = {}) {\n this.masterVolume = new Volume(this.gainToDb(options.masterGain ?? 1));\n\n // Setup effects chain if provided, otherwise connect directly to destination\n if (options.effects) {\n const cleanup = options.effects(this.masterVolume, getDestination(), false);\n if (cleanup) {\n this.effectsCleanup = cleanup;\n }\n } else {\n this.masterVolume.toDestination();\n }\n\n if (options.tracks) {\n options.tracks.forEach(track => {\n this.tracks.set(track.id, track);\n // Initialize manual mute state for constructor-provided tracks\n this.manualMuteState.set(track.id, track.muted);\n });\n }\n }\n\n private gainToDb(gain: number): number {\n return 20 * Math.log10(gain);\n }\n\n async init(): Promise<void> {\n if (this.isInitialized) return;\n\n await start();\n this.isInitialized = true;\n }\n\n addTrack(trackOptions: ToneTrackOptions): ToneTrack {\n // Ensure tracks connect to master volume instead of destination\n const optionsWithDestination = {\n ...trackOptions,\n destination: this.masterVolume,\n };\n const toneTrack = new ToneTrack(optionsWithDestination);\n this.tracks.set(toneTrack.id, toneTrack);\n // Initialize manual mute state from track options\n this.manualMuteState.set(toneTrack.id, trackOptions.track.muted ?? false);\n // Initialize solo state from track options\n if (trackOptions.track.soloed) {\n this.soloedTracks.add(toneTrack.id);\n }\n return toneTrack;\n }\n\n /**\n * Apply solo muting after all tracks have been added.\n * Call this after adding all tracks to ensure solo logic is applied correctly.\n */\n applyInitialSoloState(): void {\n this.updateSoloMuting();\n }\n\n removeTrack(trackId: string): void {\n const track = this.tracks.get(trackId);\n if (track) {\n track.dispose();\n this.tracks.delete(trackId);\n this.manualMuteState.delete(trackId);\n this.soloedTracks.delete(trackId);\n }\n }\n\n getTrack(trackId: string): ToneTrack | undefined {\n return this.tracks.get(trackId);\n }\n\n play(when?: number, offset?: number, duration?: number): void {\n if (!this.isInitialized) {\n console.warn('TonePlayout not initialized. Call init() first.');\n return;\n }\n\n // Use now() as default, but call it here after init check (not in function signature)\n const startTime = when ?? now();\n const playbackPosition = offset ?? 0;\n\n // Increment session ID to invalidate old callbacks\n this.playbackSessionId++;\n const currentSessionId = this.playbackSessionId;\n\n // Clear active tracks and set up stop callbacks if duration is specified\n this.activeTracks.clear();\n\n // Play tracks based on their individual start times\n this.tracks.forEach((toneTrack) => {\n const trackStartTime = toneTrack.startTime;\n\n if (playbackPosition >= trackStartTime) {\n // Track should be playing - calculate buffer offset and start immediately\n const bufferOffset = playbackPosition - trackStartTime;\n\n if (duration !== undefined) {\n this.activeTracks.set(toneTrack.id, currentSessionId);\n toneTrack.setOnStopCallback(() => {\n // Only process if this track is still in activeTracks with matching session ID\n if (this.activeTracks.get(toneTrack.id) === currentSessionId) {\n this.activeTracks.delete(toneTrack.id);\n if (this.activeTracks.size === 0 && this.onPlaybackCompleteCallback) {\n this.onPlaybackCompleteCallback();\n }\n }\n });\n }\n\n toneTrack.play(startTime, bufferOffset, duration);\n } else {\n // Track should start later - schedule it to start when playback reaches its start time\n const delay = trackStartTime - playbackPosition;\n\n if (duration !== undefined) {\n this.activeTracks.set(toneTrack.id, currentSessionId);\n toneTrack.setOnStopCallback(() => {\n // Only process if this track is still in activeTracks with matching session ID\n if (this.activeTracks.get(toneTrack.id) === currentSessionId) {\n this.activeTracks.delete(toneTrack.id);\n if (this.activeTracks.size === 0 && this.onPlaybackCompleteCallback) {\n this.onPlaybackCompleteCallback();\n }\n }\n });\n }\n\n toneTrack.play(startTime + delay, 0, duration);\n }\n });\n\n // Start transport\n if (offset !== undefined) {\n // Explicit offset provided - seek to that position\n getTransport().start(startTime, offset);\n } else {\n // No offset - resume from pause (Transport resumes from current position)\n getTransport().start(startTime);\n }\n }\n\n pause(): void {\n getTransport().pause();\n this.tracks.forEach(track => {\n track.pause();\n });\n }\n\n stop(): void {\n getTransport().stop();\n this.tracks.forEach(track => {\n track.stop();\n });\n }\n\n setMasterGain(gain: number): void {\n this.masterVolume.volume.value = this.gainToDb(gain);\n }\n\n setSolo(trackId: string, soloed: boolean): void {\n const track = this.tracks.get(trackId);\n if (track) {\n track.setSolo(soloed);\n if (soloed) {\n this.soloedTracks.add(trackId);\n } else {\n this.soloedTracks.delete(trackId);\n }\n\n // Update mute state of all tracks based on solo logic\n this.updateSoloMuting();\n }\n }\n\n private updateSoloMuting(): void {\n const hasSoloedTracks = this.soloedTracks.size > 0;\n\n this.tracks.forEach((track, id) => {\n if (hasSoloedTracks) {\n // If there are soloed tracks, mute all non-soloed tracks\n if (!this.soloedTracks.has(id)) {\n track.setMute(true);\n } else {\n // Restore manual mute state for soloed tracks\n const manuallyMuted = this.manualMuteState.get(id) ?? false;\n track.setMute(manuallyMuted);\n }\n } else {\n // No soloed tracks, restore original manual mute state for all tracks\n const manuallyMuted = this.manualMuteState.get(id) ?? false;\n track.setMute(manuallyMuted);\n }\n });\n }\n\n setMute(trackId: string, muted: boolean): void {\n const track = this.tracks.get(trackId);\n if (track) {\n // Store the manual mute state\n this.manualMuteState.set(trackId, muted);\n track.setMute(muted);\n }\n }\n\n getCurrentTime(): number {\n return getTransport().seconds;\n }\n\n seekTo(time: number): void {\n getTransport().seconds = time;\n }\n\n dispose(): void {\n this.tracks.forEach(track => {\n track.dispose();\n });\n this.tracks.clear();\n\n // Clean up effects if cleanup function was provided\n if (this.effectsCleanup) {\n this.effectsCleanup();\n }\n\n this.masterVolume.dispose();\n }\n\n get context(): BaseContext {\n return getContext();\n }\n\n get sampleRate(): number {\n return getContext().sampleRate;\n }\n\n setOnPlaybackComplete(callback: () => void): void {\n this.onPlaybackCompleteCallback = callback;\n }\n}\n","// Named imports for tree-shaking\nimport {\n Player,\n Volume,\n Gain,\n Panner,\n ToneAudioNode,\n getDestination,\n now,\n} from 'tone';\nimport { Track, type Fade } from '@waveform-playlist/core';\nimport { applyFadeIn, applyFadeOut } from './fades';\n\n// Effects function no longer receives ToneLib - effects should import Tone themselves\nexport type TrackEffectsFunction = (graphEnd: Gain, masterGainNode: ToneAudioNode, isOffline: boolean) => void | (() => void);\n\nexport interface ClipInfo {\n buffer: AudioBuffer;\n startTime: number; // When this clip starts in the track timeline (seconds)\n duration: number; // How long this clip plays (seconds)\n offset: number; // Where to start playing within the buffer (seconds)\n fadeIn?: Fade;\n fadeOut?: Fade;\n gain: number; // Clip-level gain\n}\n\nexport interface ToneTrackOptions {\n buffer?: AudioBuffer; // Legacy: single buffer (deprecated, use clips instead)\n clips?: ClipInfo[]; // Modern: array of clips\n track: Track;\n effects?: TrackEffectsFunction;\n destination?: ToneAudioNode;\n}\n\ninterface ClipPlayer {\n player: Player;\n clipInfo: ClipInfo;\n fadeGain: Gain;\n pausedPosition: number;\n playStartTime: number;\n}\n\nexport class ToneTrack {\n private clips: ClipPlayer[]; // Array of clip players\n private volumeNode: Volume;\n private panNode: Panner;\n private muteGain: Gain;\n private track: Track;\n private effectsCleanup?: () => void;\n private onStopCallback?: () => void;\n private activePlayers: number = 0; // Count of currently playing clips\n\n constructor(options: ToneTrackOptions) {\n this.track = options.track;\n\n // Create shared track-level nodes\n this.volumeNode = new Volume(this.gainToDb(options.track.gain));\n this.panNode = new Panner(options.track.stereoPan);\n this.muteGain = new Gain(options.track.muted ? 0 : 1);\n\n // Connect to destination or apply effects chain\n const destination = options.destination || getDestination();\n if (options.effects) {\n const cleanup = options.effects(this.muteGain, destination, false);\n if (cleanup) {\n this.effectsCleanup = cleanup;\n }\n } else {\n this.muteGain.connect(destination);\n }\n\n // Create clips array - support both legacy single buffer and modern clips array\n const clipInfos: ClipInfo[] = options.clips || (options.buffer ? [{\n buffer: options.buffer,\n startTime: 0, // Legacy: single buffer starts at timeline position 0\n duration: options.buffer.duration, // Legacy: play full buffer duration\n offset: 0,\n fadeIn: options.track.fadeIn,\n fadeOut: options.track.fadeOut,\n gain: 1,\n }] : []);\n\n // Create ClipPlayer for each clip\n this.clips = clipInfos.map(clipInfo => {\n const player = new Player({\n url: clipInfo.buffer,\n loop: false,\n onstop: () => {\n this.activePlayers--;\n if (this.activePlayers === 0 && this.onStopCallback) {\n this.onStopCallback();\n }\n },\n });\n\n const fadeGain = new Gain(clipInfo.gain);\n\n // Chain: Player -> FadeGain -> Volume -> Pan -> MuteGain\n player.connect(fadeGain);\n fadeGain.chain(this.volumeNode, this.panNode, this.muteGain);\n\n // Note: Fades are scheduled in play() method, not here in constructor,\n // because AudioParam automation requires absolute AudioContext time\n\n return {\n player,\n clipInfo,\n fadeGain,\n pausedPosition: 0,\n playStartTime: 0,\n };\n });\n }\n\n /**\n * Schedule fade envelopes for a clip at the given start time\n */\n private scheduleFades(clipPlayer: ClipPlayer, clipStartTime: number, clipOffset: number = 0): void {\n const { clipInfo, fadeGain } = clipPlayer;\n const audioParam = (fadeGain.gain as any)._param as AudioParam;\n\n // Cancel any previous automation\n audioParam.cancelScheduledValues(0);\n\n // Calculate how much of the clip we're skipping (for seeking)\n const skipTime = clipOffset - clipInfo.offset;\n\n // Apply fade in if it exists and we haven't skipped past it\n if (clipInfo.fadeIn && skipTime < clipInfo.fadeIn.duration) {\n const fadeInDuration = clipInfo.fadeIn.duration;\n\n if (skipTime <= 0) {\n // Starting from the beginning - full fade in\n applyFadeIn(\n audioParam,\n clipStartTime,\n fadeInDuration,\n clipInfo.fadeIn.type || 'linear',\n 0,\n clipInfo.gain\n );\n } else {\n // Starting partway through fade in - calculate partial fade\n const remainingFadeDuration = fadeInDuration - skipTime;\n const fadeProgress = skipTime / fadeInDuration;\n const startValue = clipInfo.gain * fadeProgress; // Approximate current fade value\n applyFadeIn(\n audioParam,\n clipStartTime,\n remainingFadeDuration,\n clipInfo.fadeIn.type || 'linear',\n startValue,\n clipInfo.gain\n );\n }\n } else {\n // No fade in or skipped past it - set to full gain\n audioParam.setValueAtTime(clipInfo.gain, clipStartTime);\n }\n\n // Apply fade out if it exists\n if (clipInfo.fadeOut) {\n const fadeOutStart = clipInfo.duration - clipInfo.fadeOut.duration;\n const fadeOutStartInClip = fadeOutStart - skipTime; // Relative to where we're starting\n\n if (fadeOutStartInClip > 0) {\n // Fade out hasn't started yet\n const absoluteFadeOutStart = clipStartTime + fadeOutStartInClip;\n applyFadeOut(\n audioParam,\n absoluteFadeOutStart,\n clipInfo.fadeOut.duration,\n clipInfo.fadeOut.type || 'linear',\n clipInfo.gain,\n 0\n );\n } else if (fadeOutStartInClip > -clipInfo.fadeOut.duration) {\n // We're starting partway through the fade out\n const elapsedFadeOut = -fadeOutStartInClip;\n const remainingFadeDuration = clipInfo.fadeOut.duration - elapsedFadeOut;\n const fadeProgress = elapsedFadeOut / clipInfo.fadeOut.duration;\n const startValue = clipInfo.gain * (1 - fadeProgress); // Approximate current fade value\n applyFadeOut(\n audioParam,\n clipStartTime,\n remainingFadeDuration,\n clipInfo.fadeOut.type || 'linear',\n startValue,\n 0\n );\n }\n // If fadeOutStartInClip <= -duration, we've skipped past the entire fade out\n }\n }\n\n private gainToDb(gain: number): number {\n return 20 * Math.log10(gain);\n }\n\n setVolume(gain: number): void {\n this.track.gain = gain;\n this.volumeNode.volume.value = this.gainToDb(gain);\n }\n\n setPan(pan: number): void {\n this.track.stereoPan = pan;\n this.panNode.pan.value = pan;\n }\n\n setMute(muted: boolean): void {\n this.track.muted = muted;\n this.muteGain.gain.value = muted ? 0 : 1;\n }\n\n setSolo(soloed: boolean): void {\n this.track.soloed = soloed;\n }\n\n play(when?: number, offset: number = 0, duration?: number): void {\n // Recreate all players to avoid Tone.js StateTimeline issues when seeking\n // See: https://github.com/Tonejs/Tone.js/issues/1076\n // The Player's internal StateTimeline doesn't properly clear on stop(),\n // so we need fresh Player instances when rescheduling\n this.clips.forEach(clipPlayer => {\n // Dispose old player\n clipPlayer.player.stop();\n clipPlayer.player.disconnect();\n clipPlayer.player.dispose();\n\n // Create new player with same buffer\n const newPlayer = new Player({\n url: clipPlayer.clipInfo.buffer,\n loop: false,\n onstop: () => {\n this.activePlayers--;\n if (this.activePlayers === 0 && this.onStopCallback) {\n this.onStopCallback();\n }\n },\n });\n\n // Reconnect to audio graph\n newPlayer.connect(clipPlayer.fadeGain);\n\n // Update reference\n clipPlayer.player = newPlayer;\n clipPlayer.pausedPosition = 0;\n });\n\n this.activePlayers = 0;\n\n // Play each clip that should be active at this offset\n this.clips.forEach(clipPlayer => {\n const { player, clipInfo } = clipPlayer;\n\n // Calculate absolute timeline position we're starting from\n const playbackPosition = offset;\n\n // Check if this clip should be playing at this position\n const clipStart = clipInfo.startTime;\n const clipEnd = clipInfo.startTime + clipInfo.duration;\n\n if (playbackPosition < clipEnd) {\n // This clip should play\n this.activePlayers++;\n\n // Get fresh now() for each clip to avoid \"time in the past\" errors\n // This is important when seeking during playback - time passes between scheduling clips\n const currentTime = when ?? now();\n clipPlayer.playStartTime = currentTime;\n\n if (playbackPosition >= clipStart) {\n // We're starting in the middle of this clip\n const clipOffset = playbackPosition - clipStart + clipInfo.offset;\n const remainingDuration = clipInfo.duration - (playbackPosition - clipStart);\n const clipDuration = duration ? Math.min(duration, remainingDuration) : remainingDuration;\n\n clipPlayer.pausedPosition = clipOffset;\n // Schedule fades at the actual playback start time\n this.scheduleFades(clipPlayer, currentTime, clipOffset);\n player.start(currentTime, clipOffset, clipDuration);\n } else {\n // This clip starts later - schedule it\n const delay = clipStart - playbackPosition;\n const clipDuration = duration ? Math.min(duration - delay, clipInfo.duration) : clipInfo.duration;\n\n if (delay < (duration ?? Infinity)) {\n clipPlayer.pausedPosition = clipInfo.offset;\n // Schedule fades at the delayed start time\n this.scheduleFades(clipPlayer, currentTime + delay, clipInfo.offset);\n player.start(currentTime + delay, clipInfo.offset, clipDuration);\n } else {\n this.activePlayers--;\n }\n }\n }\n });\n }\n\n pause(): void {\n // Stop all clips - both started and scheduled\n // Scheduled clips have state 'stopped' but still need to be cancelled\n this.clips.forEach(clipPlayer => {\n if (clipPlayer.player.state === 'started') {\n const elapsed = (now() - clipPlayer.playStartTime) * clipPlayer.player.playbackRate;\n clipPlayer.pausedPosition = clipPlayer.pausedPosition + elapsed;\n }\n // Always call stop() to cancel any scheduled playback\n clipPlayer.player.stop();\n });\n\n this.activePlayers = 0;\n }\n\n stop(when?: number): void {\n // Evaluate now() inside function body, not in parameter default (which is evaluated at module load time)\n const stopWhen = when ?? now();\n this.clips.forEach(clipPlayer => {\n clipPlayer.player.stop(stopWhen);\n clipPlayer.pausedPosition = 0;\n });\n this.activePlayers = 0;\n }\n\n dispose(): void {\n // Clean up effects if cleanup function was provided\n if (this.effectsCleanup) {\n this.effectsCleanup();\n }\n\n // Dispose all clip players\n this.clips.forEach(clipPlayer => {\n clipPlayer.player.dispose();\n clipPlayer.fadeGain.dispose();\n });\n\n // Dispose shared track nodes\n this.volumeNode.dispose();\n this.panNode.dispose();\n this.muteGain.dispose();\n }\n\n get id(): string {\n return this.track.id;\n }\n\n get duration(): number {\n // Return the end time of the last clip\n if (this.clips.length === 0) return 0;\n const lastClip = this.clips[this.clips.length - 1];\n return lastClip.clipInfo.startTime + lastClip.clipInfo.duration;\n }\n\n get buffer(): AudioBuffer {\n // For backward compatibility, return the first clip's buffer\n return this.clips[0]?.clipInfo.buffer;\n }\n\n get isPlaying(): boolean {\n // Track is playing if any clip is playing\n return this.clips.some(clipPlayer => clipPlayer.player.state === 'started');\n }\n\n get muted(): boolean {\n return this.track.muted;\n }\n\n get startTime(): number {\n // Return the track's start time from the Track object\n // This is the absolute timeline position where the track starts\n return this.track.startTime;\n }\n\n setOnStopCallback(callback: () => void): void {\n this.onStopCallback = callback;\n }\n}\n","/**\n * Fade utilities for Web Audio API\n *\n * Applies fade in/out envelopes to AudioParam (typically gain)\n * using various curve types.\n */\n\nexport type FadeType = 'linear' | 'logarithmic' | 'exponential' | 'sCurve';\n\n/**\n * Simple fade configuration - just duration and type\n */\nexport interface FadeConfig {\n /** Duration of the fade in seconds */\n duration: number;\n /** Type of fade curve (default: 'linear') */\n type?: FadeType;\n}\n\n/**\n * Generate a linear fade curve\n */\nfunction linearCurve(length: number, fadeIn: boolean): Float32Array {\n const curve = new Float32Array(length);\n const scale = length - 1;\n\n for (let i = 0; i < length; i++) {\n const x = i / scale;\n curve[i] = fadeIn ? x : 1 - x;\n }\n\n return curve;\n}\n\n/**\n * Generate an exponential fade curve\n */\nfunction exponentialCurve(length: number, fadeIn: boolean): Float32Array {\n const curve = new Float32Array(length);\n const scale = length - 1;\n\n for (let i = 0; i < length; i++) {\n const x = i / scale;\n const index = fadeIn ? i : length - 1 - i;\n curve[index] = Math.exp(2 * x - 1) / Math.E;\n }\n\n return curve;\n}\n\n/**\n * Generate an S-curve (sine-based smooth curve)\n */\nfunction sCurveCurve(length: number, fadeIn: boolean): Float32Array {\n const curve = new Float32Array(length);\n const phase = fadeIn ? Math.PI / 2 : -Math.PI / 2;\n\n for (let i = 0; i < length; i++) {\n curve[i] = Math.sin((Math.PI * i) / length - phase) / 2 + 0.5;\n }\n\n return curve;\n}\n\n/**\n * Generate a logarithmic fade curve\n */\nfunction logarithmicCurve(length: number, fadeIn: boolean, base: number = 10): Float32Array {\n const curve = new Float32Array(length);\n\n for (let i = 0; i < length; i++) {\n const index = fadeIn ? i : length - 1 - i;\n const x = i / length;\n curve[index] = Math.log(1 + base * x) / Math.log(1 + base);\n }\n\n return curve;\n}\n\n/**\n * Generate a fade curve of the specified type\n */\nfunction generateCurve(type: FadeType, length: number, fadeIn: boolean): Float32Array {\n switch (type) {\n case 'linear':\n return linearCurve(length, fadeIn);\n case 'exponential':\n return exponentialCurve(length, fadeIn);\n case 'sCurve':\n return sCurveCurve(length, fadeIn);\n case 'logarithmic':\n return logarithmicCurve(length, fadeIn);\n default:\n return linearCurve(length, fadeIn);\n }\n}\n\n/**\n * Apply a fade in to an AudioParam\n *\n * @param param - The AudioParam to apply the fade to (usually gain)\n * @param startTime - When the fade starts (in seconds, AudioContext time)\n * @param duration - Duration of the fade in seconds\n * @param type - Type of fade curve\n * @param startValue - Starting value (default: 0)\n * @param endValue - Ending value (default: 1)\n */\nexport function applyFadeIn(\n param: AudioParam,\n startTime: number,\n duration: number,\n type: FadeType = 'linear',\n startValue: number = 0,\n endValue: number = 1\n): void {\n if (duration <= 0) return;\n\n if (type === 'linear') {\n // Use native linear ramp for better performance\n param.setValueAtTime(startValue, startTime);\n param.linearRampToValueAtTime(endValue, startTime + duration);\n } else if (type === 'exponential') {\n // Exponential ramp can't start/end at 0, use small value\n param.setValueAtTime(Math.max(startValue, 0.001), startTime);\n param.exponentialRampToValueAtTime(Math.max(endValue, 0.001), startTime + duration);\n } else {\n // Use curve for sCurve and logarithmic\n const curve = generateCurve(type, 10000, true);\n // Scale curve to value range\n const scaledCurve = new Float32Array(curve.length);\n const range = endValue - startValue;\n for (let i = 0; i < curve.length; i++) {\n scaledCurve[i] = startValue + curve[i] * range;\n }\n param.setValueCurveAtTime(scaledCurve, startTime, duration);\n }\n}\n\n/**\n * Apply a fade out to an AudioParam\n *\n * @param param - The AudioParam to apply the fade to (usually gain)\n * @param startTime - When the fade starts (in seconds, AudioContext time)\n * @param duration - Duration of the fade in seconds\n * @param type - Type of fade curve\n * @param startValue - Starting value (default: 1)\n * @param endValue - Ending value (default: 0)\n */\nexport function applyFadeOut(\n param: AudioParam,\n startTime: number,\n duration: number,\n type: FadeType = 'linear',\n startValue: number = 1,\n endValue: number = 0\n): void {\n if (duration <= 0) return;\n\n if (type === 'linear') {\n // Use native linear ramp for better performance\n param.setValueAtTime(startValue, startTime);\n param.linearRampToValueAtTime(endValue, startTime + duration);\n } else if (type === 'exponential') {\n // Exponential ramp can't start/end at 0, use small value\n param.setValueAtTime(Math.max(startValue, 0.001), startTime);\n param.exponentialRampToValueAtTime(Math.max(endValue, 0.001), startTime + duration);\n } else {\n // Use curve for sCurve and logarithmic\n const curve = generateCurve(type, 10000, false);\n // Scale curve to value range\n const scaledCurve = new Float32Array(curve.length);\n const range = startValue - endValue;\n for (let i = 0; i < curve.length; i++) {\n scaledCurve[i] = endValue + curve[i] * range;\n }\n param.setValueCurveAtTime(scaledCurve, startTime, duration);\n }\n}\n","/**\n * Global AudioContext Manager\n *\n * Provides a single AudioContext shared across the entire application.\n * This context is used by Tone.js for playback and by all recording/monitoring hooks.\n *\n * Uses Tone.js's Context class which wraps standardized-audio-context for\n * cross-browser compatibility (fixes Firefox AudioListener issues).\n */\n\nimport { Context, setContext } from 'tone';\n\nlet globalToneContext: Context | null = null;\n\n/**\n * Get the global Tone.js Context\n * This is the main context for cross-browser audio operations.\n * Use context.createAudioWorkletNode(), context.createMediaStreamSource(), etc.\n * @returns The Tone.js Context instance\n */\nexport function getGlobalContext(): Context {\n if (!globalToneContext) {\n globalToneContext = new Context();\n setContext(globalToneContext);\n }\n return globalToneContext;\n}\n\n/**\n * Get or create the global AudioContext\n * Uses Tone.js Context for cross-browser compatibility\n * @returns The global AudioContext instance (rawContext from Tone.Context)\n */\nexport function getGlobalAudioContext(): AudioContext {\n return getGlobalContext().rawContext as AudioContext;\n}\n\n/**\n * @deprecated Use getGlobalContext() instead\n * Get the Tone.js Context's rawContext typed as IAudioContext\n * @returns The rawContext cast as IAudioContext\n */\nexport function getGlobalToneContext(): Context {\n return getGlobalContext();\n}\n\n/**\n * Resume the global AudioContext if it's suspended\n * Should be called in response to a user gesture (e.g., button click)\n * @returns Promise that resolves when context is running\n */\nexport async function resumeGlobalAudioContext(): Promise<void> {\n const context = getGlobalContext();\n if (context.state !== 'running') {\n await context.resume();\n }\n}\n\n/**\n * Get the current state of the global AudioContext\n * @returns The AudioContext state ('suspended', 'running', or 'closed')\n */\nexport function getGlobalAudioContextState(): AudioContextState {\n return globalToneContext?.rawContext.state || 'suspended';\n}\n\n/**\n * Close the global AudioContext\n * Should only be called when the application is shutting down\n */\nexport async function closeGlobalAudioContext(): Promise<void> {\n if (globalToneContext && globalToneContext.rawContext.state !== 'closed') {\n await globalToneContext.close();\n globalToneContext = null;\n }\n}\n","/**\n * MediaStreamSource Manager\n *\n * Manages MediaStreamAudioSourceNode instances to ensure only one source\n * is created per MediaStream per AudioContext.\n *\n * Web Audio API constraint: You can only create one MediaStreamAudioSourceNode\n * per MediaStream per AudioContext. Multiple attempts will fail or disconnect\n * previous sources.\n *\n * This manager ensures a single source is shared across multiple consumers\n * (e.g., AnalyserNode for VU meter, AudioWorkletNode for recording).\n *\n * NOTE: With Tone.js Context, you can also use context.createMediaStreamSource()\n * directly, which handles cross-browser compatibility internally.\n */\n\nimport { getContext } from 'tone';\n\n// Map of MediaStream -> MediaStreamAudioSourceNode\nconst streamSources = new Map<MediaStream, MediaStreamAudioSourceNode>();\n\n// Map of MediaStream -> cleanup handlers\nconst streamCleanupHandlers = new Map<MediaStream, () => void>();\n\n/**\n * Get or create a MediaStreamAudioSourceNode for the given stream\n *\n * @param stream - The MediaStream to create a source for\n * @returns MediaStreamAudioSourceNode that can be connected to multiple nodes\n *\n * @example\n * ```typescript\n * const source = getMediaStreamSource(stream);\n *\n * // Multiple consumers can connect to the same source\n * source.connect(analyserNode); // For VU meter\n * source.connect(workletNode); // For recording\n * ```\n */\nexport function getMediaStreamSource(\n stream: MediaStream\n): MediaStreamAudioSourceNode {\n // Return existing source if we have one for this stream\n if (streamSources.has(stream)) {\n return streamSources.get(stream)!;\n }\n\n // Create new source using Tone.js's shared context for cross-browser compatibility\n const context = getContext();\n const source = context.createMediaStreamSource(stream);\n streamSources.set(stream, source);\n\n // Set up cleanup when stream ends\n const cleanup = () => {\n source.disconnect();\n streamSources.delete(stream);\n streamCleanupHandlers.delete(stream);\n\n // Remove event listener\n stream.removeEventListener('ended', cleanup);\n stream.removeEventListener('inactive', cleanup);\n };\n\n streamCleanupHandlers.set(stream, cleanup);\n\n // Clean up when stream ends or becomes inactive\n stream.addEventListener('ended', cleanup);\n stream.addEventListener('inactive', cleanup);\n\n return source;\n}\n\n/**\n * Manually release a MediaStreamSource\n *\n * Normally you don't need to call this - cleanup happens automatically\n * when the stream ends. Only call this if you need to force cleanup.\n *\n * @param stream - The MediaStream to release the source for\n */\nexport function releaseMediaStreamSource(stream: MediaStream): void {\n const cleanup = streamCleanupHandlers.get(stream);\n if (cleanup) {\n cleanup();\n }\n}\n\n/**\n * Check if a MediaStreamSource exists for the given stream\n *\n * @param stream - The MediaStream to check\n * @returns true if a source exists for this stream\n */\nexport function hasMediaStreamSource(stream: MediaStream): boolean {\n return streamSources.has(stream);\n}\n"],"mappings":"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","names":["Volume","getDestination","now","Volume","getDestination","now","getContext"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@waveform-playlist/playout",
|
|
3
|
-
"version": "5.0.0-alpha.
|
|
3
|
+
"version": "5.0.0-alpha.9",
|
|
4
4
|
"description": "Playout engine for waveform-playlist using Tone.js",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"typescript": "^5.3.3"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@waveform-playlist/core": "5.0.0-alpha.
|
|
43
|
+
"@waveform-playlist/core": "5.0.0-alpha.9"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"tone": "^15.0.0"
|