@waveform-playlist/playout 7.1.2 → 8.0.0
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 +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +130 -17
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +135 -25
- package/dist/index.mjs.map +1 -1
- package/package.json +15 -5
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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 =
|
|
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
|
-
|
|
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":"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","names":["import_tone","import_tone","import_tone"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/TonePlayout.ts","../src/ToneTrack.ts","../src/fades.ts","../src/audioContext.ts","../src/mediaStreamSourceManager.ts","../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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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 =
|
|
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
|
-
|
|
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,
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/TonePlayout.ts","../src/ToneTrack.ts","../src/fades.ts","../src/audioContext.ts","../src/mediaStreamSourceManager.ts"],"sourcesContent":["// Named imports for tree-shaking\nimport {\n Volume,\n ToneAudioNode,\n getDestination,\n start,\n now,\n getTransport,\n getContext,\n BaseContext,\n} from 'tone';\nimport { ToneTrack, ToneTrackOptions } from './ToneTrack';\n\n// Effects function no longer receives ToneLib - effects should import Tone themselves\nexport type EffectsFunction = (masterGainNode: Volume, destination: ToneAudioNode, isOffline: boolean) => void | (() => void);\n\nexport interface TonePlayoutOptions {\n tracks?: ToneTrack[];\n masterGain?: number;\n effects?: EffectsFunction;\n}\n\nexport class TonePlayout {\n private tracks: Map<string, ToneTrack> = new Map();\n private masterVolume: Volume;\n private isInitialized = false;\n private soloedTracks: Set<string> = new Set();\n private manualMuteState: Map<string, boolean> = new Map();\n private effectsCleanup?: () => void;\n private onPlaybackCompleteCallback?: () => void;\n private activeTracks: Map<string, number> = new Map(); // Map track ID to session ID\n private playbackSessionId: number = 0;\n\n constructor(options: TonePlayoutOptions = {}) {\n this.masterVolume = new Volume(this.gainToDb(options.masterGain ?? 1));\n\n // Setup effects chain if provided, otherwise connect directly to destination\n if (options.effects) {\n const cleanup = options.effects(this.masterVolume, getDestination(), false);\n if (cleanup) {\n this.effectsCleanup = cleanup;\n }\n } else {\n this.masterVolume.toDestination();\n }\n\n if (options.tracks) {\n options.tracks.forEach(track => {\n this.tracks.set(track.id, track);\n // Initialize manual mute state for constructor-provided tracks\n this.manualMuteState.set(track.id, track.muted);\n });\n }\n }\n\n private gainToDb(gain: number): number {\n return 20 * Math.log10(gain);\n }\n\n async init(): Promise<void> {\n if (this.isInitialized) return;\n\n await start();\n this.isInitialized = true;\n }\n\n addTrack(trackOptions: ToneTrackOptions): ToneTrack {\n // Ensure tracks connect to master volume instead of destination\n const optionsWithDestination = {\n ...trackOptions,\n destination: this.masterVolume,\n };\n const toneTrack = new ToneTrack(optionsWithDestination);\n this.tracks.set(toneTrack.id, toneTrack);\n // Initialize manual mute state from track options\n this.manualMuteState.set(toneTrack.id, trackOptions.track.muted ?? false);\n // Initialize solo state from track options\n if (trackOptions.track.soloed) {\n this.soloedTracks.add(toneTrack.id);\n }\n return toneTrack;\n }\n\n /**\n * Apply solo muting after all tracks have been added.\n * Call this after adding all tracks to ensure solo logic is applied correctly.\n */\n applyInitialSoloState(): void {\n this.updateSoloMuting();\n }\n\n removeTrack(trackId: string): void {\n const track = this.tracks.get(trackId);\n if (track) {\n track.dispose();\n this.tracks.delete(trackId);\n this.manualMuteState.delete(trackId);\n this.soloedTracks.delete(trackId);\n }\n }\n\n getTrack(trackId: string): ToneTrack | undefined {\n return this.tracks.get(trackId);\n }\n\n play(when?: number, offset?: number, duration?: number): void {\n if (!this.isInitialized) {\n console.warn('TonePlayout not initialized. Call init() first.');\n return;\n }\n\n // Use now() as default, but call it here after init check (not in function signature)\n const startTime = when ?? now();\n const playbackPosition = offset ?? 0;\n\n // Increment session ID to invalidate old callbacks\n this.playbackSessionId++;\n const currentSessionId = this.playbackSessionId;\n\n // Clear active tracks and set up stop callbacks if duration is specified\n this.activeTracks.clear();\n\n // Play tracks based on their individual start times\n this.tracks.forEach((toneTrack) => {\n const trackStartTime = toneTrack.startTime;\n\n if (playbackPosition >= trackStartTime) {\n // Track should be playing - calculate buffer offset and start immediately\n const bufferOffset = playbackPosition - trackStartTime;\n\n if (duration !== undefined) {\n this.activeTracks.set(toneTrack.id, currentSessionId);\n toneTrack.setOnStopCallback(() => {\n // Only process if this track is still in activeTracks with matching session ID\n if (this.activeTracks.get(toneTrack.id) === currentSessionId) {\n this.activeTracks.delete(toneTrack.id);\n if (this.activeTracks.size === 0 && this.onPlaybackCompleteCallback) {\n this.onPlaybackCompleteCallback();\n }\n }\n });\n }\n\n toneTrack.play(startTime, bufferOffset, duration);\n } else {\n // Track should start later - schedule it to start when playback reaches its start time\n const delay = trackStartTime - playbackPosition;\n\n if (duration !== undefined) {\n this.activeTracks.set(toneTrack.id, currentSessionId);\n toneTrack.setOnStopCallback(() => {\n // Only process if this track is still in activeTracks with matching session ID\n if (this.activeTracks.get(toneTrack.id) === currentSessionId) {\n this.activeTracks.delete(toneTrack.id);\n if (this.activeTracks.size === 0 && this.onPlaybackCompleteCallback) {\n this.onPlaybackCompleteCallback();\n }\n }\n });\n }\n\n toneTrack.play(startTime + delay, 0, duration);\n }\n });\n\n // Start transport\n if (offset !== undefined) {\n // Explicit offset provided - seek to that position\n getTransport().start(startTime, offset);\n } else {\n // No offset - resume from pause (Transport resumes from current position)\n getTransport().start(startTime);\n }\n }\n\n pause(): void {\n getTransport().pause();\n this.tracks.forEach(track => {\n track.pause();\n });\n }\n\n stop(): void {\n getTransport().stop();\n this.tracks.forEach(track => {\n track.stop();\n });\n }\n\n setMasterGain(gain: number): void {\n this.masterVolume.volume.value = this.gainToDb(gain);\n }\n\n setSolo(trackId: string, soloed: boolean): void {\n const track = this.tracks.get(trackId);\n if (track) {\n track.setSolo(soloed);\n if (soloed) {\n this.soloedTracks.add(trackId);\n } else {\n this.soloedTracks.delete(trackId);\n }\n\n // Update mute state of all tracks based on solo logic\n this.updateSoloMuting();\n }\n }\n\n private updateSoloMuting(): void {\n const hasSoloedTracks = this.soloedTracks.size > 0;\n\n this.tracks.forEach((track, id) => {\n if (hasSoloedTracks) {\n // If there are soloed tracks, mute all non-soloed tracks\n if (!this.soloedTracks.has(id)) {\n track.setMute(true);\n } else {\n // Restore manual mute state for soloed tracks\n const manuallyMuted = this.manualMuteState.get(id) ?? false;\n track.setMute(manuallyMuted);\n }\n } else {\n // No soloed tracks, restore original manual mute state for all tracks\n const manuallyMuted = this.manualMuteState.get(id) ?? false;\n track.setMute(manuallyMuted);\n }\n });\n }\n\n setMute(trackId: string, muted: boolean): void {\n const track = this.tracks.get(trackId);\n if (track) {\n // Store the manual mute state\n this.manualMuteState.set(trackId, muted);\n track.setMute(muted);\n }\n }\n\n getCurrentTime(): number {\n return getTransport().seconds;\n }\n\n seekTo(time: number): void {\n getTransport().seconds = time;\n }\n\n dispose(): void {\n this.tracks.forEach(track => {\n track.dispose();\n });\n this.tracks.clear();\n\n // Clean up effects if cleanup function was provided\n if (this.effectsCleanup) {\n this.effectsCleanup();\n }\n\n this.masterVolume.dispose();\n }\n\n get context(): BaseContext {\n return getContext();\n }\n\n get sampleRate(): number {\n return getContext().sampleRate;\n }\n\n setOnPlaybackComplete(callback: () => void): void {\n this.onPlaybackCompleteCallback = callback;\n }\n}\n","// Named imports for tree-shaking\nimport {\n Player,\n Volume,\n Gain,\n Panner,\n ToneAudioNode,\n getDestination,\n now,\n} from 'tone';\nimport { Track, type Fade } from '@waveform-playlist/core';\nimport { applyFadeIn, applyFadeOut, 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": "
|
|
3
|
+
"version": "8.0.0",
|
|
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": "^8.0.0"
|
|
41
43
|
},
|
|
42
44
|
"dependencies": {
|
|
43
|
-
"@waveform-playlist/core": "
|
|
45
|
+
"@waveform-playlist/core": "8.0.0"
|
|
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
|
}
|