@waveform-playlist/playout 9.5.2 → 10.1.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 CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Gain, ToneAudioNode, SynthOptions, Volume, BaseContext, Context } from 'tone';
2
2
  import { Fade, Track, MidiNoteData } from '@waveform-playlist/core';
3
+ export { FadeConfig, FadeType, applyFadeIn, applyFadeOut } from '@waveform-playlist/core';
3
4
  import { ZoneMap, Generator, GeneratorType } from 'soundfont2';
4
5
  import { PlayoutAdapter } from '@waveform-playlist/engine';
5
6
 
@@ -536,44 +537,11 @@ declare function releaseMediaStreamSource(stream: MediaStream): void;
536
537
  declare function hasMediaStreamSource(stream: MediaStream): boolean;
537
538
 
538
539
  /**
539
- * Fade utilities for Web Audio API
540
- *
541
- * Applies fade in/out envelopes to AudioParam (typically gain)
542
- * using various curve types.
540
+ * Fade utilities re-exports from @waveform-playlist/core
541
+ * plus Tone.js-specific helpers
543
542
  */
543
+
544
544
  declare function getUnderlyingAudioParam(signal: unknown): AudioParam | undefined;
545
- type FadeType = 'linear' | 'logarithmic' | 'exponential' | 'sCurve';
546
- /**
547
- * Simple fade configuration - just duration and type
548
- */
549
- interface FadeConfig {
550
- /** Duration of the fade in seconds */
551
- duration: number;
552
- /** Type of fade curve (default: 'linear') */
553
- type?: FadeType;
554
- }
555
- /**
556
- * Apply a fade in to an AudioParam
557
- *
558
- * @param param - The AudioParam to apply the fade to (usually gain)
559
- * @param startTime - When the fade starts (in seconds, AudioContext time)
560
- * @param duration - Duration of the fade in seconds
561
- * @param type - Type of fade curve
562
- * @param startValue - Starting value (default: 0)
563
- * @param endValue - Ending value (default: 1)
564
- */
565
- declare function applyFadeIn(param: AudioParam, startTime: number, duration: number, type?: FadeType, startValue?: number, endValue?: number): void;
566
- /**
567
- * Apply a fade out to an AudioParam
568
- *
569
- * @param param - The AudioParam to apply the fade to (usually gain)
570
- * @param startTime - When the fade starts (in seconds, AudioContext time)
571
- * @param duration - Duration of the fade in seconds
572
- * @param type - Type of fade curve
573
- * @param startValue - Starting value (default: 1)
574
- * @param endValue - Ending value (default: 0)
575
- */
576
- declare function applyFadeOut(param: AudioParam, startTime: number, duration: number, type?: FadeType, startValue?: number, endValue?: number): void;
577
545
 
578
546
  interface ToneAdapterOptions {
579
547
  effects?: EffectsFunction;
@@ -582,4 +550,4 @@ interface ToneAdapterOptions {
582
550
  }
583
551
  declare function createToneAdapter(options?: ToneAdapterOptions): PlayoutAdapter;
584
552
 
585
- export { type EffectsFunction, type FadeConfig, type FadeType, type LoopAndEnvelopeParams, type MidiClipInfo, MidiToneTrack, type MidiToneTrackOptions, type PlayableTrack, type PlaybackRateParams, SoundFontCache, type SoundFontSample, SoundFontToneTrack, type SoundFontToneTrackOptions, type ToneAdapterOptions, TonePlayout, type TonePlayoutOptions, ToneTrack, type ToneTrackOptions, type TrackEffectsFunction, applyFadeIn, applyFadeOut, calculatePlaybackRate, closeGlobalAudioContext, createToneAdapter, extractLoopAndEnvelope, getGeneratorValue, getGlobalAudioContext, getGlobalAudioContextState, getGlobalContext, getGlobalToneContext, getMediaStreamSource, getUnderlyingAudioParam, hasMediaStreamSource, int16ToFloat32, releaseMediaStreamSource, resumeGlobalAudioContext, timecentsToSeconds };
553
+ export { type EffectsFunction, type LoopAndEnvelopeParams, type MidiClipInfo, MidiToneTrack, type MidiToneTrackOptions, type PlayableTrack, type PlaybackRateParams, SoundFontCache, type SoundFontSample, SoundFontToneTrack, type SoundFontToneTrackOptions, type ToneAdapterOptions, TonePlayout, type TonePlayoutOptions, ToneTrack, type ToneTrackOptions, type TrackEffectsFunction, calculatePlaybackRate, closeGlobalAudioContext, createToneAdapter, extractLoopAndEnvelope, getGeneratorValue, getGlobalAudioContext, getGlobalAudioContextState, getGlobalContext, getGlobalToneContext, getMediaStreamSource, getUnderlyingAudioParam, hasMediaStreamSource, int16ToFloat32, releaseMediaStreamSource, resumeGlobalAudioContext, timecentsToSeconds };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Gain, ToneAudioNode, SynthOptions, Volume, BaseContext, Context } from 'tone';
2
2
  import { Fade, Track, MidiNoteData } from '@waveform-playlist/core';
