@waveform-playlist/playout 12.2.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 +161 -74
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +160 -74
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -6
package/dist/index.mjs
CHANGED
|
@@ -1443,7 +1443,7 @@ function extractLoopAndEnvelope(params) {
|
|
|
1443
1443
|
releaseVolEnv
|
|
1444
1444
|
};
|
|
1445
1445
|
}
|
|
1446
|
-
var SoundFontCache = class {
|
|
1446
|
+
var SoundFontCache = class _SoundFontCache {
|
|
1447
1447
|
/**
|
|
1448
1448
|
* @param context Optional AudioContext for createBuffer(). If omitted, uses
|
|
1449
1449
|
* an OfflineAudioContext which doesn't require user gesture — safe to
|
|
@@ -1454,6 +1454,17 @@ var SoundFontCache = class {
|
|
|
1454
1454
|
this.audioBufferCache = /* @__PURE__ */ new Map();
|
|
1455
1455
|
this.context = context ?? new OfflineAudioContext(1, 1, 44100);
|
|
1456
1456
|
}
|
|
1457
|
+
/**
|
|
1458
|
+
* Fetch and parse an SF2 file, resolving only once it's ready to play.
|
|
1459
|
+
* Prefer this over `new SoundFontCache()` + `load()` — the returned cache
|
|
1460
|
+
* is always loaded, so it can't hit the "unloaded cache → PolySynth
|
|
1461
|
+
* fallback" path in createToneAdapter / setSoundFontCache.
|
|
1462
|
+
*/
|
|
1463
|
+
static async fromUrl(url, options) {
|
|
1464
|
+
const cache = new _SoundFontCache(options?.context);
|
|
1465
|
+
await cache.load(url, options?.signal);
|
|
1466
|
+
return cache;
|
|
1467
|
+
}
|
|
1457
1468
|
/**
|
|
1458
1469
|
* Load and parse an SF2 file from a URL.
|
|
1459
1470
|
*/
|
|
@@ -1632,6 +1643,9 @@ import {
|
|
|
1632
1643
|
trackChannelCount
|
|
1633
1644
|
} from "@waveform-playlist/core";
|
|
1634
1645
|
import { now as now2 } from "tone";
|
|
1646
|
+
function isToneAdapter(adapter) {
|
|
1647
|
+
return typeof adapter?.setSoundFontCache === "function";
|
|
1648
|
+
}
|
|
1635
1649
|
function createToneAdapter(options) {
|
|
1636
1650
|
getGlobalContext();
|
|
1637
1651
|
let _playoutGeneration = 1;
|
|
@@ -1649,85 +1663,98 @@ function createToneAdapter(options) {
|
|
|
1649
1663
|
let _pendingInit = null;
|
|
1650
1664
|
const _ppqn = options?.ppqn ?? 192;
|
|
1651
1665
|
let _bpm = 120;
|
|
1652
|
-
|
|
1666
|
+
let _soundFontCache = options?.soundFontCache;
|
|
1667
|
+
const _currentTracks = /* @__PURE__ */ new Map();
|
|
1668
|
+
const _midiTrackBuild = /* @__PURE__ */ new Map();
|
|
1669
|
+
function midiPlayoutTrackId(track) {
|
|
1670
|
+
const hasAudio = track.clips.some((c) => c.audioBuffer && !c.midiNotes);
|
|
1671
|
+
return hasAudio ? `${track.id}:midi` : track.id;
|
|
1672
|
+
}
|
|
1673
|
+
function addAudioTrackToPlayout(p, track) {
|
|
1653
1674
|
const audioClips = track.clips.filter((c) => c.audioBuffer && !c.midiNotes);
|
|
1675
|
+
if (audioClips.length === 0) return;
|
|
1676
|
+
const startTime = Math.min(...audioClips.map(clipStartTime));
|
|
1677
|
+
const endTime = Math.max(...audioClips.map(clipEndTime));
|
|
1678
|
+
const trackObj = {
|
|
1679
|
+
id: track.id,
|
|
1680
|
+
name: track.name,
|
|
1681
|
+
gain: track.volume,
|
|
1682
|
+
muted: track.muted,
|
|
1683
|
+
soloed: track.soloed,
|
|
1684
|
+
stereoPan: track.pan,
|
|
1685
|
+
startTime,
|
|
1686
|
+
endTime
|
|
1687
|
+
};
|
|
1688
|
+
const clipInfos = audioClips.map((clip) => ({
|
|
1689
|
+
buffer: clip.audioBuffer,
|
|
1690
|
+
startTime: clipStartTime(clip) - startTime,
|
|
1691
|
+
duration: clipDurationTime(clip),
|
|
1692
|
+
offset: clipOffsetTime(clip),
|
|
1693
|
+
fadeIn: clip.fadeIn,
|
|
1694
|
+
fadeOut: clip.fadeOut,
|
|
1695
|
+
gain: clip.gain
|
|
1696
|
+
}));
|
|
1697
|
+
p.addTrack({
|
|
1698
|
+
clips: clipInfos,
|
|
1699
|
+
track: trackObj,
|
|
1700
|
+
effects: track.effects,
|
|
1701
|
+
channelCount: trackChannelCount(track)
|
|
1702
|
+
});
|
|
1703
|
+
}
|
|
1704
|
+
function addMidiTrackToPlayout(p, track) {
|
|
1654
1705
|
const midiClips = track.clips.filter((c) => c.midiNotes && c.midiNotes.length > 0);
|
|
1655
|
-
if (
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1706
|
+
if (midiClips.length === 0) return;
|
|
1707
|
+
const startTime = Math.min(...midiClips.map(clipStartTime));
|
|
1708
|
+
const endTime = Math.max(...midiClips.map(clipEndTime));
|
|
1709
|
+
const trackId = midiPlayoutTrackId(track);
|
|
1710
|
+
const trackObj = {
|
|
1711
|
+
id: trackId,
|
|
1712
|
+
name: track.name,
|
|
1713
|
+
gain: track.volume,
|
|
1714
|
+
muted: track.muted,
|
|
1715
|
+
soloed: track.soloed,
|
|
1716
|
+
stereoPan: track.pan,
|
|
1717
|
+
startTime,
|
|
1718
|
+
endTime
|
|
1719
|
+
};
|
|
1720
|
+
const midiClipInfos = midiClips.map((clip) => ({
|
|
1721
|
+
notes: clip.midiNotes,
|
|
1722
|
+
startTime: clipStartTime(clip) - startTime,
|
|
1723
|
+
duration: clipDurationTime(clip),
|
|
1724
|
+
offset: clipOffsetTime(clip)
|
|
1725
|
+
}));
|
|
1726
|
+
if (_soundFontCache?.isLoaded) {
|
|
1727
|
+
const firstClip = midiClips[0];
|
|
1728
|
+
const midiChannel = firstClip.midiChannel;
|
|
1729
|
+
const isPercussion = midiChannel === 9;
|
|
1730
|
+
const programNumber = firstClip.midiProgram ?? 0;
|
|
1731
|
+
p.addSoundFontTrack({
|
|
1732
|
+
clips: midiClipInfos,
|
|
1679
1733
|
track: trackObj,
|
|
1680
|
-
|
|
1681
|
-
|
|
1734
|
+
soundFontCache: _soundFontCache,
|
|
1735
|
+
programNumber,
|
|
1736
|
+
isPercussion,
|
|
1737
|
+
effects: track.effects
|
|
1682
1738
|
});
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
id: trackId,
|
|
1690
|
-
name: track.name,
|
|
1691
|
-
gain: track.volume,
|
|
1692
|
-
muted: track.muted,
|
|
1693
|
-
soloed: track.soloed,
|
|
1694
|
-
stereoPan: track.pan,
|
|
1695
|
-
startTime,
|
|
1696
|
-
endTime
|
|
1697
|
-
};
|
|
1698
|
-
const midiClipInfos = midiClips.map((clip) => ({
|
|
1699
|
-
notes: clip.midiNotes,
|
|
1700
|
-
startTime: clipStartTime(clip) - startTime,
|
|
1701
|
-
duration: clipDurationTime(clip),
|
|
1702
|
-
offset: clipOffsetTime(clip)
|
|
1703
|
-
}));
|
|
1704
|
-
if (options?.soundFontCache?.isLoaded) {
|
|
1705
|
-
const firstClip = midiClips[0];
|
|
1706
|
-
const midiChannel = firstClip.midiChannel;
|
|
1707
|
-
const isPercussion = midiChannel === 9;
|
|
1708
|
-
const programNumber = firstClip.midiProgram ?? 0;
|
|
1709
|
-
p.addSoundFontTrack({
|
|
1710
|
-
clips: midiClipInfos,
|
|
1711
|
-
track: trackObj,
|
|
1712
|
-
soundFontCache: options.soundFontCache,
|
|
1713
|
-
programNumber,
|
|
1714
|
-
isPercussion,
|
|
1715
|
-
effects: track.effects
|
|
1716
|
-
});
|
|
1717
|
-
} else {
|
|
1718
|
-
if (options?.soundFontCache) {
|
|
1719
|
-
console.warn(
|
|
1720
|
-
`[waveform-playlist] SoundFont not loaded for track "${track.name}" \u2014 falling back to PolySynth.`
|
|
1721
|
-
);
|
|
1722
|
-
}
|
|
1723
|
-
p.addMidiTrack({
|
|
1724
|
-
clips: midiClipInfos,
|
|
1725
|
-
track: trackObj,
|
|
1726
|
-
effects: track.effects
|
|
1727
|
-
});
|
|
1739
|
+
_midiTrackBuild.set(trackId, _soundFontCache);
|
|
1740
|
+
} else {
|
|
1741
|
+
if (_soundFontCache) {
|
|
1742
|
+
console.warn(
|
|
1743
|
+
`[waveform-playlist] SoundFont not loaded for track "${track.name}" \u2014 falling back to PolySynth.`
|
|
1744
|
+
);
|
|
1728
1745
|
}
|
|
1746
|
+
p.addMidiTrack({
|
|
1747
|
+
clips: midiClipInfos,
|
|
1748
|
+
track: trackObj,
|
|
1749
|
+
effects: track.effects
|
|
1750
|
+
});
|
|
1751
|
+
_midiTrackBuild.set(trackId, null);
|
|
1729
1752
|
}
|
|
1730
1753
|
}
|
|
1754
|
+
function addTrackToPlayout(p, track) {
|
|
1755
|
+
addAudioTrackToPlayout(p, track);
|
|
1756
|
+
addMidiTrackToPlayout(p, track);
|
|
1757
|
+
}
|
|
1731
1758
|
function buildPlayout(tracks) {
|
|
1732
1759
|
if (playout) {
|
|
1733
1760
|
try {
|
|
@@ -1775,6 +1802,11 @@ function createToneAdapter(options) {
|
|
|
1775
1802
|
}
|
|
1776
1803
|
},
|
|
1777
1804
|
setTracks(tracks) {
|
|
1805
|
+
_currentTracks.clear();
|
|
1806
|
+
_midiTrackBuild.clear();
|
|
1807
|
+
for (const track of tracks) {
|
|
1808
|
+
_currentTracks.set(track.id, track);
|
|
1809
|
+
}
|
|
1778
1810
|
if (!playout) {
|
|
1779
1811
|
buildPlayout(tracks);
|
|
1780
1812
|
return;
|
|
@@ -1802,6 +1834,7 @@ function createToneAdapter(options) {
|
|
|
1802
1834
|
}
|
|
1803
1835
|
},
|
|
1804
1836
|
updateTrack(trackId, track) {
|
|
1837
|
+
_currentTracks.set(trackId, track);
|
|
1805
1838
|
if (!playout) return;
|
|
1806
1839
|
const audioClips = track.clips.filter((c) => c.audioBuffer && !c.midiNotes);
|
|
1807
1840
|
if (audioClips.length > 0) {
|
|
@@ -1820,7 +1853,7 @@ function createToneAdapter(options) {
|
|
|
1820
1853
|
if (midiClips.length > 0) {
|
|
1821
1854
|
const midiTrackId = trackId + ":midi";
|
|
1822
1855
|
playout.removeTrack(midiTrackId);
|
|
1823
|
-
|
|
1856
|
+
addMidiTrackToPlayout(playout, track);
|
|
1824
1857
|
if (_isPlaying) {
|
|
1825
1858
|
playout.resumeTrackMidPlayback(midiTrackId);
|
|
1826
1859
|
}
|
|
@@ -1832,6 +1865,8 @@ function createToneAdapter(options) {
|
|
|
1832
1865
|
}
|
|
1833
1866
|
playout.removeTrack(trackId);
|
|
1834
1867
|
playout.removeTrack(trackId + ":midi");
|
|
1868
|
+
_midiTrackBuild.delete(trackId);
|
|
1869
|
+
_midiTrackBuild.delete(trackId + ":midi");
|
|
1835
1870
|
addTrackToPlayout(playout, track);
|
|
1836
1871
|
playout.applyInitialSoloState();
|
|
1837
1872
|
if (_isPlaying) {
|
|
@@ -1840,6 +1875,7 @@ function createToneAdapter(options) {
|
|
|
1840
1875
|
}
|
|
1841
1876
|
},
|
|
1842
1877
|
addTrack(track) {
|
|
1878
|
+
_currentTracks.set(track.id, track);
|
|
1843
1879
|
if (!playout) {
|
|
1844
1880
|
console.warn(
|
|
1845
1881
|
"[waveform-playlist] adapter.addTrack() called but playout is not available (adapter may have been disposed)."
|
|
@@ -1850,8 +1886,12 @@ function createToneAdapter(options) {
|
|
|
1850
1886
|
playout.applyInitialSoloState();
|
|
1851
1887
|
},
|
|
1852
1888
|
removeTrack(trackId) {
|
|
1889
|
+
_currentTracks.delete(trackId);
|
|
1890
|
+
_midiTrackBuild.delete(trackId);
|
|
1891
|
+
_midiTrackBuild.delete(trackId + ":midi");
|
|
1853
1892
|
if (!playout) return;
|
|
1854
1893
|
playout.removeTrack(trackId);
|
|
1894
|
+
playout.removeTrack(trackId + ":midi");
|
|
1855
1895
|
playout.applyInitialSoloState();
|
|
1856
1896
|
},
|
|
1857
1897
|
play(startTime, endTime) {
|
|
@@ -1886,15 +1926,23 @@ function createToneAdapter(options) {
|
|
|
1886
1926
|
playout?.setMasterGain(volume);
|
|
1887
1927
|
},
|
|
1888
1928
|
setTrackVolume(trackId, volume) {
|
|
1929
|
+
const existing = _currentTracks.get(trackId);
|
|
1930
|
+
if (existing) _currentTracks.set(trackId, { ...existing, volume });
|
|
1889
1931
|
playout?.getTrack(trackId)?.setVolume(volume);
|
|
1890
1932
|
},
|
|
1891
1933
|
setTrackMute(trackId, muted) {
|
|
1934
|
+
const existing = _currentTracks.get(trackId);
|
|
1935
|
+
if (existing) _currentTracks.set(trackId, { ...existing, muted });
|
|
1892
1936
|
playout?.setMute(trackId, muted);
|
|
1893
1937
|
},
|
|
1894
1938
|
setTrackSolo(trackId, soloed) {
|
|
1939
|
+
const existing = _currentTracks.get(trackId);
|
|
1940
|
+
if (existing) _currentTracks.set(trackId, { ...existing, soloed });
|
|
1895
1941
|
playout?.setSolo(trackId, soloed);
|
|
1896
1942
|
},
|
|
1897
1943
|
setTrackPan(trackId, pan) {
|
|
1944
|
+
const existing = _currentTracks.get(trackId);
|
|
1945
|
+
if (existing) _currentTracks.set(trackId, { ...existing, pan });
|
|
1898
1946
|
playout?.getTrack(trackId)?.setPan(pan);
|
|
1899
1947
|
},
|
|
1900
1948
|
setLoop(enabled, start2, end) {
|
|
@@ -1906,6 +1954,9 @@ function createToneAdapter(options) {
|
|
|
1906
1954
|
get audioContext() {
|
|
1907
1955
|
return getGlobalAudioContext();
|
|
1908
1956
|
},
|
|
1957
|
+
get lookAhead() {
|
|
1958
|
+
return getGlobalContext().lookAhead ?? 0;
|
|
1959
|
+
},
|
|
1909
1960
|
get ppqn() {
|
|
1910
1961
|
return _ppqn;
|
|
1911
1962
|
},
|
|
@@ -1950,6 +2001,38 @@ function createToneAdapter(options) {
|
|
|
1950
2001
|
}
|
|
1951
2002
|
return playout.masterOutputNode;
|
|
1952
2003
|
},
|
|
2004
|
+
setSoundFontCache(cache) {
|
|
2005
|
+
_soundFontCache = cache;
|
|
2006
|
+
if (cache && !cache.isLoaded) {
|
|
2007
|
+
console.warn(
|
|
2008
|
+
"[waveform-playlist] setSoundFontCache called with a SoundFontCache that is not loaded \u2014 MIDI tracks remain on PolySynth. Await cache.load() and call again."
|
|
2009
|
+
);
|
|
2010
|
+
}
|
|
2011
|
+
if (!playout) return;
|
|
2012
|
+
const effective = cache?.isLoaded ? cache : null;
|
|
2013
|
+
let changed = false;
|
|
2014
|
+
for (const track of _currentTracks.values()) {
|
|
2015
|
+
const hasMidi = track.clips.some((c) => c.midiNotes && c.midiNotes.length > 0);
|
|
2016
|
+
if (!hasMidi) continue;
|
|
2017
|
+
const midiTrackId = midiPlayoutTrackId(track);
|
|
2018
|
+
if (_midiTrackBuild.get(midiTrackId) === effective) continue;
|
|
2019
|
+
try {
|
|
2020
|
+
playout.removeTrack(midiTrackId);
|
|
2021
|
+
addMidiTrackToPlayout(playout, track);
|
|
2022
|
+
if (_isPlaying) {
|
|
2023
|
+
playout.resumeTrackMidPlayback(midiTrackId);
|
|
2024
|
+
}
|
|
2025
|
+
changed = true;
|
|
2026
|
+
} catch (err) {
|
|
2027
|
+
console.warn(
|
|
2028
|
+
`[waveform-playlist] SoundFont swap failed for track "${track.name}": ` + String(err)
|
|
2029
|
+
);
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
if (changed) {
|
|
2033
|
+
playout.applyInitialSoloState();
|
|
2034
|
+
}
|
|
2035
|
+
},
|
|
1953
2036
|
dispose() {
|
|
1954
2037
|
try {
|
|
1955
2038
|
playout?.dispose();
|
|
@@ -1958,6 +2041,8 @@ function createToneAdapter(options) {
|
|
|
1958
2041
|
}
|
|
1959
2042
|
playout = null;
|
|
1960
2043
|
_isPlaying = false;
|
|
2044
|
+
_currentTracks.clear();
|
|
2045
|
+
_midiTrackBuild.clear();
|
|
1961
2046
|
}
|
|
1962
2047
|
};
|
|
1963
2048
|
}
|
|
@@ -1981,6 +2066,7 @@ export {
|
|
|
1981
2066
|
getUnderlyingAudioParam,
|
|
1982
2067
|
hasMediaStreamSource,
|
|
1983
2068
|
int16ToFloat32,
|
|
2069
|
+
isToneAdapter,
|
|
1984
2070
|
releaseMediaStreamSource,
|
|
1985
2071
|
resumeGlobalAudioContext,
|
|
1986
2072
|
timecentsToSeconds
|