midi-audio-player 1.1.2 → 2.0.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/index.d.ts ADDED
@@ -0,0 +1,905 @@
1
+ /**
2
+ * @module midi-audio-player
3
+ *
4
+ * TypeScript declarations for `midi-audio-player` v2.0.0
5
+ *
6
+ * A lightweight browser-based MIDI playback engine built on top of the Web Audio API
7
+ * and WebAudioFont. Supports real MIDI file playback, karaoke, EQ, reverb, waveform
8
+ * generation, per-channel volume control, and a rich event system.
9
+ *
10
+ * @author Maxime Larrivée-Roy
11
+ * @license MIT
12
+ * @see https://github.com/ZmotriN/midi-audio-player
13
+ */
14
+
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Catalog & preset types
18
+ // ---------------------------------------------------------------------------
19
+
20
+ /**
21
+ * A single instrument preset entry from the WebAudioFont catalog.
22
+ * Preset IDs are used to identify and load specific instrument sounds.
23
+ *
24
+ * @example
25
+ * // A preset object returned by findPreset()
26
+ * {
27
+ * id: "0000_FluidR3_GM_sf2_file",
28
+ * category: "Piano",
29
+ * instrument: "Acoustic Grand Piano",
30
+ * program: 1
31
+ * }
32
+ */
33
+ export interface PresetInfo {
34
+ /** Unique identifier for this preset (matches the remote JSON filename, e.g. `"0000_FluidR3_GM_sf2_file"`). */
35
+ id: string;
36
+ /** The top-level category name (e.g. `"Piano"`, `"Strings"`, `"Percussion"`). */
37
+ category: string;
38
+ /** The instrument name within the category (e.g. `"Acoustic Grand Piano"`). */
39
+ instrument: string;
40
+ /** The General MIDI program number (1–128) this preset belongs to. `-1` indicates percussion (channel 10). */
41
+ program: number;
42
+ }
43
+
44
+ /**
45
+ * A single instrument entry within a catalog category.
46
+ */
47
+ export interface CatalogInstrument {
48
+ /** Instrument name (e.g. `"Acoustic Grand Piano"`). */
49
+ name: string;
50
+ /** General MIDI program number (1–128). `-1` for percussion. */
51
+ program: number;
52
+ /** All available presets (sound banks) for this instrument. */
53
+ presets: PresetInfo[];
54
+ }
55
+
56
+ /**
57
+ * A top-level category grouping multiple instruments.
58
+ *
59
+ * @example
60
+ * { name: "Piano", instruments: [ ... ] }
61
+ */
62
+ export interface CatalogCategory {
63
+ /** Category name (e.g. `"Piano"`, `"Guitar"`, `"Synth Lead"`). */
64
+ name: string;
65
+ /** All instruments belonging to this category. */
66
+ instruments: CatalogInstrument[];
67
+ }
68
+
69
+ /**
70
+ * The full preset catalog fetched from the WebAudioFont endpoint.
71
+ * Contains all available instruments and their sound bank presets.
72
+ */
73
+ export interface Catalog {
74
+ /** ISO 8601 timestamp of the last catalog update. Used internally to invalidate the IndexedDB cache. */
75
+ updatedAt: string;
76
+ /** All instrument categories available from the endpoint. */
77
+ categories: CatalogCategory[];
78
+ }
79
+
80
+
81
+ // ---------------------------------------------------------------------------
82
+ // Song setup
83
+ // ---------------------------------------------------------------------------
84
+
85
+ /**
86
+ * A serializable snapshot of the current song's channel configuration.
87
+ * Can be saved and passed back to `load()` to restore the exact same
88
+ * preset and volume settings for a given MIDI file.
89
+ *
90
+ * @example
91
+ * const setup = await player.getSongSetup();
92
+ * // Later, restore it:
93
+ * await player.load('song.mid', setup);
94
+ */
95
+ export interface SongSetup {
96
+ /**
97
+ * Maps each MIDI channel number (as a string key) to a preset ID.
98
+ *
99
+ * @example { "1": "0000_FluidR3_GM_sf2_file", "10": "12800_SoundBlasterOld_sf2" }
100
+ */
101
+ presets: Record<string, string>;
102
+ /**
103
+ * Maps each MIDI channel number (as a string key) to a volume multiplier in the `[0, 1]` range.
104
+ *
105
+ * @example { "1": 0.8, "2": 1.0, "10": 0.6 }
106
+ */
107
+ volumes: Record<string, number>;
108
+ }
109
+
110
+
111
+ // ---------------------------------------------------------------------------
112
+ // EQ
113
+ // ---------------------------------------------------------------------------
114
+
115
+ /**
116
+ * A map of EQ band frequencies (in Hz) to their current gain values (in dB).
117
+ * The 10 bands cover the full audible spectrum: `32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384`.
118
+ *
119
+ * @example
120
+ * // Boost bass, cut mids
121
+ * player.setEQ({ 32: 6, 64: 4, 128: 0, 256: -2, 512: -2, 1024: 0, 2048: 0, 4096: 0, 8192: 0, 16384: 0 });
122
+ */
123
+ export type EQGains = Partial<Record<32 | 64 | 128 | 256 | 512 | 1024 | 2048 | 4096 | 8192 | 16384, number>>;
124
+
125
+ /**
126
+ * Named EQ presets available via `setEQPreset()`.
127
+ *
128
+ * | Preset | Description |
129
+ * |--------------|---------------------------------------------------|
130
+ * | `flat` | All bands at 0 dB (default) |
131
+ * | `bass` | Boosted low frequencies |
132
+ * | `treble` | Boosted high frequencies |
133
+ * | `vocal` | Mid-range boost, suited for voice clarity |
134
+ * | `loudness` | Loudness compensation curve (bass + treble boost) |
135
+ * | `classical` | Gentle wide curve for orchestral content |
136
+ * | `jazz` | Warm low-end with slight high presence |
137
+ * | `electronic` | Enhanced bass and air frequencies |
138
+ */
139
+ export type EQPresetName = 'flat' | 'bass' | 'treble' | 'vocal' | 'loudness' | 'classical' | 'jazz' | 'electronic';
140
+
141
+
142
+ // ---------------------------------------------------------------------------
143
+ // Constructor options
144
+ // ---------------------------------------------------------------------------
145
+
146
+ /**
147
+ * Configuration options passed to the `MidiAudioPlayer` constructor.
148
+ * All fields are optional; unspecified values fall back to their defaults.
149
+ *
150
+ * ---
151
+ * **Custom preset endpoint**
152
+ *
153
+ * By default, presets are fetched from the official WebAudioFont CDN
154
+ * (`https://webaudiofonts.github.io/presets/`). You can host your own presets
155
+ * by deploying the [sf2-json template](https://github.com/WebAudioFonts/sf2-json)
156
+ * and pointing `endpoint` to your own server.
157
+ *
158
+ * @example
159
+ * const player = new MidiAudioPlayer({
160
+ * endpoint: 'https://my-cdn.example.com/presets/',
161
+ * volume: 0.8,
162
+ * reverb: 0.2,
163
+ * karaoke: true,
164
+ * eqPreset: 'jazz',
165
+ * preferred: ['FluidR3_GM'],
166
+ * presets: ['0000_FluidR3_GM_sf2_file'],
167
+ * });
168
+ */
169
+ export interface MidiAudioPlayerOptions {
170
+ /**
171
+ * Base URL of the WebAudioFont preset endpoint.
172
+ * Must end with a trailing slash and expose a `catalog.json` file
173
+ * along with individual preset JSON files.
174
+ *
175
+ * You can deploy your own endpoint using the sf2-json template:
176
+ * @see https://github.com/WebAudioFonts/sf2-json
177
+ *
178
+ * @default "https://webaudiofonts.github.io/presets/"
179
+ */
180
+ endpoint?: string;
181
+
182
+ /**
183
+ * Master output volume, in the `[0, 1]` range. Applied on a logarithmic curve.
184
+ * @default 0.6
185
+ */
186
+ volume?: number;
187
+
188
+ /**
189
+ * Convolution reverb wet level, in the `[0, 1]` range.
190
+ * `0` disables reverb entirely; `1` is full wet signal.
191
+ * @default 0.3
192
+ */
193
+ reverb?: number;
194
+
195
+ /**
196
+ * Whether to cache the preset catalog and downloaded presets in
197
+ * `sessionStorage` and `IndexedDB` respectively.
198
+ * Significantly reduces network requests on repeat visits.
199
+ * @default true
200
+ */
201
+ localCache?: boolean;
202
+
203
+ /**
204
+ * When `true`, a random preset is selected for each MIDI program/channel
205
+ * rather than the first available one. Useful for creative variation.
206
+ * @default false
207
+ */
208
+ presetRandom?: boolean;
209
+
210
+ /**
211
+ * Enables karaoke mode. When active, the player parses MIDI text/lyric
212
+ * events, generates timed HTML lyric frames, and emits `karaoke` events.
213
+ * @default false
214
+ */
215
+ karaoke?: boolean;
216
+
217
+ /**
218
+ * Time offset (in seconds) to apply to karaoke lyric frames.
219
+ * Positive values display lyrics earlier than the corresponding note.
220
+ * @default 0
221
+ */
222
+ karaokeDelay?: number;
223
+
224
+ /**
225
+ * When `true`, automatically detects the vocal channel (the MIDI channel
226
+ * whose notes align most closely with the lyric events) and mutes it.
227
+ * Useful for sing-along applications.
228
+ * @default false
229
+ */
230
+ muteExpression?: boolean;
231
+
232
+ /**
233
+ * Maximum number of characters per karaoke line before a line break is forced.
234
+ * @default 48
235
+ */
236
+ maxCharPerLine?: number;
237
+
238
+ /**
239
+ * Initial EQ preset to apply on instantiation.
240
+ * @see EQPresetName
241
+ * @default "flat"
242
+ */
243
+ eqPreset?: EQPresetName;
244
+
245
+ /**
246
+ * Ordered list of preferred sound bank suffixes (e.g. `["FluidR3_GM", "Aspirin"]`).
247
+ * When resolving a preset automatically or randomly, the player will favor
248
+ * presets whose IDs end with one of these strings, in order.
249
+ * @default []
250
+ */
251
+ preferred?: string[];
252
+
253
+ /**
254
+ * A list of preset IDs to pre-register in the program-to-preset map.
255
+ * These presets will override automatic preset selection for the
256
+ * corresponding General MIDI program number when a song is loaded.
257
+ * @default []
258
+ */
259
+ presets?: string[];
260
+ }
261
+
262
+
263
+ // ---------------------------------------------------------------------------
264
+ // Event payloads
265
+ // ---------------------------------------------------------------------------
266
+
267
+ /**
268
+ * Payload emitted with the `computed` event after a MIDI file is parsed
269
+ * and analyzed but before audio presets are fully loaded.
270
+ */
271
+ export interface ComputedEventData {
272
+ /** Song title extracted from MIDI metadata, if present. */
273
+ title: string;
274
+ /** Whether the MIDI file contains parseable lyric/karaoke data. */
275
+ karaoke: boolean;
276
+ /** Current song tempo in BPM. */
277
+ tempo: number;
278
+ /** MIDI ticks-per-quarter-note (PPQ/division). */
279
+ division: number;
280
+ /** Total playback duration in seconds. */
281
+ duration: number;
282
+ /** Audio sample rate of the Web Audio context (typically 44100 or 48000 Hz). */
283
+ sampleRate: number;
284
+ /** Total number of MIDI ticks in the song. */
285
+ totalTicks: number;
286
+ /** Total number of MIDI events across all tracks. */
287
+ totalEvents: number;
288
+ /**
289
+ * Map of active MIDI channel numbers to their General MIDI program numbers.
290
+ * Channel 10 is always `-1` (percussion).
291
+ *
292
+ * @example { "1": 1, "2": 40, "10": -1 }
293
+ */
294
+ channels: Record<string, number>;
295
+ }
296
+
297
+ /**
298
+ * Payload emitted with the `karaoke` event for each lyric frame.
299
+ * The HTML content uses predictable CSS class names for easy styling.
300
+ *
301
+ * **CSS classes injected:**
302
+ * - `.karaoke-playing` — the syllable currently being sung
303
+ * - `.karaoke-played` — syllables already passed
304
+ * - `.karaoke-coming` — upcoming syllables
305
+ * - `.karaoke-clear` — empty frame (silence / between paragraphs)
306
+ * - `.karaoke-intro` — emitted before the song starts or after stop
307
+ * - `.karaoke-title` — emitted when a title is detected in the MIDI metadata
308
+ */
309
+ export interface KaraokeEventData {
310
+ /** Frame type. `"lyric"` carries displayable text; others signal state transitions. */
311
+ type: 'lyric' | 'clear' | 'intro' | 'title';
312
+ /** The MIDI tick at which this frame should be displayed. */
313
+ tick: number;
314
+ /** HTML string ready to inject into a DOM element. */
315
+ html: string;
316
+ /** Only present when `type === "title"`. Raw title text (no HTML). */
317
+ title?: string;
318
+ }
319
+
320
+ /**
321
+ * Payload emitted with the `channelState` event whenever any channel
322
+ * transitions between active (playing notes) and idle (silent).
323
+ *
324
+ * Keys are MIDI channel numbers as strings; values are `true` when the
325
+ * channel has one or more active (sustained) notes, `false` otherwise.
326
+ *
327
+ * @example
328
+ * player.on('channelState', (states) => {
329
+ * // { "1": true, "2": false, "10": true }
330
+ * });
331
+ */
332
+ export type ChannelStateData = Record<string, boolean>;
333
+
334
+
335
+ // ---------------------------------------------------------------------------
336
+ // Event map
337
+ // ---------------------------------------------------------------------------
338
+
339
+ /**
340
+ * All events that can be subscribed to via `player.on(event, handler)`.
341
+ */
342
+ export interface MidiAudioPlayerEvents {
343
+ /**
344
+ * Emitted for internal log messages throughout the loading and playback lifecycle.
345
+ * Useful for debugging preset downloads, MIDI parsing, and playback state.
346
+ *
347
+ * @example
348
+ * player.on('logs', (message) => console.log('[MidiPlayer]', message));
349
+ */
350
+ logs: (message: string) => void;
351
+
352
+ /**
353
+ * Emitted once the MIDI file has been parsed and analyzed (tick map, channels,
354
+ * duration computed), but *before* audio presets finish downloading.
355
+ * Use this event to update your UI with song metadata immediately.
356
+ *
357
+ * @example
358
+ * player.on('computed', ({ title, duration, channels }) => {
359
+ * console.log(`"${title}" — ${duration.toFixed(1)}s`);
360
+ * });
361
+ */
362
+ computed: (data: ComputedEventData) => void;
363
+
364
+ /**
365
+ * Emitted once all audio presets for the current song have finished
366
+ * downloading and the player is fully ready to play.
367
+ *
368
+ * The payload is a map of MIDI program numbers to their resolved preset objects.
369
+ *
370
+ * @example
371
+ * player.on('presetsLoaded', (instruments) => {
372
+ * console.log('Ready. Instruments loaded:', Object.keys(instruments).length);
373
+ * });
374
+ */
375
+ presetsLoaded: (instruments: Record<string, object>) => void;
376
+
377
+ /**
378
+ * Emitted when playback reaches the end of the MIDI file.
379
+ * No payload is provided.
380
+ *
381
+ * @example
382
+ * player.on('endOfFile', () => player.stop());
383
+ */
384
+ endOfFile: () => void;
385
+
386
+ /**
387
+ * Emitted for each timed lyric frame when karaoke mode is enabled.
388
+ * The payload contains an HTML string ready for direct DOM injection,
389
+ * with CSS classes marking syllable states.
390
+ *
391
+ * @example
392
+ * const lyricsEl = document.getElementById('lyrics');
393
+ * player.on('karaoke', ({ html }) => { lyricsEl.innerHTML = html; });
394
+ */
395
+ karaoke: (data: KaraokeEventData) => void;
396
+
397
+ /**
398
+ * Emitted whenever any MIDI channel transitions between active (playing notes)
399
+ * and idle (no active notes). Fires only when the state actually changes.
400
+ *
401
+ * Useful for animating per-channel indicators in a visualizer.
402
+ *
403
+ * @example
404
+ * player.on('channelState', (states) => {
405
+ * Object.entries(states).forEach(([ch, active]) => {
406
+ * document.getElementById(`ch-${ch}`)?.classList.toggle('active', active);
407
+ * });
408
+ * });
409
+ */
410
+ channelState: (data: ChannelStateData) => void;
411
+ }
412
+
413
+
414
+ // ---------------------------------------------------------------------------
415
+ // Main class
416
+ // ---------------------------------------------------------------------------
417
+
418
+ /**
419
+ * # MidiAudioPlayer
420
+ *
421
+ * A full-featured MIDI playback engine for the browser.
422
+ *
423
+ * Extends the `midi-player-js` Player and wires it to a Web Audio signal chain
424
+ * (EQ → compressor → convolution reverb → analyser → destination) with
425
+ * WebAudioFont instrument presets served from a remote CDN (or your own endpoint).
426
+ *
427
+ * ---
428
+ *
429
+ * ## Quick start
430
+ *
431
+ * ```ts
432
+ * import MidiAudioPlayer from 'midi-audio-player';
433
+ *
434
+ * const player = new MidiAudioPlayer({ volume: 0.7, reverb: 0.2 });
435
+ *
436
+ * player.on('computed', ({ title, duration }) => {
437
+ * console.log(`Loaded: "${title}" (${duration.toFixed(1)}s)`);
438
+ * });
439
+ *
440
+ * player.on('endOfFile', () => player.stop());
441
+ *
442
+ * await player.play('https://example.com/song.mid');
443
+ * ```
444
+ *
445
+ * ---
446
+ *
447
+ * ## Custom preset endpoint
448
+ *
449
+ * By default, presets are streamed from `https://webaudiofonts.github.io/presets/`.
450
+ * You can self-host using the sf2-json deployment template:
451
+ * @see https://github.com/WebAudioFonts/sf2-json
452
+ *
453
+ * ```ts
454
+ * const player = new MidiAudioPlayer({ endpoint: 'https://my-cdn.example.com/presets/' });
455
+ * ```
456
+ */
457
+ declare class MidiAudioPlayer {
458
+
459
+ // -----------------------------------------------------------------------
460
+ // Static constants
461
+ // -----------------------------------------------------------------------
462
+
463
+ /** Default preset CDN endpoint URL. Overridable via `opts.endpoint`. */
464
+ static readonly ENDPOINT: string;
465
+
466
+ /**
467
+ * Sentinel value (`-1`) used as the program number for the percussion channel (channel 10).
468
+ * Percussion presets are identified by `program === MidiAudioPlayer.DEFAULT_PRESET`.
469
+ */
470
+ static readonly DEFAULT_PRESET: number;
471
+
472
+ /** Reference gain constant applied to note velocity calculations. */
473
+ static readonly REFERENCE_GAIN: number;
474
+
475
+ /** The MIDI channel index (0-based internally) reserved for karaoke event injection. */
476
+ static readonly KARAOKE_CHANNEL: number;
477
+
478
+
479
+ // -----------------------------------------------------------------------
480
+ // Constructor
481
+ // -----------------------------------------------------------------------
482
+
483
+ /**
484
+ * Creates a new `MidiAudioPlayer` instance and initializes the Web Audio
485
+ * signal chain (EQ → compressor/limiter → reverb → analyser → destination).
486
+ *
487
+ * Must be called in response to a user gesture (click, keydown, etc.) because
488
+ * the Web Audio context requires user activation in modern browsers.
489
+ *
490
+ * @param opts - Optional configuration. All fields have sensible defaults.
491
+ *
492
+ * @example
493
+ * const player = new MidiAudioPlayer({
494
+ * volume: 0.8,
495
+ * reverb: 0.25,
496
+ * eqPreset: 'classical',
497
+ * karaoke: true,
498
+ * preferred: ['FluidR3_GM'],
499
+ * });
500
+ */
501
+ constructor(opts?: MidiAudioPlayerOptions);
502
+
503
+
504
+ // -----------------------------------------------------------------------
505
+ // Getters / Setters
506
+ // -----------------------------------------------------------------------
507
+
508
+ /**
509
+ * Resolves and returns the full preset catalog.
510
+ * Equivalent to calling `getCatalog()`.
511
+ * @see getCatalog
512
+ */
513
+ get catalog(): Promise<Catalog>;
514
+
515
+ /**
516
+ * Returns the internal map of WebAudioFont player instances, keyed by MIDI channel number.
517
+ * Each value is an active `WebAudioFontPlayer` instance managing a single channel.
518
+ */
519
+ get channels(): Record<string, object>;
520
+
521
+ /**
522
+ * Returns a snapshot of current channel activity states.
523
+ * Keys are MIDI channel numbers; values are `true` when the channel
524
+ * has one or more active (sustaining) notes.
525
+ *
526
+ * @example
527
+ * const states = player.channelStates;
528
+ * // { "1": true, "2": false, "10": false }
529
+ */
530
+ get channelStates(): Record<string, boolean>;
531
+
532
+ /**
533
+ * Gets the current master volume level (`[0, 1]`).
534
+ */
535
+ get volume(): number;
536
+
537
+ /**
538
+ * Sets the master output volume. Clamped to `[0, 1]`.
539
+ * Applied with a logarithmic curve for perceptually linear control.
540
+ */
541
+ set volume(vol: number);
542
+
543
+ /**
544
+ * Returns the per-channel volume multipliers, keyed by MIDI channel number.
545
+ * Default is `1.0` for all channels. Set via `setChannelVolume()`.
546
+ */
547
+ get volumes(): Record<string, number>;
548
+
549
+ /**
550
+ * Gets the current reverb wet level (`[0, 1]`).
551
+ */
552
+ get rever(): number;
553
+
554
+ /**
555
+ * Sets the convolution reverb wet mix level. Clamped to `[0, 1]`.
556
+ * `0` = fully dry, `1` = fully wet.
557
+ */
558
+ set rever(rev: number);
559
+
560
+ /**
561
+ * Gets the `muteExpression` flag.
562
+ * When `true`, the detected vocal channel is silenced during playback.
563
+ */
564
+ get muteExpression(): boolean;
565
+
566
+ /**
567
+ * Enables or disables vocal channel muting.
568
+ * Effective only when `karaoke` option is active.
569
+ */
570
+ set muteExpression(val: boolean);
571
+
572
+ /**
573
+ * Returns the array of EQ band frequencies (in Hz) supported by the equalizer.
574
+ * Always `[32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384]`.
575
+ */
576
+ get eqFrequencies(): number[];
577
+
578
+ /**
579
+ * Returns the current EQ state as a frequency-to-gain map.
580
+ * Equivalent to calling `getEQ()`.
581
+ * @see getEQ
582
+ */
583
+ get eq(): EQGains;
584
+
585
+
586
+ // -----------------------------------------------------------------------
587
+ // EQ methods
588
+ // -----------------------------------------------------------------------
589
+
590
+ /**
591
+ * Returns the current gain (in dB) for all 10 EQ bands.
592
+ *
593
+ * @returns A map of frequency → current gain value in dB.
594
+ *
595
+ * @example
596
+ * const gains = player.getEQ();
597
+ * console.log(gains[32]); // e.g. 6 (dB)
598
+ */
599
+ getEQ(): EQGains;
600
+
601
+ /**
602
+ * Sets one or more EQ band gains. Only the provided frequencies are updated;
603
+ * bands not included in `gains` are left unchanged.
604
+ *
605
+ * Gain values are in dB, typically in the `-12` to `+12` range.
606
+ *
607
+ * @param gains - Partial map of frequency (Hz) → gain (dB).
608
+ *
609
+ * @example
610
+ * // Boost bass, reduce high mids
611
+ * player.setEQ({ 32: 6, 64: 4, 1024: -2, 2048: -3 });
612
+ */
613
+ setEQ(gains: EQGains): void;
614
+
615
+ /**
616
+ * Applies a named EQ preset, replacing all 10 band gains at once.
617
+ *
618
+ * @param name - A named preset. One of: `flat`, `bass`, `treble`, `vocal`,
619
+ * `loudness`, `classical`, `jazz`, `electronic`.
620
+ *
621
+ * @throws {Error} If `name` does not match a known preset.
622
+ *
623
+ * @example
624
+ * player.setEQPreset('jazz');
625
+ */
626
+ setEQPreset(name: EQPresetName): void;
627
+
628
+ /**
629
+ * Overrides the volume multiplier for a specific MIDI channel.
630
+ * Takes effect immediately and persists for the duration of the current song.
631
+ *
632
+ * @param channel - MIDI channel number (1–16; use `10` for percussion).
633
+ * @param volume - Volume multiplier in the `[0, 1]` range.
634
+ * `0` fully silences the channel; `1` restores full level.
635
+ *
636
+ * @example
637
+ * player.setChannelVolume(10, 0); // Mute percussion
638
+ * player.setChannelVolume(1, 0.5); // Half volume on channel 1
639
+ */
640
+ setChannelVolume(channel: number, volume: number): void;
641
+
642
+
643
+ // -----------------------------------------------------------------------
644
+ // Preset / catalog methods
645
+ // -----------------------------------------------------------------------
646
+
647
+ /**
648
+ * Searches the catalog for a preset by its string ID and returns full metadata.
649
+ *
650
+ * @param id - A preset ID string (e.g. `"0000_FluidR3_GM_sf2_file"`).
651
+ * @returns The matching `PresetInfo` object, or `null` if not found.
652
+ *
653
+ * @example
654
+ * const info = await player.findPreset('0000_FluidR3_GM_sf2_file');
655
+ * // { id: '0000_...', category: 'Piano', instrument: 'Acoustic Grand Piano', program: 1 }
656
+ */
657
+ findPreset(id: string): Promise<PresetInfo | null>;
658
+
659
+ /**
660
+ * Fetches and returns the full WebAudioFont preset catalog.
661
+ * Results are cached in `sessionStorage` (when `localCache` is enabled)
662
+ * to avoid redundant network requests across page loads.
663
+ *
664
+ * @returns The catalog object containing all instrument categories and presets.
665
+ *
666
+ * @example
667
+ * const catalog = await player.getCatalog();
668
+ * console.log(catalog.categories.length); // e.g. 17
669
+ */
670
+ getCatalog(): Promise<Catalog>;
671
+
672
+ /**
673
+ * Returns all top-level instrument categories from the catalog.
674
+ * Shorthand for `(await getCatalog()).categories`.
675
+ *
676
+ * @example
677
+ * const categories = await player.getCategories();
678
+ * categories.forEach(c => console.log(c.name));
679
+ */
680
+ getCategories(): Promise<CatalogCategory[]>;
681
+
682
+ /**
683
+ * Loads a preset onto a specific MIDI channel at runtime, replacing whatever
684
+ * instrument was automatically assigned during `load()`.
685
+ *
686
+ * @param presetId - The preset ID string to load.
687
+ * @param channel - The target MIDI channel number.
688
+ *
689
+ * @throws {Error} If `presetId` does not exist in the catalog.
690
+ *
691
+ * @example
692
+ * // Replace channel 1 with a Steinway piano preset
693
+ * await player.loadPreset('0000_Steinway_sf2_file', 1);
694
+ */
695
+ loadPreset(presetId: string, channel: number): Promise<void>;
696
+
697
+
698
+ // -----------------------------------------------------------------------
699
+ // Song loading
700
+ // -----------------------------------------------------------------------
701
+
702
+ /**
703
+ * Loads a MIDI file and prepares the player for playback.
704
+ *
705
+ * Accepts a URL string, an `ArrayBuffer`, or a Base64-encoded string.
706
+ * If the MIDI file is malformed, an automatic repair pass is attempted before failing.
707
+ *
708
+ * An optional `setup` argument (a URL string or a pre-fetched `SongSetup` object)
709
+ * can be provided to restore previously saved channel presets and volumes.
710
+ *
711
+ * This method:
712
+ * 1. Stops current playback and clears the active note registry.
713
+ * 2. Parses and optionally repairs the MIDI buffer.
714
+ * 3. Resolves and downloads all required instrument presets.
715
+ * 4. Trims leading/trailing silence.
716
+ * 5. Generates karaoke frames (if `opts.karaoke` is `true`).
717
+ * 6. Emits `computed` (early) and `presetsLoaded` (once audio is ready).
718
+ *
719
+ * @param content - The MIDI source: a URL string, an `ArrayBuffer`, or a Base64 string.
720
+ * @param setup - Optional. A `SongSetup` object or URL to a JSON file,
721
+ * to restore preset/volume overrides.
722
+ *
723
+ * @example
724
+ * // Load from URL with a saved setup
725
+ * await player.load('https://example.com/song.mid', savedSetup);
726
+ *
727
+ * // Load from an ArrayBuffer (e.g. from a file input)
728
+ * const buffer = await file.arrayBuffer();
729
+ * await player.load(buffer);
730
+ */
731
+ load(content: string | ArrayBuffer, setup?: string | SongSetup): Promise<void>;
732
+
733
+ /**
734
+ * Returns a serializable snapshot of the current song's channel configuration,
735
+ * including the active preset ID for each channel and per-channel volume levels.
736
+ *
737
+ * The returned object can be stored and passed back to `load()` to reproduce
738
+ * the exact same instrument/volume mapping for the same MIDI file.
739
+ *
740
+ * @returns A `SongSetup` object with `presets` and `volumes` maps.
741
+ *
742
+ * @example
743
+ * const setup = await player.getSongSetup();
744
+ * localStorage.setItem('my-song-setup', JSON.stringify(setup));
745
+ */
746
+ getSongSetup(): Promise<SongSetup>;
747
+
748
+ /**
749
+ * Returns the list of preset IDs currently registered in the training preset map.
750
+ * These are the presets that override automatic selection when a song is loaded.
751
+ *
752
+ * @example
753
+ * const ids = await player.getTrainingPresets();
754
+ * console.log(ids); // ['0000_FluidR3_GM_sf2_file', ...]
755
+ */
756
+ getTrainingPresets(): Promise<string[]>;
757
+
758
+
759
+ // -----------------------------------------------------------------------
760
+ // Playback control
761
+ // -----------------------------------------------------------------------
762
+
763
+ /**
764
+ * Starts or resumes playback.
765
+ *
766
+ * If `content` is provided, the MIDI file is loaded first (equivalent to
767
+ * calling `load(content)` then `play()`). Handles Web Audio context
768
+ * resumption from suspended state automatically.
769
+ *
770
+ * @param content - Optional MIDI source to load before playing.
771
+ * @returns `true` if playback started successfully, `false` if the Audio
772
+ * context could not be resumed (usually due to browser autoplay policy).
773
+ *
774
+ * @example
775
+ * // Load and play in one call
776
+ * await player.play('https://example.com/song.mid');
777
+ *
778
+ * // Resume after a pause
779
+ * await player.play();
780
+ */
781
+ play(content?: string | ArrayBuffer | null): Promise<boolean>;
782
+
783
+ /**
784
+ * Pauses playback at the current position.
785
+ * Kills the reverb tail and cancels all queued audio to prevent lingering sound.
786
+ * Call `play()` to resume from the same position.
787
+ *
788
+ * @example
789
+ * await player.pause();
790
+ */
791
+ pause(): Promise<void>;
792
+
793
+ /**
794
+ * Stops playback and resets the position to the beginning.
795
+ * Kills the reverb tail, clears active notes, and (if karaoke mode is on)
796
+ * emits an `intro` karaoke frame.
797
+ *
798
+ * @returns The player instance, for chaining.
799
+ *
800
+ * @example
801
+ * await player.stop();
802
+ */
803
+ stop(): Promise<MidiAudioPlayer>;
804
+
805
+ /**
806
+ * Seeks to a specific position (in seconds) in the current song.
807
+ * Works whether the player is playing or paused.
808
+ * Resumes playback if it was active before the seek.
809
+ *
810
+ * @param seconds - Target position in seconds. Must be within `[0, songTime]`.
811
+ * @returns The player instance, for chaining.
812
+ *
813
+ * @throws {string} The seconds value if it falls outside the valid range.
814
+ *
815
+ * @example
816
+ * await player.skipToSeconds(30); // Jump to 0:30
817
+ */
818
+ skipToSeconds(seconds: number): Promise<MidiAudioPlayer>;
819
+
820
+
821
+ // -----------------------------------------------------------------------
822
+ // Metering & visualization
823
+ // -----------------------------------------------------------------------
824
+
825
+ /**
826
+ * Returns the real-time normalized output amplitude as a value in `[0, 1]`.
827
+ * Computed from the Web Audio `AnalyserNode`'s frequency data.
828
+ *
829
+ * Suitable for driving VU meters, level indicators, or animation loops.
830
+ *
831
+ * @example
832
+ * function updateMeter() {
833
+ * const level = player.getRealTimeVolume();
834
+ * meterEl.style.width = `${level * 100}%`;
835
+ * requestAnimationFrame(updateMeter);
836
+ * }
837
+ * updateMeter();
838
+ */
839
+ getRealTimeVolume(): number;
840
+
841
+ /**
842
+ * Returns the remaining playback time in seconds (from current position to end).
843
+ *
844
+ * @example
845
+ * const remaining = player.getSongTimeRemaining();
846
+ * console.log(`${remaining.toFixed(1)}s remaining`);
847
+ */
848
+ getSongTimeRemaining(): number;
849
+
850
+ /**
851
+ * Generates an SVG waveform representation of the loaded MIDI file,
852
+ * based on note velocities and channel expression/volume controllers.
853
+ *
854
+ * The returned SVG has the class `midiaudioplayer-waveform` and uses
855
+ * a `<path>` element — no fill color is set by default, allowing full
856
+ * CSS customization via `stroke` and `fill`.
857
+ *
858
+ * @param samples - Number of horizontal data points. Higher values produce
859
+ * finer detail at the cost of processing time.
860
+ * @default 1000
861
+ * @returns An SVG string, or an empty string if no MIDI data is loaded.
862
+ *
863
+ * @example
864
+ * const svg = await player.generateWaveformSVG(800);
865
+ * document.getElementById('waveform').innerHTML = svg;
866
+ */
867
+ generateWaveformSVG(samples?: number): Promise<string>;
868
+
869
+
870
+ // -----------------------------------------------------------------------
871
+ // Event system
872
+ // -----------------------------------------------------------------------
873
+
874
+ /**
875
+ * Registers an event handler for the specified player event.
876
+ *
877
+ * @param event - The event name. See `MidiAudioPlayerEvents` for all events.
878
+ * @param handler - The callback function. Signature varies per event.
879
+ *
880
+ * @example
881
+ * player.on('endOfFile', () => console.log('Song finished'));
882
+ * player.on('karaoke', ({ html }) => (lyricsEl.innerHTML = html));
883
+ * player.on('channelState', (states) => console.log(states));
884
+ */
885
+ on<K extends keyof MidiAudioPlayerEvents>(event: K, handler: MidiAudioPlayerEvents[K]): void;
886
+
887
+
888
+ // -----------------------------------------------------------------------
889
+ // Lifecycle
890
+ // -----------------------------------------------------------------------
891
+
892
+ /**
893
+ * Closes all WebAudioFont player instances and shuts down the Web Audio context.
894
+ * Call this when you are done with the player to release audio hardware resources.
895
+ *
896
+ * The instance cannot be used after this call. Create a new `MidiAudioPlayer`
897
+ * if you need to resume playback.
898
+ *
899
+ * @example
900
+ * await player.close();
901
+ */
902
+ close(): Promise<void>;
903
+ }
904
+
905
+ export default MidiAudioPlayer;