@waveform-playlist/playout 12.0.0 → 12.2.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
@@ -425,6 +425,7 @@ interface TonePlayoutOptions {
425
425
  declare class TonePlayout {
426
426
  private tracks;
427
427
  private masterVolume;
428
+ private _masterTap;
428
429
  private isInitialized;
429
430
  private soloedTracks;
430
431
  private manualMuteState;
@@ -464,6 +465,10 @@ declare class TonePlayout {
464
465
  pause(): void;
465
466
  stop(): void;
466
467
  setMasterGain(gain: number): void;
468
+ /** The master output tap node. In the signal chain: masterVolume → tap → destination.
469
+ * Connect analyzers/effects/recorders here — parallel or serial.
470
+ * The tap's native GainNode is on the same standardized-audio-context as adapter.audioContext. */
471
+ get masterOutputNode(): GainNode;
467
472
  setSolo(trackId: string, soloed: boolean): void;
468
473
  private updateSoloMuting;
469
474
  setMute(trackId: string, muted: boolean): void;
@@ -602,6 +607,8 @@ interface ToneAdapterOptions {
602
607
  effects?: EffectsFunction;
603
608
  /** When provided, MIDI clips use SoundFont sample playback instead of PolySynth */
604
609
  soundFontCache?: SoundFontCache;
610
+ /** Pulses per quarter note. Defaults to 192 (Tone.js native). */
611
+ ppqn?: number;
605
612
  }
606
613
  declare function createToneAdapter(options?: ToneAdapterOptions): PlayoutAdapter;
607
614
 
package/dist/index.d.ts CHANGED
@@ -425,6 +425,7 @@ interface TonePlayoutOptions {
425
425
  declare class TonePlayout {
426
426
  private tracks;
427
427
  private masterVolume;
428
+ private _masterTap;
428
429
  private isInitialized;
429
430
  private soloedTracks;
430
431
  private manualMuteState;
@@ -464,6 +465,10 @@ declare class TonePlayout {
464
465
  pause(): void;
465
466
  stop(): void;
466
467
  setMasterGain(gain: number): void;
468
+ /** The master output tap node. In the signal chain: masterVolume → tap → destination.
469
+ * Connect analyzers/effects/recorders here — parallel or serial.
470
+ * The tap's native GainNode is on the same standardized-audio-context as adapter.audioContext. */
471
+ get masterOutputNode(): GainNode;
467
472
  setSolo(trackId: string, soloed: boolean): void;
468
473
  private updateSoloMuting;
469
474
  setMute(trackId: string, muted: boolean): void;
@@ -602,6 +607,8 @@ interface ToneAdapterOptions {
602
607
  effects?: EffectsFunction;
603
608
  /** When provided, MIDI clips use SoundFont sample playback instead of PolySynth */
604
609
  soundFontCache?: SoundFontCache;
610
+ /** Pulses per quarter note. Defaults to 192 (Tone.js native). */
611
+ ppqn?: number;
605
612
  }
606
613
  declare function createToneAdapter(options?: ToneAdapterOptions): PlayoutAdapter;
607
614
 
package/dist/index.js CHANGED
@@ -1060,13 +1060,15 @@ var TonePlayout = class {
1060
1060
  this._loopStart = 0;
1061
1061
  this._loopEnd = 0;
1062
1062
  this.masterVolume = new import_tone4.Volume((0, import_core5.gainToDb)(options.masterGain ?? 1));
1063
+ this._masterTap = new import_tone4.Gain(1);
1063
1064
  if (options.effects) {
1064
- const cleanup = options.effects(this.masterVolume, (0, import_tone4.getDestination)(), false);
1065
+ const cleanup = options.effects(this.masterVolume, this._masterTap, false);
1065
1066
  if (cleanup) {
1066
1067
  this.effectsCleanup = cleanup;
1067
1068
  }
1069
+ this._masterTap.connect((0, import_tone4.getDestination)());
1068
1070
  } else {
1069
- this.masterVolume.toDestination();
1071
+ this.masterVolume.chain(this._masterTap, (0, import_tone4.getDestination)());
1070
1072
  }
1071
1073
  if (options.tracks) {
1072
1074
  options.tracks.forEach((track) => {
@@ -1271,6 +1273,12 @@ var TonePlayout = class {
1271
1273
  setMasterGain(gain) {
1272
1274
  this.masterVolume.volume.value = (0, import_core5.gainToDb)(gain);
1273
1275
  }
1276
+ /** The master output tap node. In the signal chain: masterVolume → tap → destination.
1277
+ * Connect analyzers/effects/recorders here — parallel or serial.
1278
+ * The tap's native GainNode is on the same standardized-audio-context as adapter.audioContext. */
1279
+ get masterOutputNode() {
1280
+ return this._masterTap.input;
1281
+ }
1274
1282
  setSolo(trackId, soloed) {
1275
1283
  const track = this.tracks.get(trackId);
1276
1284
  if (track) {
@@ -1370,10 +1378,15 @@ var TonePlayout = class {
1370
1378
  console.warn("[waveform-playlist] Error during master effects cleanup:", err);
1371
1379
  }
1372
1380
  }
1381
+ try {
1382
+ this._masterTap.dispose();
1383
+ } catch (err) {
1384
+ console.warn("[waveform-playlist] Error disposing master tap: " + String(err));
1385
+ }
1373
1386
  try {
1374
1387
  this.masterVolume.dispose();
1375
1388
  } catch (err) {
1376
- console.warn("[waveform-playlist] Error disposing master volume:", err);
1389
+ console.warn("[waveform-playlist] Error disposing master volume: " + String(err));
1377
1390
  }
1378
1391
  }
1379
1392
  get context() {
@@ -1626,14 +1639,22 @@ function hasMediaStreamSource(stream) {
1626
1639
  var import_core6 = require("@waveform-playlist/core");
1627
1640
  var import_tone7 = require("tone");
1628
1641
  function createToneAdapter(options) {
1629
- let playout = null;
1642
+ getGlobalContext();
1643
+ let _playoutGeneration = 1;
1644
+ let playout = new TonePlayout({ effects: options?.effects });
1645
+ playout.setOnPlaybackComplete(() => {
1646
+ if (_playoutGeneration === 1) {
1647
+ _isPlaying = false;
1648
+ }
1649
+ });
1630
1650
  let _isPlaying = false;
1631
- let _playoutGeneration = 0;
1632
1651
  let _loopEnabled = false;
1633
1652
  let _loopStart = 0;
1634
1653
  let _loopEnd = 0;
1635
1654
  let _audioInitialized = false;
1636
1655
  let _pendingInit = null;
1656
+ const _ppqn = options?.ppqn ?? 192;
1657
+ let _bpm = 120;
1637
1658
  function addTrackToPlayout(p, track) {
1638
1659
  const audioClips = track.clips.filter((c) => c.audioBuffer && !c.midiNotes);
1639
1660
  const midiClips = track.clips.filter((c) => c.midiNotes && c.midiNotes.length > 0);
@@ -1826,9 +1847,10 @@ function createToneAdapter(options) {
1826
1847
  },
1827
1848
  addTrack(track) {
1828
1849
  if (!playout) {
1829
- throw new Error(
1830
- "[waveform-playlist] adapter.addTrack() called but no playout exists. Call setTracks() first to initialize the playout."
1850
+ console.warn(
1851
+ "[waveform-playlist] adapter.addTrack() called but playout is not available (adapter may have been disposed)."
1831
1852
  );
1853
+ return;
1832
1854
  }
1833
1855
  addTrackToPlayout(playout, track);
1834
1856
  playout.applyInitialSoloState();
@@ -1887,11 +1909,58 @@ function createToneAdapter(options) {
1887
1909
  _loopEnd = end;
1888
1910
  playout?.setLoop(enabled, start2, end);
1889
1911
  },
1912
+ get audioContext() {
1913
+ return getGlobalAudioContext();
1914
+ },
1915
+ get ppqn() {
1916
+ return _ppqn;
1917
+ },
1918
+ setTempo(bpm, atTick) {
1919
+ if (atTick !== void 0) {
1920
+ throw new Error(
1921
+ "Multiple tempo changes not supported by TonePlayoutAdapter. Use NativePlayoutAdapter from @dawcore/transport for multi-tempo support."
1922
+ );
1923
+ }
1924
+ _bpm = bpm;
1925
+ },
1926
+ setMeter(_numerator, _denominator, atTick) {
1927
+ if (atTick !== void 0) {
1928
+ throw new Error(
1929
+ "Multiple meter changes not supported by TonePlayoutAdapter. Use NativePlayoutAdapter from @dawcore/transport for multi-meter support."
1930
+ );
1931
+ }
1932
+ },
1933
+ ticksToSeconds(tick) {
1934
+ return tick * 60 / (_bpm * _ppqn);
1935
+ },
1936
+ secondsToTicks(seconds) {
1937
+ return seconds * _bpm * _ppqn / 60;
1938
+ },
1939
+ // --- Cross-context worklet support ---
1940
+ // Tone.js wraps standardized-audio-context. Native AudioWorkletNode constructor
1941
+ // rejects it ("parameter 1 is not of type 'BaseAudioContext'").
1942
+ // These methods use Tone.js Context wrappers that handle both context types.
1943
+ // Note: addWorkletModule is NOT needed — rawContext.audioWorklet.addModule() works
1944
+ // identically for both native and standardized contexts. Only node/source creation differs.
1945
+ createAudioWorkletNode(name, options2) {
1946
+ return getGlobalContext().createAudioWorkletNode(name, options2);
1947
+ },
1948
+ createMediaStreamSource(stream) {
1949
+ return getGlobalContext().createMediaStreamSource(stream);
1950
+ },
1951
+ get masterOutputNode() {
1952
+ if (!playout) {
1953
+ throw new Error(
1954
+ "[waveform-playlist] adapter.masterOutputNode accessed after dispose. Disconnect your analyzer before disposing the adapter."
1955
+ );
1956
+ }
1957
+ return playout.masterOutputNode;
1958
+ },
1890
1959
  dispose() {
1891
1960
  try {
1892
1961
  playout?.dispose();
1893
1962
  } catch (err) {
1894
- console.warn("[waveform-playlist] Error disposing playout:", err);
1963
+ console.warn("[waveform-playlist] Error disposing playout: " + String(err));
1895
1964
  }
1896
1965
  playout = null;
1897
1966
  _isPlaying = false;