@waveform-playlist/playout 9.0.4 → 9.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,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): ToneTrack | undefined;
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): ToneTrack | undefined;
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 };