@waveform-playlist/playout 9.0.2 → 9.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +8 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -595,6 +595,14 @@ var TonePlayout = class {
|
|
|
595
595
|
} catch (err) {
|
|
596
596
|
console.warn("[waveform-playlist] Transport.stop() failed:", err);
|
|
597
597
|
}
|
|
598
|
+
if (this._loopHandler) {
|
|
599
|
+
try {
|
|
600
|
+
transport.off("loop", this._loopHandler);
|
|
601
|
+
} catch (err) {
|
|
602
|
+
console.warn("[waveform-playlist] Error removing loop handler:", err);
|
|
603
|
+
}
|
|
604
|
+
this._loopHandler = null;
|
|
605
|
+
}
|
|
598
606
|
this.tracks.forEach((track) => track.stopAllSources());
|
|
599
607
|
this.tracks.forEach((track) => track.cancelFades());
|
|
600
608
|
this.clearCompletionEvent();
|
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","../src/TonePlayoutAdapter.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 getUnderlyingAudioParam,\n type FadeConfig,\n type FadeType,\n} from './fades';\n\n// Export Tone.js adapter for engine integration\nexport { createToneAdapter } from './TonePlayoutAdapter';\nexport type { ToneAdapterOptions } from './TonePlayoutAdapter';\n","import {\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\nexport type EffectsFunction = (\n masterGainNode: Volume,\n destination: ToneAudioNode,\n isOffline: boolean\n) => 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 _completionEventId: number | null = null;\n private _loopHandler: (() => void) | null = null;\n private _loopEnabled = false;\n private _loopStart = 0;\n private _loopEnd = 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 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 private clearCompletionEvent(): void {\n if (this._completionEventId !== null) {\n try {\n getTransport().clear(this._completionEventId);\n } catch (err) {\n console.warn('[waveform-playlist] Error clearing Transport completion event:', err);\n }\n this._completionEventId = null;\n }\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 this.manualMuteState.set(toneTrack.id, trackOptions.track.muted ?? false);\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 throw new Error('[waveform-playlist] TonePlayout not initialized. Call init() first.');\n }\n\n const startTime = when ?? now();\n const transport = getTransport();\n\n this.clearCompletionEvent();\n\n const transportOffset = offset ?? 0;\n this.tracks.forEach((track) => {\n track.cancelFades();\n track.prepareFades(startTime, transportOffset);\n });\n\n // Schedule duration-limited stop via Transport\n if (duration !== undefined) {\n this._completionEventId = transport.scheduleOnce(() => {\n this._completionEventId = null;\n try {\n this.onPlaybackCompleteCallback?.();\n } catch (err) {\n console.warn('[waveform-playlist] Error in playback completion callback:', err);\n }\n }, transportOffset + duration);\n }\n\n // Start Transport — triggers schedule() callbacks for clips at/after offset\n try {\n // Stop all active native sources before restarting to prevent layered audio.\n // Native AudioBufferSourceNodes don't respond to Transport state changes.\n if (transport.state !== 'stopped') {\n transport.stop();\n }\n this.tracks.forEach((track) => track.stopAllSources());\n\n // Set loop boundaries BEFORE enabling loop. _processTick checks\n // `ticks >= _loopEnd` every tick; _loopEnd defaults to 0.\n transport.loopStart = this._loopStart;\n transport.loopEnd = this._loopEnd;\n transport.loop = this._loopEnabled;\n\n // Set schedule guard BEFORE transport.start(). Ghost ticks from stale\n // Clock._lastUpdate can fire schedule callbacks at past positions;\n // the guard suppresses callbacks for clips before the play offset\n // (those are handled by startMidClipSources below).\n this.tracks.forEach((track) => track.setScheduleGuardOffset(transportOffset));\n\n if (offset !== undefined) {\n transport.start(startTime, offset);\n } else {\n transport.start(startTime);\n }\n\n // Advance Clock._lastUpdate past the stop/start boundary.\n //\n // After stop/start cycles, _lastUpdate is stale (set by the previous\n // context tick, before our stop/start). The next Clock._loop() processes\n // ticks from [_lastUpdate, now()]. In the gap [_lastUpdate, startTime),\n // the TickSource is still \"started\" from the PREVIOUS play cycle with\n // its old tick offset. Those accumulated ticks can exceed _loopEnd,\n // causing _processTick to wrap immediately to loopStart.\n //\n // By advancing _lastUpdate to startTime, we skip the stale range.\n // The next Clock._loop() only processes [startTime, now()] — ticks\n // from the current play cycle with the correct offset.\n (transport as any)._clock._lastUpdate = startTime;\n\n // Start sources for clips that span the current Transport position.\n // Transport.schedule() only fires for clips at/after the offset;\n // clips whose start time is before the offset need manual creation.\n this.tracks.forEach((track) => {\n track.startMidClipSources(transportOffset, startTime);\n });\n } catch (err) {\n // Clean up scheduled events since Transport failed to start\n this.clearCompletionEvent();\n this.tracks.forEach((track) => track.cancelFades());\n console.warn(\n '[waveform-playlist] Transport.start() failed. Audio playback could not begin.',\n err\n );\n throw err;\n }\n }\n\n pause(): void {\n const transport = getTransport();\n try {\n transport.pause();\n } catch (err) {\n console.warn('[waveform-playlist] Transport.pause() failed:', err);\n }\n // Native AudioBufferSourceNodes ignore Transport state changes —\n // they must be explicitly stopped.\n this.tracks.forEach((track) => track.stopAllSources());\n this.tracks.forEach((track) => track.cancelFades());\n this.clearCompletionEvent();\n }\n\n stop(): void {\n const transport = getTransport();\n try {\n transport.stop();\n } catch (err) {\n console.warn('[waveform-playlist] Transport.stop() failed:', err);\n }\n // Stop all native sources explicitly\n this.tracks.forEach((track) => track.stopAllSources());\n this.tracks.forEach((track) => track.cancelFades());\n this.clearCompletionEvent();\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 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 (!this.soloedTracks.has(id)) {\n track.setMute(true);\n } else {\n const manuallyMuted = this.manualMuteState.get(id) ?? false;\n track.setMute(manuallyMuted);\n }\n } else {\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 this.manualMuteState.set(trackId, muted);\n track.setMute(muted);\n }\n }\n\n setLoop(enabled: boolean, loopStart: number, loopEnd: number): void {\n // Update cached state first — play() uses these values to configure\n // the Transport on next start, so they must reflect the caller's intent\n // regardless of whether Transport property setting succeeds.\n this._loopEnabled = enabled;\n this._loopStart = loopStart;\n this._loopEnd = loopEnd;\n\n const transport = getTransport();\n try {\n // Set boundaries BEFORE enabling loop. Tone.js's _processTick checks\n // `ticks >= _loopEnd` on every tick. If we set transport.loop = true\n // first, a tick could fire before loopEnd is updated, seeing the stale\n // _loopEnd value (0 from Transport default) and wrapping immediately.\n transport.loopStart = loopStart;\n transport.loopEnd = loopEnd;\n transport.loop = enabled;\n } catch (err) {\n console.warn('[waveform-playlist] Error configuring Transport loop:', err);\n return;\n }\n\n if (enabled && !this._loopHandler) {\n this._loopHandler = () => {\n // On loop boundary: stop old sources, re-schedule fades, start mid-clip sources.\n // Event ordering in Transport's tick processing (Tone.js 15.x _processTick):\n // loopEnd → ticks reset → loopStart → loop → forEachAtTime(ticks)\n // Our loop handler fires BEFORE schedule callbacks, so:\n // 1. stopAllSources + cancelFades — clean slate\n // 2. startMidClipSources — for clips spanning loopStart boundary\n // 3. prepareFades — fresh fade envelopes\n // Then Transport fires schedule callbacks for clips at/after loopStart.\n const currentTime = now();\n this.tracks.forEach((track) => {\n try {\n track.stopAllSources();\n track.cancelFades();\n track.setScheduleGuardOffset(this._loopStart);\n track.startMidClipSources(this._loopStart, currentTime);\n track.prepareFades(currentTime, this._loopStart);\n } catch (err) {\n console.warn(\n `[waveform-playlist] Error re-scheduling track \"${track.id}\" on loop:`,\n err\n );\n }\n });\n };\n transport.on('loop', this._loopHandler);\n } else if (!enabled && this._loopHandler) {\n transport.off('loop', this._loopHandler);\n this._loopHandler = null;\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.clearCompletionEvent();\n\n if (this._loopHandler) {\n try {\n getTransport().off('loop', this._loopHandler);\n } catch (err) {\n console.warn('[waveform-playlist] Error removing Transport loop handler:', err);\n }\n this._loopHandler = null;\n }\n\n this.tracks.forEach((track) => {\n try {\n track.dispose();\n } catch (err) {\n console.warn(`[waveform-playlist] Error disposing track \"${track.id}\":`, err);\n }\n });\n this.tracks.clear();\n\n if (this.effectsCleanup) {\n try {\n this.effectsCleanup();\n } catch (err) {\n console.warn('[waveform-playlist] Error during master effects cleanup:', err);\n }\n }\n\n try {\n this.masterVolume.dispose();\n } catch (err) {\n console.warn('[waveform-playlist] Error disposing master volume:', err);\n }\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","import {\n Volume,\n Gain,\n Panner,\n ToneAudioNode,\n getDestination,\n getTransport,\n getContext,\n} from 'tone';\nimport { Track, type Fade } from '@waveform-playlist/core';\nimport { applyFadeIn, applyFadeOut, getUnderlyingAudioParam } from './fades';\n\nexport type TrackEffectsFunction = (\n graphEnd: Gain,\n masterGainNode: ToneAudioNode,\n isOffline: boolean\n) => 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\n/** Per-clip scheduling info and audio nodes */\ninterface ScheduledClip {\n clipInfo: ClipInfo;\n fadeGainNode: GainNode; // Native GainNode for per-clip fade envelope\n scheduleId: number; // Transport.schedule() event ID\n}\n\nexport class ToneTrack {\n private scheduledClips: ScheduledClip[];\n private activeSources: Set<AudioBufferSourceNode> = new Set();\n private volumeNode: Volume;\n private panNode: Panner;\n private muteGain: Gain;\n private track: Track;\n private effectsCleanup?: () => void;\n // Guard against ghost tick schedule callbacks. After stop/start cycles with\n // loops, stale Clock._lastUpdate causes ticks from the previous cycle to fire\n // Transport.schedule() callbacks at past positions (e.g., time 0 clips fire\n // when starting at offset 5s). Clips before this offset are handled by\n // startMidClipSources(); schedule callbacks should only create sources for\n // clips at/after this offset.\n private _scheduleGuardOffset = 0;\n\n constructor(options: ToneTrackOptions) {\n this.track = options.track;\n\n // Create shared track-level Tone.js 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 // Chain shared Tone.js nodes: Volume → Pan → MuteGain\n this.volumeNode.chain(this.panNode, this.muteGain);\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[] =\n options.clips ||\n (options.buffer\n ? [\n {\n buffer: options.buffer,\n startTime: 0,\n duration: options.buffer.duration,\n offset: 0,\n fadeIn: options.track.fadeIn,\n fadeOut: options.track.fadeOut,\n gain: 1,\n },\n ]\n : []);\n\n const transport = getTransport();\n const rawContext = getContext().rawContext as AudioContext;\n\n // Get the native AudioNode input of the Volume for native→Tone connection.\n // Volume.input is a Tone.js Gain<\"decibels\"> whose .input is the native GainNode.\n // Cast through unknown since Gain<\"decibels\"> and Gain<\"gain\"> don't overlap.\n const volumeNativeInput = (this.volumeNode.input as unknown as Gain).input;\n\n // Schedule each clip via Transport.schedule() with native AudioBufferSourceNode\n this.scheduledClips = clipInfos.map((clipInfo) => {\n // Native GainNode for per-clip fade envelope — created once, reused across play cycles\n const fadeGainNode = rawContext.createGain();\n fadeGainNode.gain.value = clipInfo.gain;\n fadeGainNode.connect(volumeNativeInput);\n\n // Schedule a permanent Transport event at the clip's absolute timeline position.\n // This callback fires on every play and every loop iteration when Transport\n // passes this point.\n const absTransportTime = this.track.startTime + clipInfo.startTime;\n const scheduleId = transport.schedule((audioContextTime: number) => {\n // Guard: ghost ticks from stale Clock._lastUpdate can fire this callback\n // at past positions (see Tone.js #1419). Clips before the play/loop offset\n // are already handled by startMidClipSources().\n if (absTransportTime < this._scheduleGuardOffset) {\n return;\n }\n this.startClipSource(clipInfo, fadeGainNode, audioContextTime);\n }, absTransportTime);\n\n return { clipInfo, fadeGainNode, scheduleId };\n });\n }\n\n /**\n * Create and start an AudioBufferSourceNode for a clip.\n * Sources are one-shot: each play or loop iteration creates a fresh one.\n */\n private startClipSource(\n clipInfo: ClipInfo,\n fadeGainNode: GainNode,\n audioContextTime: number,\n bufferOffset?: number,\n playDuration?: number\n ): void {\n const rawContext = getContext().rawContext as AudioContext;\n const source = rawContext.createBufferSource();\n source.buffer = clipInfo.buffer;\n source.connect(fadeGainNode);\n\n const offset = bufferOffset ?? clipInfo.offset;\n const duration = playDuration ?? clipInfo.duration;\n\n try {\n source.start(audioContextTime, offset, duration);\n } catch (err) {\n console.warn(\n `[waveform-playlist] Failed to start source on track \"${this.id}\" ` +\n `(time=${audioContextTime}, offset=${offset}, duration=${duration}):`,\n err\n );\n source.disconnect();\n return;\n }\n\n this.activeSources.add(source);\n source.onended = () => {\n this.activeSources.delete(source);\n };\n }\n\n /**\n * Set the schedule guard offset. Schedule callbacks for clips before this\n * offset are suppressed (already handled by startMidClipSources).\n * Must be called before transport.start() and in the loop handler.\n */\n setScheduleGuardOffset(offset: number): void {\n this._scheduleGuardOffset = offset;\n }\n\n /**\n * Start sources for clips that span the given Transport position.\n * Used for mid-playback seeking and loop boundary handling where\n * Transport.schedule() callbacks have already passed.\n *\n * Uses strict < for absClipStart to avoid double-creation with\n * schedule callbacks at exact Transport position (e.g., loopStart).\n */\n startMidClipSources(transportOffset: number, audioContextTime: number): void {\n for (const { clipInfo, fadeGainNode } of this.scheduledClips) {\n const absClipStart = this.track.startTime + clipInfo.startTime;\n const absClipEnd = absClipStart + clipInfo.duration;\n\n // Only handle clips that started before the transport position\n // but haven't ended yet (i.e., the transport is \"inside\" the clip)\n if (absClipStart < transportOffset && absClipEnd > transportOffset) {\n const elapsed = transportOffset - absClipStart;\n const adjustedOffset = clipInfo.offset + elapsed;\n const remainingDuration = clipInfo.duration - elapsed;\n this.startClipSource(\n clipInfo,\n fadeGainNode,\n audioContextTime,\n adjustedOffset,\n remainingDuration\n );\n }\n }\n }\n\n /**\n * Stop all active AudioBufferSourceNodes and clear the set.\n * Native AudioBufferSourceNodes ignore Transport state changes —\n * they must be explicitly stopped.\n */\n stopAllSources(): void {\n this.activeSources.forEach((source) => {\n try {\n source.stop();\n } catch (err) {\n console.warn(`[waveform-playlist] Error stopping source on track \"${this.id}\":`, err);\n }\n });\n this.activeSources.clear();\n }\n\n /**\n * Schedule fade envelopes for a clip at the given AudioContext time.\n * Uses native GainNode.gain (AudioParam) directly — no _param workaround needed.\n */\n private scheduleFades(\n scheduled: ScheduledClip,\n clipStartTime: number,\n clipOffset: number = 0\n ): void {\n const { clipInfo, fadeGainNode } = scheduled;\n const audioParam = fadeGainNode.gain;\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 applyFadeIn(\n audioParam,\n clipStartTime,\n fadeInDuration,\n clipInfo.fadeIn.type || 'linear',\n 0,\n clipInfo.gain\n );\n } else {\n const remainingFadeDuration = fadeInDuration - skipTime;\n const fadeProgress = skipTime / fadeInDuration;\n const startValue = clipInfo.gain * fadeProgress;\n applyFadeIn(\n audioParam,\n clipStartTime,\n remainingFadeDuration,\n clipInfo.fadeIn.type || 'linear',\n startValue,\n clipInfo.gain\n );\n }\n } else {\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;\n\n if (fadeOutStartInClip > 0) {\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 const elapsedFadeOut = -fadeOutStartInClip;\n const remainingFadeDuration = clipInfo.fadeOut.duration - elapsedFadeOut;\n const fadeProgress = elapsedFadeOut / clipInfo.fadeOut.duration;\n const startValue = clipInfo.gain * (1 - fadeProgress);\n applyFadeOut(\n audioParam,\n clipStartTime,\n remainingFadeDuration,\n clipInfo.fadeOut.type || 'linear',\n startValue,\n 0\n );\n }\n }\n }\n\n /**\n * Prepare fade envelopes for all clips based on Transport offset.\n * Called before Transport.start() to schedule fades at correct AudioContext times.\n */\n prepareFades(when: number, transportOffset: number): void {\n this.scheduledClips.forEach((scheduled) => {\n const absClipStart = this.track.startTime + scheduled.clipInfo.startTime;\n const absClipEnd = absClipStart + scheduled.clipInfo.duration;\n\n if (transportOffset >= absClipEnd) return; // clip already finished\n\n if (transportOffset >= absClipStart) {\n // Mid-clip: playing now\n const clipOffset = transportOffset - absClipStart + scheduled.clipInfo.offset;\n this.scheduleFades(scheduled, when, clipOffset);\n } else {\n // Clip starts later\n const delay = absClipStart - transportOffset;\n this.scheduleFades(scheduled, when + delay, scheduled.clipInfo.offset);\n }\n });\n }\n\n /**\n * Cancel all scheduled fade automation and reset to nominal gain.\n * Called on pause/stop to prevent stale fade envelopes.\n */\n cancelFades(): void {\n this.scheduledClips.forEach(({ fadeGainNode, clipInfo }) => {\n const audioParam = fadeGainNode.gain;\n audioParam.cancelScheduledValues(0);\n audioParam.setValueAtTime(clipInfo.gain, 0);\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 const value = muted ? 0 : 1;\n // Use setValueAtTime on the raw AudioParam to ensure the value is applied\n // even when the AudioContext is suspended. Setting .gain.value on the Tone.js\n // Signal wrapper doesn't propagate to the underlying AudioParam until the\n // context resumes, causing a brief audio glitch (e.g., all tracks audible\n // before solo muting takes effect).\n const audioParam = getUnderlyingAudioParam(this.muteGain.gain);\n audioParam?.setValueAtTime(value, 0);\n this.muteGain.gain.value = value;\n }\n\n setSolo(soloed: boolean): void {\n this.track.soloed = soloed;\n }\n\n dispose(): void {\n const transport = getTransport();\n\n if (this.effectsCleanup) {\n try {\n this.effectsCleanup();\n } catch (err) {\n console.warn(`[waveform-playlist] Error during track \"${this.id}\" effects cleanup:`, err);\n }\n }\n\n this.stopAllSources();\n\n // Clear Transport schedule events and disconnect native fade gain nodes\n this.scheduledClips.forEach((scheduled, index) => {\n try {\n transport.clear(scheduled.scheduleId);\n } catch (err) {\n console.warn(\n `[waveform-playlist] Error clearing schedule ${index} on track \"${this.id}\":`,\n err\n );\n }\n try {\n scheduled.fadeGainNode.disconnect();\n } catch (err) {\n console.warn(\n `[waveform-playlist] Error disconnecting fadeGain ${index} on track \"${this.id}\":`,\n err\n );\n }\n });\n\n try {\n this.volumeNode.dispose();\n } catch (err) {\n console.warn(`[waveform-playlist] Error disposing volumeNode on track \"${this.id}\":`, err);\n }\n try {\n this.panNode.dispose();\n } catch (err) {\n console.warn(`[waveform-playlist] Error disposing panNode on track \"${this.id}\":`, err);\n }\n try {\n this.muteGain.dispose();\n } catch (err) {\n console.warn(`[waveform-playlist] Error disposing muteGain on track \"${this.id}\":`, err);\n }\n }\n\n get id(): string {\n return this.track.id;\n }\n\n get duration(): number {\n if (this.scheduledClips.length === 0) return 0;\n const lastClip = this.scheduledClips[this.scheduledClips.length - 1];\n return lastClip.clipInfo.startTime + lastClip.clipInfo.duration;\n }\n\n get buffer(): AudioBuffer {\n return this.scheduledClips[0]?.clipInfo.buffer;\n }\n\n get muted(): boolean {\n return this.track.muted;\n }\n\n get startTime(): number {\n return this.track.startTime;\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\n/**\n * Access the underlying Web Audio AudioParam from a Tone.js Signal/Param wrapper.\n *\n * Tone.js wraps native AudioParam in its Signal class, but sometimes we need\n * direct access to the raw AudioParam for setValueAtTime/cancelScheduledValues\n * (e.g., when the AudioContext is suspended and Tone.js Signal doesn't propagate).\n *\n * This uses `_param` which is a private Tone.js 15.x internal.\n * Pin the Tone.js version carefully if upgrading.\n *\n * @param signal - A Tone.js Signal or Param wrapper (e.g., `gain.gain`)\n * @returns The underlying AudioParam, or undefined if not found\n */\nlet hasWarned = false;\n\nexport function getUnderlyingAudioParam(signal: unknown): AudioParam | undefined {\n const param = (signal as { _param?: AudioParam })._param;\n if (!param && !hasWarned) {\n hasWarned = true;\n console.warn(\n '[waveform-playlist] Unable to access Tone.js internal _param. ' +\n 'This likely means the Tone.js version is incompatible. ' +\n 'Mute scheduling may not work correctly.'\n );\n }\n return param;\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(stream: MediaStream): 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","import type { ClipTrack, Track } from '@waveform-playlist/core';\nimport {\n clipStartTime,\n clipEndTime,\n clipOffsetTime,\n clipDurationTime,\n} from '@waveform-playlist/core';\nimport type { PlayoutAdapter } from '@waveform-playlist/engine';\nimport { TonePlayout } from './TonePlayout';\nimport type { EffectsFunction } from './TonePlayout';\nimport type { ClipInfo } from './ToneTrack';\nimport { now } from 'tone';\n\nexport interface ToneAdapterOptions {\n effects?: EffectsFunction;\n}\n\nexport function createToneAdapter(options?: ToneAdapterOptions): PlayoutAdapter {\n let playout: TonePlayout | null = null;\n let _isPlaying = false;\n let _playoutGeneration = 0;\n let _loopEnabled = false;\n let _loopStart = 0;\n let _loopEnd = 0;\n let _audioInitialized = false;\n\n function buildPlayout(tracks: ClipTrack[]): void {\n if (playout) {\n try {\n playout.dispose();\n } catch (err) {\n console.warn('[waveform-playlist] Error disposing previous playout during rebuild:', err);\n }\n playout = null;\n }\n\n _playoutGeneration++;\n const generation = _playoutGeneration;\n\n playout = new TonePlayout({\n effects: options?.effects,\n });\n\n // If Tone.start() was already called (AudioContext resumed), carry\n // initialization forward. Tone.start() is safe to call multiple times —\n // it resolves immediately if the AudioContext is already running.\n if (_audioInitialized) {\n playout.init().catch((err) => {\n console.warn(\n '[waveform-playlist] Failed to re-initialize playout after rebuild. ' +\n 'Audio playback will require another user gesture.',\n err\n );\n _audioInitialized = false;\n });\n }\n\n for (const track of tracks) {\n const playableClips = track.clips.filter((c) => c.audioBuffer);\n if (playableClips.length === 0) continue;\n\n const startTime = Math.min(...playableClips.map(clipStartTime));\n const endTime = Math.max(...playableClips.map(clipEndTime));\n\n const trackObj: Track = {\n id: track.id,\n name: track.name,\n gain: track.volume,\n muted: track.muted,\n soloed: track.soloed,\n stereoPan: track.pan,\n startTime,\n endTime,\n };\n\n const clipInfos: ClipInfo[] = playableClips.map((clip) => ({\n buffer: clip.audioBuffer!,\n startTime: clipStartTime(clip) - startTime,\n duration: clipDurationTime(clip),\n offset: clipOffsetTime(clip),\n fadeIn: clip.fadeIn,\n fadeOut: clip.fadeOut,\n gain: clip.gain,\n }));\n\n playout.addTrack({\n clips: clipInfos,\n track: trackObj,\n effects: track.effects,\n });\n }\n\n playout.applyInitialSoloState();\n playout.setLoop(_loopEnabled, _loopStart, _loopEnd);\n\n playout.setOnPlaybackComplete(() => {\n if (generation === _playoutGeneration) {\n _isPlaying = false;\n }\n });\n }\n\n return {\n async init(): Promise<void> {\n if (playout) {\n await playout.init();\n _audioInitialized = true;\n }\n },\n\n setTracks(tracks: ClipTrack[]): void {\n buildPlayout(tracks);\n },\n\n play(startTime: number, endTime?: number): void {\n if (!playout) {\n console.warn(\n '[waveform-playlist] adapter.play() called but no playout is available. ' +\n 'Tracks may not have been set, or the adapter was disposed.'\n );\n return;\n }\n const duration = endTime !== undefined ? endTime - startTime : undefined;\n playout.play(now(), startTime, duration);\n // Only set _isPlaying if play() didn't throw\n // (TonePlayout.play() re-throws after cleanup on Transport failure)\n _isPlaying = true;\n },\n\n pause(): void {\n playout?.pause();\n _isPlaying = false;\n },\n\n stop(): void {\n playout?.stop();\n _isPlaying = false;\n },\n\n seek(time: number): void {\n playout?.seekTo(time);\n },\n\n getCurrentTime(): number {\n return playout?.getCurrentTime() ?? 0;\n },\n\n isPlaying(): boolean {\n return _isPlaying;\n },\n\n setMasterVolume(volume: number): void {\n playout?.setMasterGain(volume);\n },\n\n setTrackVolume(trackId: string, volume: number): void {\n playout?.getTrack(trackId)?.setVolume(volume);\n },\n\n setTrackMute(trackId: string, muted: boolean): void {\n playout?.setMute(trackId, muted);\n },\n\n setTrackSolo(trackId: string, soloed: boolean): void {\n playout?.setSolo(trackId, soloed);\n },\n\n setTrackPan(trackId: string, pan: number): void {\n playout?.getTrack(trackId)?.setPan(pan);\n },\n\n setLoop(enabled: boolean, start: number, end: number): void {\n _loopEnabled = enabled;\n _loopStart = start;\n _loopEnd = end;\n playout?.setLoop(enabled, start, end);\n },\n\n dispose(): void {\n try {\n playout?.dispose();\n } catch (err) {\n console.warn('[waveform-playlist] Error disposing playout:', err);\n }\n playout = null;\n _isPlaying = false;\n },\n };\n}\n"],"mappings":"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","names":["import_tone","clipStartTime","import_tone","import_tone","import_tone","start"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/TonePlayout.ts","../src/ToneTrack.ts","../src/fades.ts","../src/audioContext.ts","../src/mediaStreamSourceManager.ts","../src/TonePlayoutAdapter.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 getUnderlyingAudioParam,\n type FadeConfig,\n type FadeType,\n} from './fades';\n\n// Export Tone.js adapter for engine integration\nexport { createToneAdapter } from './TonePlayoutAdapter';\nexport type { ToneAdapterOptions } from './TonePlayoutAdapter';\n","import {\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\nexport type EffectsFunction = (\n masterGainNode: Volume,\n destination: ToneAudioNode,\n isOffline: boolean\n) => 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 _completionEventId: number | null = null;\n private _loopHandler: (() => void) | null = null;\n private _loopEnabled = false;\n private _loopStart = 0;\n private _loopEnd = 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 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 private clearCompletionEvent(): void {\n if (this._completionEventId !== null) {\n try {\n getTransport().clear(this._completionEventId);\n } catch (err) {\n console.warn('[waveform-playlist] Error clearing Transport completion event:', err);\n }\n this._completionEventId = null;\n }\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 this.manualMuteState.set(toneTrack.id, trackOptions.track.muted ?? false);\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 throw new Error('[waveform-playlist] TonePlayout not initialized. Call init() first.');\n }\n\n const startTime = when ?? now();\n const transport = getTransport();\n\n this.clearCompletionEvent();\n\n const transportOffset = offset ?? 0;\n this.tracks.forEach((track) => {\n track.cancelFades();\n track.prepareFades(startTime, transportOffset);\n });\n\n // Schedule duration-limited stop via Transport\n if (duration !== undefined) {\n this._completionEventId = transport.scheduleOnce(() => {\n this._completionEventId = null;\n try {\n this.onPlaybackCompleteCallback?.();\n } catch (err) {\n console.warn('[waveform-playlist] Error in playback completion callback:', err);\n }\n }, transportOffset + duration);\n }\n\n // Start Transport — triggers schedule() callbacks for clips at/after offset\n try {\n // Stop all active native sources before restarting to prevent layered audio.\n // Native AudioBufferSourceNodes don't respond to Transport state changes.\n if (transport.state !== 'stopped') {\n transport.stop();\n }\n this.tracks.forEach((track) => track.stopAllSources());\n\n // Set loop boundaries BEFORE enabling loop. _processTick checks\n // `ticks >= _loopEnd` every tick; _loopEnd defaults to 0.\n transport.loopStart = this._loopStart;\n transport.loopEnd = this._loopEnd;\n transport.loop = this._loopEnabled;\n\n // Set schedule guard BEFORE transport.start(). Ghost ticks from stale\n // Clock._lastUpdate can fire schedule callbacks at past positions;\n // the guard suppresses callbacks for clips before the play offset\n // (those are handled by startMidClipSources below).\n this.tracks.forEach((track) => track.setScheduleGuardOffset(transportOffset));\n\n if (offset !== undefined) {\n transport.start(startTime, offset);\n } else {\n transport.start(startTime);\n }\n\n // Advance Clock._lastUpdate past the stop/start boundary.\n //\n // After stop/start cycles, _lastUpdate is stale (set by the previous\n // context tick, before our stop/start). The next Clock._loop() processes\n // ticks from [_lastUpdate, now()]. In the gap [_lastUpdate, startTime),\n // the TickSource is still \"started\" from the PREVIOUS play cycle with\n // its old tick offset. Those accumulated ticks can exceed _loopEnd,\n // causing _processTick to wrap immediately to loopStart.\n //\n // By advancing _lastUpdate to startTime, we skip the stale range.\n // The next Clock._loop() only processes [startTime, now()] — ticks\n // from the current play cycle with the correct offset.\n (transport as any)._clock._lastUpdate = startTime;\n\n // Start sources for clips that span the current Transport position.\n // Transport.schedule() only fires for clips at/after the offset;\n // clips whose start time is before the offset need manual creation.\n this.tracks.forEach((track) => {\n track.startMidClipSources(transportOffset, startTime);\n });\n } catch (err) {\n // Clean up scheduled events since Transport failed to start\n this.clearCompletionEvent();\n this.tracks.forEach((track) => track.cancelFades());\n console.warn(\n '[waveform-playlist] Transport.start() failed. Audio playback could not begin.',\n err\n );\n throw err;\n }\n }\n\n pause(): void {\n const transport = getTransport();\n try {\n transport.pause();\n } catch (err) {\n console.warn('[waveform-playlist] Transport.pause() failed:', err);\n }\n // Native AudioBufferSourceNodes ignore Transport state changes —\n // they must be explicitly stopped.\n this.tracks.forEach((track) => track.stopAllSources());\n this.tracks.forEach((track) => track.cancelFades());\n this.clearCompletionEvent();\n }\n\n stop(): void {\n const transport = getTransport();\n try {\n transport.stop();\n } catch (err) {\n console.warn('[waveform-playlist] Transport.stop() failed:', err);\n }\n // Remove loop handler before stopping sources. Prevents any deferred\n // loop event from creating new sources via startMidClipSources() after\n // stopAllSources() runs. Defense-in-depth — setLoop(false) should\n // already have removed it, but stop() must be self-contained.\n if (this._loopHandler) {\n try {\n transport.off('loop', this._loopHandler);\n } catch (err) {\n console.warn('[waveform-playlist] Error removing loop handler:', err);\n }\n this._loopHandler = null;\n }\n // Stop all native sources explicitly\n this.tracks.forEach((track) => track.stopAllSources());\n this.tracks.forEach((track) => track.cancelFades());\n this.clearCompletionEvent();\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 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 (!this.soloedTracks.has(id)) {\n track.setMute(true);\n } else {\n const manuallyMuted = this.manualMuteState.get(id) ?? false;\n track.setMute(manuallyMuted);\n }\n } else {\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 this.manualMuteState.set(trackId, muted);\n track.setMute(muted);\n }\n }\n\n setLoop(enabled: boolean, loopStart: number, loopEnd: number): void {\n // Update cached state first — play() uses these values to configure\n // the Transport on next start, so they must reflect the caller's intent\n // regardless of whether Transport property setting succeeds.\n this._loopEnabled = enabled;\n this._loopStart = loopStart;\n this._loopEnd = loopEnd;\n\n const transport = getTransport();\n try {\n // Set boundaries BEFORE enabling loop. Tone.js's _processTick checks\n // `ticks >= _loopEnd` on every tick. If we set transport.loop = true\n // first, a tick could fire before loopEnd is updated, seeing the stale\n // _loopEnd value (0 from Transport default) and wrapping immediately.\n transport.loopStart = loopStart;\n transport.loopEnd = loopEnd;\n transport.loop = enabled;\n } catch (err) {\n console.warn('[waveform-playlist] Error configuring Transport loop:', err);\n return;\n }\n\n if (enabled && !this._loopHandler) {\n this._loopHandler = () => {\n // On loop boundary: stop old sources, re-schedule fades, start mid-clip sources.\n // Event ordering in Transport's tick processing (Tone.js 15.x _processTick):\n // loopEnd → ticks reset → loopStart → loop → forEachAtTime(ticks)\n // Our loop handler fires BEFORE schedule callbacks, so:\n // 1. stopAllSources + cancelFades — clean slate\n // 2. startMidClipSources — for clips spanning loopStart boundary\n // 3. prepareFades — fresh fade envelopes\n // Then Transport fires schedule callbacks for clips at/after loopStart.\n const currentTime = now();\n this.tracks.forEach((track) => {\n try {\n track.stopAllSources();\n track.cancelFades();\n track.setScheduleGuardOffset(this._loopStart);\n track.startMidClipSources(this._loopStart, currentTime);\n track.prepareFades(currentTime, this._loopStart);\n } catch (err) {\n console.warn(\n `[waveform-playlist] Error re-scheduling track \"${track.id}\" on loop:`,\n err\n );\n }\n });\n };\n transport.on('loop', this._loopHandler);\n } else if (!enabled && this._loopHandler) {\n transport.off('loop', this._loopHandler);\n this._loopHandler = null;\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.clearCompletionEvent();\n\n if (this._loopHandler) {\n try {\n getTransport().off('loop', this._loopHandler);\n } catch (err) {\n console.warn('[waveform-playlist] Error removing Transport loop handler:', err);\n }\n this._loopHandler = null;\n }\n\n this.tracks.forEach((track) => {\n try {\n track.dispose();\n } catch (err) {\n console.warn(`[waveform-playlist] Error disposing track \"${track.id}\":`, err);\n }\n });\n this.tracks.clear();\n\n if (this.effectsCleanup) {\n try {\n this.effectsCleanup();\n } catch (err) {\n console.warn('[waveform-playlist] Error during master effects cleanup:', err);\n }\n }\n\n try {\n this.masterVolume.dispose();\n } catch (err) {\n console.warn('[waveform-playlist] Error disposing master volume:', err);\n }\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","import {\n Volume,\n Gain,\n Panner,\n ToneAudioNode,\n getDestination,\n getTransport,\n getContext,\n} from 'tone';\nimport { Track, type Fade } from '@waveform-playlist/core';\nimport { applyFadeIn, applyFadeOut, getUnderlyingAudioParam } from './fades';\n\nexport type TrackEffectsFunction = (\n graphEnd: Gain,\n masterGainNode: ToneAudioNode,\n isOffline: boolean\n) => 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\n/** Per-clip scheduling info and audio nodes */\ninterface ScheduledClip {\n clipInfo: ClipInfo;\n fadeGainNode: GainNode; // Native GainNode for per-clip fade envelope\n scheduleId: number; // Transport.schedule() event ID\n}\n\nexport class ToneTrack {\n private scheduledClips: ScheduledClip[];\n private activeSources: Set<AudioBufferSourceNode> = new Set();\n private volumeNode: Volume;\n private panNode: Panner;\n private muteGain: Gain;\n private track: Track;\n private effectsCleanup?: () => void;\n // Guard against ghost tick schedule callbacks. After stop/start cycles with\n // loops, stale Clock._lastUpdate causes ticks from the previous cycle to fire\n // Transport.schedule() callbacks at past positions (e.g., time 0 clips fire\n // when starting at offset 5s). Clips before this offset are handled by\n // startMidClipSources(); schedule callbacks should only create sources for\n // clips at/after this offset.\n private _scheduleGuardOffset = 0;\n\n constructor(options: ToneTrackOptions) {\n this.track = options.track;\n\n // Create shared track-level Tone.js 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 // Chain shared Tone.js nodes: Volume → Pan → MuteGain\n this.volumeNode.chain(this.panNode, this.muteGain);\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[] =\n options.clips ||\n (options.buffer\n ? [\n {\n buffer: options.buffer,\n startTime: 0,\n duration: options.buffer.duration,\n offset: 0,\n fadeIn: options.track.fadeIn,\n fadeOut: options.track.fadeOut,\n gain: 1,\n },\n ]\n : []);\n\n const transport = getTransport();\n const rawContext = getContext().rawContext as AudioContext;\n\n // Get the native AudioNode input of the Volume for native→Tone connection.\n // Volume.input is a Tone.js Gain<\"decibels\"> whose .input is the native GainNode.\n // Cast through unknown since Gain<\"decibels\"> and Gain<\"gain\"> don't overlap.\n const volumeNativeInput = (this.volumeNode.input as unknown as Gain).input;\n\n // Schedule each clip via Transport.schedule() with native AudioBufferSourceNode\n this.scheduledClips = clipInfos.map((clipInfo) => {\n // Native GainNode for per-clip fade envelope — created once, reused across play cycles\n const fadeGainNode = rawContext.createGain();\n fadeGainNode.gain.value = clipInfo.gain;\n fadeGainNode.connect(volumeNativeInput);\n\n // Schedule a permanent Transport event at the clip's absolute timeline position.\n // This callback fires on every play and every loop iteration when Transport\n // passes this point.\n const absTransportTime = this.track.startTime + clipInfo.startTime;\n const scheduleId = transport.schedule((audioContextTime: number) => {\n // Guard: ghost ticks from stale Clock._lastUpdate can fire this callback\n // at past positions (see Tone.js #1419). Clips before the play/loop offset\n // are already handled by startMidClipSources().\n if (absTransportTime < this._scheduleGuardOffset) {\n return;\n }\n this.startClipSource(clipInfo, fadeGainNode, audioContextTime);\n }, absTransportTime);\n\n return { clipInfo, fadeGainNode, scheduleId };\n });\n }\n\n /**\n * Create and start an AudioBufferSourceNode for a clip.\n * Sources are one-shot: each play or loop iteration creates a fresh one.\n */\n private startClipSource(\n clipInfo: ClipInfo,\n fadeGainNode: GainNode,\n audioContextTime: number,\n bufferOffset?: number,\n playDuration?: number\n ): void {\n const rawContext = getContext().rawContext as AudioContext;\n const source = rawContext.createBufferSource();\n source.buffer = clipInfo.buffer;\n source.connect(fadeGainNode);\n\n const offset = bufferOffset ?? clipInfo.offset;\n const duration = playDuration ?? clipInfo.duration;\n\n try {\n source.start(audioContextTime, offset, duration);\n } catch (err) {\n console.warn(\n `[waveform-playlist] Failed to start source on track \"${this.id}\" ` +\n `(time=${audioContextTime}, offset=${offset}, duration=${duration}):`,\n err\n );\n source.disconnect();\n return;\n }\n\n this.activeSources.add(source);\n source.onended = () => {\n this.activeSources.delete(source);\n };\n }\n\n /**\n * Set the schedule guard offset. Schedule callbacks for clips before this\n * offset are suppressed (already handled by startMidClipSources).\n * Must be called before transport.start() and in the loop handler.\n */\n setScheduleGuardOffset(offset: number): void {\n this._scheduleGuardOffset = offset;\n }\n\n /**\n * Start sources for clips that span the given Transport position.\n * Used for mid-playback seeking and loop boundary handling where\n * Transport.schedule() callbacks have already passed.\n *\n * Uses strict < for absClipStart to avoid double-creation with\n * schedule callbacks at exact Transport position (e.g., loopStart).\n */\n startMidClipSources(transportOffset: number, audioContextTime: number): void {\n for (const { clipInfo, fadeGainNode } of this.scheduledClips) {\n const absClipStart = this.track.startTime + clipInfo.startTime;\n const absClipEnd = absClipStart + clipInfo.duration;\n\n // Only handle clips that started before the transport position\n // but haven't ended yet (i.e., the transport is \"inside\" the clip)\n if (absClipStart < transportOffset && absClipEnd > transportOffset) {\n const elapsed = transportOffset - absClipStart;\n const adjustedOffset = clipInfo.offset + elapsed;\n const remainingDuration = clipInfo.duration - elapsed;\n this.startClipSource(\n clipInfo,\n fadeGainNode,\n audioContextTime,\n adjustedOffset,\n remainingDuration\n );\n }\n }\n }\n\n /**\n * Stop all active AudioBufferSourceNodes and clear the set.\n * Native AudioBufferSourceNodes ignore Transport state changes —\n * they must be explicitly stopped.\n */\n stopAllSources(): void {\n this.activeSources.forEach((source) => {\n try {\n source.stop();\n } catch (err) {\n console.warn(`[waveform-playlist] Error stopping source on track \"${this.id}\":`, err);\n }\n });\n this.activeSources.clear();\n }\n\n /**\n * Schedule fade envelopes for a clip at the given AudioContext time.\n * Uses native GainNode.gain (AudioParam) directly — no _param workaround needed.\n */\n private scheduleFades(\n scheduled: ScheduledClip,\n clipStartTime: number,\n clipOffset: number = 0\n ): void {\n const { clipInfo, fadeGainNode } = scheduled;\n const audioParam = fadeGainNode.gain;\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 applyFadeIn(\n audioParam,\n clipStartTime,\n fadeInDuration,\n clipInfo.fadeIn.type || 'linear',\n 0,\n clipInfo.gain\n );\n } else {\n const remainingFadeDuration = fadeInDuration - skipTime;\n const fadeProgress = skipTime / fadeInDuration;\n const startValue = clipInfo.gain * fadeProgress;\n applyFadeIn(\n audioParam,\n clipStartTime,\n remainingFadeDuration,\n clipInfo.fadeIn.type || 'linear',\n startValue,\n clipInfo.gain\n );\n }\n } else {\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;\n\n if (fadeOutStartInClip > 0) {\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 const elapsedFadeOut = -fadeOutStartInClip;\n const remainingFadeDuration = clipInfo.fadeOut.duration - elapsedFadeOut;\n const fadeProgress = elapsedFadeOut / clipInfo.fadeOut.duration;\n const startValue = clipInfo.gain * (1 - fadeProgress);\n applyFadeOut(\n audioParam,\n clipStartTime,\n remainingFadeDuration,\n clipInfo.fadeOut.type || 'linear',\n startValue,\n 0\n );\n }\n }\n }\n\n /**\n * Prepare fade envelopes for all clips based on Transport offset.\n * Called before Transport.start() to schedule fades at correct AudioContext times.\n */\n prepareFades(when: number, transportOffset: number): void {\n this.scheduledClips.forEach((scheduled) => {\n const absClipStart = this.track.startTime + scheduled.clipInfo.startTime;\n const absClipEnd = absClipStart + scheduled.clipInfo.duration;\n\n if (transportOffset >= absClipEnd) return; // clip already finished\n\n if (transportOffset >= absClipStart) {\n // Mid-clip: playing now\n const clipOffset = transportOffset - absClipStart + scheduled.clipInfo.offset;\n this.scheduleFades(scheduled, when, clipOffset);\n } else {\n // Clip starts later\n const delay = absClipStart - transportOffset;\n this.scheduleFades(scheduled, when + delay, scheduled.clipInfo.offset);\n }\n });\n }\n\n /**\n * Cancel all scheduled fade automation and reset to nominal gain.\n * Called on pause/stop to prevent stale fade envelopes.\n */\n cancelFades(): void {\n this.scheduledClips.forEach(({ fadeGainNode, clipInfo }) => {\n const audioParam = fadeGainNode.gain;\n audioParam.cancelScheduledValues(0);\n audioParam.setValueAtTime(clipInfo.gain, 0);\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 const value = muted ? 0 : 1;\n // Use setValueAtTime on the raw AudioParam to ensure the value is applied\n // even when the AudioContext is suspended. Setting .gain.value on the Tone.js\n // Signal wrapper doesn't propagate to the underlying AudioParam until the\n // context resumes, causing a brief audio glitch (e.g., all tracks audible\n // before solo muting takes effect).\n const audioParam = getUnderlyingAudioParam(this.muteGain.gain);\n audioParam?.setValueAtTime(value, 0);\n this.muteGain.gain.value = value;\n }\n\n setSolo(soloed: boolean): void {\n this.track.soloed = soloed;\n }\n\n dispose(): void {\n const transport = getTransport();\n\n if (this.effectsCleanup) {\n try {\n this.effectsCleanup();\n } catch (err) {\n console.warn(`[waveform-playlist] Error during track \"${this.id}\" effects cleanup:`, err);\n }\n }\n\n this.stopAllSources();\n\n // Clear Transport schedule events and disconnect native fade gain nodes\n this.scheduledClips.forEach((scheduled, index) => {\n try {\n transport.clear(scheduled.scheduleId);\n } catch (err) {\n console.warn(\n `[waveform-playlist] Error clearing schedule ${index} on track \"${this.id}\":`,\n err\n );\n }\n try {\n scheduled.fadeGainNode.disconnect();\n } catch (err) {\n console.warn(\n `[waveform-playlist] Error disconnecting fadeGain ${index} on track \"${this.id}\":`,\n err\n );\n }\n });\n\n try {\n this.volumeNode.dispose();\n } catch (err) {\n console.warn(`[waveform-playlist] Error disposing volumeNode on track \"${this.id}\":`, err);\n }\n try {\n this.panNode.dispose();\n } catch (err) {\n console.warn(`[waveform-playlist] Error disposing panNode on track \"${this.id}\":`, err);\n }\n try {\n this.muteGain.dispose();\n } catch (err) {\n console.warn(`[waveform-playlist] Error disposing muteGain on track \"${this.id}\":`, err);\n }\n }\n\n get id(): string {\n return this.track.id;\n }\n\n get duration(): number {\n if (this.scheduledClips.length === 0) return 0;\n const lastClip = this.scheduledClips[this.scheduledClips.length - 1];\n return lastClip.clipInfo.startTime + lastClip.clipInfo.duration;\n }\n\n get buffer(): AudioBuffer {\n return this.scheduledClips[0]?.clipInfo.buffer;\n }\n\n get muted(): boolean {\n return this.track.muted;\n }\n\n get startTime(): number {\n return this.track.startTime;\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\n/**\n * Access the underlying Web Audio AudioParam from a Tone.js Signal/Param wrapper.\n *\n * Tone.js wraps native AudioParam in its Signal class, but sometimes we need\n * direct access to the raw AudioParam for setValueAtTime/cancelScheduledValues\n * (e.g., when the AudioContext is suspended and Tone.js Signal doesn't propagate).\n *\n * This uses `_param` which is a private Tone.js 15.x internal.\n * Pin the Tone.js version carefully if upgrading.\n *\n * @param signal - A Tone.js Signal or Param wrapper (e.g., `gain.gain`)\n * @returns The underlying AudioParam, or undefined if not found\n */\nlet hasWarned = false;\n\nexport function getUnderlyingAudioParam(signal: unknown): AudioParam | undefined {\n const param = (signal as { _param?: AudioParam })._param;\n if (!param && !hasWarned) {\n hasWarned = true;\n console.warn(\n '[waveform-playlist] Unable to access Tone.js internal _param. ' +\n 'This likely means the Tone.js version is incompatible. ' +\n 'Mute scheduling may not work correctly.'\n );\n }\n return param;\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(stream: MediaStream): 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","import type { ClipTrack, Track } from '@waveform-playlist/core';\nimport {\n clipStartTime,\n clipEndTime,\n clipOffsetTime,\n clipDurationTime,\n} from '@waveform-playlist/core';\nimport type { PlayoutAdapter } from '@waveform-playlist/engine';\nimport { TonePlayout } from './TonePlayout';\nimport type { EffectsFunction } from './TonePlayout';\nimport type { ClipInfo } from './ToneTrack';\nimport { now } from 'tone';\n\nexport interface ToneAdapterOptions {\n effects?: EffectsFunction;\n}\n\nexport function createToneAdapter(options?: ToneAdapterOptions): PlayoutAdapter {\n let playout: TonePlayout | null = null;\n let _isPlaying = false;\n let _playoutGeneration = 0;\n let _loopEnabled = false;\n let _loopStart = 0;\n let _loopEnd = 0;\n let _audioInitialized = false;\n\n function buildPlayout(tracks: ClipTrack[]): void {\n if (playout) {\n try {\n playout.dispose();\n } catch (err) {\n console.warn('[waveform-playlist] Error disposing previous playout during rebuild:', err);\n }\n playout = null;\n }\n\n _playoutGeneration++;\n const generation = _playoutGeneration;\n\n playout = new TonePlayout({\n effects: options?.effects,\n });\n\n // If Tone.start() was already called (AudioContext resumed), carry\n // initialization forward. Tone.start() is safe to call multiple times —\n // it resolves immediately if the AudioContext is already running.\n if (_audioInitialized) {\n playout.init().catch((err) => {\n console.warn(\n '[waveform-playlist] Failed to re-initialize playout after rebuild. ' +\n 'Audio playback will require another user gesture.',\n err\n );\n _audioInitialized = false;\n });\n }\n\n for (const track of tracks) {\n const playableClips = track.clips.filter((c) => c.audioBuffer);\n if (playableClips.length === 0) continue;\n\n const startTime = Math.min(...playableClips.map(clipStartTime));\n const endTime = Math.max(...playableClips.map(clipEndTime));\n\n const trackObj: Track = {\n id: track.id,\n name: track.name,\n gain: track.volume,\n muted: track.muted,\n soloed: track.soloed,\n stereoPan: track.pan,\n startTime,\n endTime,\n };\n\n const clipInfos: ClipInfo[] = playableClips.map((clip) => ({\n buffer: clip.audioBuffer!,\n startTime: clipStartTime(clip) - startTime,\n duration: clipDurationTime(clip),\n offset: clipOffsetTime(clip),\n fadeIn: clip.fadeIn,\n fadeOut: clip.fadeOut,\n gain: clip.gain,\n }));\n\n playout.addTrack({\n clips: clipInfos,\n track: trackObj,\n effects: track.effects,\n });\n }\n\n playout.applyInitialSoloState();\n playout.setLoop(_loopEnabled, _loopStart, _loopEnd);\n\n playout.setOnPlaybackComplete(() => {\n if (generation === _playoutGeneration) {\n _isPlaying = false;\n }\n });\n }\n\n return {\n async init(): Promise<void> {\n if (playout) {\n await playout.init();\n _audioInitialized = true;\n }\n },\n\n setTracks(tracks: ClipTrack[]): void {\n buildPlayout(tracks);\n },\n\n play(startTime: number, endTime?: number): void {\n if (!playout) {\n console.warn(\n '[waveform-playlist] adapter.play() called but no playout is available. ' +\n 'Tracks may not have been set, or the adapter was disposed.'\n );\n return;\n }\n const duration = endTime !== undefined ? endTime - startTime : undefined;\n playout.play(now(), startTime, duration);\n // Only set _isPlaying if play() didn't throw\n // (TonePlayout.play() re-throws after cleanup on Transport failure)\n _isPlaying = true;\n },\n\n pause(): void {\n playout?.pause();\n _isPlaying = false;\n },\n\n stop(): void {\n playout?.stop();\n _isPlaying = false;\n },\n\n seek(time: number): void {\n playout?.seekTo(time);\n },\n\n getCurrentTime(): number {\n return playout?.getCurrentTime() ?? 0;\n },\n\n isPlaying(): boolean {\n return _isPlaying;\n },\n\n setMasterVolume(volume: number): void {\n playout?.setMasterGain(volume);\n },\n\n setTrackVolume(trackId: string, volume: number): void {\n playout?.getTrack(trackId)?.setVolume(volume);\n },\n\n setTrackMute(trackId: string, muted: boolean): void {\n playout?.setMute(trackId, muted);\n },\n\n setTrackSolo(trackId: string, soloed: boolean): void {\n playout?.setSolo(trackId, soloed);\n },\n\n setTrackPan(trackId: string, pan: number): void {\n playout?.getTrack(trackId)?.setPan(pan);\n },\n\n setLoop(enabled: boolean, start: number, end: number): void {\n _loopEnabled = enabled;\n _loopStart = start;\n _loopEnd = end;\n playout?.setLoop(enabled, start, end);\n },\n\n dispose(): void {\n try {\n playout?.dispose();\n } catch (err) {\n console.warn('[waveform-playlist] Error disposing playout:', err);\n }\n playout = null;\n _isPlaying = false;\n },\n };\n}\n"],"mappings":"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","names":["import_tone","clipStartTime","import_tone","import_tone","import_tone","start"]}
|
package/dist/index.mjs
CHANGED
|
@@ -569,6 +569,14 @@ var TonePlayout = class {
|
|
|
569
569
|
} catch (err) {
|
|
570
570
|
console.warn("[waveform-playlist] Transport.stop() failed:", err);
|
|
571
571
|
}
|
|
572
|
+
if (this._loopHandler) {
|
|
573
|
+
try {
|
|
574
|
+
transport.off("loop", this._loopHandler);
|
|
575
|
+
} catch (err) {
|
|
576
|
+
console.warn("[waveform-playlist] Error removing loop handler:", err);
|
|
577
|
+
}
|
|
578
|
+
this._loopHandler = null;
|
|
579
|
+
}
|
|
572
580
|
this.tracks.forEach((track) => track.stopAllSources());
|
|
573
581
|
this.tracks.forEach((track) => track.cancelFades());
|
|
574
582
|
this.clearCompletionEvent();
|
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","../src/TonePlayoutAdapter.ts"],"sourcesContent":["import {\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\nexport type EffectsFunction = (\n masterGainNode: Volume,\n destination: ToneAudioNode,\n isOffline: boolean\n) => 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 _completionEventId: number | null = null;\n private _loopHandler: (() => void) | null = null;\n private _loopEnabled = false;\n private _loopStart = 0;\n private _loopEnd = 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 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 private clearCompletionEvent(): void {\n if (this._completionEventId !== null) {\n try {\n getTransport().clear(this._completionEventId);\n } catch (err) {\n console.warn('[waveform-playlist] Error clearing Transport completion event:', err);\n }\n this._completionEventId = null;\n }\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 this.manualMuteState.set(toneTrack.id, trackOptions.track.muted ?? false);\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 throw new Error('[waveform-playlist] TonePlayout not initialized. Call init() first.');\n }\n\n const startTime = when ?? now();\n const transport = getTransport();\n\n this.clearCompletionEvent();\n\n const transportOffset = offset ?? 0;\n this.tracks.forEach((track) => {\n track.cancelFades();\n track.prepareFades(startTime, transportOffset);\n });\n\n // Schedule duration-limited stop via Transport\n if (duration !== undefined) {\n this._completionEventId = transport.scheduleOnce(() => {\n this._completionEventId = null;\n try {\n this.onPlaybackCompleteCallback?.();\n } catch (err) {\n console.warn('[waveform-playlist] Error in playback completion callback:', err);\n }\n }, transportOffset + duration);\n }\n\n // Start Transport — triggers schedule() callbacks for clips at/after offset\n try {\n // Stop all active native sources before restarting to prevent layered audio.\n // Native AudioBufferSourceNodes don't respond to Transport state changes.\n if (transport.state !== 'stopped') {\n transport.stop();\n }\n this.tracks.forEach((track) => track.stopAllSources());\n\n // Set loop boundaries BEFORE enabling loop. _processTick checks\n // `ticks >= _loopEnd` every tick; _loopEnd defaults to 0.\n transport.loopStart = this._loopStart;\n transport.loopEnd = this._loopEnd;\n transport.loop = this._loopEnabled;\n\n // Set schedule guard BEFORE transport.start(). Ghost ticks from stale\n // Clock._lastUpdate can fire schedule callbacks at past positions;\n // the guard suppresses callbacks for clips before the play offset\n // (those are handled by startMidClipSources below).\n this.tracks.forEach((track) => track.setScheduleGuardOffset(transportOffset));\n\n if (offset !== undefined) {\n transport.start(startTime, offset);\n } else {\n transport.start(startTime);\n }\n\n // Advance Clock._lastUpdate past the stop/start boundary.\n //\n // After stop/start cycles, _lastUpdate is stale (set by the previous\n // context tick, before our stop/start). The next Clock._loop() processes\n // ticks from [_lastUpdate, now()]. In the gap [_lastUpdate, startTime),\n // the TickSource is still \"started\" from the PREVIOUS play cycle with\n // its old tick offset. Those accumulated ticks can exceed _loopEnd,\n // causing _processTick to wrap immediately to loopStart.\n //\n // By advancing _lastUpdate to startTime, we skip the stale range.\n // The next Clock._loop() only processes [startTime, now()] — ticks\n // from the current play cycle with the correct offset.\n (transport as any)._clock._lastUpdate = startTime;\n\n // Start sources for clips that span the current Transport position.\n // Transport.schedule() only fires for clips at/after the offset;\n // clips whose start time is before the offset need manual creation.\n this.tracks.forEach((track) => {\n track.startMidClipSources(transportOffset, startTime);\n });\n } catch (err) {\n // Clean up scheduled events since Transport failed to start\n this.clearCompletionEvent();\n this.tracks.forEach((track) => track.cancelFades());\n console.warn(\n '[waveform-playlist] Transport.start() failed. Audio playback could not begin.',\n err\n );\n throw err;\n }\n }\n\n pause(): void {\n const transport = getTransport();\n try {\n transport.pause();\n } catch (err) {\n console.warn('[waveform-playlist] Transport.pause() failed:', err);\n }\n // Native AudioBufferSourceNodes ignore Transport state changes —\n // they must be explicitly stopped.\n this.tracks.forEach((track) => track.stopAllSources());\n this.tracks.forEach((track) => track.cancelFades());\n this.clearCompletionEvent();\n }\n\n stop(): void {\n const transport = getTransport();\n try {\n transport.stop();\n } catch (err) {\n console.warn('[waveform-playlist] Transport.stop() failed:', err);\n }\n // Stop all native sources explicitly\n this.tracks.forEach((track) => track.stopAllSources());\n this.tracks.forEach((track) => track.cancelFades());\n this.clearCompletionEvent();\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 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 (!this.soloedTracks.has(id)) {\n track.setMute(true);\n } else {\n const manuallyMuted = this.manualMuteState.get(id) ?? false;\n track.setMute(manuallyMuted);\n }\n } else {\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 this.manualMuteState.set(trackId, muted);\n track.setMute(muted);\n }\n }\n\n setLoop(enabled: boolean, loopStart: number, loopEnd: number): void {\n // Update cached state first — play() uses these values to configure\n // the Transport on next start, so they must reflect the caller's intent\n // regardless of whether Transport property setting succeeds.\n this._loopEnabled = enabled;\n this._loopStart = loopStart;\n this._loopEnd = loopEnd;\n\n const transport = getTransport();\n try {\n // Set boundaries BEFORE enabling loop. Tone.js's _processTick checks\n // `ticks >= _loopEnd` on every tick. If we set transport.loop = true\n // first, a tick could fire before loopEnd is updated, seeing the stale\n // _loopEnd value (0 from Transport default) and wrapping immediately.\n transport.loopStart = loopStart;\n transport.loopEnd = loopEnd;\n transport.loop = enabled;\n } catch (err) {\n console.warn('[waveform-playlist] Error configuring Transport loop:', err);\n return;\n }\n\n if (enabled && !this._loopHandler) {\n this._loopHandler = () => {\n // On loop boundary: stop old sources, re-schedule fades, start mid-clip sources.\n // Event ordering in Transport's tick processing (Tone.js 15.x _processTick):\n // loopEnd → ticks reset → loopStart → loop → forEachAtTime(ticks)\n // Our loop handler fires BEFORE schedule callbacks, so:\n // 1. stopAllSources + cancelFades — clean slate\n // 2. startMidClipSources — for clips spanning loopStart boundary\n // 3. prepareFades — fresh fade envelopes\n // Then Transport fires schedule callbacks for clips at/after loopStart.\n const currentTime = now();\n this.tracks.forEach((track) => {\n try {\n track.stopAllSources();\n track.cancelFades();\n track.setScheduleGuardOffset(this._loopStart);\n track.startMidClipSources(this._loopStart, currentTime);\n track.prepareFades(currentTime, this._loopStart);\n } catch (err) {\n console.warn(\n `[waveform-playlist] Error re-scheduling track \"${track.id}\" on loop:`,\n err\n );\n }\n });\n };\n transport.on('loop', this._loopHandler);\n } else if (!enabled && this._loopHandler) {\n transport.off('loop', this._loopHandler);\n this._loopHandler = null;\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.clearCompletionEvent();\n\n if (this._loopHandler) {\n try {\n getTransport().off('loop', this._loopHandler);\n } catch (err) {\n console.warn('[waveform-playlist] Error removing Transport loop handler:', err);\n }\n this._loopHandler = null;\n }\n\n this.tracks.forEach((track) => {\n try {\n track.dispose();\n } catch (err) {\n console.warn(`[waveform-playlist] Error disposing track \"${track.id}\":`, err);\n }\n });\n this.tracks.clear();\n\n if (this.effectsCleanup) {\n try {\n this.effectsCleanup();\n } catch (err) {\n console.warn('[waveform-playlist] Error during master effects cleanup:', err);\n }\n }\n\n try {\n this.masterVolume.dispose();\n } catch (err) {\n console.warn('[waveform-playlist] Error disposing master volume:', err);\n }\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","import {\n Volume,\n Gain,\n Panner,\n ToneAudioNode,\n getDestination,\n getTransport,\n getContext,\n} from 'tone';\nimport { Track, type Fade } from '@waveform-playlist/core';\nimport { applyFadeIn, applyFadeOut, getUnderlyingAudioParam } from './fades';\n\nexport type TrackEffectsFunction = (\n graphEnd: Gain,\n masterGainNode: ToneAudioNode,\n isOffline: boolean\n) => 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\n/** Per-clip scheduling info and audio nodes */\ninterface ScheduledClip {\n clipInfo: ClipInfo;\n fadeGainNode: GainNode; // Native GainNode for per-clip fade envelope\n scheduleId: number; // Transport.schedule() event ID\n}\n\nexport class ToneTrack {\n private scheduledClips: ScheduledClip[];\n private activeSources: Set<AudioBufferSourceNode> = new Set();\n private volumeNode: Volume;\n private panNode: Panner;\n private muteGain: Gain;\n private track: Track;\n private effectsCleanup?: () => void;\n // Guard against ghost tick schedule callbacks. After stop/start cycles with\n // loops, stale Clock._lastUpdate causes ticks from the previous cycle to fire\n // Transport.schedule() callbacks at past positions (e.g., time 0 clips fire\n // when starting at offset 5s). Clips before this offset are handled by\n // startMidClipSources(); schedule callbacks should only create sources for\n // clips at/after this offset.\n private _scheduleGuardOffset = 0;\n\n constructor(options: ToneTrackOptions) {\n this.track = options.track;\n\n // Create shared track-level Tone.js 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 // Chain shared Tone.js nodes: Volume → Pan → MuteGain\n this.volumeNode.chain(this.panNode, this.muteGain);\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[] =\n options.clips ||\n (options.buffer\n ? [\n {\n buffer: options.buffer,\n startTime: 0,\n duration: options.buffer.duration,\n offset: 0,\n fadeIn: options.track.fadeIn,\n fadeOut: options.track.fadeOut,\n gain: 1,\n },\n ]\n : []);\n\n const transport = getTransport();\n const rawContext = getContext().rawContext as AudioContext;\n\n // Get the native AudioNode input of the Volume for native→Tone connection.\n // Volume.input is a Tone.js Gain<\"decibels\"> whose .input is the native GainNode.\n // Cast through unknown since Gain<\"decibels\"> and Gain<\"gain\"> don't overlap.\n const volumeNativeInput = (this.volumeNode.input as unknown as Gain).input;\n\n // Schedule each clip via Transport.schedule() with native AudioBufferSourceNode\n this.scheduledClips = clipInfos.map((clipInfo) => {\n // Native GainNode for per-clip fade envelope — created once, reused across play cycles\n const fadeGainNode = rawContext.createGain();\n fadeGainNode.gain.value = clipInfo.gain;\n fadeGainNode.connect(volumeNativeInput);\n\n // Schedule a permanent Transport event at the clip's absolute timeline position.\n // This callback fires on every play and every loop iteration when Transport\n // passes this point.\n const absTransportTime = this.track.startTime + clipInfo.startTime;\n const scheduleId = transport.schedule((audioContextTime: number) => {\n // Guard: ghost ticks from stale Clock._lastUpdate can fire this callback\n // at past positions (see Tone.js #1419). Clips before the play/loop offset\n // are already handled by startMidClipSources().\n if (absTransportTime < this._scheduleGuardOffset) {\n return;\n }\n this.startClipSource(clipInfo, fadeGainNode, audioContextTime);\n }, absTransportTime);\n\n return { clipInfo, fadeGainNode, scheduleId };\n });\n }\n\n /**\n * Create and start an AudioBufferSourceNode for a clip.\n * Sources are one-shot: each play or loop iteration creates a fresh one.\n */\n private startClipSource(\n clipInfo: ClipInfo,\n fadeGainNode: GainNode,\n audioContextTime: number,\n bufferOffset?: number,\n playDuration?: number\n ): void {\n const rawContext = getContext().rawContext as AudioContext;\n const source = rawContext.createBufferSource();\n source.buffer = clipInfo.buffer;\n source.connect(fadeGainNode);\n\n const offset = bufferOffset ?? clipInfo.offset;\n const duration = playDuration ?? clipInfo.duration;\n\n try {\n source.start(audioContextTime, offset, duration);\n } catch (err) {\n console.warn(\n `[waveform-playlist] Failed to start source on track \"${this.id}\" ` +\n `(time=${audioContextTime}, offset=${offset}, duration=${duration}):`,\n err\n );\n source.disconnect();\n return;\n }\n\n this.activeSources.add(source);\n source.onended = () => {\n this.activeSources.delete(source);\n };\n }\n\n /**\n * Set the schedule guard offset. Schedule callbacks for clips before this\n * offset are suppressed (already handled by startMidClipSources).\n * Must be called before transport.start() and in the loop handler.\n */\n setScheduleGuardOffset(offset: number): void {\n this._scheduleGuardOffset = offset;\n }\n\n /**\n * Start sources for clips that span the given Transport position.\n * Used for mid-playback seeking and loop boundary handling where\n * Transport.schedule() callbacks have already passed.\n *\n * Uses strict < for absClipStart to avoid double-creation with\n * schedule callbacks at exact Transport position (e.g., loopStart).\n */\n startMidClipSources(transportOffset: number, audioContextTime: number): void {\n for (const { clipInfo, fadeGainNode } of this.scheduledClips) {\n const absClipStart = this.track.startTime + clipInfo.startTime;\n const absClipEnd = absClipStart + clipInfo.duration;\n\n // Only handle clips that started before the transport position\n // but haven't ended yet (i.e., the transport is \"inside\" the clip)\n if (absClipStart < transportOffset && absClipEnd > transportOffset) {\n const elapsed = transportOffset - absClipStart;\n const adjustedOffset = clipInfo.offset + elapsed;\n const remainingDuration = clipInfo.duration - elapsed;\n this.startClipSource(\n clipInfo,\n fadeGainNode,\n audioContextTime,\n adjustedOffset,\n remainingDuration\n );\n }\n }\n }\n\n /**\n * Stop all active AudioBufferSourceNodes and clear the set.\n * Native AudioBufferSourceNodes ignore Transport state changes —\n * they must be explicitly stopped.\n */\n stopAllSources(): void {\n this.activeSources.forEach((source) => {\n try {\n source.stop();\n } catch (err) {\n console.warn(`[waveform-playlist] Error stopping source on track \"${this.id}\":`, err);\n }\n });\n this.activeSources.clear();\n }\n\n /**\n * Schedule fade envelopes for a clip at the given AudioContext time.\n * Uses native GainNode.gain (AudioParam) directly — no _param workaround needed.\n */\n private scheduleFades(\n scheduled: ScheduledClip,\n clipStartTime: number,\n clipOffset: number = 0\n ): void {\n const { clipInfo, fadeGainNode } = scheduled;\n const audioParam = fadeGainNode.gain;\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 applyFadeIn(\n audioParam,\n clipStartTime,\n fadeInDuration,\n clipInfo.fadeIn.type || 'linear',\n 0,\n clipInfo.gain\n );\n } else {\n const remainingFadeDuration = fadeInDuration - skipTime;\n const fadeProgress = skipTime / fadeInDuration;\n const startValue = clipInfo.gain * fadeProgress;\n applyFadeIn(\n audioParam,\n clipStartTime,\n remainingFadeDuration,\n clipInfo.fadeIn.type || 'linear',\n startValue,\n clipInfo.gain\n );\n }\n } else {\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;\n\n if (fadeOutStartInClip > 0) {\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 const elapsedFadeOut = -fadeOutStartInClip;\n const remainingFadeDuration = clipInfo.fadeOut.duration - elapsedFadeOut;\n const fadeProgress = elapsedFadeOut / clipInfo.fadeOut.duration;\n const startValue = clipInfo.gain * (1 - fadeProgress);\n applyFadeOut(\n audioParam,\n clipStartTime,\n remainingFadeDuration,\n clipInfo.fadeOut.type || 'linear',\n startValue,\n 0\n );\n }\n }\n }\n\n /**\n * Prepare fade envelopes for all clips based on Transport offset.\n * Called before Transport.start() to schedule fades at correct AudioContext times.\n */\n prepareFades(when: number, transportOffset: number): void {\n this.scheduledClips.forEach((scheduled) => {\n const absClipStart = this.track.startTime + scheduled.clipInfo.startTime;\n const absClipEnd = absClipStart + scheduled.clipInfo.duration;\n\n if (transportOffset >= absClipEnd) return; // clip already finished\n\n if (transportOffset >= absClipStart) {\n // Mid-clip: playing now\n const clipOffset = transportOffset - absClipStart + scheduled.clipInfo.offset;\n this.scheduleFades(scheduled, when, clipOffset);\n } else {\n // Clip starts later\n const delay = absClipStart - transportOffset;\n this.scheduleFades(scheduled, when + delay, scheduled.clipInfo.offset);\n }\n });\n }\n\n /**\n * Cancel all scheduled fade automation and reset to nominal gain.\n * Called on pause/stop to prevent stale fade envelopes.\n */\n cancelFades(): void {\n this.scheduledClips.forEach(({ fadeGainNode, clipInfo }) => {\n const audioParam = fadeGainNode.gain;\n audioParam.cancelScheduledValues(0);\n audioParam.setValueAtTime(clipInfo.gain, 0);\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 const value = muted ? 0 : 1;\n // Use setValueAtTime on the raw AudioParam to ensure the value is applied\n // even when the AudioContext is suspended. Setting .gain.value on the Tone.js\n // Signal wrapper doesn't propagate to the underlying AudioParam until the\n // context resumes, causing a brief audio glitch (e.g., all tracks audible\n // before solo muting takes effect).\n const audioParam = getUnderlyingAudioParam(this.muteGain.gain);\n audioParam?.setValueAtTime(value, 0);\n this.muteGain.gain.value = value;\n }\n\n setSolo(soloed: boolean): void {\n this.track.soloed = soloed;\n }\n\n dispose(): void {\n const transport = getTransport();\n\n if (this.effectsCleanup) {\n try {\n this.effectsCleanup();\n } catch (err) {\n console.warn(`[waveform-playlist] Error during track \"${this.id}\" effects cleanup:`, err);\n }\n }\n\n this.stopAllSources();\n\n // Clear Transport schedule events and disconnect native fade gain nodes\n this.scheduledClips.forEach((scheduled, index) => {\n try {\n transport.clear(scheduled.scheduleId);\n } catch (err) {\n console.warn(\n `[waveform-playlist] Error clearing schedule ${index} on track \"${this.id}\":`,\n err\n );\n }\n try {\n scheduled.fadeGainNode.disconnect();\n } catch (err) {\n console.warn(\n `[waveform-playlist] Error disconnecting fadeGain ${index} on track \"${this.id}\":`,\n err\n );\n }\n });\n\n try {\n this.volumeNode.dispose();\n } catch (err) {\n console.warn(`[waveform-playlist] Error disposing volumeNode on track \"${this.id}\":`, err);\n }\n try {\n this.panNode.dispose();\n } catch (err) {\n console.warn(`[waveform-playlist] Error disposing panNode on track \"${this.id}\":`, err);\n }\n try {\n this.muteGain.dispose();\n } catch (err) {\n console.warn(`[waveform-playlist] Error disposing muteGain on track \"${this.id}\":`, err);\n }\n }\n\n get id(): string {\n return this.track.id;\n }\n\n get duration(): number {\n if (this.scheduledClips.length === 0) return 0;\n const lastClip = this.scheduledClips[this.scheduledClips.length - 1];\n return lastClip.clipInfo.startTime + lastClip.clipInfo.duration;\n }\n\n get buffer(): AudioBuffer {\n return this.scheduledClips[0]?.clipInfo.buffer;\n }\n\n get muted(): boolean {\n return this.track.muted;\n }\n\n get startTime(): number {\n return this.track.startTime;\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\n/**\n * Access the underlying Web Audio AudioParam from a Tone.js Signal/Param wrapper.\n *\n * Tone.js wraps native AudioParam in its Signal class, but sometimes we need\n * direct access to the raw AudioParam for setValueAtTime/cancelScheduledValues\n * (e.g., when the AudioContext is suspended and Tone.js Signal doesn't propagate).\n *\n * This uses `_param` which is a private Tone.js 15.x internal.\n * Pin the Tone.js version carefully if upgrading.\n *\n * @param signal - A Tone.js Signal or Param wrapper (e.g., `gain.gain`)\n * @returns The underlying AudioParam, or undefined if not found\n */\nlet hasWarned = false;\n\nexport function getUnderlyingAudioParam(signal: unknown): AudioParam | undefined {\n const param = (signal as { _param?: AudioParam })._param;\n if (!param && !hasWarned) {\n hasWarned = true;\n console.warn(\n '[waveform-playlist] Unable to access Tone.js internal _param. ' +\n 'This likely means the Tone.js version is incompatible. ' +\n 'Mute scheduling may not work correctly.'\n );\n }\n return param;\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(stream: MediaStream): 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","import type { ClipTrack, Track } from '@waveform-playlist/core';\nimport {\n clipStartTime,\n clipEndTime,\n clipOffsetTime,\n clipDurationTime,\n} from '@waveform-playlist/core';\nimport type { PlayoutAdapter } from '@waveform-playlist/engine';\nimport { TonePlayout } from './TonePlayout';\nimport type { EffectsFunction } from './TonePlayout';\nimport type { ClipInfo } from './ToneTrack';\nimport { now } from 'tone';\n\nexport interface ToneAdapterOptions {\n effects?: EffectsFunction;\n}\n\nexport function createToneAdapter(options?: ToneAdapterOptions): PlayoutAdapter {\n let playout: TonePlayout | null = null;\n let _isPlaying = false;\n let _playoutGeneration = 0;\n let _loopEnabled = false;\n let _loopStart = 0;\n let _loopEnd = 0;\n let _audioInitialized = false;\n\n function buildPlayout(tracks: ClipTrack[]): void {\n if (playout) {\n try {\n playout.dispose();\n } catch (err) {\n console.warn('[waveform-playlist] Error disposing previous playout during rebuild:', err);\n }\n playout = null;\n }\n\n _playoutGeneration++;\n const generation = _playoutGeneration;\n\n playout = new TonePlayout({\n effects: options?.effects,\n });\n\n // If Tone.start() was already called (AudioContext resumed), carry\n // initialization forward. Tone.start() is safe to call multiple times —\n // it resolves immediately if the AudioContext is already running.\n if (_audioInitialized) {\n playout.init().catch((err) => {\n console.warn(\n '[waveform-playlist] Failed to re-initialize playout after rebuild. ' +\n 'Audio playback will require another user gesture.',\n err\n );\n _audioInitialized = false;\n });\n }\n\n for (const track of tracks) {\n const playableClips = track.clips.filter((c) => c.audioBuffer);\n if (playableClips.length === 0) continue;\n\n const startTime = Math.min(...playableClips.map(clipStartTime));\n const endTime = Math.max(...playableClips.map(clipEndTime));\n\n const trackObj: Track = {\n id: track.id,\n name: track.name,\n gain: track.volume,\n muted: track.muted,\n soloed: track.soloed,\n stereoPan: track.pan,\n startTime,\n endTime,\n };\n\n const clipInfos: ClipInfo[] = playableClips.map((clip) => ({\n buffer: clip.audioBuffer!,\n startTime: clipStartTime(clip) - startTime,\n duration: clipDurationTime(clip),\n offset: clipOffsetTime(clip),\n fadeIn: clip.fadeIn,\n fadeOut: clip.fadeOut,\n gain: clip.gain,\n }));\n\n playout.addTrack({\n clips: clipInfos,\n track: trackObj,\n effects: track.effects,\n });\n }\n\n playout.applyInitialSoloState();\n playout.setLoop(_loopEnabled, _loopStart, _loopEnd);\n\n playout.setOnPlaybackComplete(() => {\n if (generation === _playoutGeneration) {\n _isPlaying = false;\n }\n });\n }\n\n return {\n async init(): Promise<void> {\n if (playout) {\n await playout.init();\n _audioInitialized = true;\n }\n },\n\n setTracks(tracks: ClipTrack[]): void {\n buildPlayout(tracks);\n },\n\n play(startTime: number, endTime?: number): void {\n if (!playout) {\n console.warn(\n '[waveform-playlist] adapter.play() called but no playout is available. ' +\n 'Tracks may not have been set, or the adapter was disposed.'\n );\n return;\n }\n const duration = endTime !== undefined ? endTime - startTime : undefined;\n playout.play(now(), startTime, duration);\n // Only set _isPlaying if play() didn't throw\n // (TonePlayout.play() re-throws after cleanup on Transport failure)\n _isPlaying = true;\n },\n\n pause(): void {\n playout?.pause();\n _isPlaying = false;\n },\n\n stop(): void {\n playout?.stop();\n _isPlaying = false;\n },\n\n seek(time: number): void {\n playout?.seekTo(time);\n },\n\n getCurrentTime(): number {\n return playout?.getCurrentTime() ?? 0;\n },\n\n isPlaying(): boolean {\n return _isPlaying;\n },\n\n setMasterVolume(volume: number): void {\n playout?.setMasterGain(volume);\n },\n\n setTrackVolume(trackId: string, volume: number): void {\n playout?.getTrack(trackId)?.setVolume(volume);\n },\n\n setTrackMute(trackId: string, muted: boolean): void {\n playout?.setMute(trackId, muted);\n },\n\n setTrackSolo(trackId: string, soloed: boolean): void {\n playout?.setSolo(trackId, soloed);\n },\n\n setTrackPan(trackId: string, pan: number): void {\n playout?.getTrack(trackId)?.setPan(pan);\n },\n\n setLoop(enabled: boolean, start: number, end: number): void {\n _loopEnabled = enabled;\n _loopStart = start;\n _loopEnd = end;\n playout?.setLoop(enabled, start, end);\n },\n\n dispose(): void {\n try {\n playout?.dispose();\n } catch (err) {\n console.warn('[waveform-playlist] Error disposing playout:', err);\n }\n playout = null;\n _isPlaying = false;\n },\n };\n}\n"],"mappings":"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","names":["Volume","getDestination","getTransport","getContext","clipStartTime","Volume","getDestination","getTransport","getContext","getContext","now","start"]}
|
|
1
|
+
{"version":3,"sources":["../src/TonePlayout.ts","../src/ToneTrack.ts","../src/fades.ts","../src/audioContext.ts","../src/mediaStreamSourceManager.ts","../src/TonePlayoutAdapter.ts"],"sourcesContent":["import {\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\nexport type EffectsFunction = (\n masterGainNode: Volume,\n destination: ToneAudioNode,\n isOffline: boolean\n) => 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 _completionEventId: number | null = null;\n private _loopHandler: (() => void) | null = null;\n private _loopEnabled = false;\n private _loopStart = 0;\n private _loopEnd = 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 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 private clearCompletionEvent(): void {\n if (this._completionEventId !== null) {\n try {\n getTransport().clear(this._completionEventId);\n } catch (err) {\n console.warn('[waveform-playlist] Error clearing Transport completion event:', err);\n }\n this._completionEventId = null;\n }\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 this.manualMuteState.set(toneTrack.id, trackOptions.track.muted ?? false);\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 throw new Error('[waveform-playlist] TonePlayout not initialized. Call init() first.');\n }\n\n const startTime = when ?? now();\n const transport = getTransport();\n\n this.clearCompletionEvent();\n\n const transportOffset = offset ?? 0;\n this.tracks.forEach((track) => {\n track.cancelFades();\n track.prepareFades(startTime, transportOffset);\n });\n\n // Schedule duration-limited stop via Transport\n if (duration !== undefined) {\n this._completionEventId = transport.scheduleOnce(() => {\n this._completionEventId = null;\n try {\n this.onPlaybackCompleteCallback?.();\n } catch (err) {\n console.warn('[waveform-playlist] Error in playback completion callback:', err);\n }\n }, transportOffset + duration);\n }\n\n // Start Transport — triggers schedule() callbacks for clips at/after offset\n try {\n // Stop all active native sources before restarting to prevent layered audio.\n // Native AudioBufferSourceNodes don't respond to Transport state changes.\n if (transport.state !== 'stopped') {\n transport.stop();\n }\n this.tracks.forEach((track) => track.stopAllSources());\n\n // Set loop boundaries BEFORE enabling loop. _processTick checks\n // `ticks >= _loopEnd` every tick; _loopEnd defaults to 0.\n transport.loopStart = this._loopStart;\n transport.loopEnd = this._loopEnd;\n transport.loop = this._loopEnabled;\n\n // Set schedule guard BEFORE transport.start(). Ghost ticks from stale\n // Clock._lastUpdate can fire schedule callbacks at past positions;\n // the guard suppresses callbacks for clips before the play offset\n // (those are handled by startMidClipSources below).\n this.tracks.forEach((track) => track.setScheduleGuardOffset(transportOffset));\n\n if (offset !== undefined) {\n transport.start(startTime, offset);\n } else {\n transport.start(startTime);\n }\n\n // Advance Clock._lastUpdate past the stop/start boundary.\n //\n // After stop/start cycles, _lastUpdate is stale (set by the previous\n // context tick, before our stop/start). The next Clock._loop() processes\n // ticks from [_lastUpdate, now()]. In the gap [_lastUpdate, startTime),\n // the TickSource is still \"started\" from the PREVIOUS play cycle with\n // its old tick offset. Those accumulated ticks can exceed _loopEnd,\n // causing _processTick to wrap immediately to loopStart.\n //\n // By advancing _lastUpdate to startTime, we skip the stale range.\n // The next Clock._loop() only processes [startTime, now()] — ticks\n // from the current play cycle with the correct offset.\n (transport as any)._clock._lastUpdate = startTime;\n\n // Start sources for clips that span the current Transport position.\n // Transport.schedule() only fires for clips at/after the offset;\n // clips whose start time is before the offset need manual creation.\n this.tracks.forEach((track) => {\n track.startMidClipSources(transportOffset, startTime);\n });\n } catch (err) {\n // Clean up scheduled events since Transport failed to start\n this.clearCompletionEvent();\n this.tracks.forEach((track) => track.cancelFades());\n console.warn(\n '[waveform-playlist] Transport.start() failed. Audio playback could not begin.',\n err\n );\n throw err;\n }\n }\n\n pause(): void {\n const transport = getTransport();\n try {\n transport.pause();\n } catch (err) {\n console.warn('[waveform-playlist] Transport.pause() failed:', err);\n }\n // Native AudioBufferSourceNodes ignore Transport state changes —\n // they must be explicitly stopped.\n this.tracks.forEach((track) => track.stopAllSources());\n this.tracks.forEach((track) => track.cancelFades());\n this.clearCompletionEvent();\n }\n\n stop(): void {\n const transport = getTransport();\n try {\n transport.stop();\n } catch (err) {\n console.warn('[waveform-playlist] Transport.stop() failed:', err);\n }\n // Remove loop handler before stopping sources. Prevents any deferred\n // loop event from creating new sources via startMidClipSources() after\n // stopAllSources() runs. Defense-in-depth — setLoop(false) should\n // already have removed it, but stop() must be self-contained.\n if (this._loopHandler) {\n try {\n transport.off('loop', this._loopHandler);\n } catch (err) {\n console.warn('[waveform-playlist] Error removing loop handler:', err);\n }\n this._loopHandler = null;\n }\n // Stop all native sources explicitly\n this.tracks.forEach((track) => track.stopAllSources());\n this.tracks.forEach((track) => track.cancelFades());\n this.clearCompletionEvent();\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 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 (!this.soloedTracks.has(id)) {\n track.setMute(true);\n } else {\n const manuallyMuted = this.manualMuteState.get(id) ?? false;\n track.setMute(manuallyMuted);\n }\n } else {\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 this.manualMuteState.set(trackId, muted);\n track.setMute(muted);\n }\n }\n\n setLoop(enabled: boolean, loopStart: number, loopEnd: number): void {\n // Update cached state first — play() uses these values to configure\n // the Transport on next start, so they must reflect the caller's intent\n // regardless of whether Transport property setting succeeds.\n this._loopEnabled = enabled;\n this._loopStart = loopStart;\n this._loopEnd = loopEnd;\n\n const transport = getTransport();\n try {\n // Set boundaries BEFORE enabling loop. Tone.js's _processTick checks\n // `ticks >= _loopEnd` on every tick. If we set transport.loop = true\n // first, a tick could fire before loopEnd is updated, seeing the stale\n // _loopEnd value (0 from Transport default) and wrapping immediately.\n transport.loopStart = loopStart;\n transport.loopEnd = loopEnd;\n transport.loop = enabled;\n } catch (err) {\n console.warn('[waveform-playlist] Error configuring Transport loop:', err);\n return;\n }\n\n if (enabled && !this._loopHandler) {\n this._loopHandler = () => {\n // On loop boundary: stop old sources, re-schedule fades, start mid-clip sources.\n // Event ordering in Transport's tick processing (Tone.js 15.x _processTick):\n // loopEnd → ticks reset → loopStart → loop → forEachAtTime(ticks)\n // Our loop handler fires BEFORE schedule callbacks, so:\n // 1. stopAllSources + cancelFades — clean slate\n // 2. startMidClipSources — for clips spanning loopStart boundary\n // 3. prepareFades — fresh fade envelopes\n // Then Transport fires schedule callbacks for clips at/after loopStart.\n const currentTime = now();\n this.tracks.forEach((track) => {\n try {\n track.stopAllSources();\n track.cancelFades();\n track.setScheduleGuardOffset(this._loopStart);\n track.startMidClipSources(this._loopStart, currentTime);\n track.prepareFades(currentTime, this._loopStart);\n } catch (err) {\n console.warn(\n `[waveform-playlist] Error re-scheduling track \"${track.id}\" on loop:`,\n err\n );\n }\n });\n };\n transport.on('loop', this._loopHandler);\n } else if (!enabled && this._loopHandler) {\n transport.off('loop', this._loopHandler);\n this._loopHandler = null;\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.clearCompletionEvent();\n\n if (this._loopHandler) {\n try {\n getTransport().off('loop', this._loopHandler);\n } catch (err) {\n console.warn('[waveform-playlist] Error removing Transport loop handler:', err);\n }\n this._loopHandler = null;\n }\n\n this.tracks.forEach((track) => {\n try {\n track.dispose();\n } catch (err) {\n console.warn(`[waveform-playlist] Error disposing track \"${track.id}\":`, err);\n }\n });\n this.tracks.clear();\n\n if (this.effectsCleanup) {\n try {\n this.effectsCleanup();\n } catch (err) {\n console.warn('[waveform-playlist] Error during master effects cleanup:', err);\n }\n }\n\n try {\n this.masterVolume.dispose();\n } catch (err) {\n console.warn('[waveform-playlist] Error disposing master volume:', err);\n }\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","import {\n Volume,\n Gain,\n Panner,\n ToneAudioNode,\n getDestination,\n getTransport,\n getContext,\n} from 'tone';\nimport { Track, type Fade } from '@waveform-playlist/core';\nimport { applyFadeIn, applyFadeOut, getUnderlyingAudioParam } from './fades';\n\nexport type TrackEffectsFunction = (\n graphEnd: Gain,\n masterGainNode: ToneAudioNode,\n isOffline: boolean\n) => 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\n/** Per-clip scheduling info and audio nodes */\ninterface ScheduledClip {\n clipInfo: ClipInfo;\n fadeGainNode: GainNode; // Native GainNode for per-clip fade envelope\n scheduleId: number; // Transport.schedule() event ID\n}\n\nexport class ToneTrack {\n private scheduledClips: ScheduledClip[];\n private activeSources: Set<AudioBufferSourceNode> = new Set();\n private volumeNode: Volume;\n private panNode: Panner;\n private muteGain: Gain;\n private track: Track;\n private effectsCleanup?: () => void;\n // Guard against ghost tick schedule callbacks. After stop/start cycles with\n // loops, stale Clock._lastUpdate causes ticks from the previous cycle to fire\n // Transport.schedule() callbacks at past positions (e.g., time 0 clips fire\n // when starting at offset 5s). Clips before this offset are handled by\n // startMidClipSources(); schedule callbacks should only create sources for\n // clips at/after this offset.\n private _scheduleGuardOffset = 0;\n\n constructor(options: ToneTrackOptions) {\n this.track = options.track;\n\n // Create shared track-level Tone.js 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 // Chain shared Tone.js nodes: Volume → Pan → MuteGain\n this.volumeNode.chain(this.panNode, this.muteGain);\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[] =\n options.clips ||\n (options.buffer\n ? [\n {\n buffer: options.buffer,\n startTime: 0,\n duration: options.buffer.duration,\n offset: 0,\n fadeIn: options.track.fadeIn,\n fadeOut: options.track.fadeOut,\n gain: 1,\n },\n ]\n : []);\n\n const transport = getTransport();\n const rawContext = getContext().rawContext as AudioContext;\n\n // Get the native AudioNode input of the Volume for native→Tone connection.\n // Volume.input is a Tone.js Gain<\"decibels\"> whose .input is the native GainNode.\n // Cast through unknown since Gain<\"decibels\"> and Gain<\"gain\"> don't overlap.\n const volumeNativeInput = (this.volumeNode.input as unknown as Gain).input;\n\n // Schedule each clip via Transport.schedule() with native AudioBufferSourceNode\n this.scheduledClips = clipInfos.map((clipInfo) => {\n // Native GainNode for per-clip fade envelope — created once, reused across play cycles\n const fadeGainNode = rawContext.createGain();\n fadeGainNode.gain.value = clipInfo.gain;\n fadeGainNode.connect(volumeNativeInput);\n\n // Schedule a permanent Transport event at the clip's absolute timeline position.\n // This callback fires on every play and every loop iteration when Transport\n // passes this point.\n const absTransportTime = this.track.startTime + clipInfo.startTime;\n const scheduleId = transport.schedule((audioContextTime: number) => {\n // Guard: ghost ticks from stale Clock._lastUpdate can fire this callback\n // at past positions (see Tone.js #1419). Clips before the play/loop offset\n // are already handled by startMidClipSources().\n if (absTransportTime < this._scheduleGuardOffset) {\n return;\n }\n this.startClipSource(clipInfo, fadeGainNode, audioContextTime);\n }, absTransportTime);\n\n return { clipInfo, fadeGainNode, scheduleId };\n });\n }\n\n /**\n * Create and start an AudioBufferSourceNode for a clip.\n * Sources are one-shot: each play or loop iteration creates a fresh one.\n */\n private startClipSource(\n clipInfo: ClipInfo,\n fadeGainNode: GainNode,\n audioContextTime: number,\n bufferOffset?: number,\n playDuration?: number\n ): void {\n const rawContext = getContext().rawContext as AudioContext;\n const source = rawContext.createBufferSource();\n source.buffer = clipInfo.buffer;\n source.connect(fadeGainNode);\n\n const offset = bufferOffset ?? clipInfo.offset;\n const duration = playDuration ?? clipInfo.duration;\n\n try {\n source.start(audioContextTime, offset, duration);\n } catch (err) {\n console.warn(\n `[waveform-playlist] Failed to start source on track \"${this.id}\" ` +\n `(time=${audioContextTime}, offset=${offset}, duration=${duration}):`,\n err\n );\n source.disconnect();\n return;\n }\n\n this.activeSources.add(source);\n source.onended = () => {\n this.activeSources.delete(source);\n };\n }\n\n /**\n * Set the schedule guard offset. Schedule callbacks for clips before this\n * offset are suppressed (already handled by startMidClipSources).\n * Must be called before transport.start() and in the loop handler.\n */\n setScheduleGuardOffset(offset: number): void {\n this._scheduleGuardOffset = offset;\n }\n\n /**\n * Start sources for clips that span the given Transport position.\n * Used for mid-playback seeking and loop boundary handling where\n * Transport.schedule() callbacks have already passed.\n *\n * Uses strict < for absClipStart to avoid double-creation with\n * schedule callbacks at exact Transport position (e.g., loopStart).\n */\n startMidClipSources(transportOffset: number, audioContextTime: number): void {\n for (const { clipInfo, fadeGainNode } of this.scheduledClips) {\n const absClipStart = this.track.startTime + clipInfo.startTime;\n const absClipEnd = absClipStart + clipInfo.duration;\n\n // Only handle clips that started before the transport position\n // but haven't ended yet (i.e., the transport is \"inside\" the clip)\n if (absClipStart < transportOffset && absClipEnd > transportOffset) {\n const elapsed = transportOffset - absClipStart;\n const adjustedOffset = clipInfo.offset + elapsed;\n const remainingDuration = clipInfo.duration - elapsed;\n this.startClipSource(\n clipInfo,\n fadeGainNode,\n audioContextTime,\n adjustedOffset,\n remainingDuration\n );\n }\n }\n }\n\n /**\n * Stop all active AudioBufferSourceNodes and clear the set.\n * Native AudioBufferSourceNodes ignore Transport state changes —\n * they must be explicitly stopped.\n */\n stopAllSources(): void {\n this.activeSources.forEach((source) => {\n try {\n source.stop();\n } catch (err) {\n console.warn(`[waveform-playlist] Error stopping source on track \"${this.id}\":`, err);\n }\n });\n this.activeSources.clear();\n }\n\n /**\n * Schedule fade envelopes for a clip at the given AudioContext time.\n * Uses native GainNode.gain (AudioParam) directly — no _param workaround needed.\n */\n private scheduleFades(\n scheduled: ScheduledClip,\n clipStartTime: number,\n clipOffset: number = 0\n ): void {\n const { clipInfo, fadeGainNode } = scheduled;\n const audioParam = fadeGainNode.gain;\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 applyFadeIn(\n audioParam,\n clipStartTime,\n fadeInDuration,\n clipInfo.fadeIn.type || 'linear',\n 0,\n clipInfo.gain\n );\n } else {\n const remainingFadeDuration = fadeInDuration - skipTime;\n const fadeProgress = skipTime / fadeInDuration;\n const startValue = clipInfo.gain * fadeProgress;\n applyFadeIn(\n audioParam,\n clipStartTime,\n remainingFadeDuration,\n clipInfo.fadeIn.type || 'linear',\n startValue,\n clipInfo.gain\n );\n }\n } else {\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;\n\n if (fadeOutStartInClip > 0) {\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 const elapsedFadeOut = -fadeOutStartInClip;\n const remainingFadeDuration = clipInfo.fadeOut.duration - elapsedFadeOut;\n const fadeProgress = elapsedFadeOut / clipInfo.fadeOut.duration;\n const startValue = clipInfo.gain * (1 - fadeProgress);\n applyFadeOut(\n audioParam,\n clipStartTime,\n remainingFadeDuration,\n clipInfo.fadeOut.type || 'linear',\n startValue,\n 0\n );\n }\n }\n }\n\n /**\n * Prepare fade envelopes for all clips based on Transport offset.\n * Called before Transport.start() to schedule fades at correct AudioContext times.\n */\n prepareFades(when: number, transportOffset: number): void {\n this.scheduledClips.forEach((scheduled) => {\n const absClipStart = this.track.startTime + scheduled.clipInfo.startTime;\n const absClipEnd = absClipStart + scheduled.clipInfo.duration;\n\n if (transportOffset >= absClipEnd) return; // clip already finished\n\n if (transportOffset >= absClipStart) {\n // Mid-clip: playing now\n const clipOffset = transportOffset - absClipStart + scheduled.clipInfo.offset;\n this.scheduleFades(scheduled, when, clipOffset);\n } else {\n // Clip starts later\n const delay = absClipStart - transportOffset;\n this.scheduleFades(scheduled, when + delay, scheduled.clipInfo.offset);\n }\n });\n }\n\n /**\n * Cancel all scheduled fade automation and reset to nominal gain.\n * Called on pause/stop to prevent stale fade envelopes.\n */\n cancelFades(): void {\n this.scheduledClips.forEach(({ fadeGainNode, clipInfo }) => {\n const audioParam = fadeGainNode.gain;\n audioParam.cancelScheduledValues(0);\n audioParam.setValueAtTime(clipInfo.gain, 0);\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 const value = muted ? 0 : 1;\n // Use setValueAtTime on the raw AudioParam to ensure the value is applied\n // even when the AudioContext is suspended. Setting .gain.value on the Tone.js\n // Signal wrapper doesn't propagate to the underlying AudioParam until the\n // context resumes, causing a brief audio glitch (e.g., all tracks audible\n // before solo muting takes effect).\n const audioParam = getUnderlyingAudioParam(this.muteGain.gain);\n audioParam?.setValueAtTime(value, 0);\n this.muteGain.gain.value = value;\n }\n\n setSolo(soloed: boolean): void {\n this.track.soloed = soloed;\n }\n\n dispose(): void {\n const transport = getTransport();\n\n if (this.effectsCleanup) {\n try {\n this.effectsCleanup();\n } catch (err) {\n console.warn(`[waveform-playlist] Error during track \"${this.id}\" effects cleanup:`, err);\n }\n }\n\n this.stopAllSources();\n\n // Clear Transport schedule events and disconnect native fade gain nodes\n this.scheduledClips.forEach((scheduled, index) => {\n try {\n transport.clear(scheduled.scheduleId);\n } catch (err) {\n console.warn(\n `[waveform-playlist] Error clearing schedule ${index} on track \"${this.id}\":`,\n err\n );\n }\n try {\n scheduled.fadeGainNode.disconnect();\n } catch (err) {\n console.warn(\n `[waveform-playlist] Error disconnecting fadeGain ${index} on track \"${this.id}\":`,\n err\n );\n }\n });\n\n try {\n this.volumeNode.dispose();\n } catch (err) {\n console.warn(`[waveform-playlist] Error disposing volumeNode on track \"${this.id}\":`, err);\n }\n try {\n this.panNode.dispose();\n } catch (err) {\n console.warn(`[waveform-playlist] Error disposing panNode on track \"${this.id}\":`, err);\n }\n try {\n this.muteGain.dispose();\n } catch (err) {\n console.warn(`[waveform-playlist] Error disposing muteGain on track \"${this.id}\":`, err);\n }\n }\n\n get id(): string {\n return this.track.id;\n }\n\n get duration(): number {\n if (this.scheduledClips.length === 0) return 0;\n const lastClip = this.scheduledClips[this.scheduledClips.length - 1];\n return lastClip.clipInfo.startTime + lastClip.clipInfo.duration;\n }\n\n get buffer(): AudioBuffer {\n return this.scheduledClips[0]?.clipInfo.buffer;\n }\n\n get muted(): boolean {\n return this.track.muted;\n }\n\n get startTime(): number {\n return this.track.startTime;\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\n/**\n * Access the underlying Web Audio AudioParam from a Tone.js Signal/Param wrapper.\n *\n * Tone.js wraps native AudioParam in its Signal class, but sometimes we need\n * direct access to the raw AudioParam for setValueAtTime/cancelScheduledValues\n * (e.g., when the AudioContext is suspended and Tone.js Signal doesn't propagate).\n *\n * This uses `_param` which is a private Tone.js 15.x internal.\n * Pin the Tone.js version carefully if upgrading.\n *\n * @param signal - A Tone.js Signal or Param wrapper (e.g., `gain.gain`)\n * @returns The underlying AudioParam, or undefined if not found\n */\nlet hasWarned = false;\n\nexport function getUnderlyingAudioParam(signal: unknown): AudioParam | undefined {\n const param = (signal as { _param?: AudioParam })._param;\n if (!param && !hasWarned) {\n hasWarned = true;\n console.warn(\n '[waveform-playlist] Unable to access Tone.js internal _param. ' +\n 'This likely means the Tone.js version is incompatible. ' +\n 'Mute scheduling may not work correctly.'\n );\n }\n return param;\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(stream: MediaStream): 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","import type { ClipTrack, Track } from '@waveform-playlist/core';\nimport {\n clipStartTime,\n clipEndTime,\n clipOffsetTime,\n clipDurationTime,\n} from '@waveform-playlist/core';\nimport type { PlayoutAdapter } from '@waveform-playlist/engine';\nimport { TonePlayout } from './TonePlayout';\nimport type { EffectsFunction } from './TonePlayout';\nimport type { ClipInfo } from './ToneTrack';\nimport { now } from 'tone';\n\nexport interface ToneAdapterOptions {\n effects?: EffectsFunction;\n}\n\nexport function createToneAdapter(options?: ToneAdapterOptions): PlayoutAdapter {\n let playout: TonePlayout | null = null;\n let _isPlaying = false;\n let _playoutGeneration = 0;\n let _loopEnabled = false;\n let _loopStart = 0;\n let _loopEnd = 0;\n let _audioInitialized = false;\n\n function buildPlayout(tracks: ClipTrack[]): void {\n if (playout) {\n try {\n playout.dispose();\n } catch (err) {\n console.warn('[waveform-playlist] Error disposing previous playout during rebuild:', err);\n }\n playout = null;\n }\n\n _playoutGeneration++;\n const generation = _playoutGeneration;\n\n playout = new TonePlayout({\n effects: options?.effects,\n });\n\n // If Tone.start() was already called (AudioContext resumed), carry\n // initialization forward. Tone.start() is safe to call multiple times —\n // it resolves immediately if the AudioContext is already running.\n if (_audioInitialized) {\n playout.init().catch((err) => {\n console.warn(\n '[waveform-playlist] Failed to re-initialize playout after rebuild. ' +\n 'Audio playback will require another user gesture.',\n err\n );\n _audioInitialized = false;\n });\n }\n\n for (const track of tracks) {\n const playableClips = track.clips.filter((c) => c.audioBuffer);\n if (playableClips.length === 0) continue;\n\n const startTime = Math.min(...playableClips.map(clipStartTime));\n const endTime = Math.max(...playableClips.map(clipEndTime));\n\n const trackObj: Track = {\n id: track.id,\n name: track.name,\n gain: track.volume,\n muted: track.muted,\n soloed: track.soloed,\n stereoPan: track.pan,\n startTime,\n endTime,\n };\n\n const clipInfos: ClipInfo[] = playableClips.map((clip) => ({\n buffer: clip.audioBuffer!,\n startTime: clipStartTime(clip) - startTime,\n duration: clipDurationTime(clip),\n offset: clipOffsetTime(clip),\n fadeIn: clip.fadeIn,\n fadeOut: clip.fadeOut,\n gain: clip.gain,\n }));\n\n playout.addTrack({\n clips: clipInfos,\n track: trackObj,\n effects: track.effects,\n });\n }\n\n playout.applyInitialSoloState();\n playout.setLoop(_loopEnabled, _loopStart, _loopEnd);\n\n playout.setOnPlaybackComplete(() => {\n if (generation === _playoutGeneration) {\n _isPlaying = false;\n }\n });\n }\n\n return {\n async init(): Promise<void> {\n if (playout) {\n await playout.init();\n _audioInitialized = true;\n }\n },\n\n setTracks(tracks: ClipTrack[]): void {\n buildPlayout(tracks);\n },\n\n play(startTime: number, endTime?: number): void {\n if (!playout) {\n console.warn(\n '[waveform-playlist] adapter.play() called but no playout is available. ' +\n 'Tracks may not have been set, or the adapter was disposed.'\n );\n return;\n }\n const duration = endTime !== undefined ? endTime - startTime : undefined;\n playout.play(now(), startTime, duration);\n // Only set _isPlaying if play() didn't throw\n // (TonePlayout.play() re-throws after cleanup on Transport failure)\n _isPlaying = true;\n },\n\n pause(): void {\n playout?.pause();\n _isPlaying = false;\n },\n\n stop(): void {\n playout?.stop();\n _isPlaying = false;\n },\n\n seek(time: number): void {\n playout?.seekTo(time);\n },\n\n getCurrentTime(): number {\n return playout?.getCurrentTime() ?? 0;\n },\n\n isPlaying(): boolean {\n return _isPlaying;\n },\n\n setMasterVolume(volume: number): void {\n playout?.setMasterGain(volume);\n },\n\n setTrackVolume(trackId: string, volume: number): void {\n playout?.getTrack(trackId)?.setVolume(volume);\n },\n\n setTrackMute(trackId: string, muted: boolean): void {\n playout?.setMute(trackId, muted);\n },\n\n setTrackSolo(trackId: string, soloed: boolean): void {\n playout?.setSolo(trackId, soloed);\n },\n\n setTrackPan(trackId: string, pan: number): void {\n playout?.getTrack(trackId)?.setPan(pan);\n },\n\n setLoop(enabled: boolean, start: number, end: number): void {\n _loopEnabled = enabled;\n _loopStart = start;\n _loopEnd = end;\n playout?.setLoop(enabled, start, end);\n },\n\n dispose(): void {\n try {\n playout?.dispose();\n } catch (err) {\n console.warn('[waveform-playlist] Error disposing playout:', err);\n }\n playout = null;\n _isPlaying = false;\n },\n };\n}\n"],"mappings":"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","names":["Volume","getDestination","getTransport","getContext","clipStartTime","Volume","getDestination","getTransport","getContext","getContext","now","start"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@waveform-playlist/playout",
|
|
3
|
-
"version": "9.0.
|
|
3
|
+
"version": "9.0.3",
|
|
4
4
|
"description": "Playout engine for waveform-playlist using Tone.js",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -39,10 +39,10 @@
|
|
|
39
39
|
"tsup": "^8.0.1",
|
|
40
40
|
"typescript": "^5.3.3",
|
|
41
41
|
"vitest": "^3.0.0",
|
|
42
|
-
"@waveform-playlist/engine": "^9.0.
|
|
42
|
+
"@waveform-playlist/engine": "^9.0.3"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@waveform-playlist/core": "9.0.
|
|
45
|
+
"@waveform-playlist/core": "9.0.3"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"tone": "^15.0.0",
|