3
+ export { FadeConfig, FadeType, applyFadeIn, applyFadeOut } from '@waveform-playlist/core';
3
4
  import { ZoneMap, Generator, GeneratorType } from 'soundfont2';
4
5
  import { PlayoutAdapter } from '@waveform-playlist/engine';
5
6
 
@@ -536,44 +537,11 @@ declare function releaseMediaStreamSource(stream: MediaStream): void;
536
537
  declare function hasMediaStreamSource(stream: MediaStream): boolean;
537
538
 
538
539
  /**
539
- * Fade utilities for Web Audio API
540
- *
541
- * Applies fade in/out envelopes to AudioParam (typically gain)
542
- * using various curve types.
540
+ * Fade utilities re-exports from @waveform-playlist/core
541
+ * plus Tone.js-specific helpers
543
542
  */
543
+
544
544
  declare function getUnderlyingAudioParam(signal: unknown): AudioParam | undefined;
545
- type FadeType = 'linear' | 'logarithmic' | 'exponential' | 'sCurve';
546
- /**
547
- * Simple fade configuration - just duration and type
548
- */
549
- interface FadeConfig {
550
- /** Duration of the fade in seconds */
551
- duration: number;
552
- /** Type of fade curve (default: 'linear') */
553
- type?: FadeType;
554
- }
555
- /**
556
- * Apply a fade in to an AudioParam
557
- *
558
- * @param param - The AudioParam to apply the fade to (usually gain)
559
- * @param startTime - When the fade starts (in seconds, AudioContext time)
560
- * @param duration - Duration of the fade in seconds
561
- * @param type - Type of fade curve
562
- * @param startValue - Starting value (default: 0)
563
- * @param endValue - Ending value (default: 1)
564
- */
565
- declare function applyFadeIn(param: AudioParam, startTime: number, duration: number, type?: FadeType, startValue?: number, endValue?: number): void;
566
- /**
567
- * Apply a fade out to an AudioParam
568
- *
569
- * @param param - The AudioParam to apply the fade to (usually gain)
570
- * @param startTime - When the fade starts (in seconds, AudioContext time)
571
- * @param duration - Duration of the fade in seconds
572
- * @param type - Type of fade curve
573
- * @param startValue - Starting value (default: 1)
574
- * @param endValue - Ending value (default: 0)
575
- */
576
- declare function applyFadeOut(param: AudioParam, startTime: number, duration: number, type?: FadeType, startValue?: number, endValue?: number): void;
577
545
 
