@waveform-playlist/playout 9.0.4 → 9.1.1
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 +270 -4
- package/dist/index.d.ts +270 -4
- package/dist/index.js +850 -51
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +845 -37
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -5
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Gain, ToneAudioNode, Volume, BaseContext, Context } from 'tone';
|
|
2
|
-
import { Fade, Track } from '@waveform-playlist/core';
|
|
1
|
+
import { Gain, ToneAudioNode, SynthOptions, Volume, BaseContext, Context } from 'tone';
|
|
2
|
+
import { Fade, Track, MidiNoteData } from '@waveform-playlist/core';
|
|
3
3
|
import { PlayoutAdapter } from '@waveform-playlist/engine';
|
|
4
4
|
|
|
5
5
|
type TrackEffectsFunction = (graphEnd: Gain, masterGainNode: ToneAudioNode, isOffline: boolean) => void | (() => void);
|
|
@@ -83,6 +83,268 @@ declare class ToneTrack {
|
|
|
83
83
|
get startTime(): number;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Shared interface for tracks managed by TonePlayout.
|
|
88
|
+
* Both ToneTrack (audio) and MidiToneTrack (MIDI) implement this,
|
|
89
|
+
* allowing TonePlayout to manage them uniformly.
|
|
90
|
+
*/
|
|
91
|
+
interface PlayableTrack {
|
|
92
|
+
id: string;
|
|
93
|
+
startTime: number;
|
|
94
|
+
muted: boolean;
|
|
95
|
+
duration: number;
|
|
96
|
+
stopAllSources(): void;
|
|
97
|
+
startMidClipSources(offset: number, time: number): void;
|
|
98
|
+
setScheduleGuardOffset(offset: number): void;
|
|
99
|
+
prepareFades(when: number, offset: number): void;
|
|
100
|
+
cancelFades(): void;
|
|
101
|
+
setVolume(gain: number): void;
|
|
102
|
+
setPan(pan: number): void;
|
|
103
|
+
setMute(muted: boolean): void;
|
|
104
|
+
setSolo(soloed: boolean): void;
|
|
105
|
+
dispose(): void;
|
|
106
|
+
}
|
|
107
|
+
interface MidiClipInfo {
|
|
108
|
+
notes: MidiNoteData[];
|
|
109
|
+
startTime: number;
|
|
110
|
+
duration: number;
|
|
111
|
+
offset: number;
|
|
112
|
+
}
|
|
113
|
+
interface MidiToneTrackOptions {
|
|
114
|
+
clips: MidiClipInfo[];
|
|
115
|
+
track: Track;
|
|
116
|
+
effects?: TrackEffectsFunction;
|
|
117
|
+
destination?: ToneAudioNode;
|
|
118
|
+
synthOptions?: Partial<SynthOptions>;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* MIDI track that always creates both melodic and percussion synths.
|
|
122
|
+
* Per-note routing uses the `channel` field on each MidiNoteData:
|
|
123
|
+
* channel 9 → percussion synths, all others → melodic PolySynth.
|
|
124
|
+
* This enables flattened tracks (mixed channels) to play correctly.
|
|
125
|
+
*/
|
|
126
|
+
declare class MidiToneTrack implements PlayableTrack {
|
|
127
|
+
private scheduledClips;
|
|
128
|
+
private synth;
|
|
129
|
+
private kickSynth;
|
|
130
|
+
private snareSynth;
|
|
131
|
+
private cymbalSynth;
|
|
132
|
+
private tomSynth;
|
|
133
|
+
private volumeNode;
|
|
134
|
+
private panNode;
|
|
135
|
+
private muteGain;
|
|
136
|
+
private track;
|
|
137
|
+
private effectsCleanup?;
|
|
138
|
+
constructor(options: MidiToneTrackOptions);
|
|
139
|
+
/**
|
|
140
|
+
* Trigger a note using the appropriate synth.
|
|
141
|
+
* Routes per-note: channel 9 → percussion synths, others → melodic PolySynth.
|
|
142
|
+
*/
|
|
143
|
+
private triggerNote;
|
|
144
|
+
private gainToDb;
|
|
145
|
+
/**
|
|
146
|
+
* No-op for MIDI — schedule guard is for AudioBufferSourceNode ghost tick prevention.
|
|
147
|
+
* Tone.Part handles its own scheduling relative to Transport.
|
|
148
|
+
*/
|
|
149
|
+
setScheduleGuardOffset(_offset: number): void;
|
|
150
|
+
/**
|
|
151
|
+
* For MIDI, mid-clip sources are notes that should already be sounding.
|
|
152
|
+
* We trigger them with their remaining duration.
|
|
153
|
+
*/
|
|
154
|
+
startMidClipSources(transportOffset: number, audioContextTime: number): void;
|
|
155
|
+
/**
|
|
156
|
+
* Stop all sounding notes and cancel scheduled Part events.
|
|
157
|
+
*/
|
|
158
|
+
stopAllSources(): void;
|
|
159
|
+
/**
|
|
160
|
+
* No-op for MIDI — MIDI uses note velocity, not gain fades.
|
|
161
|
+
*/
|
|
162
|
+
prepareFades(_when: number, _offset: number): void;
|
|
163
|
+
/**
|
|
164
|
+
* No-op for MIDI — no fade automation to cancel.
|
|
165
|
+
*/
|
|
166
|
+
cancelFades(): void;
|
|
167
|
+
setVolume(gain: number): void;
|
|
168
|
+
setPan(pan: number): void;
|
|
169
|
+
setMute(muted: boolean): void;
|
|
170
|
+
setSolo(soloed: boolean): void;
|
|
171
|
+
dispose(): void;
|
|
172
|
+
get id(): string;
|
|
173
|
+
get duration(): number;
|
|
174
|
+
get muted(): boolean;
|
|
175
|
+
get startTime(): number;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Result of looking up a MIDI note in the SoundFont.
|
|
180
|
+
* Contains the AudioBuffer, playbackRate, loop points, and volume envelope.
|
|
181
|
+
*/
|
|
182
|
+
interface SoundFontSample {
|
|
183
|
+
/** Cached AudioBuffer for this sample */
|
|
184
|
+
buffer: AudioBuffer;
|
|
185
|
+
/** Playback rate to pitch-shift from originalPitch to target note */
|
|
186
|
+
playbackRate: number;
|
|
187
|
+
/** Loop mode: 0=no loop, 1=continuous, 3=sustain loop */
|
|
188
|
+
loopMode: number;
|
|
189
|
+
/** Loop start in seconds, relative to AudioBuffer start */
|
|
190
|
+
loopStart: number;
|
|
191
|
+
/** Loop end in seconds, relative to AudioBuffer start */
|
|
192
|
+
loopEnd: number;
|
|
193
|
+
/** Volume envelope attack time in seconds */
|
|
194
|
+
attackVolEnv: number;
|
|
195
|
+
/** Volume envelope hold time in seconds */
|
|
196
|
+
holdVolEnv: number;
|
|
197
|
+
/** Volume envelope decay time in seconds */
|
|
198
|
+
decayVolEnv: number;
|
|
199
|
+
/** Volume envelope sustain level as linear gain 0-1 */
|
|
200
|
+
sustainVolEnv: number;
|
|
201
|
+
/** Volume envelope release time in seconds */
|
|
202
|
+
releaseVolEnv: number;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Caches parsed SoundFont2 data and AudioBuffers for efficient playback.
|
|
206
|
+
*
|
|
207
|
+
* AudioBuffers are created lazily on first access and cached by sample index.
|
|
208
|
+
* Pitch calculation uses the SF2 generator chain:
|
|
209
|
+
* OverridingRootKey → sample.header.originalPitch → fallback 60
|
|
210
|
+
*
|
|
211
|
+
* Audio graph per note:
|
|
212
|
+
* AudioBufferSourceNode (playbackRate for pitch) → GainNode (velocity) → track chain
|
|
213
|
+
*/
|
|
214
|
+
declare class SoundFontCache {
|
|
215
|
+
private sf2;
|
|
216
|
+
private audioBufferCache;
|
|
217
|
+
private context;
|
|
218
|
+
/**
|
|
219
|
+
* @param context Optional AudioContext for createBuffer(). If omitted, uses
|
|
220
|
+
* an OfflineAudioContext which doesn't require user gesture — safe to
|
|
221
|
+
* construct before user interaction (avoids Firefox autoplay warnings).
|
|
222
|
+
*/
|
|
223
|
+
constructor(context?: BaseAudioContext);
|
|
224
|
+
/**
|
|
225
|
+
* Load and parse an SF2 file from a URL.
|
|
226
|
+
*/
|
|
227
|
+
load(url: string, signal?: AbortSignal): Promise<void>;
|
|
228
|
+
/**
|
|
229
|
+
* Load from an already-fetched ArrayBuffer.
|
|
230
|
+
*/
|
|
231
|
+
loadFromBuffer(data: ArrayBuffer): void;
|
|
232
|
+
get isLoaded(): boolean;
|
|
233
|
+
/**
|
|
234
|
+
* Look up a MIDI note and return the AudioBuffer + playbackRate.
|
|
235
|
+
*
|
|
236
|
+
* @param midiNote - MIDI note number (0-127)
|
|
237
|
+
* @param bankNumber - Bank number (0 for melodic, 128 for percussion/drums)
|
|
238
|
+
* @param presetNumber - GM program number (0-127)
|
|
239
|
+
* @returns SoundFontSample or null if no sample found for this note
|
|
240
|
+
*/
|
|
241
|
+
getAudioBuffer(midiNote: number, bankNumber?: number, presetNumber?: number): SoundFontSample | null;
|
|
242
|
+
/**
|
|
243
|
+
* Extract loop points and volume envelope data from per-zone generators.
|
|
244
|
+
*
|
|
245
|
+
* Loop points are stored as absolute indices into the SF2 sample pool.
|
|
246
|
+
* We convert to AudioBuffer-relative seconds by subtracting header.start
|
|
247
|
+
* and dividing by sampleRate.
|
|
248
|
+
*
|
|
249
|
+
* Volume envelope times are in SF2 timecents; sustain is centibels attenuation.
|
|
250
|
+
*/
|
|
251
|
+
private extractLoopAndEnvelope;
|
|
252
|
+
/**
|
|
253
|
+
* Calculate playback rate for a MIDI note using the SF2 generator chain.
|
|
254
|
+
*
|
|
255
|
+
* SF2 root key resolution priority:
|
|
256
|
+
* 1. OverridingRootKey generator (per-zone, most specific)
|
|
257
|
+
* 2. sample.header.originalPitch (sample header)
|
|
258
|
+
* 3. MIDI note 60 (middle C fallback)
|
|
259
|
+
*
|
|
260
|
+
* Tuning adjustments:
|
|
261
|
+
* - CoarseTune generator (semitones, additive)
|
|
262
|
+
* - FineTune generator (cents, additive)
|
|
263
|
+
* - sample.header.pitchCorrection (cents, additive)
|
|
264
|
+
*/
|
|
265
|
+
private calculatePlaybackRate;
|
|
266
|
+
/**
|
|
267
|
+
* Convert Int16Array sample data to an AudioBuffer.
|
|
268
|
+
* SF2 samples are 16-bit signed integers; Web Audio needs Float32 [-1, 1].
|
|
269
|
+
*/
|
|
270
|
+
private int16ToAudioBuffer;
|
|
271
|
+
/**
|
|
272
|
+
* Clear all cached AudioBuffers and release the parsed SF2.
|
|
273
|
+
*/
|
|
274
|
+
dispose(): void;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
interface SoundFontToneTrackOptions {
|
|
278
|
+
clips: MidiClipInfo[];
|
|
279
|
+
track: Track;
|
|
280
|
+
soundFontCache: SoundFontCache;
|
|
281
|
+
/** GM program number (0-127) for melodic instruments */
|
|
282
|
+
programNumber?: number;
|
|
283
|
+
/** Whether this track uses percussion bank (channel 9) */
|
|
284
|
+
isPercussion?: boolean;
|
|
285
|
+
effects?: TrackEffectsFunction;
|
|
286
|
+
destination?: ToneAudioNode;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* MIDI track that uses SoundFont samples for playback.
|
|
290
|
+
*
|
|
291
|
+
* Instead of PolySynth synthesis, each note triggers the correct instrument
|
|
292
|
+
* sample from an SF2 file, pitch-shifted via AudioBufferSourceNode.playbackRate.
|
|
293
|
+
*
|
|
294
|
+
* Audio graph per note:
|
|
295
|
+
* AudioBufferSourceNode (native, one-shot, pitch-shifted)
|
|
296
|
+
* → GainNode (native, per-note velocity)
|
|
297
|
+
* → Volume.input (Tone.js, shared per-track)
|
|
298
|
+
* → Panner → muteGain → effects/destination
|
|
299
|
+
*/
|
|
300
|
+
declare class SoundFontToneTrack implements PlayableTrack {
|
|
301
|
+
/** Rate-limit missing sample warnings — one per class lifetime */
|
|
302
|
+
private static _missingSampleWarned;
|
|
303
|
+
private scheduledClips;
|
|
304
|
+
private activeSources;
|
|
305
|
+
private soundFontCache;
|
|
306
|
+
private programNumber;
|
|
307
|
+
private bankNumber;
|
|
308
|
+
private volumeNode;
|
|
309
|
+
private panNode;
|
|
310
|
+
private muteGain;
|
|
311
|
+
private track;
|
|
312
|
+
private effectsCleanup?;
|
|
313
|
+
constructor(options: SoundFontToneTrackOptions);
|
|
314
|
+
/**
|
|
315
|
+
* Trigger a note by creating a native AudioBufferSourceNode from the SoundFont cache.
|
|
316
|
+
*
|
|
317
|
+
* Per-note routing: channel 9 → bank 128 (drums), others → bank 0 with programNumber.
|
|
318
|
+
*/
|
|
319
|
+
private triggerNote;
|
|
320
|
+
private gainToDb;
|
|
321
|
+
/**
|
|
322
|
+
* No-op — Tone.Part handles scheduling internally, no ghost tick guard needed.
|
|
323
|
+
*/
|
|
324
|
+
setScheduleGuardOffset(_offset: number): void;
|
|
325
|
+
/**
|
|
326
|
+
* Start notes that should already be sounding at the current transport offset.
|
|
327
|
+
*/
|
|
328
|
+
startMidClipSources(transportOffset: number, audioContextTime: number): void;
|
|
329
|
+
/**
|
|
330
|
+
* Stop all active AudioBufferSourceNodes.
|
|
331
|
+
*/
|
|
332
|
+
stopAllSources(): void;
|
|
333
|
+
/** No-op for MIDI — MIDI uses note velocity, not gain fades. */
|
|
334
|
+
prepareFades(_when: number, _offset: number): void;
|
|
335
|
+
/** No-op for MIDI — no fade automation to cancel. */
|
|
336
|
+
cancelFades(): void;
|
|
337
|
+
setVolume(gain: number): void;
|
|
338
|
+
setPan(pan: number): void;
|
|
339
|
+
setMute(muted: boolean): void;
|
|
340
|
+
setSolo(soloed: boolean): void;
|
|
341
|
+
dispose(): void;
|
|
342
|
+
get id(): string;
|
|
343
|
+
get duration(): number;
|
|
344
|
+
get muted(): boolean;
|
|
345
|
+
get startTime(): number;
|
|
346
|
+
}
|
|
347
|
+
|
|
86
348
|
type EffectsFunction = (masterGainNode: Volume, destination: ToneAudioNode, isOffline: boolean) => void | (() => void);
|
|
87
349
|
interface TonePlayoutOptions {
|
|
88
350
|
tracks?: ToneTrack[];
|
|
@@ -107,13 +369,15 @@ declare class TonePlayout {
|
|
|
107
369
|
private clearCompletionEvent;
|
|
108
370
|
init(): Promise<void>;
|
|
109
371
|
addTrack(trackOptions: ToneTrackOptions): ToneTrack;
|
|
372
|
+
addMidiTrack(trackOptions: MidiToneTrackOptions): MidiToneTrack;
|
|
373
|
+
addSoundFontTrack(trackOptions: SoundFontToneTrackOptions): SoundFontToneTrack;
|
|
110
374
|
/**
|
|
111
375
|
* Apply solo muting after all tracks have been added.
|
|
112
376
|
* Call this after adding all tracks to ensure solo logic is applied correctly.
|
|
113
377
|
*/
|
|
114
378
|
applyInitialSoloState(): void;
|
|
115
379
|
removeTrack(trackId: string): void;
|
|
116
|
-
getTrack(trackId: string):
|
|
380
|
+
getTrack(trackId: string): PlayableTrack | undefined;
|
|
117
381
|
play(when?: number, offset?: number, duration?: number): void;
|
|
118
382
|
pause(): void;
|
|
119
383
|
stop(): void;
|
|
@@ -267,7 +531,9 @@ declare function applyFadeOut(param: AudioParam, startTime: number, duration: nu
|
|
|
267
531
|
|
|
268
532
|
interface ToneAdapterOptions {
|
|
269
533
|
effects?: EffectsFunction;
|
|
534
|
+
/** When provided, MIDI clips use SoundFont sample playback instead of PolySynth */
|
|
535
|
+
soundFontCache?: SoundFontCache;
|
|
270
536
|
}
|
|
271
537
|
declare function createToneAdapter(options?: ToneAdapterOptions): PlayoutAdapter;
|
|
272
538
|
|
|
273
|
-
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 };
|
|
539
|
+
export { type EffectsFunction, type FadeConfig, type FadeType, type MidiClipInfo, MidiToneTrack, type MidiToneTrackOptions, type PlayableTrack, SoundFontCache, type SoundFontSample, SoundFontToneTrack, type SoundFontToneTrackOptions, 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,5 @@
|
|
|
1
|
-
import { Gain, ToneAudioNode, Volume, BaseContext, Context } from 'tone';
|
|
2
|
-
import { Fade, Track } from '@waveform-playlist/core';
|
|
1
|
+
import { Gain, ToneAudioNode, SynthOptions, Volume, BaseContext, Context } from 'tone';
|
|
2
|
+
import { Fade, Track, MidiNoteData } from '@waveform-playlist/core';
|
|
3
3
|
import { PlayoutAdapter } from '@waveform-playlist/engine';
|
|
4
4
|
|
|
5
5
|
type TrackEffectsFunction = (graphEnd: Gain, masterGainNode: ToneAudioNode, isOffline: boolean) => void | (() => void);
|
|
@@ -83,6 +83,268 @@ declare class ToneTrack {
|
|
|
83
83
|
get startTime(): number;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Shared interface for tracks managed by TonePlayout.
|
|
88
|
+
* Both ToneTrack (audio) and MidiToneTrack (MIDI) implement this,
|
|
89
|
+
* allowing TonePlayout to manage them uniformly.
|
|
90
|
+
*/
|
|
91
|
+
interface PlayableTrack {
|
|
92
|
+
id: string;
|
|
93
|
+
startTime: number;
|
|
94
|
+
muted: boolean;
|
|
95
|
+
duration: number;
|
|
96
|
+
stopAllSources(): void;
|
|
97
|
+
startMidClipSources(offset: number, time: number): void;
|
|
98
|
+
setScheduleGuardOffset(offset: number): void;
|
|
99
|
+
prepareFades(when: number, offset: number): void;
|
|
100
|
+
cancelFades(): void;
|
|
101
|
+
setVolume(gain: number): void;
|
|
102
|
+
setPan(pan: number): void;
|
|
103
|
+
setMute(muted: boolean): void;
|
|
104
|
+
setSolo(soloed: boolean): void;
|
|
105
|
+
dispose(): void;
|
|
106
|
+
}
|
|
107
|
+
interface MidiClipInfo {
|
|
108
|
+
notes: MidiNoteData[];
|
|
109
|
+
startTime: number;
|
|
110
|
+
duration: number;
|
|
111
|
+
offset: number;
|
|
112
|
+
}
|
|
113
|
+
interface MidiToneTrackOptions {
|
|
114
|
+
clips: MidiClipInfo[];
|
|
115
|
+
track: Track;
|
|
116
|
+
effects?: TrackEffectsFunction;
|
|
117
|
+
destination?: ToneAudioNode;
|
|
118
|
+
synthOptions?: Partial<SynthOptions>;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* MIDI track that always creates both melodic and percussion synths.
|
|
122
|
+
* Per-note routing uses the `channel` field on each MidiNoteData:
|
|
123
|
+
* channel 9 → percussion synths, all others → melodic PolySynth.
|
|
124
|
+
* This enables flattened tracks (mixed channels) to play correctly.
|
|
125
|
+
*/
|
|
126
|
+
declare class MidiToneTrack implements PlayableTrack {
|
|
127
|
+
private scheduledClips;
|
|
128
|
+
private synth;
|
|
129
|
+
private kickSynth;
|
|
130
|
+
private snareSynth;
|
|
131
|
+
private cymbalSynth;
|
|
132
|
+
private tomSynth;
|
|
133
|
+
private volumeNode;
|
|
134
|
+
private panNode;
|
|
135
|
+
private muteGain;
|
|
136
|
+
private track;
|
|
137
|
+
private effectsCleanup?;
|
|
138
|
+
constructor(options: MidiToneTrackOptions);
|
|
139
|
+
/**
|
|
140
|
+
* Trigger a note using the appropriate synth.
|
|
141
|
+
* Routes per-note: channel 9 → percussion synths, others → melodic PolySynth.
|
|
142
|
+
*/
|
|
143
|
+
private triggerNote;
|
|
144
|
+
private gainToDb;
|
|
145
|
+
/**
|
|
146
|
+
* No-op for MIDI — schedule guard is for AudioBufferSourceNode ghost tick prevention.
|
|
147
|
+
* Tone.Part handles its own scheduling relative to Transport.
|
|
148
|
+
*/
|
|
149
|
+
setScheduleGuardOffset(_offset: number): void;
|
|
150
|
+
/**
|
|
151
|
+
* For MIDI, mid-clip sources are notes that should already be sounding.
|
|
152
|
+
* We trigger them with their remaining duration.
|
|
153
|
+
*/
|
|
154
|
+
startMidClipSources(transportOffset: number, audioContextTime: number): void;
|
|
155
|
+
/**
|
|
156
|
+
* Stop all sounding notes and cancel scheduled Part events.
|
|
157
|
+
*/
|
|
158
|
+
stopAllSources(): void;
|
|
159
|
+
/**
|
|
160
|
+
* No-op for MIDI — MIDI uses note velocity, not gain fades.
|
|
161
|
+
*/
|
|
162
|
+
prepareFades(_when: number, _offset: number): void;
|
|
163
|
+
/**
|
|
164
|
+
* No-op for MIDI — no fade automation to cancel.
|
|
165
|
+
*/
|
|
166
|
+
cancelFades(): void;
|
|
167
|
+
setVolume(gain: number): void;
|
|
168
|
+
setPan(pan: number): void;
|
|
169
|
+
setMute(muted: boolean): void;
|
|
170
|
+
setSolo(soloed: boolean): void;
|
|
171
|
+
dispose(): void;
|
|
172
|
+
get id(): string;
|
|
173
|
+
get duration(): number;
|
|
174
|
+
get muted(): boolean;
|
|
175
|
+
get startTime(): number;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Result of looking up a MIDI note in the SoundFont.
|
|
180
|
+
* Contains the AudioBuffer, playbackRate, loop points, and volume envelope.
|
|
181
|
+
*/
|
|
182
|
+
interface SoundFontSample {
|
|
183
|
+
/** Cached AudioBuffer for this sample */
|
|
184
|
+
buffer: AudioBuffer;
|
|
185
|
+
/** Playback rate to pitch-shift from originalPitch to target note */
|
|
186
|
+
playbackRate: number;
|
|
187
|
+
/** Loop mode: 0=no loop, 1=continuous, 3=sustain loop */
|
|
188
|
+
loopMode: number;
|
|
189
|
+
/** Loop start in seconds, relative to AudioBuffer start */
|
|
190
|
+
loopStart: number;
|
|
191
|
+
/** Loop end in seconds, relative to AudioBuffer start */
|
|
192
|
+
loopEnd: number;
|
|
193
|
+
/** Volume envelope attack time in seconds */
|
|
194
|
+
attackVolEnv: number;
|
|
195
|
+
/** Volume envelope hold time in seconds */
|
|
196
|
+
holdVolEnv: number;
|
|
197
|
+
/** Volume envelope decay time in seconds */
|
|
198
|
+
decayVolEnv: number;
|
|
199
|
+
/** Volume envelope sustain level as linear gain 0-1 */
|
|
200
|
+
sustainVolEnv: number;
|
|
201
|
+
/** Volume envelope release time in seconds */
|
|
202
|
+
releaseVolEnv: number;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Caches parsed SoundFont2 data and AudioBuffers for efficient playback.
|
|
206
|
+
*
|
|
207
|
+
* AudioBuffers are created lazily on first access and cached by sample index.
|
|
208
|
+
* Pitch calculation uses the SF2 generator chain:
|
|
209
|
+
* OverridingRootKey → sample.header.originalPitch → fallback 60
|
|
210
|
+
*
|
|
211
|
+
* Audio graph per note:
|
|
212
|
+
* AudioBufferSourceNode (playbackRate for pitch) → GainNode (velocity) → track chain
|
|
213
|
+
*/
|
|
214
|
+
declare class SoundFontCache {
|
|
215
|
+
private sf2;
|
|
216
|
+
private audioBufferCache;
|
|
217
|
+
private context;
|
|
218
|
+
/**
|
|
219
|
+
* @param context Optional AudioContext for createBuffer(). If omitted, uses
|
|
220
|
+
* an OfflineAudioContext which doesn't require user gesture — safe to
|
|
221
|
+
* construct before user interaction (avoids Firefox autoplay warnings).
|
|
222
|
+
*/
|
|
223
|
+
constructor(context?: BaseAudioContext);
|
|
224
|
+
/**
|
|
225
|
+
* Load and parse an SF2 file from a URL.
|
|
226
|
+
*/
|
|
227
|
+
load(url: string, signal?: AbortSignal): Promise<void>;
|
|
228
|
+
/**
|
|
229
|
+
* Load from an already-fetched ArrayBuffer.
|
|
230
|
+
*/
|
|
231
|
+
loadFromBuffer(data: ArrayBuffer): void;
|
|
232
|
+
get isLoaded(): boolean;
|
|
233
|
+
/**
|
|
234
|
+
* Look up a MIDI note and return the AudioBuffer + playbackRate.
|
|
235
|
+
*
|
|
236
|
+
* @param midiNote - MIDI note number (0-127)
|
|
237
|
+
* @param bankNumber - Bank number (0 for melodic, 128 for percussion/drums)
|
|
238
|
+
* @param presetNumber - GM program number (0-127)
|
|
239
|
+
* @returns SoundFontSample or null if no sample found for this note
|
|
240
|
+
*/
|
|
241
|
+
getAudioBuffer(midiNote: number, bankNumber?: number, presetNumber?: number): SoundFontSample | null;
|
|
242
|
+
/**
|
|
243
|
+
* Extract loop points and volume envelope data from per-zone generators.
|
|
244
|
+
*
|
|
245
|
+
* Loop points are stored as absolute indices into the SF2 sample pool.
|
|
246
|
+
* We convert to AudioBuffer-relative seconds by subtracting header.start
|
|
247
|
+
* and dividing by sampleRate.
|
|
248
|
+
*
|
|
249
|
+
* Volume envelope times are in SF2 timecents; sustain is centibels attenuation.
|
|
250
|
+
*/
|
|
251
|
+
private extractLoopAndEnvelope;
|
|
252
|
+
/**
|
|
253
|
+
* Calculate playback rate for a MIDI note using the SF2 generator chain.
|
|
254
|
+
*
|
|
255
|
+
* SF2 root key resolution priority:
|
|
256
|
+
* 1. OverridingRootKey generator (per-zone, most specific)
|
|
257
|
+
* 2. sample.header.originalPitch (sample header)
|
|
258
|
+
* 3. MIDI note 60 (middle C fallback)
|
|
259
|
+
*
|
|
260
|
+
* Tuning adjustments:
|
|
261
|
+
* - CoarseTune generator (semitones, additive)
|
|
262
|
+
* - FineTune generator (cents, additive)
|
|
263
|
+
* - sample.header.pitchCorrection (cents, additive)
|
|
264
|
+
*/
|
|
265
|
+
private calculatePlaybackRate;
|
|
266
|
+
/**
|
|
267
|
+
* Convert Int16Array sample data to an AudioBuffer.
|
|
268
|
+
* SF2 samples are 16-bit signed integers; Web Audio needs Float32 [-1, 1].
|
|
269
|
+
*/
|
|
270
|
+
private int16ToAudioBuffer;
|
|
271
|
+
/**
|
|
272
|
+
* Clear all cached AudioBuffers and release the parsed SF2.
|
|
273
|
+
*/
|
|
274
|
+
dispose(): void;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
interface SoundFontToneTrackOptions {
|
|
278
|
+
clips: MidiClipInfo[];
|
|
279
|
+
track: Track;
|
|
280
|
+
soundFontCache: SoundFontCache;
|
|
281
|
+
/** GM program number (0-127) for melodic instruments */
|
|
282
|
+
programNumber?: number;
|
|
283
|
+
/** Whether this track uses percussion bank (channel 9) */
|
|
284
|
+
isPercussion?: boolean;
|
|
285
|
+
effects?: TrackEffectsFunction;
|
|
286
|
+
destination?: ToneAudioNode;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* MIDI track that uses SoundFont samples for playback.
|
|
290
|
+
*
|
|
291
|
+
* Instead of PolySynth synthesis, each note triggers the correct instrument
|
|
292
|
+
* sample from an SF2 file, pitch-shifted via AudioBufferSourceNode.playbackRate.
|
|
293
|
+
*
|
|
294
|
+
* Audio graph per note:
|
|
295
|
+
* AudioBufferSourceNode (native, one-shot, pitch-shifted)
|
|
296
|
+
* → GainNode (native, per-note velocity)
|
|
297
|
+
* → Volume.input (Tone.js, shared per-track)
|
|
298
|
+
* → Panner → muteGain → effects/destination
|
|
299
|
+
*/
|
|
300
|
+
declare class SoundFontToneTrack implements PlayableTrack {
|
|
301
|
+
/** Rate-limit missing sample warnings — one per class lifetime */
|
|
302
|
+
private static _missingSampleWarned;
|
|
303
|
+
private scheduledClips;
|
|
304
|
+
private activeSources;
|
|
305
|
+
private soundFontCache;
|
|
306
|
+
private programNumber;
|
|
307
|
+
private bankNumber;
|
|
308
|
+
private volumeNode;
|
|
309
|
+
private panNode;
|
|
310
|
+
private muteGain;
|
|
311
|
+
private track;
|
|
312
|
+
private effectsCleanup?;
|
|
313
|
+
constructor(options: SoundFontToneTrackOptions);
|
|
314
|
+
/**
|
|
315
|
+
* Trigger a note by creating a native AudioBufferSourceNode from the SoundFont cache.
|
|
316
|
+
*
|
|
317
|
+
* Per-note routing: channel 9 → bank 128 (drums), others → bank 0 with programNumber.
|
|
318
|
+
*/
|
|
319
|
+
private triggerNote;
|
|
320
|
+
private gainToDb;
|
|
321
|
+
/**
|
|
322
|
+
* No-op — Tone.Part handles scheduling internally, no ghost tick guard needed.
|
|
323
|
+
*/
|
|
324
|
+
setScheduleGuardOffset(_offset: number): void;
|
|
325
|
+
/**
|
|
326
|
+
* Start notes that should already be sounding at the current transport offset.
|
|
327
|
+
*/
|
|
328
|
+
startMidClipSources(transportOffset: number, audioContextTime: number): void;
|
|
329
|
+
/**
|
|
330
|
+
* Stop all active AudioBufferSourceNodes.
|
|
331
|
+
*/
|
|
332
|
+
stopAllSources(): void;
|
|
333
|
+
/** No-op for MIDI — MIDI uses note velocity, not gain fades. */
|
|
334
|
+
prepareFades(_when: number, _offset: number): void;
|
|
335
|
+
/** No-op for MIDI — no fade automation to cancel. */
|
|
336
|
+
cancelFades(): void;
|
|
337
|
+
setVolume(gain: number): void;
|
|
338
|
+
setPan(pan: number): void;
|
|
339
|
+
setMute(muted: boolean): void;
|
|
340
|
+
setSolo(soloed: boolean): void;
|
|
341
|
+
dispose(): void;
|
|
342
|
+
get id(): string;
|
|
343
|
+
get duration(): number;
|
|
344
|
+
get muted(): boolean;
|
|
345
|
+
get startTime(): number;
|
|
346
|
+
}
|
|
347
|
+
|
|
86
348
|
type EffectsFunction = (masterGainNode: Volume, destination: ToneAudioNode, isOffline: boolean) => void | (() => void);
|
|
87
349
|
interface TonePlayoutOptions {
|
|
88
350
|
tracks?: ToneTrack[];
|
|
@@ -107,13 +369,15 @@ declare class TonePlayout {
|
|
|
107
369
|
private clearCompletionEvent;
|
|
108
370
|
init(): Promise<void>;
|
|
109
371
|
addTrack(trackOptions: ToneTrackOptions): ToneTrack;
|
|
372
|
+
addMidiTrack(trackOptions: MidiToneTrackOptions): MidiToneTrack;
|
|
373
|
+
addSoundFontTrack(trackOptions: SoundFontToneTrackOptions): SoundFontToneTrack;
|
|
110
374
|
/**
|
|
111
375
|
* Apply solo muting after all tracks have been added.
|
|
112
376
|
* Call this after adding all tracks to ensure solo logic is applied correctly.
|
|
113
377
|
*/
|
|
114
378
|
applyInitialSoloState(): void;
|
|
115
379
|
removeTrack(trackId: string): void;
|
|
116
|
-
getTrack(trackId: string):
|
|
380
|
+
getTrack(trackId: string): PlayableTrack | undefined;
|
|
117
381
|
play(when?: number, offset?: number, duration?: number): void;
|
|
118
382
|
pause(): void;
|
|
119
383
|
stop(): void;
|
|
@@ -267,7 +531,9 @@ declare function applyFadeOut(param: AudioParam, startTime: number, duration: nu
|
|
|
267
531
|
|
|
268
532
|
interface ToneAdapterOptions {
|
|
269
533
|
effects?: EffectsFunction;
|
|
534
|
+
/** When provided, MIDI clips use SoundFont sample playback instead of PolySynth */
|
|
535
|
+
soundFontCache?: SoundFontCache;
|
|
270
536
|
}
|
|
271
537
|
declare function createToneAdapter(options?: ToneAdapterOptions): PlayoutAdapter;
|
|
272
538
|
|
|
273
|
-
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 };
|
|
539
|
+
export { type EffectsFunction, type FadeConfig, type FadeType, type MidiClipInfo, MidiToneTrack, type MidiToneTrackOptions, type PlayableTrack, SoundFontCache, type SoundFontSample, SoundFontToneTrack, type SoundFontToneTrackOptions, type ToneAdapterOptions, TonePlayout, type TonePlayoutOptions, ToneTrack, type ToneTrackOptions, type TrackEffectsFunction, applyFadeIn, applyFadeOut, closeGlobalAudioContext, createToneAdapter, getGlobalAudioContext, getGlobalAudioContextState, getGlobalContext, getGlobalToneContext, getMediaStreamSource, getUnderlyingAudioParam, hasMediaStreamSource, releaseMediaStreamSource, resumeGlobalAudioContext };
|