@waveform-playlist/playout 7.1.2 → 7.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Gain, ToneAudioNode, Volume, BaseContext, Context } from 'tone';
2
2
  import { Fade, Track } from '@waveform-playlist/core';
3
+ import { PlayoutAdapter } from '@waveform-playlist/engine';
3
4
 
4
5
  type TrackEffectsFunction = (graphEnd: Gain, masterGainNode: ToneAudioNode, isOffline: boolean) => void | (() => void);
5
6
  interface ClipInfo {
@@ -227,4 +228,9 @@ declare function applyFadeIn(param: AudioParam, startTime: number, duration: num
227
228
  */
228
229
  declare function applyFadeOut(param: AudioParam, startTime: number, duration: number, type?: FadeType, startValue?: number, endValue?: number): void;
229
230
 
230
- export { type EffectsFunction, type FadeConfig, type FadeType, TonePlayout, type TonePlayoutOptions, ToneTrack, type ToneTrackOptions, type TrackEffectsFunction, applyFadeIn, applyFadeOut, closeGlobalAudioContext, getGlobalAudioContext, getGlobalAudioContextState, getGlobalContext, getGlobalToneContext, getMediaStreamSource, getUnderlyingAudioParam, hasMediaStreamSource, releaseMediaStreamSource, resumeGlobalAudioContext };
231
+ interface ToneAdapterOptions {
232
+ effects?: EffectsFunction;
233
+ }
234
+ declare function createToneAdapter(options?: ToneAdapterOptions): PlayoutAdapter;
235
+
236
+ export { type EffectsFunction, type FadeConfig, type FadeType, type ToneAdapterOptions, TonePlayout, type TonePlayoutOptions, ToneTrack, type ToneTrackOptions, type TrackEffectsFunction, applyFadeIn, applyFadeOut, closeGlobalAudioContext, createToneAdapter, getGlobalAudioContext, getGlobalAudioContextState, getGlobalContext, getGlobalToneContext, getMediaStreamSource, getUnderlyingAudioParam, hasMediaStreamSource, releaseMediaStreamSource, resumeGlobalAudioContext };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Gain, ToneAudioNode, Volume, BaseContext, Context } from 'tone';
2
2
  import { Fade, Track } from '@waveform-playlist/core';
3
+ import { PlayoutAdapter } from '@waveform-playlist/engine';
3
4
 
4
5
  type TrackEffectsFunction = (graphEnd: Gain, masterGainNode: ToneAudioNode, isOffline: boolean) => void | (() => void);
5
6
  interface ClipInfo {
@@ -227,4 +228,9 @@ declare function applyFadeIn(param: AudioParam, startTime: number, duration: num
227
228
  */
228
229
  declare function applyFadeOut(param: AudioParam, startTime: number, duration: number, type?: FadeType, startValue?: number, endValue?: number): void;
229
230
 
230
- export { type EffectsFunction, type FadeConfig, type FadeType, TonePlayout, type TonePlayoutOptions, ToneTrack, type ToneTrackOptions, type TrackEffectsFunction, applyFadeIn, applyFadeOut, closeGlobalAudioContext, getGlobalAudioContext, getGlobalAudioContextState, getGlobalContext, getGlobalToneContext, getMediaStreamSource, getUnderlyingAudioParam, hasMediaStreamSource, releaseMediaStreamSource, resumeGlobalAudioContext };
231
+ interface ToneAdapterOptions {
232
+ effects?: EffectsFunction;
233
+ }
234
+ declare function createToneAdapter(options?: ToneAdapterOptions): PlayoutAdapter;
235
+
236
+ export { type EffectsFunction, type FadeConfig, type FadeType, type ToneAdapterOptions, TonePlayout, type TonePlayoutOptions, ToneTrack, type ToneTrackOptions, type TrackEffectsFunction, applyFadeIn, applyFadeOut, closeGlobalAudioContext, createToneAdapter, getGlobalAudioContext, getGlobalAudioContextState, getGlobalContext, getGlobalToneContext, getMediaStreamSource, getUnderlyingAudioParam, hasMediaStreamSource, releaseMediaStreamSource, resumeGlobalAudioContext };
package/dist/index.js CHANGED
@@ -25,6 +25,7 @@ __export(index_exports, {
25
25
  applyFadeIn: () => applyFadeIn,
26
26
  applyFadeOut: () => applyFadeOut,
27
27
  closeGlobalAudioContext: () => closeGlobalAudioContext,
28
+ createToneAdapter: () => createToneAdapter,
28
29
  getGlobalAudioContext: () => getGlobalAudioContext,
29
30
  getGlobalAudioContextState: () => getGlobalAudioContextState,
30
31
  getGlobalContext: () => getGlobalContext,
@@ -160,17 +161,19 @@ var ToneTrack = class {
160
161
  } else {
161
162
  this.muteGain.connect(destination);
162
163
  }
163
- const clipInfos = options.clips || (options.buffer ? [{
164
- buffer: options.buffer,
165
- startTime: 0,
166
- // Legacy: single buffer starts at timeline position 0
167
- duration: options.buffer.duration,
168
- // Legacy: play full buffer duration
169
- offset: 0,
170
- fadeIn: options.track.fadeIn,
171
- fadeOut: options.track.fadeOut,
172
- gain: 1
173
- }] : []);
164
+ const clipInfos = options.clips || (options.buffer ? [
165
+ {
166
+ buffer: options.buffer,
167
+ startTime: 0,
168
+ // Legacy: single buffer starts at timeline position 0
169
+ duration: options.buffer.duration,
170
+ // Legacy: play full buffer duration
171
+ offset: 0,
172
+ fadeIn: options.track.fadeIn,
173
+ fadeOut: options.track.fadeOut,
174
+ gain: 1
175
+ }
176
+ ] : []);
174
177
  this.clips = clipInfos.map((clipInfo) => {
175
178
  const player = new import_tone.Player({
176
179
  url: clipInfo.buffer,
@@ -197,7 +200,7 @@ var ToneTrack = class {
197
200
  /**
198
201
  * Schedule fade envelopes for a clip at the given start time
199
202
  */
200
- scheduleFades(clipPlayer, clipStartTime, clipOffset = 0) {
203
+ scheduleFades(clipPlayer, clipStartTime2, clipOffset = 0) {
201
204
  const { clipInfo, fadeGain } = clipPlayer;
202
205
  const audioParam = getUnderlyingAudioParam(fadeGain.gain);
203
206
  if (!audioParam) return;
@@ -208,7 +211,7 @@ var ToneTrack = class {
208
211
  if (skipTime <= 0) {
209
212
  applyFadeIn(
210
213
  audioParam,
211
- clipStartTime,
214
+ clipStartTime2,
212
215
  fadeInDuration,
213
216
  clipInfo.fadeIn.type || "linear",
214
217
  0,
@@ -220,7 +223,7 @@ var ToneTrack = class {
220
223
  const startValue = clipInfo.gain * fadeProgress;
221
224
  applyFadeIn(
222
225
  audioParam,
223
- clipStartTime,
226
+ clipStartTime2,
224
227
  remainingFadeDuration,
225
228
  clipInfo.fadeIn.type || "linear",
226
229
  startValue,
@@ -228,13 +231,13 @@ var ToneTrack = class {
228
231
  );
229
232
  }
230
233
  } else {
231
- audioParam.setValueAtTime(clipInfo.gain, clipStartTime);
234
+ audioParam.setValueAtTime(clipInfo.gain, clipStartTime2);
232
235
  }
233
236
  if (clipInfo.fadeOut) {
234
237
  const fadeOutStart = clipInfo.duration - clipInfo.fadeOut.duration;
235
238
  const fadeOutStartInClip = fadeOutStart - skipTime;
236
239
  if (fadeOutStartInClip > 0) {
237
- const absoluteFadeOutStart = clipStartTime + fadeOutStartInClip;
240
+ const absoluteFadeOutStart = clipStartTime2 + fadeOutStartInClip;
238
241
  applyFadeOut(
239
242
  audioParam,
240
243
  absoluteFadeOutStart,
@@ -250,7 +253,7 @@ var ToneTrack = class {
250
253
  const startValue = clipInfo.gain * (1 - fadeProgress);
251
254
  applyFadeOut(
252
255
  audioParam,
253
- clipStartTime,
256
+ clipStartTime2,
254
257
  remainingFadeDuration,
255
258
  clipInfo.fadeOut.type || "linear",
256
259
  startValue,
@@ -640,6 +643,115 @@ function releaseMediaStreamSource(stream) {
640
643
  function hasMediaStreamSource(stream) {
641
644
  return streamSources.has(stream);
642
645
  }
646
+
647
+ // src/TonePlayoutAdapter.ts
648
+ var import_core = require("@waveform-playlist/core");
649
+ var import_tone5 = require("tone");
650
+ function createToneAdapter(options) {
651
+ let playout = null;
652
+ let _isPlaying = false;
653
+ let _playoutGeneration = 0;
654
+ function buildPlayout(tracks) {
655
+ if (playout) {
656
+ playout.dispose();
657
+ }
658
+ _playoutGeneration++;
659
+ const generation = _playoutGeneration;
660
+ playout = new TonePlayout({
661
+ effects: options?.effects
662
+ });
663
+ for (const track of tracks) {
664
+ const playableClips = track.clips.filter((c) => c.audioBuffer);
665
+ if (playableClips.length === 0) continue;
666
+ const startTime = Math.min(...playableClips.map(import_core.clipStartTime));
667
+ const endTime = Math.max(...playableClips.map(import_core.clipEndTime));
668
+ const trackObj = {
669
+ id: track.id,
670
+ name: track.name,
671
+ gain: track.volume,
672
+ muted: track.muted,
673
+ soloed: track.soloed,
674
+ stereoPan: track.pan,
675
+ startTime,
676
+ endTime
677
+ };
678
+ const clipInfos = playableClips.map((clip) => ({
679
+ buffer: clip.audioBuffer,
680
+ startTime: (0, import_core.clipStartTime)(clip) - startTime,
681
+ duration: (0, import_core.clipDurationTime)(clip),
682
+ offset: (0, import_core.clipOffsetTime)(clip),
683
+ fadeIn: clip.fadeIn,
684
+ fadeOut: clip.fadeOut,
685
+ gain: clip.gain
686
+ }));
687
+ playout.addTrack({
688
+ clips: clipInfos,
689
+ track: trackObj,
690
+ effects: track.effects
691
+ });
692
+ }
693
+ playout.applyInitialSoloState();
694
+ playout.setOnPlaybackComplete(() => {
695
+ if (generation === _playoutGeneration) {
696
+ _isPlaying = false;
697
+ }
698
+ });
699
+ }
700
+ return {
701
+ async init() {
702
+ if (playout) {
703
+ await playout.init();
704
+ }
705
+ },
706
+ setTracks(tracks) {
707
+ buildPlayout(tracks);
708
+ },
709
+ async play(startTime, endTime) {
710
+ if (!playout) return;
711
+ await playout.init();
712
+ const duration = endTime !== void 0 ? endTime - startTime : void 0;
713
+ playout.play((0, import_tone5.now)(), startTime, duration);
714
+ _isPlaying = true;
715
+ },
716
+ pause() {
717
+ playout?.pause();
718
+ _isPlaying = false;
719
+ },
720
+ stop() {
721
+ playout?.stop();
722
+ _isPlaying = false;
723
+ },
724
+ seek(time) {
725
+ playout?.seekTo(time);
726
+ },
727
+ getCurrentTime() {
728
+ return playout?.getCurrentTime() ?? 0;
729
+ },
730
+ isPlaying() {
731
+ return _isPlaying;
732
+ },
733
+ setMasterVolume(volume) {
734
+ playout?.setMasterGain(volume);
735
+ },
736
+ setTrackVolume(trackId, volume) {
737
+ playout?.getTrack(trackId)?.setVolume(volume);
738
+ },
739
+ setTrackMute(trackId, muted) {
740
+ playout?.setMute(trackId, muted);
741
+ },
742
+ setTrackSolo(trackId, soloed) {
743
+ playout?.setSolo(trackId, soloed);
744
+ },
745
+ setTrackPan(trackId, pan) {
746
+ playout?.getTrack(trackId)?.setPan(pan);
747
+ },
748
+ dispose() {
749
+ playout?.dispose();
750
+ playout = null;
751
+ _isPlaying = false;
752
+ }
753
+ };
754
+ }
643
755
  // Annotate the CommonJS export names for ESM import in node:
644
756
  0 && (module.exports = {
645
757
  TonePlayout,
@@ -647,6 +759,7 @@ function hasMediaStreamSource(stream) {
647
759
  applyFadeIn,
648
760
  applyFadeOut,
649
761
  closeGlobalAudioContext,
762
+ createToneAdapter,
650
763
  getGlobalAudioContext,
651
764
  getGlobalAudioContextState,
652
765
  getGlobalContext,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/TonePlayout.ts","../src/ToneTrack.ts","../src/fades.ts","../src/audioContext.ts","../src/mediaStreamSourceManager.ts"],"sourcesContent":["export { TonePlayout } from './TonePlayout';\nexport { ToneTrack } from './ToneTrack';\nexport type { TonePlayoutOptions, EffectsFunction } from './TonePlayout';\nexport type { ToneTrackOptions, TrackEffectsFunction } from './ToneTrack';\n\n// Export global AudioContext manager\nexport {\n getGlobalContext,\n getGlobalAudioContext,\n getGlobalToneContext,\n resumeGlobalAudioContext,\n getGlobalAudioContextState,\n closeGlobalAudioContext,\n} from './audioContext';\n\n// Export MediaStreamSource manager\nexport {\n getMediaStreamSource,\n releaseMediaStreamSource,\n hasMediaStreamSource,\n} from './mediaStreamSourceManager';\n\n// Export fade utilities\nexport {\n applyFadeIn,\n applyFadeOut,\n getUnderlyingAudioParam,\n type FadeConfig,\n type FadeType,\n} from './fades';\n","// Named imports for tree-shaking\nimport {\n Volume,\n ToneAudioNode,\n getDestination,\n start,\n now,\n getTransport,\n getContext,\n BaseContext,\n} from 'tone';\nimport { ToneTrack, ToneTrackOptions } from './ToneTrack';\n\n// Effects function no longer receives ToneLib - effects should import Tone themselves\nexport type EffectsFunction = (masterGainNode: Volume, destination: ToneAudioNode, isOffline: boolean) => void | (() => void);\n\nexport interface TonePlayoutOptions {\n tracks?: ToneTrack[];\n masterGain?: number;\n effects?: EffectsFunction;\n}\n\nexport class TonePlayout {\n private tracks: Map<string, ToneTrack> = new Map();\n private masterVolume: Volume;\n private isInitialized = false;\n private soloedTracks: Set<string> = new Set();\n private manualMuteState: Map<string, boolean> = new Map();\n private effectsCleanup?: () => void;\n private onPlaybackCompleteCallback?: () => void;\n private activeTracks: Map<string, number> = new Map(); // Map track ID to session ID\n private playbackSessionId: number = 0;\n\n constructor(options: TonePlayoutOptions = {}) {\n this.masterVolume = new Volume(this.gainToDb(options.masterGain ?? 1));\n\n // Setup effects chain if provided, otherwise connect directly to destination\n if (options.effects) {\n const cleanup = options.effects(this.masterVolume, getDestination(), false);\n if (cleanup) {\n this.effectsCleanup = cleanup;\n }\n } else {\n this.masterVolume.toDestination();\n }\n\n if (options.tracks) {\n options.tracks.forEach(track => {\n this.tracks.set(track.id, track);\n // Initialize manual mute state for constructor-provided tracks\n this.manualMuteState.set(track.id, track.muted);\n });\n }\n }\n\n private gainToDb(gain: number): number {\n return 20 * Math.log10(gain);\n }\n\n async init(): Promise<void> {\n if (this.isInitialized) return;\n\n await start();\n this.isInitialized = true;\n }\n\n addTrack(trackOptions: ToneTrackOptions): ToneTrack {\n // Ensure tracks connect to master volume instead of destination\n const optionsWithDestination = {\n ...trackOptions,\n destination: this.masterVolume,\n };\n const toneTrack = new ToneTrack(optionsWithDestination);\n this.tracks.set(toneTrack.id, toneTrack);\n // Initialize manual mute state from track options\n this.manualMuteState.set(toneTrack.id, trackOptions.track.muted ?? false);\n // Initialize solo state from track options\n if (trackOptions.track.soloed) {\n this.soloedTracks.add(toneTrack.id);\n }\n return toneTrack;\n }\n\n /**\n * Apply solo muting after all tracks have been added.\n * Call this after adding all tracks to ensure solo logic is applied correctly.\n */\n applyInitialSoloState(): void {\n this.updateSoloMuting();\n }\n\n removeTrack(trackId: string): void {\n const track = this.tracks.get(trackId);\n if (track) {\n track.dispose();\n this.tracks.delete(trackId);\n this.manualMuteState.delete(trackId);\n this.soloedTracks.delete(trackId);\n }\n }\n\n getTrack(trackId: string): ToneTrack | undefined {\n return this.tracks.get(trackId);\n }\n\n play(when?: number, offset?: number, duration?: number): void {\n if (!this.isInitialized) {\n console.warn('TonePlayout not initialized. Call init() first.');\n return;\n }\n\n // Use now() as default, but call it here after init check (not in function signature)\n const startTime = when ?? now();\n const playbackPosition = offset ?? 0;\n\n // Increment session ID to invalidate old callbacks\n this.playbackSessionId++;\n const currentSessionId = this.playbackSessionId;\n\n // Clear active tracks and set up stop callbacks if duration is specified\n this.activeTracks.clear();\n\n // Play tracks based on their individual start times\n this.tracks.forEach((toneTrack) => {\n const trackStartTime = toneTrack.startTime;\n\n if (playbackPosition >= trackStartTime) {\n // Track should be playing - calculate buffer offset and start immediately\n const bufferOffset = playbackPosition - trackStartTime;\n\n if (duration !== undefined) {\n this.activeTracks.set(toneTrack.id, currentSessionId);\n toneTrack.setOnStopCallback(() => {\n // Only process if this track is still in activeTracks with matching session ID\n if (this.activeTracks.get(toneTrack.id) === currentSessionId) {\n this.activeTracks.delete(toneTrack.id);\n if (this.activeTracks.size === 0 && this.onPlaybackCompleteCallback) {\n this.onPlaybackCompleteCallback();\n }\n }\n });\n }\n\n toneTrack.play(startTime, bufferOffset, duration);\n } else {\n // Track should start later - schedule it to start when playback reaches its start time\n const delay = trackStartTime - playbackPosition;\n\n if (duration !== undefined) {\n this.activeTracks.set(toneTrack.id, currentSessionId);\n toneTrack.setOnStopCallback(() => {\n // Only process if this track is still in activeTracks with matching session ID\n if (this.activeTracks.get(toneTrack.id) === currentSessionId) {\n this.activeTracks.delete(toneTrack.id);\n if (this.activeTracks.size === 0 && this.onPlaybackCompleteCallback) {\n this.onPlaybackCompleteCallback();\n }\n }\n });\n }\n\n toneTrack.play(startTime + delay, 0, duration);\n }\n });\n\n // Start transport\n if (offset !== undefined) {\n // Explicit offset provided - seek to that position\n getTransport().start(startTime, offset);\n } else {\n // No offset - resume from pause (Transport resumes from current position)\n getTransport().start(startTime);\n }\n }\n\n pause(): void {\n getTransport().pause();\n this.tracks.forEach(track => {\n track.pause();\n });\n }\n\n stop(): void {\n getTransport().stop();\n this.tracks.forEach(track => {\n track.stop();\n });\n }\n\n setMasterGain(gain: number): void {\n this.masterVolume.volume.value = this.gainToDb(gain);\n }\n\n setSolo(trackId: string, soloed: boolean): void {\n const track = this.tracks.get(trackId);\n if (track) {\n track.setSolo(soloed);\n if (soloed) {\n this.soloedTracks.add(trackId);\n } else {\n this.soloedTracks.delete(trackId);\n }\n\n // Update mute state of all tracks based on solo logic\n this.updateSoloMuting();\n }\n }\n\n private updateSoloMuting(): void {\n const hasSoloedTracks = this.soloedTracks.size > 0;\n\n this.tracks.forEach((track, id) => {\n if (hasSoloedTracks) {\n // If there are soloed tracks, mute all non-soloed tracks\n if (!this.soloedTracks.has(id)) {\n track.setMute(true);\n } else {\n // Restore manual mute state for soloed tracks\n const manuallyMuted = this.manualMuteState.get(id) ?? false;\n track.setMute(manuallyMuted);\n }\n } else {\n // No soloed tracks, restore original manual mute state for all tracks\n const manuallyMuted = this.manualMuteState.get(id) ?? false;\n track.setMute(manuallyMuted);\n }\n });\n }\n\n setMute(trackId: string, muted: boolean): void {\n const track = this.tracks.get(trackId);\n if (track) {\n // Store the manual mute state\n this.manualMuteState.set(trackId, muted);\n track.setMute(muted);\n }\n }\n\n getCurrentTime(): number {\n return getTransport().seconds;\n }\n\n seekTo(time: number): void {\n getTransport().seconds = time;\n }\n\n dispose(): void {\n this.tracks.forEach(track => {\n track.dispose();\n });\n this.tracks.clear();\n\n // Clean up effects if cleanup function was provided\n if (this.effectsCleanup) {\n this.effectsCleanup();\n }\n\n this.masterVolume.dispose();\n }\n\n get context(): BaseContext {\n return getContext();\n }\n\n get sampleRate(): number {\n return getContext().sampleRate;\n }\n\n setOnPlaybackComplete(callback: () => void): void {\n this.onPlaybackCompleteCallback = callback;\n }\n}\n","// Named imports for tree-shaking\nimport {\n Player,\n Volume,\n Gain,\n Panner,\n ToneAudioNode,\n getDestination,\n now,\n} from 'tone';\nimport { Track, type Fade } from '@waveform-playlist/core';\nimport { applyFadeIn, applyFadeOut, getUnderlyingAudioParam } from './fades';\n\n// Effects function no longer receives ToneLib - effects should import Tone themselves\nexport type TrackEffectsFunction = (graphEnd: Gain, masterGainNode: ToneAudioNode, isOffline: boolean) => void | (() => void);\n\nexport interface ClipInfo {\n buffer: AudioBuffer;\n startTime: number; // When this clip starts in the track timeline (seconds)\n duration: number; // How long this clip plays (seconds)\n offset: number; // Where to start playing within the buffer (seconds)\n fadeIn?: Fade;\n fadeOut?: Fade;\n gain: number; // Clip-level gain\n}\n\nexport interface ToneTrackOptions {\n buffer?: AudioBuffer; // Legacy: single buffer (deprecated, use clips instead)\n clips?: ClipInfo[]; // Modern: array of clips\n track: Track;\n effects?: TrackEffectsFunction;\n destination?: ToneAudioNode;\n}\n\ninterface ClipPlayer {\n player: Player;\n clipInfo: ClipInfo;\n fadeGain: Gain;\n pausedPosition: number;\n playStartTime: number;\n}\n\nexport class ToneTrack {\n private clips: ClipPlayer[]; // Array of clip players\n private volumeNode: Volume;\n private panNode: Panner;\n private muteGain: Gain;\n private track: Track;\n private effectsCleanup?: () => void;\n private onStopCallback?: () => void;\n private activePlayers: number = 0; // Count of currently playing clips\n\n constructor(options: ToneTrackOptions) {\n this.track = options.track;\n\n // Create shared track-level nodes\n this.volumeNode = new Volume(this.gainToDb(options.track.gain));\n this.panNode = new Panner(options.track.stereoPan);\n this.muteGain = new Gain(options.track.muted ? 0 : 1);\n\n // Connect to destination or apply effects chain\n const destination = options.destination || getDestination();\n if (options.effects) {\n const cleanup = options.effects(this.muteGain, destination, false);\n if (cleanup) {\n this.effectsCleanup = cleanup;\n }\n } else {\n this.muteGain.connect(destination);\n }\n\n // Create clips array - support both legacy single buffer and modern clips array\n const clipInfos: ClipInfo[] = options.clips || (options.buffer ? [{\n buffer: options.buffer,\n startTime: 0, // Legacy: single buffer starts at timeline position 0\n duration: options.buffer.duration, // Legacy: play full buffer duration\n offset: 0,\n fadeIn: options.track.fadeIn,\n fadeOut: options.track.fadeOut,\n gain: 1,\n }] : []);\n\n // Create ClipPlayer for each clip\n this.clips = clipInfos.map(clipInfo => {\n const player = new Player({\n url: clipInfo.buffer,\n loop: false,\n onstop: () => {\n this.activePlayers--;\n if (this.activePlayers === 0 && this.onStopCallback) {\n this.onStopCallback();\n }\n },\n });\n\n const fadeGain = new Gain(clipInfo.gain);\n\n // Chain: Player -> FadeGain -> Volume -> Pan -> MuteGain\n player.connect(fadeGain);\n fadeGain.chain(this.volumeNode, this.panNode, this.muteGain);\n\n // Note: Fades are scheduled in play() method, not here in constructor,\n // because AudioParam automation requires absolute AudioContext time\n\n return {\n player,\n clipInfo,\n fadeGain,\n pausedPosition: 0,\n playStartTime: 0,\n };\n });\n }\n\n /**\n * Schedule fade envelopes for a clip at the given start time\n */\n private scheduleFades(clipPlayer: ClipPlayer, clipStartTime: number, clipOffset: number = 0): void {\n const { clipInfo, fadeGain } = clipPlayer;\n const audioParam = getUnderlyingAudioParam(fadeGain.gain);\n if (!audioParam) return;\n\n // Cancel any previous automation\n audioParam.cancelScheduledValues(0);\n\n // Calculate how much of the clip we're skipping (for seeking)\n const skipTime = clipOffset - clipInfo.offset;\n\n // Apply fade in if it exists and we haven't skipped past it\n if (clipInfo.fadeIn && skipTime < clipInfo.fadeIn.duration) {\n const fadeInDuration = clipInfo.fadeIn.duration;\n\n if (skipTime <= 0) {\n // Starting from the beginning - full fade in\n applyFadeIn(\n audioParam,\n clipStartTime,\n fadeInDuration,\n clipInfo.fadeIn.type || 'linear',\n 0,\n clipInfo.gain\n );\n } else {\n // Starting partway through fade in - calculate partial fade\n const remainingFadeDuration = fadeInDuration - skipTime;\n const fadeProgress = skipTime / fadeInDuration;\n const startValue = clipInfo.gain * fadeProgress; // Approximate current fade value\n applyFadeIn(\n audioParam,\n clipStartTime,\n remainingFadeDuration,\n clipInfo.fadeIn.type || 'linear',\n startValue,\n clipInfo.gain\n );\n }\n } else {\n // No fade in or skipped past it - set to full gain\n audioParam.setValueAtTime(clipInfo.gain, clipStartTime);\n }\n\n // Apply fade out if it exists\n if (clipInfo.fadeOut) {\n const fadeOutStart = clipInfo.duration - clipInfo.fadeOut.duration;\n const fadeOutStartInClip = fadeOutStart - skipTime; // Relative to where we're starting\n\n if (fadeOutStartInClip > 0) {\n // Fade out hasn't started yet\n const absoluteFadeOutStart = clipStartTime + fadeOutStartInClip;\n applyFadeOut(\n audioParam,\n absoluteFadeOutStart,\n clipInfo.fadeOut.duration,\n clipInfo.fadeOut.type || 'linear',\n clipInfo.gain,\n 0\n );\n } else if (fadeOutStartInClip > -clipInfo.fadeOut.duration) {\n // We're starting partway through the fade out\n const elapsedFadeOut = -fadeOutStartInClip;\n const remainingFadeDuration = clipInfo.fadeOut.duration - elapsedFadeOut;\n const fadeProgress = elapsedFadeOut / clipInfo.fadeOut.duration;\n const startValue = clipInfo.gain * (1 - fadeProgress); // Approximate current fade value\n applyFadeOut(\n audioParam,\n clipStartTime,\n remainingFadeDuration,\n clipInfo.fadeOut.type || 'linear',\n startValue,\n 0\n );\n }\n // If fadeOutStartInClip <= -duration, we've skipped past the entire fade out\n }\n }\n\n private gainToDb(gain: number): number {\n return 20 * Math.log10(gain);\n }\n\n setVolume(gain: number): void {\n this.track.gain = gain;\n this.volumeNode.volume.value = this.gainToDb(gain);\n }\n\n setPan(pan: number): void {\n this.track.stereoPan = pan;\n this.panNode.pan.value = pan;\n }\n\n setMute(muted: boolean): void {\n this.track.muted = muted;\n 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 play(when?: number, offset: number = 0, duration?: number): void {\n // Recreate all players to avoid Tone.js StateTimeline issues when seeking\n // See: https://github.com/Tonejs/Tone.js/issues/1076\n // The Player's internal StateTimeline doesn't properly clear on stop(),\n // so we need fresh Player instances when rescheduling\n this.clips.forEach(clipPlayer => {\n // Dispose old player\n clipPlayer.player.stop();\n clipPlayer.player.disconnect();\n clipPlayer.player.dispose();\n\n // Create new player with same buffer\n const newPlayer = new Player({\n url: clipPlayer.clipInfo.buffer,\n loop: false,\n onstop: () => {\n this.activePlayers--;\n if (this.activePlayers === 0 && this.onStopCallback) {\n this.onStopCallback();\n }\n },\n });\n\n // Reconnect to audio graph\n newPlayer.connect(clipPlayer.fadeGain);\n\n // Update reference\n clipPlayer.player = newPlayer;\n clipPlayer.pausedPosition = 0;\n });\n\n this.activePlayers = 0;\n // Play each clip that should be active at this offset\n this.clips.forEach(clipPlayer => {\n const { player, clipInfo } = clipPlayer;\n\n // Calculate absolute timeline position we're starting from\n const playbackPosition = offset;\n\n // Check if this clip should be playing at this position\n const clipStart = clipInfo.startTime;\n const clipEnd = clipInfo.startTime + clipInfo.duration;\n\n if (playbackPosition < clipEnd) {\n // This clip should play\n this.activePlayers++;\n\n // Get fresh now() for each clip to avoid \"time in the past\" errors\n // This is important when seeking during playback - time passes between scheduling clips\n const currentTime = when ?? now();\n clipPlayer.playStartTime = currentTime;\n\n if (playbackPosition >= clipStart) {\n // We're starting in the middle of this clip\n const clipOffset = playbackPosition - clipStart + clipInfo.offset;\n const remainingDuration = clipInfo.duration - (playbackPosition - clipStart);\n const clipDuration = duration ? Math.min(duration, remainingDuration) : remainingDuration;\n\n clipPlayer.pausedPosition = clipOffset;\n // Schedule fades at the actual playback start time\n this.scheduleFades(clipPlayer, currentTime, clipOffset);\n player.start(currentTime, clipOffset, clipDuration);\n } else {\n // This clip starts later - schedule it\n const delay = clipStart - playbackPosition;\n const clipDuration = duration ? Math.min(duration - delay, clipInfo.duration) : clipInfo.duration;\n\n if (delay < (duration ?? Infinity)) {\n clipPlayer.pausedPosition = clipInfo.offset;\n // Schedule fades at the delayed start time\n this.scheduleFades(clipPlayer, currentTime + delay, clipInfo.offset);\n player.start(currentTime + delay, clipInfo.offset, clipDuration);\n } else {\n this.activePlayers--;\n }\n }\n }\n });\n }\n\n pause(): void {\n // Stop all clips - both started and scheduled\n // Scheduled clips have state 'stopped' but still need to be cancelled\n this.clips.forEach(clipPlayer => {\n if (clipPlayer.player.state === 'started') {\n const elapsed = (now() - clipPlayer.playStartTime) * clipPlayer.player.playbackRate;\n clipPlayer.pausedPosition = clipPlayer.pausedPosition + elapsed;\n }\n // Always call stop() to cancel any scheduled playback\n clipPlayer.player.stop();\n });\n\n this.activePlayers = 0;\n }\n\n stop(when?: number): void {\n // Evaluate now() inside function body, not in parameter default (which is evaluated at module load time)\n const stopWhen = when ?? now();\n this.clips.forEach(clipPlayer => {\n clipPlayer.player.stop(stopWhen);\n clipPlayer.pausedPosition = 0;\n });\n this.activePlayers = 0;\n }\n\n dispose(): void {\n // Clean up effects if cleanup function was provided\n if (this.effectsCleanup) {\n this.effectsCleanup();\n }\n\n // Dispose all clip players\n this.clips.forEach(clipPlayer => {\n clipPlayer.player.dispose();\n clipPlayer.fadeGain.dispose();\n });\n\n // Dispose shared track nodes\n this.volumeNode.dispose();\n this.panNode.dispose();\n this.muteGain.dispose();\n }\n\n get id(): string {\n return this.track.id;\n }\n\n get duration(): number {\n // Return the end time of the last clip\n if (this.clips.length === 0) return 0;\n const lastClip = this.clips[this.clips.length - 1];\n return lastClip.clipInfo.startTime + lastClip.clipInfo.duration;\n }\n\n get buffer(): AudioBuffer {\n // For backward compatibility, return the first clip's buffer\n return this.clips[0]?.clipInfo.buffer;\n }\n\n get isPlaying(): boolean {\n // Track is playing if any clip is playing\n return this.clips.some(clipPlayer => clipPlayer.player.state === 'started');\n }\n\n get muted(): boolean {\n return this.track.muted;\n }\n\n get startTime(): number {\n // Return the track's start time from the Track object\n // This is the absolute timeline position where the track starts\n return this.track.startTime;\n }\n\n setOnStopCallback(callback: () => void): void {\n this.onStopCallback = callback;\n }\n}\n","/**\n * Fade utilities for Web Audio API\n *\n * Applies fade in/out envelopes to AudioParam (typically gain)\n * using various curve types.\n */\n\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 'Fades and 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(\n stream: MediaStream\n): MediaStreamAudioSourceNode {\n // Return existing source if we have one for this stream\n if (streamSources.has(stream)) {\n return streamSources.get(stream)!;\n }\n\n // Create new source using Tone.js's shared context for cross-browser compatibility\n const context = getContext();\n const source = context.createMediaStreamSource(stream);\n streamSources.set(stream, source);\n\n // Set up cleanup when stream ends\n const cleanup = () => {\n source.disconnect();\n streamSources.delete(stream);\n streamCleanupHandlers.delete(stream);\n\n // Remove event listener\n stream.removeEventListener('ended', cleanup);\n stream.removeEventListener('inactive', cleanup);\n };\n\n streamCleanupHandlers.set(stream, cleanup);\n\n // Clean up when stream ends or becomes inactive\n stream.addEventListener('ended', cleanup);\n stream.addEventListener('inactive', cleanup);\n\n return source;\n}\n\n/**\n * Manually release a MediaStreamSource\n *\n * Normally you don't need to call this - cleanup happens automatically\n * when the stream ends. Only call this if you need to force cleanup.\n *\n * @param stream - The MediaStream to release the source for\n */\nexport function releaseMediaStreamSource(stream: MediaStream): void {\n const cleanup = streamCleanupHandlers.get(stream);\n if (cleanup) {\n cleanup();\n }\n}\n\n/**\n * Check if a MediaStreamSource exists for the given stream\n *\n * @param stream - The MediaStream to check\n * @returns true if a source exists for this stream\n */\nexport function hasMediaStreamSource(stream: MediaStream): boolean {\n return streamSources.has(stream);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACCA,IAAAA,eASO;;;ACTP,kBAQO;;;ACWP,IAAI,YAAY;AAET,SAAS,wBAAwB,QAAyC;AAC/E,QAAM,QAAS,OAAmC;AAClD,MAAI,CAAC,SAAS,CAAC,WAAW;AACxB,gBAAY;AACZ,YAAQ;AAAA,MACN;AAAA,IAGF;AAAA,EACF;AACA,SAAO;AACT;AAiBA,SAAS,YAAY,QAAgB,QAA+B;AAClE,QAAM,QAAQ,IAAI,aAAa,MAAM;AACrC,QAAM,QAAQ,SAAS;AAEvB,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,UAAM,IAAI,IAAI;AACd,UAAM,CAAC,IAAI,SAAS,IAAI,IAAI;AAAA,EAC9B;AAEA,SAAO;AACT;AAKA,SAAS,iBAAiB,QAAgB,QAA+B;AACvE,QAAM,QAAQ,IAAI,aAAa,MAAM;AACrC,QAAM,QAAQ,SAAS;AAEvB,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,UAAM,IAAI,IAAI;AACd,UAAM,QAAQ,SAAS,IAAI,SAAS,IAAI;AACxC,UAAM,KAAK,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK;AAAA,EAC5C;AAEA,SAAO;AACT;AAKA,SAAS,YAAY,QAAgB,QAA+B;AAClE,QAAM,QAAQ,IAAI,aAAa,MAAM;AACrC,QAAM,QAAQ,SAAS,KAAK,KAAK,IAAI,CAAC,KAAK,KAAK;AAEhD,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,UAAM,CAAC,IAAI,KAAK,IAAK,KAAK,KAAK,IAAK,SAAS,KAAK,IAAI,IAAI;AAAA,EAC5D;AAEA,SAAO;AACT;AAKA,SAAS,iBAAiB,QAAgB,QAAiB,OAAe,IAAkB;AAC1F,QAAM,QAAQ,IAAI,aAAa,MAAM;AAErC,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,UAAM,QAAQ,SAAS,IAAI,SAAS,IAAI;AACxC,UAAM,IAAI,IAAI;AACd,UAAM,KAAK,IAAI,KAAK,IAAI,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,IAAI,IAAI;AAAA,EAC3D;AAEA,SAAO;AACT;AAKA,SAAS,cAAc,MAAgB,QAAgB,QAA+B;AACpF,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO,YAAY,QAAQ,MAAM;AAAA,IACnC,KAAK;AACH,aAAO,iBAAiB,QAAQ,MAAM;AAAA,IACxC,KAAK;AACH,aAAO,YAAY,QAAQ,MAAM;AAAA,IACnC,KAAK;AACH,aAAO,iBAAiB,QAAQ,MAAM;AAAA,IACxC;AACE,aAAO,YAAY,QAAQ,MAAM;AAAA,EACrC;AACF;AAYO,SAAS,YACd,OACA,WACA,UACA,OAAiB,UACjB,aAAqB,GACrB,WAAmB,GACb;AACN,MAAI,YAAY,EAAG;AAEnB,MAAI,SAAS,UAAU;AAErB,UAAM,eAAe,YAAY,SAAS;AAC1C,UAAM,wBAAwB,UAAU,YAAY,QAAQ;AAAA,EAC9D,WAAW,SAAS,eAAe;AAEjC,UAAM,eAAe,KAAK,IAAI,YAAY,IAAK,GAAG,SAAS;AAC3D,UAAM,6BAA6B,KAAK,IAAI,UAAU,IAAK,GAAG,YAAY,QAAQ;AAAA,EACpF,OAAO;AAEL,UAAM,QAAQ,cAAc,MAAM,KAAO,IAAI;AAE7C,UAAM,cAAc,IAAI,aAAa,MAAM,MAAM;AACjD,UAAM,QAAQ,WAAW;AACzB,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,kBAAY,CAAC,IAAI,aAAa,MAAM,CAAC,IAAI;AAAA,IAC3C;AACA,UAAM,oBAAoB,aAAa,WAAW,QAAQ;AAAA,EAC5D;AACF;AAYO,SAAS,aACd,OACA,WACA,UACA,OAAiB,UACjB,aAAqB,GACrB,WAAmB,GACb;AACN,MAAI,YAAY,EAAG;AAEnB,MAAI,SAAS,UAAU;AAErB,UAAM,eAAe,YAAY,SAAS;AAC1C,UAAM,wBAAwB,UAAU,YAAY,QAAQ;AAAA,EAC9D,WAAW,SAAS,eAAe;AAEjC,UAAM,eAAe,KAAK,IAAI,YAAY,IAAK,GAAG,SAAS;AAC3D,UAAM,6BAA6B,KAAK,IAAI,UAAU,IAAK,GAAG,YAAY,QAAQ;AAAA,EACpF,OAAO;AAEL,UAAM,QAAQ,cAAc,MAAM,KAAO,KAAK;AAE9C,UAAM,cAAc,IAAI,aAAa,MAAM,MAAM;AACjD,UAAM,QAAQ,aAAa;AAC3B,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,kBAAY,CAAC,IAAI,WAAW,MAAM,CAAC,IAAI;AAAA,IACzC;AACA,UAAM,oBAAoB,aAAa,WAAW,QAAQ;AAAA,EAC5D;AACF;;;ADnKO,IAAM,YAAN,MAAgB;AAAA;AAAA,EAUrB,YAAY,SAA2B;AAFvC,SAAQ,gBAAwB;AAG9B,SAAK,QAAQ,QAAQ;AAGrB,SAAK,aAAa,IAAI,mBAAO,KAAK,SAAS,QAAQ,MAAM,IAAI,CAAC;AAC9D,SAAK,UAAU,IAAI,mBAAO,QAAQ,MAAM,SAAS;AACjD,SAAK,WAAW,IAAI,iBAAK,QAAQ,MAAM,QAAQ,IAAI,CAAC;AAGpD,UAAM,cAAc,QAAQ,mBAAe,4BAAe;AAC1D,QAAI,QAAQ,SAAS;AACnB,YAAM,UAAU,QAAQ,QAAQ,KAAK,UAAU,aAAa,KAAK;AACjE,UAAI,SAAS;AACX,aAAK,iBAAiB;AAAA,MACxB;AAAA,IACF,OAAO;AACL,WAAK,SAAS,QAAQ,WAAW;AAAA,IACnC;AAGA,UAAM,YAAwB,QAAQ,UAAU,QAAQ,SAAS,CAAC;AAAA,MAChE,QAAQ,QAAQ;AAAA,MAChB,WAAW;AAAA;AAAA,MACX,UAAU,QAAQ,OAAO;AAAA;AAAA,MACzB,QAAQ;AAAA,MACR,QAAQ,QAAQ,MAAM;AAAA,MACtB,SAAS,QAAQ,MAAM;AAAA,MACvB,MAAM;AAAA,IACR,CAAC,IAAI,CAAC;AAGN,SAAK,QAAQ,UAAU,IAAI,cAAY;AACrC,YAAM,SAAS,IAAI,mBAAO;AAAA,QACxB,KAAK,SAAS;AAAA,QACd,MAAM;AAAA,QACN,QAAQ,MAAM;AACZ,eAAK;AACL,cAAI,KAAK,kBAAkB,KAAK,KAAK,gBAAgB;AACnD,iBAAK,eAAe;AAAA,UACtB;AAAA,QACF;AAAA,MACF,CAAC;AAED,YAAM,WAAW,IAAI,iBAAK,SAAS,IAAI;AAGvC,aAAO,QAAQ,QAAQ;AACvB,eAAS,MAAM,KAAK,YAAY,KAAK,SAAS,KAAK,QAAQ;AAK3D,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA,gBAAgB;AAAA,QAChB,eAAe;AAAA,MACjB;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKQ,cAAc,YAAwB,eAAuB,aAAqB,GAAS;AACjG,UAAM,EAAE,UAAU,SAAS,IAAI;AAC/B,UAAM,aAAa,wBAAwB,SAAS,IAAI;AACxD,QAAI,CAAC,WAAY;AAGjB,eAAW,sBAAsB,CAAC;AAGlC,UAAM,WAAW,aAAa,SAAS;AAGvC,QAAI,SAAS,UAAU,WAAW,SAAS,OAAO,UAAU;AAC1D,YAAM,iBAAiB,SAAS,OAAO;AAEvC,UAAI,YAAY,GAAG;AAEjB;AAAA,UACE;AAAA,UACA;AAAA,UACA;AAAA,UACA,SAAS,OAAO,QAAQ;AAAA,UACxB;AAAA,UACA,SAAS;AAAA,QACX;AAAA,MACF,OAAO;AAEL,cAAM,wBAAwB,iBAAiB;AAC/C,cAAM,eAAe,WAAW;AAChC,cAAM,aAAa,SAAS,OAAO;AACnC;AAAA,UACE;AAAA,UACA;AAAA,UACA;AAAA,UACA,SAAS,OAAO,QAAQ;AAAA,UACxB;AAAA,UACA,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF,OAAO;AAEL,iBAAW,eAAe,SAAS,MAAM,aAAa;AAAA,IACxD;AAGA,QAAI,SAAS,SAAS;AACpB,YAAM,eAAe,SAAS,WAAW,SAAS,QAAQ;AAC1D,YAAM,qBAAqB,eAAe;AAE1C,UAAI,qBAAqB,GAAG;AAE1B,cAAM,uBAAuB,gBAAgB;AAC7C;AAAA,UACE;AAAA,UACA;AAAA,UACA,SAAS,QAAQ;AAAA,UACjB,SAAS,QAAQ,QAAQ;AAAA,UACzB,SAAS;AAAA,UACT;AAAA,QACF;AAAA,MACF,WAAW,qBAAqB,CAAC,SAAS,QAAQ,UAAU;AAE1D,cAAM,iBAAiB,CAAC;AACxB,cAAM,wBAAwB,SAAS,QAAQ,WAAW;AAC1D,cAAM,eAAe,iBAAiB,SAAS,QAAQ;AACvD,cAAM,aAAa,SAAS,QAAQ,IAAI;AACxC;AAAA,UACE;AAAA,UACA;AAAA,UACA;AAAA,UACA,SAAS,QAAQ,QAAQ;AAAA,UACzB;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IAEF;AAAA,EACF;AAAA,EAEQ,SAAS,MAAsB;AACrC,WAAO,KAAK,KAAK,MAAM,IAAI;AAAA,EAC7B;AAAA,EAEA,UAAU,MAAoB;AAC5B,SAAK,MAAM,OAAO;AAClB,SAAK,WAAW,OAAO,QAAQ,KAAK,SAAS,IAAI;AAAA,EACnD;AAAA,EAEA,OAAO,KAAmB;AACxB,SAAK,MAAM,YAAY;AACvB,SAAK,QAAQ,IAAI,QAAQ;AAAA,EAC3B;AAAA,EAEA,QAAQ,OAAsB;AAC5B,SAAK,MAAM,QAAQ;AACnB,UAAM,QAAQ,QAAQ,IAAI;AAM1B,UAAM,aAAa,wBAAwB,KAAK,SAAS,IAAI;AAC7D,gBAAY,eAAe,OAAO,CAAC;AACnC,SAAK,SAAS,KAAK,QAAQ;AAAA,EAC7B;AAAA,EAEA,QAAQ,QAAuB;AAC7B,SAAK,MAAM,SAAS;AAAA,EACtB;AAAA,EAEA,KAAK,MAAe,SAAiB,GAAG,UAAyB;AAK/D,SAAK,MAAM,QAAQ,gBAAc;AAE/B,iBAAW,OAAO,KAAK;AACvB,iBAAW,OAAO,WAAW;AAC7B,iBAAW,OAAO,QAAQ;AAG1B,YAAM,YAAY,IAAI,mBAAO;AAAA,QAC3B,KAAK,WAAW,SAAS;AAAA,QACzB,MAAM;AAAA,QACN,QAAQ,MAAM;AACZ,eAAK;AACL,cAAI,KAAK,kBAAkB,KAAK,KAAK,gBAAgB;AACnD,iBAAK,eAAe;AAAA,UACtB;AAAA,QACF;AAAA,MACF,CAAC;AAGD,gBAAU,QAAQ,WAAW,QAAQ;AAGrC,iBAAW,SAAS;AACpB,iBAAW,iBAAiB;AAAA,IAC9B,CAAC;AAED,SAAK,gBAAgB;AAErB,SAAK,MAAM,QAAQ,gBAAc;AAC/B,YAAM,EAAE,QAAQ,SAAS,IAAI;AAG7B,YAAM,mBAAmB;AAGzB,YAAM,YAAY,SAAS;AAC3B,YAAM,UAAU,SAAS,YAAY,SAAS;AAE9C,UAAI,mBAAmB,SAAS;AAE9B,aAAK;AAIL,cAAM,cAAc,YAAQ,iBAAI;AAChC,mBAAW,gBAAgB;AAE3B,YAAI,oBAAoB,WAAW;AAEjC,gBAAM,aAAa,mBAAmB,YAAY,SAAS;AAC3D,gBAAM,oBAAoB,SAAS,YAAY,mBAAmB;AAClE,gBAAM,eAAe,WAAW,KAAK,IAAI,UAAU,iBAAiB,IAAI;AAExE,qBAAW,iBAAiB;AAE5B,eAAK,cAAc,YAAY,aAAa,UAAU;AACtD,iBAAO,MAAM,aAAa,YAAY,YAAY;AAAA,QACpD,OAAO;AAEL,gBAAM,QAAQ,YAAY;AAC1B,gBAAM,eAAe,WAAW,KAAK,IAAI,WAAW,OAAO,SAAS,QAAQ,IAAI,SAAS;AAEzF,cAAI,SAAS,YAAY,WAAW;AAClC,uBAAW,iBAAiB,SAAS;AAErC,iBAAK,cAAc,YAAY,cAAc,OAAO,SAAS,MAAM;AACnE,mBAAO,MAAM,cAAc,OAAO,SAAS,QAAQ,YAAY;AAAA,UACjE,OAAO;AACL,iBAAK;AAAA,UACP;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,QAAc;AAGZ,SAAK,MAAM,QAAQ,gBAAc;AAC/B,UAAI,WAAW,OAAO,UAAU,WAAW;AACzC,cAAM,eAAW,iBAAI,IAAI,WAAW,iBAAiB,WAAW,OAAO;AACvE,mBAAW,iBAAiB,WAAW,iBAAiB;AAAA,MAC1D;AAEA,iBAAW,OAAO,KAAK;AAAA,IACzB,CAAC;AAED,SAAK,gBAAgB;AAAA,EACvB;AAAA,EAEA,KAAK,MAAqB;AAExB,UAAM,WAAW,YAAQ,iBAAI;AAC7B,SAAK,MAAM,QAAQ,gBAAc;AAC/B,iBAAW,OAAO,KAAK,QAAQ;AAC/B,iBAAW,iBAAiB;AAAA,IAC9B,CAAC;AACD,SAAK,gBAAgB;AAAA,EACvB;AAAA,EAEA,UAAgB;AAEd,QAAI,KAAK,gBAAgB;AACvB,WAAK,eAAe;AAAA,IACtB;AAGA,SAAK,MAAM,QAAQ,gBAAc;AAC/B,iBAAW,OAAO,QAAQ;AAC1B,iBAAW,SAAS,QAAQ;AAAA,IAC9B,CAAC;AAGD,SAAK,WAAW,QAAQ;AACxB,SAAK,QAAQ,QAAQ;AACrB,SAAK,SAAS,QAAQ;AAAA,EACxB;AAAA,EAEA,IAAI,KAAa;AACf,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,IAAI,WAAmB;AAErB,QAAI,KAAK,MAAM,WAAW,EAAG,QAAO;AACpC,UAAM,WAAW,KAAK,MAAM,KAAK,MAAM,SAAS,CAAC;AACjD,WAAO,SAAS,SAAS,YAAY,SAAS,SAAS;AAAA,EACzD;AAAA,EAEA,IAAI,SAAsB;AAExB,WAAO,KAAK,MAAM,CAAC,GAAG,SAAS;AAAA,EACjC;AAAA,EAEA,IAAI,YAAqB;AAEvB,WAAO,KAAK,MAAM,KAAK,gBAAc,WAAW,OAAO,UAAU,SAAS;AAAA,EAC5E;AAAA,EAEA,IAAI,QAAiB;AACnB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,IAAI,YAAoB;AAGtB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,kBAAkB,UAA4B;AAC5C,SAAK,iBAAiB;AAAA,EACxB;AACF;;;AD1WO,IAAM,cAAN,MAAkB;AAAA,EAWvB,YAAY,UAA8B,CAAC,GAAG;AAV9C,SAAQ,SAAiC,oBAAI,IAAI;AAEjD,SAAQ,gBAAgB;AACxB,SAAQ,eAA4B,oBAAI,IAAI;AAC5C,SAAQ,kBAAwC,oBAAI,IAAI;AAGxD,SAAQ,eAAoC,oBAAI,IAAI;AACpD;AAAA,SAAQ,oBAA4B;AAGlC,SAAK,eAAe,IAAI,oBAAO,KAAK,SAAS,QAAQ,cAAc,CAAC,CAAC;AAGrE,QAAI,QAAQ,SAAS;AACnB,YAAM,UAAU,QAAQ,QAAQ,KAAK,kBAAc,6BAAe,GAAG,KAAK;AAC1E,UAAI,SAAS;AACX,aAAK,iBAAiB;AAAA,MACxB;AAAA,IACF,OAAO;AACL,WAAK,aAAa,cAAc;AAAA,IAClC;AAEA,QAAI,QAAQ,QAAQ;AAClB,cAAQ,OAAO,QAAQ,WAAS;AAC9B,aAAK,OAAO,IAAI,MAAM,IAAI,KAAK;AAE/B,aAAK,gBAAgB,IAAI,MAAM,IAAI,MAAM,KAAK;AAAA,MAChD,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEQ,SAAS,MAAsB;AACrC,WAAO,KAAK,KAAK,MAAM,IAAI;AAAA,EAC7B;AAAA,EAEA,MAAM,OAAsB;AAC1B,QAAI,KAAK,cAAe;AAExB,cAAM,oBAAM;AACZ,SAAK,gBAAgB;AAAA,EACvB;AAAA,EAEA,SAAS,cAA2C;AAElD,UAAM,yBAAyB;AAAA,MAC7B,GAAG;AAAA,MACH,aAAa,KAAK;AAAA,IACpB;AACA,UAAM,YAAY,IAAI,UAAU,sBAAsB;AACtD,SAAK,OAAO,IAAI,UAAU,IAAI,SAAS;AAEvC,SAAK,gBAAgB,IAAI,UAAU,IAAI,aAAa,MAAM,SAAS,KAAK;AAExE,QAAI,aAAa,MAAM,QAAQ;AAC7B,WAAK,aAAa,IAAI,UAAU,EAAE;AAAA,IACpC;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,wBAA8B;AAC5B,SAAK,iBAAiB;AAAA,EACxB;AAAA,EAEA,YAAY,SAAuB;AACjC,UAAM,QAAQ,KAAK,OAAO,IAAI,OAAO;AACrC,QAAI,OAAO;AACT,YAAM,QAAQ;AACd,WAAK,OAAO,OAAO,OAAO;AAC1B,WAAK,gBAAgB,OAAO,OAAO;AACnC,WAAK,aAAa,OAAO,OAAO;AAAA,IAClC;AAAA,EACF;AAAA,EAEA,SAAS,SAAwC;AAC/C,WAAO,KAAK,OAAO,IAAI,OAAO;AAAA,EAChC;AAAA,EAEA,KAAK,MAAe,QAAiB,UAAyB;AAC5D,QAAI,CAAC,KAAK,eAAe;AACvB,cAAQ,KAAK,iDAAiD;AAC9D;AAAA,IACF;AAGA,UAAM,YAAY,YAAQ,kBAAI;AAC9B,UAAM,mBAAmB,UAAU;AAGnC,SAAK;AACL,UAAM,mBAAmB,KAAK;AAG9B,SAAK,aAAa,MAAM;AAGxB,SAAK,OAAO,QAAQ,CAAC,cAAc;AACjC,YAAM,iBAAiB,UAAU;AAEjC,UAAI,oBAAoB,gBAAgB;AAEtC,cAAM,eAAe,mBAAmB;AAExC,YAAI,aAAa,QAAW;AAC1B,eAAK,aAAa,IAAI,UAAU,IAAI,gBAAgB;AACpD,oBAAU,kBAAkB,MAAM;AAEhC,gBAAI,KAAK,aAAa,IAAI,UAAU,EAAE,MAAM,kBAAkB;AAC5D,mBAAK,aAAa,OAAO,UAAU,EAAE;AACrC,kBAAI,KAAK,aAAa,SAAS,KAAK,KAAK,4BAA4B;AACnE,qBAAK,2BAA2B;AAAA,cAClC;AAAA,YACF;AAAA,UACF,CAAC;AAAA,QACH;AAEA,kBAAU,KAAK,WAAW,cAAc,QAAQ;AAAA,MAClD,OAAO;AAEL,cAAM,QAAQ,iBAAiB;AAE/B,YAAI,aAAa,QAAW;AAC1B,eAAK,aAAa,IAAI,UAAU,IAAI,gBAAgB;AACpD,oBAAU,kBAAkB,MAAM;AAEhC,gBAAI,KAAK,aAAa,IAAI,UAAU,EAAE,MAAM,kBAAkB;AAC5D,mBAAK,aAAa,OAAO,UAAU,EAAE;AACrC,kBAAI,KAAK,aAAa,SAAS,KAAK,KAAK,4BAA4B;AACnE,qBAAK,2BAA2B;AAAA,cAClC;AAAA,YACF;AAAA,UACF,CAAC;AAAA,QACH;AAEA,kBAAU,KAAK,YAAY,OAAO,GAAG,QAAQ;AAAA,MAC/C;AAAA,IACF,CAAC;AAGD,QAAI,WAAW,QAAW;AAExB,qCAAa,EAAE,MAAM,WAAW,MAAM;AAAA,IACxC,OAAO;AAEL,qCAAa,EAAE,MAAM,SAAS;AAAA,IAChC;AAAA,EACF;AAAA,EAEA,QAAc;AACZ,mCAAa,EAAE,MAAM;AACrB,SAAK,OAAO,QAAQ,WAAS;AAC3B,YAAM,MAAM;AAAA,IACd,CAAC;AAAA,EACH;AAAA,EAEA,OAAa;AACX,mCAAa,EAAE,KAAK;AACpB,SAAK,OAAO,QAAQ,WAAS;AAC3B,YAAM,KAAK;AAAA,IACb,CAAC;AAAA,EACH;AAAA,EAEA,cAAc,MAAoB;AAChC,SAAK,aAAa,OAAO,QAAQ,KAAK,SAAS,IAAI;AAAA,EACrD;AAAA,EAEA,QAAQ,SAAiB,QAAuB;AAC9C,UAAM,QAAQ,KAAK,OAAO,IAAI,OAAO;AACrC,QAAI,OAAO;AACT,YAAM,QAAQ,MAAM;AACpB,UAAI,QAAQ;AACV,aAAK,aAAa,IAAI,OAAO;AAAA,MAC/B,OAAO;AACL,aAAK,aAAa,OAAO,OAAO;AAAA,MAClC;AAGA,WAAK,iBAAiB;AAAA,IACxB;AAAA,EACF;AAAA,EAEQ,mBAAyB;AAC/B,UAAM,kBAAkB,KAAK,aAAa,OAAO;AAEjD,SAAK,OAAO,QAAQ,CAAC,OAAO,OAAO;AACjC,UAAI,iBAAiB;AAEnB,YAAI,CAAC,KAAK,aAAa,IAAI,EAAE,GAAG;AAC9B,gBAAM,QAAQ,IAAI;AAAA,QACpB,OAAO;AAEL,gBAAM,gBAAgB,KAAK,gBAAgB,IAAI,EAAE,KAAK;AACtD,gBAAM,QAAQ,aAAa;AAAA,QAC7B;AAAA,MACF,OAAO;AAEL,cAAM,gBAAgB,KAAK,gBAAgB,IAAI,EAAE,KAAK;AACtD,cAAM,QAAQ,aAAa;AAAA,MAC7B;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,QAAQ,SAAiB,OAAsB;AAC7C,UAAM,QAAQ,KAAK,OAAO,IAAI,OAAO;AACrC,QAAI,OAAO;AAET,WAAK,gBAAgB,IAAI,SAAS,KAAK;AACvC,YAAM,QAAQ,KAAK;AAAA,IACrB;AAAA,EACF;AAAA,EAEA,iBAAyB;AACvB,eAAO,2BAAa,EAAE;AAAA,EACxB;AAAA,EAEA,OAAO,MAAoB;AACzB,mCAAa,EAAE,UAAU;AAAA,EAC3B;AAAA,EAEA,UAAgB;AACd,SAAK,OAAO,QAAQ,WAAS;AAC3B,YAAM,QAAQ;AAAA,IAChB,CAAC;AACD,SAAK,OAAO,MAAM;AAGlB,QAAI,KAAK,gBAAgB;AACvB,WAAK,eAAe;AAAA,IACtB;AAEA,SAAK,aAAa,QAAQ;AAAA,EAC5B;AAAA,EAEA,IAAI,UAAuB;AACzB,eAAO,yBAAW;AAAA,EACpB;AAAA,EAEA,IAAI,aAAqB;AACvB,eAAO,yBAAW,EAAE;AAAA,EACtB;AAAA,EAEA,sBAAsB,UAA4B;AAChD,SAAK,6BAA6B;AAAA,EACpC;AACF;;;AGrQA,IAAAC,eAAoC;AAEpC,IAAI,oBAAoC;AAQjC,SAAS,mBAA4B;AAC1C,MAAI,CAAC,mBAAmB;AACtB,wBAAoB,IAAI,qBAAQ;AAChC,iCAAW,iBAAiB;AAAA,EAC9B;AACA,SAAO;AACT;AAOO,SAAS,wBAAsC;AACpD,SAAO,iBAAiB,EAAE;AAC5B;AAOO,SAAS,uBAAgC;AAC9C,SAAO,iBAAiB;AAC1B;AAOA,eAAsB,2BAA0C;AAC9D,QAAM,UAAU,iBAAiB;AACjC,MAAI,QAAQ,UAAU,WAAW;AAC/B,UAAM,QAAQ,OAAO;AAAA,EACvB;AACF;AAMO,SAAS,6BAAgD;AAC9D,SAAO,mBAAmB,WAAW,SAAS;AAChD;AAMA,eAAsB,0BAAyC;AAC7D,MAAI,qBAAqB,kBAAkB,WAAW,UAAU,UAAU;AACxE,UAAM,kBAAkB,MAAM;AAC9B,wBAAoB;AAAA,EACtB;AACF;;;AC1DA,IAAAC,eAA2B;AAG3B,IAAM,gBAAgB,oBAAI,IAA6C;AAGvE,IAAM,wBAAwB,oBAAI,IAA6B;AAiBxD,SAAS,qBACd,QAC4B;AAE5B,MAAI,cAAc,IAAI,MAAM,GAAG;AAC7B,WAAO,cAAc,IAAI,MAAM;AAAA,EACjC;AAGA,QAAM,cAAU,yBAAW;AAC3B,QAAM,SAAS,QAAQ,wBAAwB,MAAM;AACrD,gBAAc,IAAI,QAAQ,MAAM;AAGhC,QAAM,UAAU,MAAM;AACpB,WAAO,WAAW;AAClB,kBAAc,OAAO,MAAM;AAC3B,0BAAsB,OAAO,MAAM;AAGnC,WAAO,oBAAoB,SAAS,OAAO;AAC3C,WAAO,oBAAoB,YAAY,OAAO;AAAA,EAChD;AAEA,wBAAsB,IAAI,QAAQ,OAAO;AAGzC,SAAO,iBAAiB,SAAS,OAAO;AACxC,SAAO,iBAAiB,YAAY,OAAO;AAE3C,SAAO;AACT;AAUO,SAAS,yBAAyB,QAA2B;AAClE,QAAM,UAAU,sBAAsB,IAAI,MAAM;AAChD,MAAI,SAAS;AACX,YAAQ;AAAA,EACV;AACF;AAQO,SAAS,qBAAqB,QAA8B;AACjE,SAAO,cAAc,IAAI,MAAM;AACjC;","names":["import_tone","import_tone","import_tone"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/TonePlayout.ts","../src/ToneTrack.ts","../src/fades.ts","../src/audioContext.ts","../src/mediaStreamSourceManager.ts","../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","// Named imports for tree-shaking\nimport {\n Volume,\n ToneAudioNode,\n getDestination,\n start,\n now,\n getTransport,\n getContext,\n BaseContext,\n} from 'tone';\nimport { ToneTrack, ToneTrackOptions } from './ToneTrack';\n\n// Effects function no longer receives ToneLib - effects should import Tone themselves\nexport type EffectsFunction = (\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 activeTracks: Map<string, number> = new Map(); // Map track ID to session ID\n private playbackSessionId: number = 0;\n\n constructor(options: TonePlayoutOptions = {}) {\n this.masterVolume = new Volume(this.gainToDb(options.masterGain ?? 1));\n\n // Setup effects chain if provided, otherwise connect directly to destination\n if (options.effects) {\n const cleanup = options.effects(this.masterVolume, getDestination(), false);\n if (cleanup) {\n this.effectsCleanup = cleanup;\n }\n } else {\n this.masterVolume.toDestination();\n }\n\n if (options.tracks) {\n options.tracks.forEach((track) => {\n this.tracks.set(track.id, track);\n // Initialize manual mute state for constructor-provided tracks\n this.manualMuteState.set(track.id, track.muted);\n });\n }\n }\n\n private gainToDb(gain: number): number {\n return 20 * Math.log10(gain);\n }\n\n async init(): Promise<void> {\n if (this.isInitialized) return;\n\n await start();\n this.isInitialized = true;\n }\n\n addTrack(trackOptions: ToneTrackOptions): ToneTrack {\n // Ensure tracks connect to master volume instead of destination\n const optionsWithDestination = {\n ...trackOptions,\n destination: this.masterVolume,\n };\n const toneTrack = new ToneTrack(optionsWithDestination);\n this.tracks.set(toneTrack.id, toneTrack);\n // Initialize manual mute state from track options\n this.manualMuteState.set(toneTrack.id, trackOptions.track.muted ?? false);\n // Initialize solo state from track options\n if (trackOptions.track.soloed) {\n this.soloedTracks.add(toneTrack.id);\n }\n return toneTrack;\n }\n\n /**\n * Apply solo muting after all tracks have been added.\n * Call this after adding all tracks to ensure solo logic is applied correctly.\n */\n applyInitialSoloState(): void {\n this.updateSoloMuting();\n }\n\n removeTrack(trackId: string): void {\n const track = this.tracks.get(trackId);\n if (track) {\n track.dispose();\n this.tracks.delete(trackId);\n this.manualMuteState.delete(trackId);\n this.soloedTracks.delete(trackId);\n }\n }\n\n getTrack(trackId: string): ToneTrack | undefined {\n return this.tracks.get(trackId);\n }\n\n play(when?: number, offset?: number, duration?: number): void {\n if (!this.isInitialized) {\n console.warn('TonePlayout not initialized. Call init() first.');\n return;\n }\n\n // Use now() as default, but call it here after init check (not in function signature)\n const startTime = when ?? now();\n const playbackPosition = offset ?? 0;\n\n // Increment session ID to invalidate old callbacks\n this.playbackSessionId++;\n const currentSessionId = this.playbackSessionId;\n\n // Clear active tracks and set up stop callbacks if duration is specified\n this.activeTracks.clear();\n\n // Play tracks based on their individual start times\n this.tracks.forEach((toneTrack) => {\n const trackStartTime = toneTrack.startTime;\n\n if (playbackPosition >= trackStartTime) {\n // Track should be playing - calculate buffer offset and start immediately\n const bufferOffset = playbackPosition - trackStartTime;\n\n if (duration !== undefined) {\n this.activeTracks.set(toneTrack.id, currentSessionId);\n toneTrack.setOnStopCallback(() => {\n // Only process if this track is still in activeTracks with matching session ID\n if (this.activeTracks.get(toneTrack.id) === currentSessionId) {\n this.activeTracks.delete(toneTrack.id);\n if (this.activeTracks.size === 0 && this.onPlaybackCompleteCallback) {\n this.onPlaybackCompleteCallback();\n }\n }\n });\n }\n\n toneTrack.play(startTime, bufferOffset, duration);\n } else {\n // Track should start later - schedule it to start when playback reaches its start time\n const delay = trackStartTime - playbackPosition;\n\n if (duration !== undefined) {\n this.activeTracks.set(toneTrack.id, currentSessionId);\n toneTrack.setOnStopCallback(() => {\n // Only process if this track is still in activeTracks with matching session ID\n if (this.activeTracks.get(toneTrack.id) === currentSessionId) {\n this.activeTracks.delete(toneTrack.id);\n if (this.activeTracks.size === 0 && this.onPlaybackCompleteCallback) {\n this.onPlaybackCompleteCallback();\n }\n }\n });\n }\n\n toneTrack.play(startTime + delay, 0, duration);\n }\n });\n\n // Start transport\n if (offset !== undefined) {\n // Explicit offset provided - seek to that position\n getTransport().start(startTime, offset);\n } else {\n // No offset - resume from pause (Transport resumes from current position)\n getTransport().start(startTime);\n }\n }\n\n pause(): void {\n getTransport().pause();\n this.tracks.forEach((track) => {\n track.pause();\n });\n }\n\n stop(): void {\n getTransport().stop();\n this.tracks.forEach((track) => {\n track.stop();\n });\n }\n\n setMasterGain(gain: number): void {\n this.masterVolume.volume.value = this.gainToDb(gain);\n }\n\n setSolo(trackId: string, soloed: boolean): void {\n const track = this.tracks.get(trackId);\n if (track) {\n track.setSolo(soloed);\n if (soloed) {\n this.soloedTracks.add(trackId);\n } else {\n this.soloedTracks.delete(trackId);\n }\n\n // Update mute state of all tracks based on solo logic\n this.updateSoloMuting();\n }\n }\n\n private updateSoloMuting(): void {\n const hasSoloedTracks = this.soloedTracks.size > 0;\n\n this.tracks.forEach((track, id) => {\n if (hasSoloedTracks) {\n // If there are soloed tracks, mute all non-soloed tracks\n if (!this.soloedTracks.has(id)) {\n track.setMute(true);\n } else {\n // Restore manual mute state for soloed tracks\n const manuallyMuted = this.manualMuteState.get(id) ?? false;\n track.setMute(manuallyMuted);\n }\n } else {\n // No soloed tracks, restore original manual mute state for all tracks\n const manuallyMuted = this.manualMuteState.get(id) ?? false;\n track.setMute(manuallyMuted);\n }\n });\n }\n\n setMute(trackId: string, muted: boolean): void {\n const track = this.tracks.get(trackId);\n if (track) {\n // Store the manual mute state\n this.manualMuteState.set(trackId, muted);\n track.setMute(muted);\n }\n }\n\n getCurrentTime(): number {\n return getTransport().seconds;\n }\n\n seekTo(time: number): void {\n getTransport().seconds = time;\n }\n\n dispose(): void {\n this.tracks.forEach((track) => {\n track.dispose();\n });\n this.tracks.clear();\n\n // Clean up effects if cleanup function was provided\n if (this.effectsCleanup) {\n this.effectsCleanup();\n }\n\n this.masterVolume.dispose();\n }\n\n get context(): BaseContext {\n return getContext();\n }\n\n get sampleRate(): number {\n return getContext().sampleRate;\n }\n\n setOnPlaybackComplete(callback: () => void): void {\n this.onPlaybackCompleteCallback = callback;\n }\n}\n","// Named imports for tree-shaking\nimport { Player, Volume, Gain, Panner, ToneAudioNode, getDestination, now } from 'tone';\nimport { Track, type Fade } from '@waveform-playlist/core';\nimport { applyFadeIn, applyFadeOut, getUnderlyingAudioParam } from './fades';\n\n// Effects function no longer receives ToneLib - effects should import Tone themselves\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\ninterface ClipPlayer {\n player: Player;\n clipInfo: ClipInfo;\n fadeGain: Gain;\n pausedPosition: number;\n playStartTime: number;\n}\n\nexport class ToneTrack {\n private clips: ClipPlayer[]; // Array of clip players\n private volumeNode: Volume;\n private panNode: Panner;\n private muteGain: Gain;\n private track: Track;\n private effectsCleanup?: () => void;\n private onStopCallback?: () => void;\n private activePlayers: number = 0; // Count of currently playing clips\n\n constructor(options: ToneTrackOptions) {\n this.track = options.track;\n\n // Create shared track-level nodes\n this.volumeNode = new Volume(this.gainToDb(options.track.gain));\n this.panNode = new Panner(options.track.stereoPan);\n this.muteGain = new Gain(options.track.muted ? 0 : 1);\n\n // Connect to destination or apply effects chain\n const destination = options.destination || getDestination();\n if (options.effects) {\n const cleanup = options.effects(this.muteGain, destination, false);\n if (cleanup) {\n this.effectsCleanup = cleanup;\n }\n } else {\n this.muteGain.connect(destination);\n }\n\n // Create clips array - support both legacy single buffer and modern clips array\n const clipInfos: ClipInfo[] =\n options.clips ||\n (options.buffer\n ? [\n {\n buffer: options.buffer,\n startTime: 0, // Legacy: single buffer starts at timeline position 0\n duration: options.buffer.duration, // Legacy: play full buffer duration\n offset: 0,\n fadeIn: options.track.fadeIn,\n fadeOut: options.track.fadeOut,\n gain: 1,\n },\n ]\n : []);\n\n // Create ClipPlayer for each clip\n this.clips = clipInfos.map((clipInfo) => {\n const player = new Player({\n url: clipInfo.buffer,\n loop: false,\n onstop: () => {\n this.activePlayers--;\n if (this.activePlayers === 0 && this.onStopCallback) {\n this.onStopCallback();\n }\n },\n });\n\n const fadeGain = new Gain(clipInfo.gain);\n\n // Chain: Player -> FadeGain -> Volume -> Pan -> MuteGain\n player.connect(fadeGain);\n fadeGain.chain(this.volumeNode, this.panNode, this.muteGain);\n\n // Note: Fades are scheduled in play() method, not here in constructor,\n // because AudioParam automation requires absolute AudioContext time\n\n return {\n player,\n clipInfo,\n fadeGain,\n pausedPosition: 0,\n playStartTime: 0,\n };\n });\n }\n\n /**\n * Schedule fade envelopes for a clip at the given start time\n */\n private scheduleFades(\n clipPlayer: ClipPlayer,\n clipStartTime: number,\n clipOffset: number = 0\n ): void {\n const { clipInfo, fadeGain } = clipPlayer;\n const audioParam = getUnderlyingAudioParam(fadeGain.gain);\n if (!audioParam) return;\n\n // Cancel any previous automation\n audioParam.cancelScheduledValues(0);\n\n // Calculate how much of the clip we're skipping (for seeking)\n const skipTime = clipOffset - clipInfo.offset;\n\n // Apply fade in if it exists and we haven't skipped past it\n if (clipInfo.fadeIn && skipTime < clipInfo.fadeIn.duration) {\n const fadeInDuration = clipInfo.fadeIn.duration;\n\n if (skipTime <= 0) {\n // Starting from the beginning - full fade in\n applyFadeIn(\n audioParam,\n clipStartTime,\n fadeInDuration,\n clipInfo.fadeIn.type || 'linear',\n 0,\n clipInfo.gain\n );\n } else {\n // Starting partway through fade in - calculate partial fade\n const remainingFadeDuration = fadeInDuration - skipTime;\n const fadeProgress = skipTime / fadeInDuration;\n const startValue = clipInfo.gain * fadeProgress; // Approximate current fade value\n applyFadeIn(\n audioParam,\n clipStartTime,\n remainingFadeDuration,\n clipInfo.fadeIn.type || 'linear',\n startValue,\n clipInfo.gain\n );\n }\n } else {\n // No fade in or skipped past it - set to full gain\n audioParam.setValueAtTime(clipInfo.gain, clipStartTime);\n }\n\n // Apply fade out if it exists\n if (clipInfo.fadeOut) {\n const fadeOutStart = clipInfo.duration - clipInfo.fadeOut.duration;\n const fadeOutStartInClip = fadeOutStart - skipTime; // Relative to where we're starting\n\n if (fadeOutStartInClip > 0) {\n // Fade out hasn't started yet\n const absoluteFadeOutStart = clipStartTime + fadeOutStartInClip;\n applyFadeOut(\n audioParam,\n absoluteFadeOutStart,\n clipInfo.fadeOut.duration,\n clipInfo.fadeOut.type || 'linear',\n clipInfo.gain,\n 0\n );\n } else if (fadeOutStartInClip > -clipInfo.fadeOut.duration) {\n // We're starting partway through the fade out\n const elapsedFadeOut = -fadeOutStartInClip;\n const remainingFadeDuration = clipInfo.fadeOut.duration - elapsedFadeOut;\n const fadeProgress = elapsedFadeOut / clipInfo.fadeOut.duration;\n const startValue = clipInfo.gain * (1 - fadeProgress); // Approximate current fade value\n applyFadeOut(\n audioParam,\n clipStartTime,\n remainingFadeDuration,\n clipInfo.fadeOut.type || 'linear',\n startValue,\n 0\n );\n }\n // If fadeOutStartInClip <= -duration, we've skipped past the entire fade out\n }\n }\n\n private gainToDb(gain: number): number {\n return 20 * Math.log10(gain);\n }\n\n setVolume(gain: number): void {\n this.track.gain = gain;\n this.volumeNode.volume.value = this.gainToDb(gain);\n }\n\n setPan(pan: number): void {\n this.track.stereoPan = pan;\n this.panNode.pan.value = pan;\n }\n\n setMute(muted: boolean): void {\n this.track.muted = muted;\n 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 play(when?: number, offset: number = 0, duration?: number): void {\n // Recreate all players to avoid Tone.js StateTimeline issues when seeking\n // See: https://github.com/Tonejs/Tone.js/issues/1076\n // The Player's internal StateTimeline doesn't properly clear on stop(),\n // so we need fresh Player instances when rescheduling\n this.clips.forEach((clipPlayer) => {\n // Dispose old player\n clipPlayer.player.stop();\n clipPlayer.player.disconnect();\n clipPlayer.player.dispose();\n\n // Create new player with same buffer\n const newPlayer = new Player({\n url: clipPlayer.clipInfo.buffer,\n loop: false,\n onstop: () => {\n this.activePlayers--;\n if (this.activePlayers === 0 && this.onStopCallback) {\n this.onStopCallback();\n }\n },\n });\n\n // Reconnect to audio graph\n newPlayer.connect(clipPlayer.fadeGain);\n\n // Update reference\n clipPlayer.player = newPlayer;\n clipPlayer.pausedPosition = 0;\n });\n\n this.activePlayers = 0;\n // Play each clip that should be active at this offset\n this.clips.forEach((clipPlayer) => {\n const { player, clipInfo } = clipPlayer;\n\n // Calculate absolute timeline position we're starting from\n const playbackPosition = offset;\n\n // Check if this clip should be playing at this position\n const clipStart = clipInfo.startTime;\n const clipEnd = clipInfo.startTime + clipInfo.duration;\n\n if (playbackPosition < clipEnd) {\n // This clip should play\n this.activePlayers++;\n\n // Get fresh now() for each clip to avoid \"time in the past\" errors\n // This is important when seeking during playback - time passes between scheduling clips\n const currentTime = when ?? now();\n clipPlayer.playStartTime = currentTime;\n\n if (playbackPosition >= clipStart) {\n // We're starting in the middle of this clip\n const clipOffset = playbackPosition - clipStart + clipInfo.offset;\n const remainingDuration = clipInfo.duration - (playbackPosition - clipStart);\n const clipDuration = duration ? Math.min(duration, remainingDuration) : remainingDuration;\n\n clipPlayer.pausedPosition = clipOffset;\n // Schedule fades at the actual playback start time\n this.scheduleFades(clipPlayer, currentTime, clipOffset);\n player.start(currentTime, clipOffset, clipDuration);\n } else {\n // This clip starts later - schedule it\n const delay = clipStart - playbackPosition;\n const clipDuration = duration\n ? Math.min(duration - delay, clipInfo.duration)\n : clipInfo.duration;\n\n if (delay < (duration ?? Infinity)) {\n clipPlayer.pausedPosition = clipInfo.offset;\n // Schedule fades at the delayed start time\n this.scheduleFades(clipPlayer, currentTime + delay, clipInfo.offset);\n player.start(currentTime + delay, clipInfo.offset, clipDuration);\n } else {\n this.activePlayers--;\n }\n }\n }\n });\n }\n\n pause(): void {\n // Stop all clips - both started and scheduled\n // Scheduled clips have state 'stopped' but still need to be cancelled\n this.clips.forEach((clipPlayer) => {\n if (clipPlayer.player.state === 'started') {\n const elapsed = (now() - clipPlayer.playStartTime) * clipPlayer.player.playbackRate;\n clipPlayer.pausedPosition = clipPlayer.pausedPosition + elapsed;\n }\n // Always call stop() to cancel any scheduled playback\n clipPlayer.player.stop();\n });\n\n this.activePlayers = 0;\n }\n\n stop(when?: number): void {\n // Evaluate now() inside function body, not in parameter default (which is evaluated at module load time)\n const stopWhen = when ?? now();\n this.clips.forEach((clipPlayer) => {\n clipPlayer.player.stop(stopWhen);\n clipPlayer.pausedPosition = 0;\n });\n this.activePlayers = 0;\n }\n\n dispose(): void {\n // Clean up effects if cleanup function was provided\n if (this.effectsCleanup) {\n this.effectsCleanup();\n }\n\n // Dispose all clip players\n this.clips.forEach((clipPlayer) => {\n clipPlayer.player.dispose();\n clipPlayer.fadeGain.dispose();\n });\n\n // Dispose shared track nodes\n this.volumeNode.dispose();\n this.panNode.dispose();\n this.muteGain.dispose();\n }\n\n get id(): string {\n return this.track.id;\n }\n\n get duration(): number {\n // Return the end time of the last clip\n if (this.clips.length === 0) return 0;\n const lastClip = this.clips[this.clips.length - 1];\n return lastClip.clipInfo.startTime + lastClip.clipInfo.duration;\n }\n\n get buffer(): AudioBuffer {\n // For backward compatibility, return the first clip's buffer\n return this.clips[0]?.clipInfo.buffer;\n }\n\n get isPlaying(): boolean {\n // Track is playing if any clip is playing\n return this.clips.some((clipPlayer) => clipPlayer.player.state === 'started');\n }\n\n get muted(): boolean {\n return this.track.muted;\n }\n\n get startTime(): number {\n // Return the track's start time from the Track object\n // This is the absolute timeline position where the track starts\n return this.track.startTime;\n }\n\n setOnStopCallback(callback: () => void): void {\n this.onStopCallback = callback;\n }\n}\n","/**\n * Fade utilities for Web Audio API\n *\n * Applies fade in/out envelopes to AudioParam (typically gain)\n * using various curve types.\n */\n\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 'Fades and 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\n function buildPlayout(tracks: ClipTrack[]): void {\n if (playout) {\n playout.dispose();\n }\n\n _playoutGeneration++;\n const generation = _playoutGeneration;\n\n playout = new TonePlayout({\n effects: options?.effects,\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\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 }\n },\n\n setTracks(tracks: ClipTrack[]): void {\n buildPlayout(tracks);\n },\n\n async play(startTime: number, endTime?: number): Promise<void> {\n if (!playout) return;\n await playout.init();\n const duration = endTime !== undefined ? endTime - startTime : undefined;\n playout.play(now(), startTime, duration);\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 dispose(): void {\n playout?.dispose();\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"]}
package/dist/index.mjs CHANGED
@@ -9,14 +9,7 @@ import {
9
9
  } from "tone";
10
10
 
11
11
  // src/ToneTrack.ts
12
- import {
13
- Player,
14
- Volume,
15
- Gain,
16
- Panner,
17
- getDestination,
18
- now
19
- } from "tone";
12
+ import { Player, Volume, Gain, Panner, getDestination, now } from "tone";
20
13
 
21
14
  // src/fades.ts
22
15
  var hasWarned = false;
@@ -135,17 +128,19 @@ var ToneTrack = class {
135
128
  } else {
136
129
  this.muteGain.connect(destination);
137
130
  }
138
- const clipInfos = options.clips || (options.buffer ? [{
139
- buffer: options.buffer,
140
- startTime: 0,
141
- // Legacy: single buffer starts at timeline position 0
142
- duration: options.buffer.duration,
143
- // Legacy: play full buffer duration
144
- offset: 0,
145
- fadeIn: options.track.fadeIn,
146
- fadeOut: options.track.fadeOut,
147
- gain: 1
148
- }] : []);
131
+ const clipInfos = options.clips || (options.buffer ? [
132
+ {
133
+ buffer: options.buffer,
134
+ startTime: 0,
135
+ // Legacy: single buffer starts at timeline position 0
136
+ duration: options.buffer.duration,
137
+ // Legacy: play full buffer duration
138
+ offset: 0,
139
+ fadeIn: options.track.fadeIn,
140
+ fadeOut: options.track.fadeOut,
141
+ gain: 1
142
+ }
143
+ ] : []);
149
144
  this.clips = clipInfos.map((clipInfo) => {
150
145
  const player = new Player({
151
146
  url: clipInfo.buffer,
@@ -172,7 +167,7 @@ var ToneTrack = class {
172
167
  /**
173
168
  * Schedule fade envelopes for a clip at the given start time
174
169
  */
175
- scheduleFades(clipPlayer, clipStartTime, clipOffset = 0) {
170
+ scheduleFades(clipPlayer, clipStartTime2, clipOffset = 0) {
176
171
  const { clipInfo, fadeGain } = clipPlayer;
177
172
  const audioParam = getUnderlyingAudioParam(fadeGain.gain);
178
173
  if (!audioParam) return;
@@ -183,7 +178,7 @@ var ToneTrack = class {
183
178
  if (skipTime <= 0) {
184
179
  applyFadeIn(
185
180
  audioParam,
186
- clipStartTime,
181
+ clipStartTime2,
187
182
  fadeInDuration,
188
183
  clipInfo.fadeIn.type || "linear",
189
184
  0,
@@ -195,7 +190,7 @@ var ToneTrack = class {
195
190
  const startValue = clipInfo.gain * fadeProgress;
196
191
  applyFadeIn(
197
192
  audioParam,
198
- clipStartTime,
193
+ clipStartTime2,
199
194
  remainingFadeDuration,
200
195
  clipInfo.fadeIn.type || "linear",
201
196
  startValue,
@@ -203,13 +198,13 @@ var ToneTrack = class {
203
198
  );
204
199
  }
205
200
  } else {
206
- audioParam.setValueAtTime(clipInfo.gain, clipStartTime);
201
+ audioParam.setValueAtTime(clipInfo.gain, clipStartTime2);
207
202
  }
208
203
  if (clipInfo.fadeOut) {
209
204
  const fadeOutStart = clipInfo.duration - clipInfo.fadeOut.duration;
210
205
  const fadeOutStartInClip = fadeOutStart - skipTime;
211
206
  if (fadeOutStartInClip > 0) {
212
- const absoluteFadeOutStart = clipStartTime + fadeOutStartInClip;
207
+ const absoluteFadeOutStart = clipStartTime2 + fadeOutStartInClip;
213
208
  applyFadeOut(
214
209
  audioParam,
215
210
  absoluteFadeOutStart,
@@ -225,7 +220,7 @@ var ToneTrack = class {
225
220
  const startValue = clipInfo.gain * (1 - fadeProgress);
226
221
  applyFadeOut(
227
222
  audioParam,
228
- clipStartTime,
223
+ clipStartTime2,
229
224
  remainingFadeDuration,
230
225
  clipInfo.fadeOut.type || "linear",
231
226
  startValue,
@@ -615,12 +610,127 @@ function releaseMediaStreamSource(stream) {
615
610
  function hasMediaStreamSource(stream) {
616
611
  return streamSources.has(stream);
617
612
  }
613
+
614
+ // src/TonePlayoutAdapter.ts
615
+ import {
616
+ clipStartTime,
617
+ clipEndTime,
618
+ clipOffsetTime,
619
+ clipDurationTime
620
+ } from "@waveform-playlist/core";
621
+ import { now as now3 } from "tone";
622
+ function createToneAdapter(options) {
623
+ let playout = null;
624
+ let _isPlaying = false;
625
+ let _playoutGeneration = 0;
626
+ function buildPlayout(tracks) {
627
+ if (playout) {
628
+ playout.dispose();
629
+ }
630
+ _playoutGeneration++;
631
+ const generation = _playoutGeneration;
632
+ playout = new TonePlayout({
633
+ effects: options?.effects
634
+ });
635
+ for (const track of tracks) {
636
+ const playableClips = track.clips.filter((c) => c.audioBuffer);
637
+ if (playableClips.length === 0) continue;
638
+ const startTime = Math.min(...playableClips.map(clipStartTime));
639
+ const endTime = Math.max(...playableClips.map(clipEndTime));
640
+ const trackObj = {
641
+ id: track.id,
642
+ name: track.name,
643
+ gain: track.volume,
644
+ muted: track.muted,
645
+ soloed: track.soloed,
646
+ stereoPan: track.pan,
647
+ startTime,
648
+ endTime
649
+ };
650
+ const clipInfos = playableClips.map((clip) => ({
651
+ buffer: clip.audioBuffer,
652
+ startTime: clipStartTime(clip) - startTime,
653
+ duration: clipDurationTime(clip),
654
+ offset: clipOffsetTime(clip),
655
+ fadeIn: clip.fadeIn,
656
+ fadeOut: clip.fadeOut,
657
+ gain: clip.gain
658
+ }));
659
+ playout.addTrack({
660
+ clips: clipInfos,
661
+ track: trackObj,
662
+ effects: track.effects
663
+ });
664
+ }
665
+ playout.applyInitialSoloState();
666
+ playout.setOnPlaybackComplete(() => {
667
+ if (generation === _playoutGeneration) {
668
+ _isPlaying = false;
669
+ }
670
+ });
671
+ }
672
+ return {
673
+ async init() {
674
+ if (playout) {
675
+ await playout.init();
676
+ }
677
+ },
678
+ setTracks(tracks) {
679
+ buildPlayout(tracks);
680
+ },
681
+ async play(startTime, endTime) {
682
+ if (!playout) return;
683
+ await playout.init();
684
+ const duration = endTime !== void 0 ? endTime - startTime : void 0;
685
+ playout.play(now3(), startTime, duration);
686
+ _isPlaying = true;
687
+ },
688
+ pause() {
689
+ playout?.pause();
690
+ _isPlaying = false;
691
+ },
692
+ stop() {
693
+ playout?.stop();
694
+ _isPlaying = false;
695
+ },
696
+ seek(time) {
697
+ playout?.seekTo(time);
698
+ },
699
+ getCurrentTime() {
700
+ return playout?.getCurrentTime() ?? 0;
701
+ },
702
+ isPlaying() {
703
+ return _isPlaying;
704
+ },
705
+ setMasterVolume(volume) {
706
+ playout?.setMasterGain(volume);
707
+ },
708
+ setTrackVolume(trackId, volume) {
709
+ playout?.getTrack(trackId)?.setVolume(volume);
710
+ },
711
+ setTrackMute(trackId, muted) {
712
+ playout?.setMute(trackId, muted);
713
+ },
714
+ setTrackSolo(trackId, soloed) {
715
+ playout?.setSolo(trackId, soloed);
716
+ },
717
+ setTrackPan(trackId, pan) {
718
+ playout?.getTrack(trackId)?.setPan(pan);
719
+ },
720
+ dispose() {
721
+ playout?.dispose();
722
+ playout = null;
723
+ _isPlaying = false;
724
+ }
725
+ };
726
+ }
618
727
  export {
619
728
  TonePlayout,
620
729
  ToneTrack,
621
730
  applyFadeIn,
622
731
  applyFadeOut,
623
732
  closeGlobalAudioContext,
733
+ createToneAdapter,
624
734
  getGlobalAudioContext,
625
735
  getGlobalAudioContextState,
626
736
  getGlobalContext,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/TonePlayout.ts","../src/ToneTrack.ts","../src/fades.ts","../src/audioContext.ts","../src/mediaStreamSourceManager.ts"],"sourcesContent":["// Named imports for tree-shaking\nimport {\n Volume,\n ToneAudioNode,\n getDestination,\n start,\n now,\n getTransport,\n getContext,\n BaseContext,\n} from 'tone';\nimport { ToneTrack, ToneTrackOptions } from './ToneTrack';\n\n// Effects function no longer receives ToneLib - effects should import Tone themselves\nexport type EffectsFunction = (masterGainNode: Volume, destination: ToneAudioNode, isOffline: boolean) => void | (() => void);\n\nexport interface TonePlayoutOptions {\n tracks?: ToneTrack[];\n masterGain?: number;\n effects?: EffectsFunction;\n}\n\nexport class TonePlayout {\n private tracks: Map<string, ToneTrack> = new Map();\n private masterVolume: Volume;\n private isInitialized = false;\n private soloedTracks: Set<string> = new Set();\n private manualMuteState: Map<string, boolean> = new Map();\n private effectsCleanup?: () => void;\n private onPlaybackCompleteCallback?: () => void;\n private activeTracks: Map<string, number> = new Map(); // Map track ID to session ID\n private playbackSessionId: number = 0;\n\n constructor(options: TonePlayoutOptions = {}) {\n this.masterVolume = new Volume(this.gainToDb(options.masterGain ?? 1));\n\n // Setup effects chain if provided, otherwise connect directly to destination\n if (options.effects) {\n const cleanup = options.effects(this.masterVolume, getDestination(), false);\n if (cleanup) {\n this.effectsCleanup = cleanup;\n }\n } else {\n this.masterVolume.toDestination();\n }\n\n if (options.tracks) {\n options.tracks.forEach(track => {\n this.tracks.set(track.id, track);\n // Initialize manual mute state for constructor-provided tracks\n this.manualMuteState.set(track.id, track.muted);\n });\n }\n }\n\n private gainToDb(gain: number): number {\n return 20 * Math.log10(gain);\n }\n\n async init(): Promise<void> {\n if (this.isInitialized) return;\n\n await start();\n this.isInitialized = true;\n }\n\n addTrack(trackOptions: ToneTrackOptions): ToneTrack {\n // Ensure tracks connect to master volume instead of destination\n const optionsWithDestination = {\n ...trackOptions,\n destination: this.masterVolume,\n };\n const toneTrack = new ToneTrack(optionsWithDestination);\n this.tracks.set(toneTrack.id, toneTrack);\n // Initialize manual mute state from track options\n this.manualMuteState.set(toneTrack.id, trackOptions.track.muted ?? false);\n // Initialize solo state from track options\n if (trackOptions.track.soloed) {\n this.soloedTracks.add(toneTrack.id);\n }\n return toneTrack;\n }\n\n /**\n * Apply solo muting after all tracks have been added.\n * Call this after adding all tracks to ensure solo logic is applied correctly.\n */\n applyInitialSoloState(): void {\n this.updateSoloMuting();\n }\n\n removeTrack(trackId: string): void {\n const track = this.tracks.get(trackId);\n if (track) {\n track.dispose();\n this.tracks.delete(trackId);\n this.manualMuteState.delete(trackId);\n this.soloedTracks.delete(trackId);\n }\n }\n\n getTrack(trackId: string): ToneTrack | undefined {\n return this.tracks.get(trackId);\n }\n\n play(when?: number, offset?: number, duration?: number): void {\n if (!this.isInitialized) {\n console.warn('TonePlayout not initialized. Call init() first.');\n return;\n }\n\n // Use now() as default, but call it here after init check (not in function signature)\n const startTime = when ?? now();\n const playbackPosition = offset ?? 0;\n\n // Increment session ID to invalidate old callbacks\n this.playbackSessionId++;\n const currentSessionId = this.playbackSessionId;\n\n // Clear active tracks and set up stop callbacks if duration is specified\n this.activeTracks.clear();\n\n // Play tracks based on their individual start times\n this.tracks.forEach((toneTrack) => {\n const trackStartTime = toneTrack.startTime;\n\n if (playbackPosition >= trackStartTime) {\n // Track should be playing - calculate buffer offset and start immediately\n const bufferOffset = playbackPosition - trackStartTime;\n\n if (duration !== undefined) {\n this.activeTracks.set(toneTrack.id, currentSessionId);\n toneTrack.setOnStopCallback(() => {\n // Only process if this track is still in activeTracks with matching session ID\n if (this.activeTracks.get(toneTrack.id) === currentSessionId) {\n this.activeTracks.delete(toneTrack.id);\n if (this.activeTracks.size === 0 && this.onPlaybackCompleteCallback) {\n this.onPlaybackCompleteCallback();\n }\n }\n });\n }\n\n toneTrack.play(startTime, bufferOffset, duration);\n } else {\n // Track should start later - schedule it to start when playback reaches its start time\n const delay = trackStartTime - playbackPosition;\n\n if (duration !== undefined) {\n this.activeTracks.set(toneTrack.id, currentSessionId);\n toneTrack.setOnStopCallback(() => {\n // Only process if this track is still in activeTracks with matching session ID\n if (this.activeTracks.get(toneTrack.id) === currentSessionId) {\n this.activeTracks.delete(toneTrack.id);\n if (this.activeTracks.size === 0 && this.onPlaybackCompleteCallback) {\n this.onPlaybackCompleteCallback();\n }\n }\n });\n }\n\n toneTrack.play(startTime + delay, 0, duration);\n }\n });\n\n // Start transport\n if (offset !== undefined) {\n // Explicit offset provided - seek to that position\n getTransport().start(startTime, offset);\n } else {\n // No offset - resume from pause (Transport resumes from current position)\n getTransport().start(startTime);\n }\n }\n\n pause(): void {\n getTransport().pause();\n this.tracks.forEach(track => {\n track.pause();\n });\n }\n\n stop(): void {\n getTransport().stop();\n this.tracks.forEach(track => {\n track.stop();\n });\n }\n\n setMasterGain(gain: number): void {\n this.masterVolume.volume.value = this.gainToDb(gain);\n }\n\n setSolo(trackId: string, soloed: boolean): void {\n const track = this.tracks.get(trackId);\n if (track) {\n track.setSolo(soloed);\n if (soloed) {\n this.soloedTracks.add(trackId);\n } else {\n this.soloedTracks.delete(trackId);\n }\n\n // Update mute state of all tracks based on solo logic\n this.updateSoloMuting();\n }\n }\n\n private updateSoloMuting(): void {\n const hasSoloedTracks = this.soloedTracks.size > 0;\n\n this.tracks.forEach((track, id) => {\n if (hasSoloedTracks) {\n // If there are soloed tracks, mute all non-soloed tracks\n if (!this.soloedTracks.has(id)) {\n track.setMute(true);\n } else {\n // Restore manual mute state for soloed tracks\n const manuallyMuted = this.manualMuteState.get(id) ?? false;\n track.setMute(manuallyMuted);\n }\n } else {\n // No soloed tracks, restore original manual mute state for all tracks\n const manuallyMuted = this.manualMuteState.get(id) ?? false;\n track.setMute(manuallyMuted);\n }\n });\n }\n\n setMute(trackId: string, muted: boolean): void {\n const track = this.tracks.get(trackId);\n if (track) {\n // Store the manual mute state\n this.manualMuteState.set(trackId, muted);\n track.setMute(muted);\n }\n }\n\n getCurrentTime(): number {\n return getTransport().seconds;\n }\n\n seekTo(time: number): void {\n getTransport().seconds = time;\n }\n\n dispose(): void {\n this.tracks.forEach(track => {\n track.dispose();\n });\n this.tracks.clear();\n\n // Clean up effects if cleanup function was provided\n if (this.effectsCleanup) {\n this.effectsCleanup();\n }\n\n this.masterVolume.dispose();\n }\n\n get context(): BaseContext {\n return getContext();\n }\n\n get sampleRate(): number {\n return getContext().sampleRate;\n }\n\n setOnPlaybackComplete(callback: () => void): void {\n this.onPlaybackCompleteCallback = callback;\n }\n}\n","// Named imports for tree-shaking\nimport {\n Player,\n Volume,\n Gain,\n Panner,\n ToneAudioNode,\n getDestination,\n now,\n} from 'tone';\nimport { Track, type Fade } from '@waveform-playlist/core';\nimport { applyFadeIn, applyFadeOut, getUnderlyingAudioParam } from './fades';\n\n// Effects function no longer receives ToneLib - effects should import Tone themselves\nexport type TrackEffectsFunction = (graphEnd: Gain, masterGainNode: ToneAudioNode, isOffline: boolean) => void | (() => void);\n\nexport interface ClipInfo {\n buffer: AudioBuffer;\n startTime: number; // When this clip starts in the track timeline (seconds)\n duration: number; // How long this clip plays (seconds)\n offset: number; // Where to start playing within the buffer (seconds)\n fadeIn?: Fade;\n fadeOut?: Fade;\n gain: number; // Clip-level gain\n}\n\nexport interface ToneTrackOptions {\n buffer?: AudioBuffer; // Legacy: single buffer (deprecated, use clips instead)\n clips?: ClipInfo[]; // Modern: array of clips\n track: Track;\n effects?: TrackEffectsFunction;\n destination?: ToneAudioNode;\n}\n\ninterface ClipPlayer {\n player: Player;\n clipInfo: ClipInfo;\n fadeGain: Gain;\n pausedPosition: number;\n playStartTime: number;\n}\n\nexport class ToneTrack {\n private clips: ClipPlayer[]; // Array of clip players\n private volumeNode: Volume;\n private panNode: Panner;\n private muteGain: Gain;\n private track: Track;\n private effectsCleanup?: () => void;\n private onStopCallback?: () => void;\n private activePlayers: number = 0; // Count of currently playing clips\n\n constructor(options: ToneTrackOptions) {\n this.track = options.track;\n\n // Create shared track-level nodes\n this.volumeNode = new Volume(this.gainToDb(options.track.gain));\n this.panNode = new Panner(options.track.stereoPan);\n this.muteGain = new Gain(options.track.muted ? 0 : 1);\n\n // Connect to destination or apply effects chain\n const destination = options.destination || getDestination();\n if (options.effects) {\n const cleanup = options.effects(this.muteGain, destination, false);\n if (cleanup) {\n this.effectsCleanup = cleanup;\n }\n } else {\n this.muteGain.connect(destination);\n }\n\n // Create clips array - support both legacy single buffer and modern clips array\n const clipInfos: ClipInfo[] = options.clips || (options.buffer ? [{\n buffer: options.buffer,\n startTime: 0, // Legacy: single buffer starts at timeline position 0\n duration: options.buffer.duration, // Legacy: play full buffer duration\n offset: 0,\n fadeIn: options.track.fadeIn,\n fadeOut: options.track.fadeOut,\n gain: 1,\n }] : []);\n\n // Create ClipPlayer for each clip\n this.clips = clipInfos.map(clipInfo => {\n const player = new Player({\n url: clipInfo.buffer,\n loop: false,\n onstop: () => {\n this.activePlayers--;\n if (this.activePlayers === 0 && this.onStopCallback) {\n this.onStopCallback();\n }\n },\n });\n\n const fadeGain = new Gain(clipInfo.gain);\n\n // Chain: Player -> FadeGain -> Volume -> Pan -> MuteGain\n player.connect(fadeGain);\n fadeGain.chain(this.volumeNode, this.panNode, this.muteGain);\n\n // Note: Fades are scheduled in play() method, not here in constructor,\n // because AudioParam automation requires absolute AudioContext time\n\n return {\n player,\n clipInfo,\n fadeGain,\n pausedPosition: 0,\n playStartTime: 0,\n };\n });\n }\n\n /**\n * Schedule fade envelopes for a clip at the given start time\n */\n private scheduleFades(clipPlayer: ClipPlayer, clipStartTime: number, clipOffset: number = 0): void {\n const { clipInfo, fadeGain } = clipPlayer;\n const audioParam = getUnderlyingAudioParam(fadeGain.gain);\n if (!audioParam) return;\n\n // Cancel any previous automation\n audioParam.cancelScheduledValues(0);\n\n // Calculate how much of the clip we're skipping (for seeking)\n const skipTime = clipOffset - clipInfo.offset;\n\n // Apply fade in if it exists and we haven't skipped past it\n if (clipInfo.fadeIn && skipTime < clipInfo.fadeIn.duration) {\n const fadeInDuration = clipInfo.fadeIn.duration;\n\n if (skipTime <= 0) {\n // Starting from the beginning - full fade in\n applyFadeIn(\n audioParam,\n clipStartTime,\n fadeInDuration,\n clipInfo.fadeIn.type || 'linear',\n 0,\n clipInfo.gain\n );\n } else {\n // Starting partway through fade in - calculate partial fade\n const remainingFadeDuration = fadeInDuration - skipTime;\n const fadeProgress = skipTime / fadeInDuration;\n const startValue = clipInfo.gain * fadeProgress; // Approximate current fade value\n applyFadeIn(\n audioParam,\n clipStartTime,\n remainingFadeDuration,\n clipInfo.fadeIn.type || 'linear',\n startValue,\n clipInfo.gain\n );\n }\n } else {\n // No fade in or skipped past it - set to full gain\n audioParam.setValueAtTime(clipInfo.gain, clipStartTime);\n }\n\n // Apply fade out if it exists\n if (clipInfo.fadeOut) {\n const fadeOutStart = clipInfo.duration - clipInfo.fadeOut.duration;\n const fadeOutStartInClip = fadeOutStart - skipTime; // Relative to where we're starting\n\n if (fadeOutStartInClip > 0) {\n // Fade out hasn't started yet\n const absoluteFadeOutStart = clipStartTime + fadeOutStartInClip;\n applyFadeOut(\n audioParam,\n absoluteFadeOutStart,\n clipInfo.fadeOut.duration,\n clipInfo.fadeOut.type || 'linear',\n clipInfo.gain,\n 0\n );\n } else if (fadeOutStartInClip > -clipInfo.fadeOut.duration) {\n // We're starting partway through the fade out\n const elapsedFadeOut = -fadeOutStartInClip;\n const remainingFadeDuration = clipInfo.fadeOut.duration - elapsedFadeOut;\n const fadeProgress = elapsedFadeOut / clipInfo.fadeOut.duration;\n const startValue = clipInfo.gain * (1 - fadeProgress); // Approximate current fade value\n applyFadeOut(\n audioParam,\n clipStartTime,\n remainingFadeDuration,\n clipInfo.fadeOut.type || 'linear',\n startValue,\n 0\n );\n }\n // If fadeOutStartInClip <= -duration, we've skipped past the entire fade out\n }\n }\n\n private gainToDb(gain: number): number {\n return 20 * Math.log10(gain);\n }\n\n setVolume(gain: number): void {\n this.track.gain = gain;\n this.volumeNode.volume.value = this.gainToDb(gain);\n }\n\n setPan(pan: number): void {\n this.track.stereoPan = pan;\n this.panNode.pan.value = pan;\n }\n\n setMute(muted: boolean): void {\n this.track.muted = muted;\n 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 play(when?: number, offset: number = 0, duration?: number): void {\n // Recreate all players to avoid Tone.js StateTimeline issues when seeking\n // See: https://github.com/Tonejs/Tone.js/issues/1076\n // The Player's internal StateTimeline doesn't properly clear on stop(),\n // so we need fresh Player instances when rescheduling\n this.clips.forEach(clipPlayer => {\n // Dispose old player\n clipPlayer.player.stop();\n clipPlayer.player.disconnect();\n clipPlayer.player.dispose();\n\n // Create new player with same buffer\n const newPlayer = new Player({\n url: clipPlayer.clipInfo.buffer,\n loop: false,\n onstop: () => {\n this.activePlayers--;\n if (this.activePlayers === 0 && this.onStopCallback) {\n this.onStopCallback();\n }\n },\n });\n\n // Reconnect to audio graph\n newPlayer.connect(clipPlayer.fadeGain);\n\n // Update reference\n clipPlayer.player = newPlayer;\n clipPlayer.pausedPosition = 0;\n });\n\n this.activePlayers = 0;\n // Play each clip that should be active at this offset\n this.clips.forEach(clipPlayer => {\n const { player, clipInfo } = clipPlayer;\n\n // Calculate absolute timeline position we're starting from\n const playbackPosition = offset;\n\n // Check if this clip should be playing at this position\n const clipStart = clipInfo.startTime;\n const clipEnd = clipInfo.startTime + clipInfo.duration;\n\n if (playbackPosition < clipEnd) {\n // This clip should play\n this.activePlayers++;\n\n // Get fresh now() for each clip to avoid \"time in the past\" errors\n // This is important when seeking during playback - time passes between scheduling clips\n const currentTime = when ?? now();\n clipPlayer.playStartTime = currentTime;\n\n if (playbackPosition >= clipStart) {\n // We're starting in the middle of this clip\n const clipOffset = playbackPosition - clipStart + clipInfo.offset;\n const remainingDuration = clipInfo.duration - (playbackPosition - clipStart);\n const clipDuration = duration ? Math.min(duration, remainingDuration) : remainingDuration;\n\n clipPlayer.pausedPosition = clipOffset;\n // Schedule fades at the actual playback start time\n this.scheduleFades(clipPlayer, currentTime, clipOffset);\n player.start(currentTime, clipOffset, clipDuration);\n } else {\n // This clip starts later - schedule it\n const delay = clipStart - playbackPosition;\n const clipDuration = duration ? Math.min(duration - delay, clipInfo.duration) : clipInfo.duration;\n\n if (delay < (duration ?? Infinity)) {\n clipPlayer.pausedPosition = clipInfo.offset;\n // Schedule fades at the delayed start time\n this.scheduleFades(clipPlayer, currentTime + delay, clipInfo.offset);\n player.start(currentTime + delay, clipInfo.offset, clipDuration);\n } else {\n this.activePlayers--;\n }\n }\n }\n });\n }\n\n pause(): void {\n // Stop all clips - both started and scheduled\n // Scheduled clips have state 'stopped' but still need to be cancelled\n this.clips.forEach(clipPlayer => {\n if (clipPlayer.player.state === 'started') {\n const elapsed = (now() - clipPlayer.playStartTime) * clipPlayer.player.playbackRate;\n clipPlayer.pausedPosition = clipPlayer.pausedPosition + elapsed;\n }\n // Always call stop() to cancel any scheduled playback\n clipPlayer.player.stop();\n });\n\n this.activePlayers = 0;\n }\n\n stop(when?: number): void {\n // Evaluate now() inside function body, not in parameter default (which is evaluated at module load time)\n const stopWhen = when ?? now();\n this.clips.forEach(clipPlayer => {\n clipPlayer.player.stop(stopWhen);\n clipPlayer.pausedPosition = 0;\n });\n this.activePlayers = 0;\n }\n\n dispose(): void {\n // Clean up effects if cleanup function was provided\n if (this.effectsCleanup) {\n this.effectsCleanup();\n }\n\n // Dispose all clip players\n this.clips.forEach(clipPlayer => {\n clipPlayer.player.dispose();\n clipPlayer.fadeGain.dispose();\n });\n\n // Dispose shared track nodes\n this.volumeNode.dispose();\n this.panNode.dispose();\n this.muteGain.dispose();\n }\n\n get id(): string {\n return this.track.id;\n }\n\n get duration(): number {\n // Return the end time of the last clip\n if (this.clips.length === 0) return 0;\n const lastClip = this.clips[this.clips.length - 1];\n return lastClip.clipInfo.startTime + lastClip.clipInfo.duration;\n }\n\n get buffer(): AudioBuffer {\n // For backward compatibility, return the first clip's buffer\n return this.clips[0]?.clipInfo.buffer;\n }\n\n get isPlaying(): boolean {\n // Track is playing if any clip is playing\n return this.clips.some(clipPlayer => clipPlayer.player.state === 'started');\n }\n\n get muted(): boolean {\n return this.track.muted;\n }\n\n get startTime(): number {\n // Return the track's start time from the Track object\n // This is the absolute timeline position where the track starts\n return this.track.startTime;\n }\n\n setOnStopCallback(callback: () => void): void {\n this.onStopCallback = callback;\n }\n}\n","/**\n * Fade utilities for Web Audio API\n *\n * Applies fade in/out envelopes to AudioParam (typically gain)\n * using various curve types.\n */\n\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 'Fades and 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(\n stream: MediaStream\n): MediaStreamAudioSourceNode {\n // Return existing source if we have one for this stream\n if (streamSources.has(stream)) {\n return streamSources.get(stream)!;\n }\n\n // Create new source using Tone.js's shared context for cross-browser compatibility\n const context = getContext();\n const source = context.createMediaStreamSource(stream);\n streamSources.set(stream, source);\n\n // Set up cleanup when stream ends\n const cleanup = () => {\n source.disconnect();\n streamSources.delete(stream);\n streamCleanupHandlers.delete(stream);\n\n // Remove event listener\n stream.removeEventListener('ended', cleanup);\n stream.removeEventListener('inactive', cleanup);\n };\n\n streamCleanupHandlers.set(stream, cleanup);\n\n // Clean up when stream ends or becomes inactive\n stream.addEventListener('ended', cleanup);\n stream.addEventListener('inactive', cleanup);\n\n return source;\n}\n\n/**\n * Manually release a MediaStreamSource\n *\n * Normally you don't need to call this - cleanup happens automatically\n * when the stream ends. Only call this if you need to force cleanup.\n *\n * @param stream - The MediaStream to release the source for\n */\nexport function releaseMediaStreamSource(stream: MediaStream): void {\n const cleanup = streamCleanupHandlers.get(stream);\n if (cleanup) {\n cleanup();\n }\n}\n\n/**\n * Check if a MediaStreamSource exists for the given stream\n *\n * @param stream - The MediaStream to check\n * @returns true if a source exists for this stream\n */\nexport function hasMediaStreamSource(stream: MediaStream): boolean {\n return streamSources.has(stream);\n}\n"],"mappings":"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","names":["Volume","getDestination","now","Volume","getDestination","now","getContext"]}
1
+ {"version":3,"sources":["../src/TonePlayout.ts","../src/ToneTrack.ts","../src/fades.ts","../src/audioContext.ts","../src/mediaStreamSourceManager.ts","../src/TonePlayoutAdapter.ts"],"sourcesContent":["// Named imports for tree-shaking\nimport {\n Volume,\n ToneAudioNode,\n getDestination,\n start,\n now,\n getTransport,\n getContext,\n BaseContext,\n} from 'tone';\nimport { ToneTrack, ToneTrackOptions } from './ToneTrack';\n\n// Effects function no longer receives ToneLib - effects should import Tone themselves\nexport type EffectsFunction = (\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 activeTracks: Map<string, number> = new Map(); // Map track ID to session ID\n private playbackSessionId: number = 0;\n\n constructor(options: TonePlayoutOptions = {}) {\n this.masterVolume = new Volume(this.gainToDb(options.masterGain ?? 1));\n\n // Setup effects chain if provided, otherwise connect directly to destination\n if (options.effects) {\n const cleanup = options.effects(this.masterVolume, getDestination(), false);\n if (cleanup) {\n this.effectsCleanup = cleanup;\n }\n } else {\n this.masterVolume.toDestination();\n }\n\n if (options.tracks) {\n options.tracks.forEach((track) => {\n this.tracks.set(track.id, track);\n // Initialize manual mute state for constructor-provided tracks\n this.manualMuteState.set(track.id, track.muted);\n });\n }\n }\n\n private gainToDb(gain: number): number {\n return 20 * Math.log10(gain);\n }\n\n async init(): Promise<void> {\n if (this.isInitialized) return;\n\n await start();\n this.isInitialized = true;\n }\n\n addTrack(trackOptions: ToneTrackOptions): ToneTrack {\n // Ensure tracks connect to master volume instead of destination\n const optionsWithDestination = {\n ...trackOptions,\n destination: this.masterVolume,\n };\n const toneTrack = new ToneTrack(optionsWithDestination);\n this.tracks.set(toneTrack.id, toneTrack);\n // Initialize manual mute state from track options\n this.manualMuteState.set(toneTrack.id, trackOptions.track.muted ?? false);\n // Initialize solo state from track options\n if (trackOptions.track.soloed) {\n this.soloedTracks.add(toneTrack.id);\n }\n return toneTrack;\n }\n\n /**\n * Apply solo muting after all tracks have been added.\n * Call this after adding all tracks to ensure solo logic is applied correctly.\n */\n applyInitialSoloState(): void {\n this.updateSoloMuting();\n }\n\n removeTrack(trackId: string): void {\n const track = this.tracks.get(trackId);\n if (track) {\n track.dispose();\n this.tracks.delete(trackId);\n this.manualMuteState.delete(trackId);\n this.soloedTracks.delete(trackId);\n }\n }\n\n getTrack(trackId: string): ToneTrack | undefined {\n return this.tracks.get(trackId);\n }\n\n play(when?: number, offset?: number, duration?: number): void {\n if (!this.isInitialized) {\n console.warn('TonePlayout not initialized. Call init() first.');\n return;\n }\n\n // Use now() as default, but call it here after init check (not in function signature)\n const startTime = when ?? now();\n const playbackPosition = offset ?? 0;\n\n // Increment session ID to invalidate old callbacks\n this.playbackSessionId++;\n const currentSessionId = this.playbackSessionId;\n\n // Clear active tracks and set up stop callbacks if duration is specified\n this.activeTracks.clear();\n\n // Play tracks based on their individual start times\n this.tracks.forEach((toneTrack) => {\n const trackStartTime = toneTrack.startTime;\n\n if (playbackPosition >= trackStartTime) {\n // Track should be playing - calculate buffer offset and start immediately\n const bufferOffset = playbackPosition - trackStartTime;\n\n if (duration !== undefined) {\n this.activeTracks.set(toneTrack.id, currentSessionId);\n toneTrack.setOnStopCallback(() => {\n // Only process if this track is still in activeTracks with matching session ID\n if (this.activeTracks.get(toneTrack.id) === currentSessionId) {\n this.activeTracks.delete(toneTrack.id);\n if (this.activeTracks.size === 0 && this.onPlaybackCompleteCallback) {\n this.onPlaybackCompleteCallback();\n }\n }\n });\n }\n\n toneTrack.play(startTime, bufferOffset, duration);\n } else {\n // Track should start later - schedule it to start when playback reaches its start time\n const delay = trackStartTime - playbackPosition;\n\n if (duration !== undefined) {\n this.activeTracks.set(toneTrack.id, currentSessionId);\n toneTrack.setOnStopCallback(() => {\n // Only process if this track is still in activeTracks with matching session ID\n if (this.activeTracks.get(toneTrack.id) === currentSessionId) {\n this.activeTracks.delete(toneTrack.id);\n if (this.activeTracks.size === 0 && this.onPlaybackCompleteCallback) {\n this.onPlaybackCompleteCallback();\n }\n }\n });\n }\n\n toneTrack.play(startTime + delay, 0, duration);\n }\n });\n\n // Start transport\n if (offset !== undefined) {\n // Explicit offset provided - seek to that position\n getTransport().start(startTime, offset);\n } else {\n // No offset - resume from pause (Transport resumes from current position)\n getTransport().start(startTime);\n }\n }\n\n pause(): void {\n getTransport().pause();\n this.tracks.forEach((track) => {\n track.pause();\n });\n }\n\n stop(): void {\n getTransport().stop();\n this.tracks.forEach((track) => {\n track.stop();\n });\n }\n\n setMasterGain(gain: number): void {\n this.masterVolume.volume.value = this.gainToDb(gain);\n }\n\n setSolo(trackId: string, soloed: boolean): void {\n const track = this.tracks.get(trackId);\n if (track) {\n track.setSolo(soloed);\n if (soloed) {\n this.soloedTracks.add(trackId);\n } else {\n this.soloedTracks.delete(trackId);\n }\n\n // Update mute state of all tracks based on solo logic\n this.updateSoloMuting();\n }\n }\n\n private updateSoloMuting(): void {\n const hasSoloedTracks = this.soloedTracks.size > 0;\n\n this.tracks.forEach((track, id) => {\n if (hasSoloedTracks) {\n // If there are soloed tracks, mute all non-soloed tracks\n if (!this.soloedTracks.has(id)) {\n track.setMute(true);\n } else {\n // Restore manual mute state for soloed tracks\n const manuallyMuted = this.manualMuteState.get(id) ?? false;\n track.setMute(manuallyMuted);\n }\n } else {\n // No soloed tracks, restore original manual mute state for all tracks\n const manuallyMuted = this.manualMuteState.get(id) ?? false;\n track.setMute(manuallyMuted);\n }\n });\n }\n\n setMute(trackId: string, muted: boolean): void {\n const track = this.tracks.get(trackId);\n if (track) {\n // Store the manual mute state\n this.manualMuteState.set(trackId, muted);\n track.setMute(muted);\n }\n }\n\n getCurrentTime(): number {\n return getTransport().seconds;\n }\n\n seekTo(time: number): void {\n getTransport().seconds = time;\n }\n\n dispose(): void {\n this.tracks.forEach((track) => {\n track.dispose();\n });\n this.tracks.clear();\n\n // Clean up effects if cleanup function was provided\n if (this.effectsCleanup) {\n this.effectsCleanup();\n }\n\n this.masterVolume.dispose();\n }\n\n get context(): BaseContext {\n return getContext();\n }\n\n get sampleRate(): number {\n return getContext().sampleRate;\n }\n\n setOnPlaybackComplete(callback: () => void): void {\n this.onPlaybackCompleteCallback = callback;\n }\n}\n","// Named imports for tree-shaking\nimport { Player, Volume, Gain, Panner, ToneAudioNode, getDestination, now } from 'tone';\nimport { Track, type Fade } from '@waveform-playlist/core';\nimport { applyFadeIn, applyFadeOut, getUnderlyingAudioParam } from './fades';\n\n// Effects function no longer receives ToneLib - effects should import Tone themselves\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\ninterface ClipPlayer {\n player: Player;\n clipInfo: ClipInfo;\n fadeGain: Gain;\n pausedPosition: number;\n playStartTime: number;\n}\n\nexport class ToneTrack {\n private clips: ClipPlayer[]; // Array of clip players\n private volumeNode: Volume;\n private panNode: Panner;\n private muteGain: Gain;\n private track: Track;\n private effectsCleanup?: () => void;\n private onStopCallback?: () => void;\n private activePlayers: number = 0; // Count of currently playing clips\n\n constructor(options: ToneTrackOptions) {\n this.track = options.track;\n\n // Create shared track-level nodes\n this.volumeNode = new Volume(this.gainToDb(options.track.gain));\n this.panNode = new Panner(options.track.stereoPan);\n this.muteGain = new Gain(options.track.muted ? 0 : 1);\n\n // Connect to destination or apply effects chain\n const destination = options.destination || getDestination();\n if (options.effects) {\n const cleanup = options.effects(this.muteGain, destination, false);\n if (cleanup) {\n this.effectsCleanup = cleanup;\n }\n } else {\n this.muteGain.connect(destination);\n }\n\n // Create clips array - support both legacy single buffer and modern clips array\n const clipInfos: ClipInfo[] =\n options.clips ||\n (options.buffer\n ? [\n {\n buffer: options.buffer,\n startTime: 0, // Legacy: single buffer starts at timeline position 0\n duration: options.buffer.duration, // Legacy: play full buffer duration\n offset: 0,\n fadeIn: options.track.fadeIn,\n fadeOut: options.track.fadeOut,\n gain: 1,\n },\n ]\n : []);\n\n // Create ClipPlayer for each clip\n this.clips = clipInfos.map((clipInfo) => {\n const player = new Player({\n url: clipInfo.buffer,\n loop: false,\n onstop: () => {\n this.activePlayers--;\n if (this.activePlayers === 0 && this.onStopCallback) {\n this.onStopCallback();\n }\n },\n });\n\n const fadeGain = new Gain(clipInfo.gain);\n\n // Chain: Player -> FadeGain -> Volume -> Pan -> MuteGain\n player.connect(fadeGain);\n fadeGain.chain(this.volumeNode, this.panNode, this.muteGain);\n\n // Note: Fades are scheduled in play() method, not here in constructor,\n // because AudioParam automation requires absolute AudioContext time\n\n return {\n player,\n clipInfo,\n fadeGain,\n pausedPosition: 0,\n playStartTime: 0,\n };\n });\n }\n\n /**\n * Schedule fade envelopes for a clip at the given start time\n */\n private scheduleFades(\n clipPlayer: ClipPlayer,\n clipStartTime: number,\n clipOffset: number = 0\n ): void {\n const { clipInfo, fadeGain } = clipPlayer;\n const audioParam = getUnderlyingAudioParam(fadeGain.gain);\n if (!audioParam) return;\n\n // Cancel any previous automation\n audioParam.cancelScheduledValues(0);\n\n // Calculate how much of the clip we're skipping (for seeking)\n const skipTime = clipOffset - clipInfo.offset;\n\n // Apply fade in if it exists and we haven't skipped past it\n if (clipInfo.fadeIn && skipTime < clipInfo.fadeIn.duration) {\n const fadeInDuration = clipInfo.fadeIn.duration;\n\n if (skipTime <= 0) {\n // Starting from the beginning - full fade in\n applyFadeIn(\n audioParam,\n clipStartTime,\n fadeInDuration,\n clipInfo.fadeIn.type || 'linear',\n 0,\n clipInfo.gain\n );\n } else {\n // Starting partway through fade in - calculate partial fade\n const remainingFadeDuration = fadeInDuration - skipTime;\n const fadeProgress = skipTime / fadeInDuration;\n const startValue = clipInfo.gain * fadeProgress; // Approximate current fade value\n applyFadeIn(\n audioParam,\n clipStartTime,\n remainingFadeDuration,\n clipInfo.fadeIn.type || 'linear',\n startValue,\n clipInfo.gain\n );\n }\n } else {\n // No fade in or skipped past it - set to full gain\n audioParam.setValueAtTime(clipInfo.gain, clipStartTime);\n }\n\n // Apply fade out if it exists\n if (clipInfo.fadeOut) {\n const fadeOutStart = clipInfo.duration - clipInfo.fadeOut.duration;\n const fadeOutStartInClip = fadeOutStart - skipTime; // Relative to where we're starting\n\n if (fadeOutStartInClip > 0) {\n // Fade out hasn't started yet\n const absoluteFadeOutStart = clipStartTime + fadeOutStartInClip;\n applyFadeOut(\n audioParam,\n absoluteFadeOutStart,\n clipInfo.fadeOut.duration,\n clipInfo.fadeOut.type || 'linear',\n clipInfo.gain,\n 0\n );\n } else if (fadeOutStartInClip > -clipInfo.fadeOut.duration) {\n // We're starting partway through the fade out\n const elapsedFadeOut = -fadeOutStartInClip;\n const remainingFadeDuration = clipInfo.fadeOut.duration - elapsedFadeOut;\n const fadeProgress = elapsedFadeOut / clipInfo.fadeOut.duration;\n const startValue = clipInfo.gain * (1 - fadeProgress); // Approximate current fade value\n applyFadeOut(\n audioParam,\n clipStartTime,\n remainingFadeDuration,\n clipInfo.fadeOut.type || 'linear',\n startValue,\n 0\n );\n }\n // If fadeOutStartInClip <= -duration, we've skipped past the entire fade out\n }\n }\n\n private gainToDb(gain: number): number {\n return 20 * Math.log10(gain);\n }\n\n setVolume(gain: number): void {\n this.track.gain = gain;\n this.volumeNode.volume.value = this.gainToDb(gain);\n }\n\n setPan(pan: number): void {\n this.track.stereoPan = pan;\n this.panNode.pan.value = pan;\n }\n\n setMute(muted: boolean): void {\n this.track.muted = muted;\n 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 play(when?: number, offset: number = 0, duration?: number): void {\n // Recreate all players to avoid Tone.js StateTimeline issues when seeking\n // See: https://github.com/Tonejs/Tone.js/issues/1076\n // The Player's internal StateTimeline doesn't properly clear on stop(),\n // so we need fresh Player instances when rescheduling\n this.clips.forEach((clipPlayer) => {\n // Dispose old player\n clipPlayer.player.stop();\n clipPlayer.player.disconnect();\n clipPlayer.player.dispose();\n\n // Create new player with same buffer\n const newPlayer = new Player({\n url: clipPlayer.clipInfo.buffer,\n loop: false,\n onstop: () => {\n this.activePlayers--;\n if (this.activePlayers === 0 && this.onStopCallback) {\n this.onStopCallback();\n }\n },\n });\n\n // Reconnect to audio graph\n newPlayer.connect(clipPlayer.fadeGain);\n\n // Update reference\n clipPlayer.player = newPlayer;\n clipPlayer.pausedPosition = 0;\n });\n\n this.activePlayers = 0;\n // Play each clip that should be active at this offset\n this.clips.forEach((clipPlayer) => {\n const { player, clipInfo } = clipPlayer;\n\n // Calculate absolute timeline position we're starting from\n const playbackPosition = offset;\n\n // Check if this clip should be playing at this position\n const clipStart = clipInfo.startTime;\n const clipEnd = clipInfo.startTime + clipInfo.duration;\n\n if (playbackPosition < clipEnd) {\n // This clip should play\n this.activePlayers++;\n\n // Get fresh now() for each clip to avoid \"time in the past\" errors\n // This is important when seeking during playback - time passes between scheduling clips\n const currentTime = when ?? now();\n clipPlayer.playStartTime = currentTime;\n\n if (playbackPosition >= clipStart) {\n // We're starting in the middle of this clip\n const clipOffset = playbackPosition - clipStart + clipInfo.offset;\n const remainingDuration = clipInfo.duration - (playbackPosition - clipStart);\n const clipDuration = duration ? Math.min(duration, remainingDuration) : remainingDuration;\n\n clipPlayer.pausedPosition = clipOffset;\n // Schedule fades at the actual playback start time\n this.scheduleFades(clipPlayer, currentTime, clipOffset);\n player.start(currentTime, clipOffset, clipDuration);\n } else {\n // This clip starts later - schedule it\n const delay = clipStart - playbackPosition;\n const clipDuration = duration\n ? Math.min(duration - delay, clipInfo.duration)\n : clipInfo.duration;\n\n if (delay < (duration ?? Infinity)) {\n clipPlayer.pausedPosition = clipInfo.offset;\n // Schedule fades at the delayed start time\n this.scheduleFades(clipPlayer, currentTime + delay, clipInfo.offset);\n player.start(currentTime + delay, clipInfo.offset, clipDuration);\n } else {\n this.activePlayers--;\n }\n }\n }\n });\n }\n\n pause(): void {\n // Stop all clips - both started and scheduled\n // Scheduled clips have state 'stopped' but still need to be cancelled\n this.clips.forEach((clipPlayer) => {\n if (clipPlayer.player.state === 'started') {\n const elapsed = (now() - clipPlayer.playStartTime) * clipPlayer.player.playbackRate;\n clipPlayer.pausedPosition = clipPlayer.pausedPosition + elapsed;\n }\n // Always call stop() to cancel any scheduled playback\n clipPlayer.player.stop();\n });\n\n this.activePlayers = 0;\n }\n\n stop(when?: number): void {\n // Evaluate now() inside function body, not in parameter default (which is evaluated at module load time)\n const stopWhen = when ?? now();\n this.clips.forEach((clipPlayer) => {\n clipPlayer.player.stop(stopWhen);\n clipPlayer.pausedPosition = 0;\n });\n this.activePlayers = 0;\n }\n\n dispose(): void {\n // Clean up effects if cleanup function was provided\n if (this.effectsCleanup) {\n this.effectsCleanup();\n }\n\n // Dispose all clip players\n this.clips.forEach((clipPlayer) => {\n clipPlayer.player.dispose();\n clipPlayer.fadeGain.dispose();\n });\n\n // Dispose shared track nodes\n this.volumeNode.dispose();\n this.panNode.dispose();\n this.muteGain.dispose();\n }\n\n get id(): string {\n return this.track.id;\n }\n\n get duration(): number {\n // Return the end time of the last clip\n if (this.clips.length === 0) return 0;\n const lastClip = this.clips[this.clips.length - 1];\n return lastClip.clipInfo.startTime + lastClip.clipInfo.duration;\n }\n\n get buffer(): AudioBuffer {\n // For backward compatibility, return the first clip's buffer\n return this.clips[0]?.clipInfo.buffer;\n }\n\n get isPlaying(): boolean {\n // Track is playing if any clip is playing\n return this.clips.some((clipPlayer) => clipPlayer.player.state === 'started');\n }\n\n get muted(): boolean {\n return this.track.muted;\n }\n\n get startTime(): number {\n // Return the track's start time from the Track object\n // This is the absolute timeline position where the track starts\n return this.track.startTime;\n }\n\n setOnStopCallback(callback: () => void): void {\n this.onStopCallback = callback;\n }\n}\n","/**\n * Fade utilities for Web Audio API\n *\n * Applies fade in/out envelopes to AudioParam (typically gain)\n * using various curve types.\n */\n\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 'Fades and 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\n function buildPlayout(tracks: ClipTrack[]): void {\n if (playout) {\n playout.dispose();\n }\n\n _playoutGeneration++;\n const generation = _playoutGeneration;\n\n playout = new TonePlayout({\n effects: options?.effects,\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\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 }\n },\n\n setTracks(tracks: ClipTrack[]): void {\n buildPlayout(tracks);\n },\n\n async play(startTime: number, endTime?: number): Promise<void> {\n if (!playout) return;\n await playout.init();\n const duration = endTime !== undefined ? endTime - startTime : undefined;\n playout.play(now(), startTime, duration);\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 dispose(): void {\n playout?.dispose();\n playout = null;\n _isPlaying = false;\n },\n };\n}\n"],"mappings":"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","names":["Volume","getDestination","now","clipStartTime","Volume","getDestination","now","getContext","now"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@waveform-playlist/playout",
3
- "version": "7.1.2",
3
+ "version": "7.1.3",
4
4
  "description": "Playout engine for waveform-playlist using Tone.js",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -37,17 +37,27 @@
37
37
  ],
38
38
  "devDependencies": {
39
39
  "tsup": "^8.0.1",
40
- "typescript": "^5.3.3"
40
+ "typescript": "^5.3.3",
41
+ "vitest": "^3.0.0",
42
+ "@waveform-playlist/engine": "^7.1.3"
41
43
  },
42
44
  "dependencies": {
43
- "@waveform-playlist/core": "7.1.2"
45
+ "@waveform-playlist/core": "7.1.3"
44
46
  },
45
47
  "peerDependencies": {
46
- "tone": "^15.0.0"
48
+ "tone": "^15.0.0",
49
+ "@waveform-playlist/engine": ">=7.0.0"
50
+ },
51
+ "peerDependenciesMeta": {
52
+ "@waveform-playlist/engine": {
53
+ "optional": true
54
+ }
47
55
  },
48
56
  "scripts": {
49
57
  "build": "tsup",
50
58
  "dev": "tsup --watch",
51
- "typecheck": "tsc --noEmit"
59
+ "typecheck": "tsc --noEmit",
60
+ "test": "vitest run",
61
+ "test:watch": "vitest"
52
62
  }
53
63
  }