@waveform-playlist/playout 12.3.0 → 12.4.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 +26 -2
- package/dist/index.d.ts +26 -2
- package/dist/index.js +158 -74
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +157 -74
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
package/dist/index.d.mts
CHANGED
|
@@ -317,6 +317,16 @@ declare class SoundFontCache {
|
|
|
317
317
|
* construct before user interaction (avoids Firefox autoplay warnings).
|
|
318
318
|
*/
|
|
319
319
|
constructor(context?: BaseAudioContext);
|
|
320
|
+
/**
|
|
321
|
+
* Fetch and parse an SF2 file, resolving only once it's ready to play.
|
|
322
|
+
* Prefer this over `new SoundFontCache()` + `load()` — the returned cache
|
|
323
|
+
* is always loaded, so it can't hit the "unloaded cache → PolySynth
|
|
324
|
+
* fallback" path in createToneAdapter / setSoundFontCache.
|
|
325
|
+
*/
|
|
326
|
+
static fromUrl(url: string, options?: {
|
|
327
|
+
context?: BaseAudioContext;
|
|
328
|
+
signal?: AbortSignal;
|
|
329
|
+
}): Promise<SoundFontCache>;
|
|
320
330
|
/**
|
|
321
331
|
* Load and parse an SF2 file from a URL.
|
|
322
332
|
*/
|
|
@@ -610,6 +620,20 @@ interface ToneAdapterOptions {
|
|
|
610
620
|
/** Pulses per quarter note. Defaults to 192 (Tone.js native). */
|
|
611
621
|
ppqn?: number;
|
|
612
622
|
}
|
|
613
|
-
|
|
623
|
+
interface ToneAdapter extends PlayoutAdapter {
|
|
624
|
+
/**
|
|
625
|
+
* Provide or swap the SoundFont after creation. Rebuilds only the MIDI
|
|
626
|
+
* tracks whose routing changes; audio tracks keep playing untouched.
|
|
627
|
+
* Pass undefined to revert MIDI tracks to PolySynth synthesis.
|
|
628
|
+
*/
|
|
629
|
+
setSoundFontCache(cache: SoundFontCache | undefined): void;
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Capability check for Tone-specific adapter features. Structural, not
|
|
633
|
+
* instanceof — any adapter implementing setSoundFontCache passes. Narrows a
|
|
634
|
+
* generic PlayoutAdapter so soundfont calls typecheck without casts.
|
|
635
|
+
*/
|
|
636
|
+
declare function isToneAdapter(adapter: PlayoutAdapter | null | undefined): adapter is ToneAdapter;
|
|
637
|
+
declare function createToneAdapter(options?: ToneAdapterOptions): ToneAdapter;
|
|
614
638
|
|
|
615
|
-
export { type AudioContextOptions, 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, configureGlobalContext, createToneAdapter, extractLoopAndEnvelope, getGeneratorValue, getGlobalAudioContext, getGlobalAudioContextState, getGlobalContext, getGlobalToneContext, getMediaStreamSource, getUnderlyingAudioParam, hasMediaStreamSource, int16ToFloat32, releaseMediaStreamSource, resumeGlobalAudioContext, timecentsToSeconds };
|
|
639
|
+
export { type AudioContextOptions, type EffectsFunction, type LoopAndEnvelopeParams, type MidiClipInfo, MidiToneTrack, type MidiToneTrackOptions, type PlayableTrack, type PlaybackRateParams, SoundFontCache, type SoundFontSample, SoundFontToneTrack, type SoundFontToneTrackOptions, type ToneAdapter, type ToneAdapterOptions, TonePlayout, type TonePlayoutOptions, ToneTrack, type ToneTrackOptions, type TrackEffectsFunction, calculatePlaybackRate, closeGlobalAudioContext, configureGlobalContext, createToneAdapter, extractLoopAndEnvelope, getGeneratorValue, getGlobalAudioContext, getGlobalAudioContextState, getGlobalContext, getGlobalToneContext, getMediaStreamSource, getUnderlyingAudioParam, hasMediaStreamSource, int16ToFloat32, isToneAdapter, releaseMediaStreamSource, resumeGlobalAudioContext, timecentsToSeconds };
|
package/dist/index.d.ts
CHANGED
|
@@ -317,6 +317,16 @@ declare class SoundFontCache {
|
|
|
317
317
|
* construct before user interaction (avoids Firefox autoplay warnings).
|
|
318
318
|
*/
|
|
319
319
|
constructor(context?: BaseAudioContext);
|
|
320
|
+
/**
|
|
321
|
+
* Fetch and parse an SF2 file, resolving only once it's ready to play.
|
|
322
|
+
* Prefer this over `new SoundFontCache()` + `load()` — the returned cache
|
|
323
|
+
* is always loaded, so it can't hit the "unloaded cache → PolySynth
|
|
324
|
+
* fallback" path in createToneAdapter / setSoundFontCache.
|
|
325
|
+
*/
|
|
326
|
+
static fromUrl(url: string, options?: {
|
|
327
|
+
context?: BaseAudioContext;
|
|
328
|
+
signal?: AbortSignal;
|
|
329
|
+
}): Promise<SoundFontCache>;
|
|
320
330
|
/**
|
|
321
331
|
* Load and parse an SF2 file from a URL.
|
|
322
332
|
*/
|
|
@@ -610,6 +620,20 @@ interface ToneAdapterOptions {
|
|
|
610
620
|
/** Pulses per quarter note. Defaults to 192 (Tone.js native). */
|
|
611
621
|
ppqn?: number;
|
|
612
622
|
}
|
|
613
|
-
|
|
623
|
+
interface ToneAdapter extends PlayoutAdapter {
|
|
624
|
+
/**
|
|
625
|
+
* Provide or swap the SoundFont after creation. Rebuilds only the MIDI
|
|
626
|
+
* tracks whose routing changes; audio tracks keep playing untouched.
|
|
627
|
+
* Pass undefined to revert MIDI tracks to PolySynth synthesis.
|
|
628
|
+
*/
|
|
629
|
+
setSoundFontCache(cache: SoundFontCache | undefined): void;
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Capability check for Tone-specific adapter features. Structural, not
|
|
633
|
+
* instanceof — any adapter implementing setSoundFontCache passes. Narrows a
|
|
634
|
+
* generic PlayoutAdapter so soundfont calls typecheck without casts.
|
|
635
|
+
*/
|
|
636
|
+
declare function isToneAdapter(adapter: PlayoutAdapter | null | undefined): adapter is ToneAdapter;
|
|
637
|
+
declare function createToneAdapter(options?: ToneAdapterOptions): ToneAdapter;
|
|
614
638
|
|
|
615
|
-
export { type AudioContextOptions, 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, configureGlobalContext, createToneAdapter, extractLoopAndEnvelope, getGeneratorValue, getGlobalAudioContext, getGlobalAudioContextState, getGlobalContext, getGlobalToneContext, getMediaStreamSource, getUnderlyingAudioParam, hasMediaStreamSource, int16ToFloat32, releaseMediaStreamSource, resumeGlobalAudioContext, timecentsToSeconds };
|
|
639
|
+
export { type AudioContextOptions, type EffectsFunction, type LoopAndEnvelopeParams, type MidiClipInfo, MidiToneTrack, type MidiToneTrackOptions, type PlayableTrack, type PlaybackRateParams, SoundFontCache, type SoundFontSample, SoundFontToneTrack, type SoundFontToneTrackOptions, type ToneAdapter, type ToneAdapterOptions, TonePlayout, type TonePlayoutOptions, ToneTrack, type ToneTrackOptions, type TrackEffectsFunction, calculatePlaybackRate, closeGlobalAudioContext, configureGlobalContext, createToneAdapter, extractLoopAndEnvelope, getGeneratorValue, getGlobalAudioContext, getGlobalAudioContextState, getGlobalContext, getGlobalToneContext, getMediaStreamSource, getUnderlyingAudioParam, hasMediaStreamSource, int16ToFloat32, isToneAdapter, releaseMediaStreamSource, resumeGlobalAudioContext, timecentsToSeconds };
|
package/dist/index.js
CHANGED
|
@@ -39,6 +39,7 @@ __export(index_exports, {
|
|
|
39
39
|
getUnderlyingAudioParam: () => getUnderlyingAudioParam,
|
|
40
40
|
hasMediaStreamSource: () => hasMediaStreamSource,
|
|
41
41
|
int16ToFloat32: () => int16ToFloat32,
|
|
42
|
+
isToneAdapter: () => isToneAdapter,
|
|
42
43
|
releaseMediaStreamSource: () => releaseMediaStreamSource,
|
|
43
44
|
resumeGlobalAudioContext: () => resumeGlobalAudioContext,
|
|
44
45
|
timecentsToSeconds: () => timecentsToSeconds
|
|
@@ -1455,7 +1456,7 @@ function extractLoopAndEnvelope(params) {
|
|
|
1455
1456
|
releaseVolEnv
|
|
1456
1457
|
};
|
|
1457
1458
|
}
|
|
1458
|
-
var SoundFontCache = class {
|
|
1459
|
+
var SoundFontCache = class _SoundFontCache {
|
|
1459
1460
|
/**
|
|
1460
1461
|
* @param context Optional AudioContext for createBuffer(). If omitted, uses
|
|
1461
1462
|
* an OfflineAudioContext which doesn't require user gesture — safe to
|
|
@@ -1466,6 +1467,17 @@ var SoundFontCache = class {
|
|
|
1466
1467
|
this.audioBufferCache = /* @__PURE__ */ new Map();
|
|
1467
1468
|
this.context = context ?? new OfflineAudioContext(1, 1, 44100);
|
|
1468
1469
|
}
|
|
1470
|
+
/**
|
|
1471
|
+
* Fetch and parse an SF2 file, resolving only once it's ready to play.
|
|
1472
|
+
* Prefer this over `new SoundFontCache()` + `load()` — the returned cache
|
|
1473
|
+
* is always loaded, so it can't hit the "unloaded cache → PolySynth
|
|
1474
|
+
* fallback" path in createToneAdapter / setSoundFontCache.
|
|
1475
|
+
*/
|
|
1476
|
+
static async fromUrl(url, options) {
|
|
1477
|
+
const cache = new _SoundFontCache(options?.context);
|
|
1478
|
+
await cache.load(url, options?.signal);
|
|
1479
|
+
return cache;
|
|
1480
|
+
}
|
|
1469
1481
|
/**
|
|
1470
1482
|
* Load and parse an SF2 file from a URL.
|
|
1471
1483
|
*/
|
|
@@ -1638,6 +1650,9 @@ function hasMediaStreamSource(stream) {
|
|
|
1638
1650
|
// src/TonePlayoutAdapter.ts
|
|
1639
1651
|
var import_core6 = require("@waveform-playlist/core");
|
|
1640
1652
|
var import_tone7 = require("tone");
|
|
1653
|
+
function isToneAdapter(adapter) {
|
|
1654
|
+
return typeof adapter?.setSoundFontCache === "function";
|
|
1655
|
+
}
|
|
1641
1656
|
function createToneAdapter(options) {
|
|
1642
1657
|
getGlobalContext();
|
|
1643
1658
|
let _playoutGeneration = 1;
|
|
@@ -1655,85 +1670,98 @@ function createToneAdapter(options) {
|
|
|
1655
1670
|
let _pendingInit = null;
|
|
1656
1671
|
const _ppqn = options?.ppqn ?? 192;
|
|
1657
1672
|
let _bpm = 120;
|
|
1658
|
-
|
|
1673
|
+
let _soundFontCache = options?.soundFontCache;
|
|
1674
|
+
const _currentTracks = /* @__PURE__ */ new Map();
|
|
1675
|
+
const _midiTrackBuild = /* @__PURE__ */ new Map();
|
|
1676
|
+
function midiPlayoutTrackId(track) {
|
|
1677
|
+
const hasAudio = track.clips.some((c) => c.audioBuffer && !c.midiNotes);
|
|
1678
|
+
return hasAudio ? `${track.id}:midi` : track.id;
|
|
1679
|
+
}
|
|
1680
|
+
function addAudioTrackToPlayout(p, track) {
|
|
1659
1681
|
const audioClips = track.clips.filter((c) => c.audioBuffer && !c.midiNotes);
|
|
1682
|
+
if (audioClips.length === 0) return;
|
|
1683
|
+
const startTime = Math.min(...audioClips.map(import_core6.clipStartTime));
|
|
1684
|
+
const endTime = Math.max(...audioClips.map(import_core6.clipEndTime));
|
|
1685
|
+
const trackObj = {
|
|
1686
|
+
id: track.id,
|
|
1687
|
+
name: track.name,
|
|
1688
|
+
gain: track.volume,
|
|
1689
|
+
muted: track.muted,
|
|
1690
|
+
soloed: track.soloed,
|
|
1691
|
+
stereoPan: track.pan,
|
|
1692
|
+
startTime,
|
|
1693
|
+
endTime
|
|
1694
|
+
};
|
|
1695
|
+
const clipInfos = audioClips.map((clip) => ({
|
|
1696
|
+
buffer: clip.audioBuffer,
|
|
1697
|
+
startTime: (0, import_core6.clipStartTime)(clip) - startTime,
|
|
1698
|
+
duration: (0, import_core6.clipDurationTime)(clip),
|
|
1699
|
+
offset: (0, import_core6.clipOffsetTime)(clip),
|
|
1700
|
+
fadeIn: clip.fadeIn,
|
|
1701
|
+
fadeOut: clip.fadeOut,
|
|
1702
|
+
gain: clip.gain
|
|
1703
|
+
}));
|
|
1704
|
+
p.addTrack({
|
|
1705
|
+
clips: clipInfos,
|
|
1706
|
+
track: trackObj,
|
|
1707
|
+
effects: track.effects,
|
|
1708
|
+
channelCount: (0, import_core6.trackChannelCount)(track)
|
|
1709
|
+
});
|
|
1710
|
+
}
|
|
1711
|
+
function addMidiTrackToPlayout(p, track) {
|
|
1660
1712
|
const midiClips = track.clips.filter((c) => c.midiNotes && c.midiNotes.length > 0);
|
|
1661
|
-
if (
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1713
|
+
if (midiClips.length === 0) return;
|
|
1714
|
+
const startTime = Math.min(...midiClips.map(import_core6.clipStartTime));
|
|
1715
|
+
const endTime = Math.max(...midiClips.map(import_core6.clipEndTime));
|
|
1716
|
+
const trackId = midiPlayoutTrackId(track);
|
|
1717
|
+
const trackObj = {
|
|
1718
|
+
id: trackId,
|
|
1719
|
+
name: track.name,
|
|
1720
|
+
gain: track.volume,
|
|
1721
|
+
muted: track.muted,
|
|
1722
|
+
soloed: track.soloed,
|
|
1723
|
+
stereoPan: track.pan,
|
|
1724
|
+
startTime,
|
|
1725
|
+
endTime
|
|
1726
|
+
};
|
|
1727
|
+
const midiClipInfos = midiClips.map((clip) => ({
|
|
1728
|
+
notes: clip.midiNotes,
|
|
1729
|
+
startTime: (0, import_core6.clipStartTime)(clip) - startTime,
|
|
1730
|
+
duration: (0, import_core6.clipDurationTime)(clip),
|
|
1731
|
+
offset: (0, import_core6.clipOffsetTime)(clip)
|
|
1732
|
+
}));
|
|
1733
|
+
if (_soundFontCache?.isLoaded) {
|
|
1734
|
+
const firstClip = midiClips[0];
|
|
1735
|
+
const midiChannel = firstClip.midiChannel;
|
|
1736
|
+
const isPercussion = midiChannel === 9;
|
|
1737
|
+
const programNumber = firstClip.midiProgram ?? 0;
|
|
1738
|
+
p.addSoundFontTrack({
|
|
1739
|
+
clips: midiClipInfos,
|
|
1685
1740
|
track: trackObj,
|
|
1686
|
-
|
|
1687
|
-
|
|
1741
|
+
soundFontCache: _soundFontCache,
|
|
1742
|
+
programNumber,
|
|
1743
|
+
isPercussion,
|
|
1744
|
+
effects: track.effects
|
|
1688
1745
|
});
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
id: trackId,
|
|
1696
|
-
name: track.name,
|
|
1697
|
-
gain: track.volume,
|
|
1698
|
-
muted: track.muted,
|
|
1699
|
-
soloed: track.soloed,
|
|
1700
|
-
stereoPan: track.pan,
|
|
1701
|
-
startTime,
|
|
1702
|
-
endTime
|
|
1703
|
-
};
|
|
1704
|
-
const midiClipInfos = midiClips.map((clip) => ({
|
|
1705
|
-
notes: clip.midiNotes,
|
|
1706
|
-
startTime: (0, import_core6.clipStartTime)(clip) - startTime,
|
|
1707
|
-
duration: (0, import_core6.clipDurationTime)(clip),
|
|
1708
|
-
offset: (0, import_core6.clipOffsetTime)(clip)
|
|
1709
|
-
}));
|
|
1710
|
-
if (options?.soundFontCache?.isLoaded) {
|
|
1711
|
-
const firstClip = midiClips[0];
|
|
1712
|
-
const midiChannel = firstClip.midiChannel;
|
|
1713
|
-
const isPercussion = midiChannel === 9;
|
|
1714
|
-
const programNumber = firstClip.midiProgram ?? 0;
|
|
1715
|
-
p.addSoundFontTrack({
|
|
1716
|
-
clips: midiClipInfos,
|
|
1717
|
-
track: trackObj,
|
|
1718
|
-
soundFontCache: options.soundFontCache,
|
|
1719
|
-
programNumber,
|
|
1720
|
-
isPercussion,
|
|
1721
|
-
effects: track.effects
|
|
1722
|
-
});
|
|
1723
|
-
} else {
|
|
1724
|
-
if (options?.soundFontCache) {
|
|
1725
|
-
console.warn(
|
|
1726
|
-
`[waveform-playlist] SoundFont not loaded for track "${track.name}" \u2014 falling back to PolySynth.`
|
|
1727
|
-
);
|
|
1728
|
-
}
|
|
1729
|
-
p.addMidiTrack({
|
|
1730
|
-
clips: midiClipInfos,
|
|
1731
|
-
track: trackObj,
|
|
1732
|
-
effects: track.effects
|
|
1733
|
-
});
|
|
1746
|
+
_midiTrackBuild.set(trackId, _soundFontCache);
|
|
1747
|
+
} else {
|
|
1748
|
+
if (_soundFontCache) {
|
|
1749
|
+
console.warn(
|
|
1750
|
+
`[waveform-playlist] SoundFont not loaded for track "${track.name}" \u2014 falling back to PolySynth.`
|
|
1751
|
+
);
|
|
1734
1752
|
}
|
|
1753
|
+
p.addMidiTrack({
|
|
1754
|
+
clips: midiClipInfos,
|
|
1755
|
+
track: trackObj,
|
|
1756
|
+
effects: track.effects
|
|
1757
|
+
});
|
|
1758
|
+
_midiTrackBuild.set(trackId, null);
|
|
1735
1759
|
}
|
|
1736
1760
|
}
|
|
1761
|
+
function addTrackToPlayout(p, track) {
|
|
1762
|
+
addAudioTrackToPlayout(p, track);
|
|
1763
|
+
addMidiTrackToPlayout(p, track);
|
|
1764
|
+
}
|
|
1737
1765
|
function buildPlayout(tracks) {
|
|
1738
1766
|
if (playout) {
|
|
1739
1767
|
try {
|
|
@@ -1781,6 +1809,11 @@ function createToneAdapter(options) {
|
|
|
1781
1809
|
}
|
|
1782
1810
|
},
|
|
1783
1811
|
setTracks(tracks) {
|
|
1812
|
+
_currentTracks.clear();
|
|
1813
|
+
_midiTrackBuild.clear();
|
|
1814
|
+
for (const track of tracks) {
|
|
1815
|
+
_currentTracks.set(track.id, track);
|
|
1816
|
+
}
|
|
1784
1817
|
if (!playout) {
|
|
1785
1818
|
buildPlayout(tracks);
|
|
1786
1819
|
return;
|
|
@@ -1808,6 +1841,7 @@ function createToneAdapter(options) {
|
|
|
1808
1841
|
}
|
|
1809
1842
|
},
|
|
1810
1843
|
updateTrack(trackId, track) {
|
|
1844
|
+
_currentTracks.set(trackId, track);
|
|
1811
1845
|
if (!playout) return;
|
|
1812
1846
|
const audioClips = track.clips.filter((c) => c.audioBuffer && !c.midiNotes);
|
|
1813
1847
|
if (audioClips.length > 0) {
|
|
@@ -1826,7 +1860,7 @@ function createToneAdapter(options) {
|
|
|
1826
1860
|
if (midiClips.length > 0) {
|
|
1827
1861
|
const midiTrackId = trackId + ":midi";
|
|
1828
1862
|
playout.removeTrack(midiTrackId);
|
|
1829
|
-
|
|
1863
|
+
addMidiTrackToPlayout(playout, track);
|
|
1830
1864
|
if (_isPlaying) {
|
|
1831
1865
|
playout.resumeTrackMidPlayback(midiTrackId);
|
|
1832
1866
|
}
|
|
@@ -1838,6 +1872,8 @@ function createToneAdapter(options) {
|
|
|
1838
1872
|
}
|
|
1839
1873
|
playout.removeTrack(trackId);
|
|
1840
1874
|
playout.removeTrack(trackId + ":midi");
|
|
1875
|
+
_midiTrackBuild.delete(trackId);
|
|
1876
|
+
_midiTrackBuild.delete(trackId + ":midi");
|
|
1841
1877
|
addTrackToPlayout(playout, track);
|
|
1842
1878
|
playout.applyInitialSoloState();
|
|
1843
1879
|
if (_isPlaying) {
|
|
@@ -1846,6 +1882,7 @@ function createToneAdapter(options) {
|
|
|
1846
1882
|
}
|
|
1847
1883
|
},
|
|
1848
1884
|
addTrack(track) {
|
|
1885
|
+
_currentTracks.set(track.id, track);
|
|
1849
1886
|
if (!playout) {
|
|
1850
1887
|
console.warn(
|
|
1851
1888
|
"[waveform-playlist] adapter.addTrack() called but playout is not available (adapter may have been disposed)."
|
|
@@ -1856,8 +1893,12 @@ function createToneAdapter(options) {
|
|
|
1856
1893
|
playout.applyInitialSoloState();
|
|
1857
1894
|
},
|
|
1858
1895
|
removeTrack(trackId) {
|
|
1896
|
+
_currentTracks.delete(trackId);
|
|
1897
|
+
_midiTrackBuild.delete(trackId);
|
|
1898
|
+
_midiTrackBuild.delete(trackId + ":midi");
|
|
1859
1899
|
if (!playout) return;
|
|
1860
1900
|
playout.removeTrack(trackId);
|
|
1901
|
+
playout.removeTrack(trackId + ":midi");
|
|
1861
1902
|
playout.applyInitialSoloState();
|
|
1862
1903
|
},
|
|
1863
1904
|
play(startTime, endTime) {
|
|
@@ -1892,15 +1933,23 @@ function createToneAdapter(options) {
|
|
|
1892
1933
|
playout?.setMasterGain(volume);
|
|
1893
1934
|
},
|
|
1894
1935
|
setTrackVolume(trackId, volume) {
|
|
1936
|
+
const existing = _currentTracks.get(trackId);
|
|
1937
|
+
if (existing) _currentTracks.set(trackId, { ...existing, volume });
|
|
1895
1938
|
playout?.getTrack(trackId)?.setVolume(volume);
|
|
1896
1939
|
},
|
|
1897
1940
|
setTrackMute(trackId, muted) {
|
|
1941
|
+
const existing = _currentTracks.get(trackId);
|
|
1942
|
+
if (existing) _currentTracks.set(trackId, { ...existing, muted });
|
|
1898
1943
|
playout?.setMute(trackId, muted);
|
|
1899
1944
|
},
|
|
1900
1945
|
setTrackSolo(trackId, soloed) {
|
|
1946
|
+
const existing = _currentTracks.get(trackId);
|
|
1947
|
+
if (existing) _currentTracks.set(trackId, { ...existing, soloed });
|
|
1901
1948
|
playout?.setSolo(trackId, soloed);
|
|
1902
1949
|
},
|
|
1903
1950
|
setTrackPan(trackId, pan) {
|
|
1951
|
+
const existing = _currentTracks.get(trackId);
|
|
1952
|
+
if (existing) _currentTracks.set(trackId, { ...existing, pan });
|
|
1904
1953
|
playout?.getTrack(trackId)?.setPan(pan);
|
|
1905
1954
|
},
|
|
1906
1955
|
setLoop(enabled, start2, end) {
|
|
@@ -1959,6 +2008,38 @@ function createToneAdapter(options) {
|
|
|
1959
2008
|
}
|
|
1960
2009
|
return playout.masterOutputNode;
|
|
1961
2010
|
},
|
|
2011
|
+
setSoundFontCache(cache) {
|
|
2012
|
+
_soundFontCache = cache;
|
|
2013
|
+
if (cache && !cache.isLoaded) {
|
|
2014
|
+
console.warn(
|
|
2015
|
+
"[waveform-playlist] setSoundFontCache called with a SoundFontCache that is not loaded \u2014 MIDI tracks remain on PolySynth. Await cache.load() and call again."
|
|
2016
|
+
);
|
|
2017
|
+
}
|
|
2018
|
+
if (!playout) return;
|
|
2019
|
+
const effective = cache?.isLoaded ? cache : null;
|
|
2020
|
+
let changed = false;
|
|
2021
|
+
for (const track of _currentTracks.values()) {
|
|
2022
|
+
const hasMidi = track.clips.some((c) => c.midiNotes && c.midiNotes.length > 0);
|
|
2023
|
+
if (!hasMidi) continue;
|
|
2024
|
+
const midiTrackId = midiPlayoutTrackId(track);
|
|
2025
|
+
if (_midiTrackBuild.get(midiTrackId) === effective) continue;
|
|
2026
|
+
try {
|
|
2027
|
+
playout.removeTrack(midiTrackId);
|
|
2028
|
+
addMidiTrackToPlayout(playout, track);
|
|
2029
|
+
if (_isPlaying) {
|
|
2030
|
+
playout.resumeTrackMidPlayback(midiTrackId);
|
|
2031
|
+
}
|
|
2032
|
+
changed = true;
|
|
2033
|
+
} catch (err) {
|
|
2034
|
+
console.warn(
|
|
2035
|
+
`[waveform-playlist] SoundFont swap failed for track "${track.name}": ` + String(err)
|
|
2036
|
+
);
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
if (changed) {
|
|
2040
|
+
playout.applyInitialSoloState();
|
|
2041
|
+
}
|
|
2042
|
+
},
|
|
1962
2043
|
dispose() {
|
|
1963
2044
|
try {
|
|
1964
2045
|
playout?.dispose();
|
|
@@ -1967,6 +2048,8 @@ function createToneAdapter(options) {
|
|
|
1967
2048
|
}
|
|
1968
2049
|
playout = null;
|
|
1969
2050
|
_isPlaying = false;
|
|
2051
|
+
_currentTracks.clear();
|
|
2052
|
+
_midiTrackBuild.clear();
|
|
1970
2053
|
}
|
|
1971
2054
|
};
|
|
1972
2055
|
}
|
|
@@ -1991,6 +2074,7 @@ function createToneAdapter(options) {
|
|
|
1991
2074
|
getUnderlyingAudioParam,
|
|
1992
2075
|
hasMediaStreamSource,
|
|
1993
2076
|
int16ToFloat32,
|
|
2077
|
+
isToneAdapter,
|
|
1994
2078
|
releaseMediaStreamSource,
|
|
1995
2079
|
resumeGlobalAudioContext,
|
|
1996
2080
|
timecentsToSeconds
|