midi-audio-player 1.1.1 → 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/CHANGELOG.md +26 -0
- package/README.md +472 -93
- package/dist/index.js +1715 -212
- package/dist/index.js.map +4 -4
- package/dist/index.mjs +13 -13
- package/dist/index.mjs.map +4 -4
- package/dist/midi-audio-player.js +1714 -212
- package/dist/midi-audio-player.min.js +13 -14
- package/index.d.ts +905 -0
- package/package.json +13 -17
- package/src/libraries/audiocompressor.js +186 -0
- package/src/libraries/indexeddbstorage.js +107 -0
- package/src/midiaudioplayer.js +1379 -52
- package/bin/cli.js +0 -27
- package/dist/midi-audio-player.js.map +0 -7
- package/dist/midi-audio-player.min.js.map +0 -7
- package/src/downloader.js +0 -33
- package/src/presets/defaultpreset.json +0 -116
- package/src/webaudiofontplayer.js +0 -264
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;
|