578
546
  interface ToneAdapterOptions {
579
547
  effects?: EffectsFunction;
@@ -582,4 +550,4 @@ interface ToneAdapterOptions {
582
550
  }
583
551
  declare function createToneAdapter(options?: ToneAdapterOptions): PlayoutAdapter;
584
552
 
585
- export { type EffectsFunction, type FadeConfig, type FadeType, type LoopAndEnvelopeParams, type MidiClipInfo, MidiToneTrack, type MidiToneTrackOptions, type PlayableTrack, type PlaybackRateParams, SoundFontCache, type SoundFontSample, SoundFontToneTrack, type SoundFontToneTrackOptions, type ToneAdapterOptions, TonePlayout, type TonePlayoutOptions, ToneTrack, type ToneTrackOptions, type TrackEffectsFunction, applyFadeIn, applyFadeOut, calculatePlaybackRate, closeGlobalAudioContext, createToneAdapter, extractLoopAndEnvelope, getGeneratorValue, getGlobalAudioContext, getGlobalAudioContextState, getGlobalContext, getGlobalToneContext, getMediaStreamSource, getUnderlyingAudioParam, hasMediaStreamSource, int16ToFloat32, releaseMediaStreamSource, resumeGlobalAudioContext, timecentsToSeconds };
553
+ export { type EffectsFunction, type LoopAndEnvelopeParams, type MidiClipInfo, MidiToneTrack, type MidiToneTrackOptions, type PlayableTrack, type PlaybackRateParams, SoundFontCache, type SoundFontSample, SoundFontToneTrack, type SoundFontToneTrackOptions, type ToneAdapterOptions, TonePlayout, type TonePlayoutOptions, ToneTrack, type ToneTrackOptions, type TrackEffectsFunction, calculatePlaybackRate, closeGlobalAudioContext, createToneAdapter, extractLoopAndEnvelope, getGeneratorValue, getGlobalAudioContext, getGlobalAudioContextState, getGlobalContext, getGlobalToneContext, getMediaStreamSource, getUnderlyingAudioParam, hasMediaStreamSource, int16ToFloat32, releaseMediaStreamSource, resumeGlobalAudioContext, timecentsToSeconds };
package/dist/index.js CHANGED
@@ -25,8 +25,8 @@ __export(index_exports, {
25
25
  SoundFontToneTrack: () => SoundFontToneTrack,
26
26
  TonePlayout: () => TonePlayout,
27
27
  ToneTrack: () => ToneTrack,
28
- applyFadeIn: () => applyFadeIn,
29
- applyFadeOut: () => applyFadeOut,
28
+ applyFadeIn: () => import_core.applyFadeIn,
29
+ applyFadeOut: () => import_core.applyFadeOut,
30
30
  calculatePlaybackRate: () => calculatePlaybackRate,
31
31
  closeGlobalAudioContext: () => closeGlobalAudioContext,
32
32
  createToneAdapter: () => createToneAdapter,
@@ -53,6 +53,7 @@ var import_tone4 = require("tone");
53
53
  var import_tone = require("tone");
54
54
 
55
55
  // src/fades.ts
56
+ var import_core = require("@waveform-playlist/core");
56
57
  var hasWarned = false;
57
58
  function getUnderlyingAudioParam(signal) {
58
59
  const param = signal._param;
@@ -64,92 +65,6 @@ function getUnderlyingAudioParam(signal) {
64
65
  }
65
66
  return param;
66
67
  }
67
- function linearCurve(length, fadeIn) {
68
- const curve = new Float32Array(length);
69
- const scale = length - 1;
70
- for (let i = 0; i < length; i++) {
71
- const x = i / scale;
72
- curve[i] = fadeIn ? x : 1 - x;
73
- }
74
- return curve;
75
- }
76
- function exponentialCurve(length, fadeIn) {
77
- const curve = new Float32Array(length);
78
- const scale = length - 1;
79
- for (let i = 0; i < length; i++) {
80
- const x = i / scale;
81
- const index = fadeIn ? i : length - 1 - i;
82
- curve[index] = Math.exp(2 * x - 1) / Math.E;
83
- }
84
- return curve;
85
- }
86
- function sCurveCurve(length, fadeIn) {
87
- const curve = new Float32Array(length);
88
- const phase = fadeIn ? Math.PI / 2 : -Math.PI / 2;
89
- for (let i = 0; i < length; i++) {
90
- curve[i] = Math.sin(Math.PI * i / length - phase) / 2 + 0.5;
91
- }
92
- return curve;
93
- }
94
- function logarithmicCurve(length, fadeIn, base = 10) {
95
- const curve = new Float32Array(length);
96
- for (let i = 0; i < length; i++) {
97
- const index = fadeIn ? i : length - 1 - i;
98
- const x = i / length;
99
- curve[index] = Math.log(1 + base * x) / Math.log(1 + base);
100
- }
101
- return curve;
102
- }
103
- function generateCurve(type, length, fadeIn) {
104
- switch (type) {
105
- case "linear":
106
- return linearCurve(length, fadeIn);
107
- case "exponential":
108
- return exponentialCurve(length, fadeIn);
109
- case "sCurve":
110
- return sCurveCurve(length, fadeIn);
111
- case "logarithmic":
112
- return logarithmicCurve(length, fadeIn);
113
- default:
114
- return linearCurve(length, fadeIn);
115
- }
116
- }
117
- function applyFadeIn(param, startTime, duration, type = "linear", startValue = 0, endValue = 1) {
118
- if (duration <= 0) return;
119
- if (type === "linear") {
120
- param.setValueAtTime(startValue, startTime);
121
- param.linearRampToValueAtTime(endValue, startTime + duration);
122
- } else if (type === "exponential") {
123
- param.setValueAtTime(Math.max(startValue, 1e-3), startTime);
124
- param.exponentialRampToValueAtTime(Math.max(endValue, 1e-3), startTime + duration);
125
- } else {
126
- const curve = generateCurve(type, 1e4, true);
127
- const scaledCurve = new Float32Array(curve.length);
128
- const range = endValue - startValue;
129
- for (let i = 0; i < curve.length; i++) {
130
- scaledCurve[i] = startValue + curve[i] * range;
131
- }
132
- param.setValueCurveAtTime(scaledCurve, startTime, duration);
133
- }
134
- }
135
- function applyFadeOut(param, startTime, duration, type = "linear", startValue = 1, endValue = 0) {
136
- if (duration <= 0) return;
137
- if (type === "linear") {
138
- param.setValueAtTime(startValue, startTime);
139
- param.linearRampToValueAtTime(endValue, startTime + duration);
140
- } else if (type === "exponential") {
141
- param.setValueAtTime(Math.max(startValue, 1e-3), startTime);
142
- param.exponentialRampToValueAtTime(Math.max(endValue, 1e-3), startTime + duration);
143
- } else {
144
- const curve = generateCurve(type, 1e4, false);
145
- const scaledCurve = new Float32Array(curve.length);
146
- const range = startValue - endValue;
147
- for (let i = 0; i < curve.length; i++) {
148
- scaledCurve[i] = endValue + curve[i] * range;
149
- }
150
- param.setValueCurveAtTime(scaledCurve, startTime, duration);
151
- }
152
- }
153
68
 
154
69
  // src/ToneTrack.ts
155
70
  var ToneTrack = class {
@@ -164,7 +79,7 @@ var ToneTrack = class {
164
79
  this._scheduleGuardOffset = 0;
165
80
  this.track = options.track;
166
81
  this.volumeNode = new import_tone.Volume(this.gainToDb(options.track.gain));
167
- this.panNode = new import_tone.Panner(options.track.stereoPan);
82
+ this.panNode = new import_tone.Panner({ pan: options.track.stereoPan, channelCount: 2 });
168
83
  this.muteGain = new import_tone.Gain(options.track.muted ? 0 : 1);
169
84
  this.volumeNode.chain(this.panNode, this.muteGain);
170
85
  const destination = options.destination || (0, import_tone.getDestination)();
@@ -291,7 +206,7 @@ var ToneTrack = class {
291
206
  if (clipInfo.fadeIn && skipTime < clipInfo.fadeIn.duration) {
292
207
  const fadeInDuration = clipInfo.fadeIn.duration;
293
208
  if (skipTime <= 0) {
294
- applyFadeIn(
209
+ (0, import_core.applyFadeIn)(
295
210
  audioParam,
296
211
  clipStartTime2,
297
212
  fadeInDuration,
@@ -303,7 +218,7 @@ var ToneTrack = class {
303
218
  const remainingFadeDuration = fadeInDuration - skipTime;
304
219
  const fadeProgress = skipTime / fadeInDuration;
305
220
  const startValue = clipInfo.gain * fadeProgress;
306
- applyFadeIn(
221
+ (0, import_core.applyFadeIn)(
307
222
  audioParam,
308
223
  clipStartTime2,
309
224
  remainingFadeDuration,
@@ -320,7 +235,7 @@ var ToneTrack = class {
320
235
  const fadeOutStartInClip = fadeOutStart - skipTime;
321
236
  if (fadeOutStartInClip > 0) {
322
237
  const absoluteFadeOutStart = clipStartTime2 + fadeOutStartInClip;
323
- applyFadeOut(
238
+ (0, import_core.applyFadeOut)(
324
239
  audioParam,
325
240
  absoluteFadeOutStart,
326
241
  clipInfo.fadeOut.duration,
@@ -333,7 +248,7 @@ var ToneTrack = class {
333
248
  const remainingFadeDuration = clipInfo.fadeOut.duration - elapsedFadeOut;
334
249
  const fadeProgress = elapsedFadeOut / clipInfo.fadeOut.duration;
335
250
  const startValue = clipInfo.gain * (1 - fadeProgress);
336
- applyFadeOut(
251
+ (0, import_core.applyFadeOut)(
337
252
  audioParam,
338
253
  clipStartTime2,
339
254
  remainingFadeDuration,
@@ -1543,7 +1458,7 @@ function hasMediaStreamSource(stream) {
1543
1458
  }
1544
1459
 
1545
1460
  // src/TonePlayoutAdapter.ts
1546
- var import_core = require("@waveform-playlist/core");
1461
+ var import_core2 = require("@waveform-playlist/core");
1547
1462
  var import_tone7 = require("tone");
1548
1463
  function createToneAdapter(options) {
1549
1464
  let playout = null;
@@ -1553,6 +1468,84 @@ function createToneAdapter(options) {
1553
1468
  let _loopStart = 0;
1554
1469
  let _loopEnd = 0;
1555
1470
  let _audioInitialized = false;
1471
+ function addTrackToPlayout(p, track) {
1472
+ const audioClips = track.clips.filter((c) => c.audioBuffer && !c.midiNotes);
1473
+ const midiClips = track.clips.filter((c) => c.midiNotes && c.midiNotes.length > 0);
1474
+ if (audioClips.length > 0) {
1475
+ const startTime = Math.min(...audioClips.map(import_core2.clipStartTime));
1476
+ const endTime = Math.max(...audioClips.map(import_core2.clipEndTime));
1477
+ const trackObj = {
1478
+ id: track.id,
1479
+ name: track.name,
1480
+ gain: track.volume,
1481
+ muted: track.muted,
1482
+ soloed: track.soloed,
1483
+ stereoPan: track.pan,
1484
+ startTime,
1485
+ endTime
1486
+ };
1487
+ const clipInfos = audioClips.map((clip) => ({
1488
+ buffer: clip.audioBuffer,
1489
+ startTime: (0, import_core2.clipStartTime)(clip) - startTime,
1490
+ duration: (0, import_core2.clipDurationTime)(clip),
1491
+ offset: (0, import_core2.clipOffsetTime)(clip),
1492
+ fadeIn: clip.fadeIn,
1493
+ fadeOut: clip.fadeOut,
1494
+ gain: clip.gain
1495
+ }));
1496
+ p.addTrack({
1497
+ clips: clipInfos,
1498
+ track: trackObj,
1499
+ effects: track.effects
1500
+ });
1501
+ }
1502
+ if (midiClips.length > 0) {
1503
+ const startTime = Math.min(...midiClips.map(import_core2.clipStartTime));
1504
+ const endTime = Math.max(...midiClips.map(import_core2.clipEndTime));
1505
+ const trackId = audioClips.length > 0 ? `${track.id}:midi` : track.id;
1506
+ const trackObj = {
1507
+ id: trackId,
1508
+ name: track.name,
1509
+ gain: track.volume,
1510
+ muted: track.muted,
1511
+ soloed: track.soloed,
1512
+ stereoPan: track.pan,
1513
+ startTime,
1514
+ endTime
1515
+ };
1516
+ const midiClipInfos = midiClips.map((clip) => ({
1517
+ notes: clip.midiNotes,
1518
+ startTime: (0, import_core2.clipStartTime)(clip) - startTime,
1519
+ duration: (0, import_core2.clipDurationTime)(clip),
1520
+ offset: (0, import_core2.clipOffsetTime)(clip)
1521
+ }));
1522
+ if (options?.soundFontCache?.isLoaded) {
1523
+ const firstClip = midiClips[0];
1524
+ const midiChannel = firstClip.midiChannel;
1525
+ const isPercussion = midiChannel === 9;
1526
+ const programNumber = firstClip.midiProgram ?? 0;
1527
+ p.addSoundFontTrack({
1528
+ clips: midiClipInfos,
1529
+ track: trackObj,
1530
+ soundFontCache: options.soundFontCache,
1531
+ programNumber,
1532
+ isPercussion,
1533
+ effects: track.effects
1534
+ });
1535
+ } else {
1536
+ if (options?.soundFontCache) {
1537
+ console.warn(
1538
+ `[waveform-playlist] SoundFont not loaded for track "${track.name}" \u2014 falling back to PolySynth.`
1539
+ );
1540
+ }
1541
+ p.addMidiTrack({
1542
+ clips: midiClipInfos,
1543
+ track: trackObj,
1544
+ effects: track.effects
1545
+ });
1546
+ }
1547
+ }
1548
+ }
1556
1549
  function buildPlayout(tracks) {
1557
1550
  if (playout) {
1558
1551
  try {
@@ -1577,82 +1570,7 @@ function createToneAdapter(options) {
1577
1570
  });
1578
1571
  }
1579
1572
  for (const track of tracks) {
1580
- const audioClips = track.clips.filter((c) => c.audioBuffer && !c.midiNotes);
1581
- const midiClips = track.clips.filter((c) => c.midiNotes && c.midiNotes.length > 0);
1582
- if (audioClips.length > 0) {
1583
- const startTime = Math.min(...audioClips.map(import_core.clipStartTime));
1584
- const endTime = Math.max(...audioClips.map(import_core.clipEndTime));
1585
- const trackObj = {
1586
- id: track.id,
1587
- name: track.name,
1588
- gain: track.volume,
1589
- muted: track.muted,
1590
- soloed: track.soloed,
1591
- stereoPan: track.pan,
1592
- startTime,
1593
- endTime
1594
- };
1595
- const clipInfos = audioClips.map((clip) => ({
1596
- buffer: clip.audioBuffer,
1597
- startTime: (0, import_core.clipStartTime)(clip) - startTime,
1598
- duration: (0, import_core.clipDurationTime)(clip),
1599
- offset: (0, import_core.clipOffsetTime)(clip),
1600
- fadeIn: clip.fadeIn,
1601
- fadeOut: clip.fadeOut,
1602
- gain: clip.gain
1603
- }));
1604
- playout.addTrack({
1605
- clips: clipInfos,
1606
- track: trackObj,
1607
- effects: track.effects
1608
- });
1609
- }
1610
- if (midiClips.length > 0) {
1611
- const startTime = Math.min(...midiClips.map(import_core.clipStartTime));
1612
- const endTime = Math.max(...midiClips.map(import_core.clipEndTime));
1613
- const trackId = audioClips.length > 0 ? `${track.id}:midi` : track.id;
1614
- const trackObj = {
1615
- id: trackId,
1616
- name: track.name,
1617
- gain: track.volume,
1618
- muted: track.muted,
1619
- soloed: track.soloed,
1620
- stereoPan: track.pan,
1621
- startTime,
1622
- endTime
1623
- };
1624
- const midiClipInfos = midiClips.map((clip) => ({
1625
- notes: clip.midiNotes,
1626
- startTime: (0, import_core.clipStartTime)(clip) - startTime,
1627
- duration: (0, import_core.clipDurationTime)(clip),
1628
- offset: (0, import_core.clipOffsetTime)(clip)
1629
- }));
1630
- if (options?.soundFontCache?.isLoaded) {
1631
- const firstClip = midiClips[0];
1632
- const midiChannel = firstClip.midiChannel;
1633
- const isPercussion = midiChannel === 9;
1634
- const programNumber = firstClip.midiProgram ?? 0;
1635
- playout.addSoundFontTrack({
1636
- clips: midiClipInfos,
1637
- track: trackObj,
1638
- soundFontCache: options.soundFontCache,
1639
- programNumber,
1640
- isPercussion,
1641
- effects: track.effects
1642
- });
1643
- } else {
1644
- if (options?.soundFontCache) {
1645
- console.warn(
1646
- `[waveform-playlist] SoundFont not loaded for track "${track.name}" \u2014 falling back to PolySynth.`
1647
- );
1648
- }
1649
- playout.addMidiTrack({
1650
- clips: midiClipInfos,
1651
- track: trackObj,
1652
- effects: track.effects
1653
- });
1654
- }
1655
- }
1573
+ addTrackToPlayout(playout, track);
1656
1574
  }
1657
1575
  playout.applyInitialSoloState();
1658
1576
  playout.setLoop(_loopEnabled, _loopStart, _loopEnd);
@@ -1672,6 +1590,15 @@ function createToneAdapter(options) {
1672
1590
  setTracks(tracks) {
1673
1591
  buildPlayout(tracks);
1674
1592
  },
1593
+ addTrack(track) {
1594
+ if (!playout) {
1595
+ throw new Error(
1596
+ "[waveform-playlist] adapter.addTrack() called but no playout exists. Call setTracks() first to initialize the playout."
1597
+ );
1598
+ }
1599
+ addTrackToPlayout(playout, track);
1600
+ playout.applyInitialSoloState();
1601
+ },
1675
1602
  play(startTime, endTime) {
1676
1603
  if (!playout) {
1677
1604
  console.warn(