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/CHANGELOG.md +26 -0
- package/README.md +472 -86
- package/dist/index.js +1712 -209
- 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 +1711 -209
- package/dist/midi-audio-player.min.js +13 -14
- package/index.d.ts +905 -0
- package/index.js +0 -1
- package/package.json +13 -17
- package/src/libraries/audiocompressor.js +186 -0
- package/src/libraries/indexeddbstorage.js +107 -0
- package/src/midiaudioplayer.js +1379 -51
- 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/CHANGELOG.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
## Changelog
|
|
2
|
+
|
|
3
|
+
### [2.0.0] - Unreleased
|
|
4
|
+
|
|
5
|
+
#### Changed
|
|
6
|
+
* Complete refactor
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### [1.1.2] - 2026-05-10 00:19:40
|
|
10
|
+
|
|
11
|
+
#### Changed
|
|
12
|
+
* load() method now public
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### [1.1.1] - 2026-05-09 19:45:23
|
|
16
|
+
|
|
17
|
+
#### Added
|
|
18
|
+
* index.js in the root
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### [1.1.0] - 2026-05-09 17:16:45
|
|
22
|
+
|
|
23
|
+
#### Added
|
|
24
|
+
* Complete refactor
|
|
25
|
+
* Optimized WebAudioFont handling
|
|
26
|
+
* Change instrument option to preset
|
package/README.md
CHANGED
|
@@ -1,86 +1,472 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
player
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
# midi-audio-player
|
|
4
|
+
|
|
5
|
+
**Real MIDI playback in the browser — powered by Web Audio API and WebAudioFont.**
|
|
6
|
+
|
|
7
|
+
[See full feature demo here](https://webaudiofonts.com/demo/)
|
|
8
|
+
|
|
9
|
+
No server. No heavy runtime. Just load a `.mid` or `.kar` file and play it — with reverb, a 10-band EQ, per-channel volume control, karaoke support, and over 3,000 instrument presets.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
const player = new MidiAudioPlayer({ volume: 0.8, reverb: 0.2, eqPreset: 'jazz' });
|
|
13
|
+
player.on('endOfFile', () => playAnotherSong());
|
|
14
|
+
await player.play('https://example.com/song.mid');
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- Full General MIDI playback via WebAudioFont instrument presets
|
|
22
|
+
- 3,000+ free instrument presets — piano, strings, brass, synths, drums, and more
|
|
23
|
+
- Convolution reverb with adjustable wet/dry mix
|
|
24
|
+
- 10-band parametric EQ with named presets
|
|
25
|
+
- Per-channel volume control
|
|
26
|
+
- Karaoke mode — parses MIDI text/lyric events and emits timed HTML frames
|
|
27
|
+
- Vocal channel auto-detection and muting
|
|
28
|
+
- Auto-repair for corrupted MIDI files
|
|
29
|
+
- Leading/trailing silence trimming
|
|
30
|
+
- SVG waveform generation
|
|
31
|
+
- Real-time amplitude metering
|
|
32
|
+
- Full MIDI protocol support (pitch bend, controllers, program change, etc.)
|
|
33
|
+
- Works with URLs, `ArrayBuffer`, and Base64 input
|
|
34
|
+
- IndexedDB preset cache — presets are only downloaded once
|
|
35
|
+
- Bring your own preset endpoint (self-hosted sf2-json deployment)
|
|
36
|
+
- ESM-native, compatible with bundlers and vanilla browser environments
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm install midi-audio-player
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Or via CDN (UMD):
|
|
47
|
+
|
|
48
|
+
```html
|
|
49
|
+
<script src="https://cdn.jsdelivr.net/npm/midi-audio-player/dist/midi-audio-player.min.js"></script>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Quick Start
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import MidiAudioPlayer from 'midi-audio-player';
|
|
58
|
+
|
|
59
|
+
const player = new MidiAudioPlayer({
|
|
60
|
+
volume: 0.7,
|
|
61
|
+
reverb: 0.25,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
player.on('computed', ({ title, duration }) => {
|
|
65
|
+
console.log(`"${title}" — ${duration.toFixed(1)}s`);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
player.on('presetsLoaded', () => {
|
|
69
|
+
console.log('All instruments ready.');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
player.on('endOfFile', () => player.stop());
|
|
73
|
+
|
|
74
|
+
await player.play('https://example.com/song.mid');
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
> **Note:** `MidiAudioPlayer` must be instantiated in response to a user gesture (click, keydown, etc.), as browsers require user activation before creating a `Web Audio` context.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## API Reference
|
|
82
|
+
|
|
83
|
+
### `new MidiAudioPlayer(opts?)`
|
|
84
|
+
|
|
85
|
+
Creates a new player instance and initializes the full Web Audio signal chain.
|
|
86
|
+
|
|
87
|
+
| Option | Type | Default | Description |
|
|
88
|
+
|---|---|---|---|
|
|
89
|
+
| `endpoint` | `string` | `"https://webaudiofonts.github.io/presets/"` | Base URL of the preset endpoint. Must expose `catalog.json` and individual preset JSON files. [See custom endpoint](#custom-preset-endpoint). |
|
|
90
|
+
| `volume` | `number` | `0.6` | Master output volume `[0, 1]`. Applied with a logarithmic curve. |
|
|
91
|
+
| `reverb` | `number` | `0.3` | Convolution reverb wet level `[0, 1]`. `0` = dry, `1` = full wet. |
|
|
92
|
+
| `localCache` | `boolean` | `true` | Cache the catalog in `sessionStorage` and presets in `IndexedDB`. |
|
|
93
|
+
| `presetRandom` | `boolean` | `false` | Pick a random preset for each MIDI program instead of the first available. |
|
|
94
|
+
| `karaoke` | `boolean` | `false` | Enable karaoke mode. Parses MIDI lyric events and emits timed HTML frames. |
|
|
95
|
+
| `karaokeDelay` | `number` | `0` | Advance karaoke frames by this many seconds (lyrics appear earlier). |
|
|
96
|
+
| `muteExpression` | `boolean` | `false` | Auto-detect and mute the vocal channel. Requires `karaoke: true`. |
|
|
97
|
+
| `maxCharPerLine` | `number` | `48` | Max characters per karaoke line before forcing a line break. |
|
|
98
|
+
| `eqPreset` | `EQPresetName` | `"flat"` | EQ preset applied on instantiation. |
|
|
99
|
+
| `preferred` | `string[]` | `[]` | Ordered list of preferred sound bank suffixes (e.g. `["FluidR3_GM"]`). |
|
|
100
|
+
| `presets` | `string[]` | `[]` | Preset IDs to pre-register in the program map, overriding auto-selection. |
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
### Getters / Setters
|
|
105
|
+
|
|
106
|
+
| Property | Type | Description |
|
|
107
|
+
|---|---|---|
|
|
108
|
+
| `volume` | `number` (get/set) | Master output volume `[0, 1]`. |
|
|
109
|
+
| `reverb` | `number` (get/set) | Reverb wet mix `[0, 1]`. |
|
|
110
|
+
| `muteExpression` | `boolean` (get/set) | Enable/disable vocal channel muting. |
|
|
111
|
+
| `eq` | `EQGains` (get) | Current EQ band gains as a frequency → dB map. |
|
|
112
|
+
| `eqFrequencies` | `number[]` (get) | The 10 fixed EQ frequencies: `[32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384]`. |
|
|
113
|
+
| `channels` | `Record<string, object>` (get) | Active `WebAudioFontPlayer` instances, keyed by MIDI channel number. |
|
|
114
|
+
| `channelStates` | `Record<string, boolean>` (get) | Snapshot of which channels are currently playing notes. |
|
|
115
|
+
| `catalog` | `Promise<Catalog>` (get) | Resolves the full preset catalog. Equivalent to `getCatalog()`. |
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
### EQ
|
|
120
|
+
|
|
121
|
+
#### `getEQ() → EQGains`
|
|
122
|
+
|
|
123
|
+
Returns the current gain (in dB) for all 10 EQ bands.
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
const gains = player.getEQ();
|
|
127
|
+
console.log(gains[32]); // e.g. 6
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
#### `setEQ(gains: EQGains) → void`
|
|
131
|
+
|
|
132
|
+
Updates one or more EQ bands. Unspecified bands are unchanged.
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
player.setEQ({ 32: 6, 64: 4, 1024: -2 });
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
#### `setEQPreset(name: EQPresetName) → void`
|
|
139
|
+
|
|
140
|
+
Applies a named EQ preset across all 10 bands.
|
|
141
|
+
|
|
142
|
+
Available presets: `flat`, `bass`, `treble`, `vocal`, `loudness`, `classical`, `jazz`, `electronic`.
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
player.setEQPreset('classical');
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
#### `setChannelVolume(channel: number, volume: number) → void`
|
|
149
|
+
|
|
150
|
+
Overrides the volume multiplier for a specific MIDI channel. Takes effect immediately.
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
player.setChannelVolume(10, 0); // Mute drums
|
|
154
|
+
player.setChannelVolume(1, 0.5); // Half volume on channel 1
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
### Preset Catalog
|
|
160
|
+
|
|
161
|
+
#### `getCatalog() → Promise<Catalog>`
|
|
162
|
+
|
|
163
|
+
Downloads and returns the full WebAudioFont preset catalog. Cached in `sessionStorage` after the first fetch.
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
const catalog = await player.getCatalog();
|
|
167
|
+
console.log(catalog.categories.length);
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
#### `getCategories() → Promise<CatalogCategory[]>`
|
|
171
|
+
|
|
172
|
+
Returns all top-level instrument categories from the catalog.
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
const categories = await player.getCategories();
|
|
176
|
+
categories.forEach(c => console.log(c.name));
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
#### `findPreset(id: string) → Promise<PresetInfo | null>`
|
|
180
|
+
|
|
181
|
+
Finds a preset by its string ID and returns its full metadata, including `category`, `instrument`, and `program`.
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
const info = await player.findPreset('0000_FluidR3');
|
|
185
|
+
// { id: '0000_...', category: 'Piano', instrument: 'Acoustic Grand Piano', program: 1 }
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
#### `loadPreset(presetId: string, channel: number) → Promise<void>`
|
|
189
|
+
|
|
190
|
+
Replaces the instrument on a specific MIDI channel at runtime.
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
await player.loadPreset('0000_Steinway', 1);
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
### Song Loading
|
|
199
|
+
|
|
200
|
+
#### `load(content, setup?) → Promise<void>`
|
|
201
|
+
|
|
202
|
+
Loads a MIDI file and prepares the audio engine for playback.
|
|
203
|
+
|
|
204
|
+
**`content`** accepts:
|
|
205
|
+
- A URL string — fetched automatically
|
|
206
|
+
- An `ArrayBuffer` — e.g. from a `<input type="file">`
|
|
207
|
+
- A Base64-encoded MIDI string
|
|
208
|
+
|
|
209
|
+
**`setup`** (optional) accepts a `SongSetup` object or a URL to a JSON file,
|
|
210
|
+
to restore previously saved preset/volume overrides.
|
|
211
|
+
|
|
212
|
+
The loading sequence is:
|
|
213
|
+
1. Stop current playback and clear note registry
|
|
214
|
+
2. Parse and repair the MIDI buffer if necessary
|
|
215
|
+
3. Resolve and download all required instrument presets
|
|
216
|
+
4. Trim leading and trailing silence
|
|
217
|
+
5. Generate karaoke frames (if enabled)
|
|
218
|
+
6. Emit `computed` early, then `presetsLoaded` when audio is fully ready
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
// From URL
|
|
222
|
+
await player.load('https://example.com/song.mid');
|
|
223
|
+
|
|
224
|
+
// From file input
|
|
225
|
+
const buffer = await file.arrayBuffer();
|
|
226
|
+
await player.load(buffer);
|
|
227
|
+
|
|
228
|
+
// With a saved setup
|
|
229
|
+
await player.load('song.mid', savedSetup);
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
#### `getSongSetup() → Promise<SongSetup>`
|
|
233
|
+
|
|
234
|
+
Returns a serializable snapshot of the current channel configuration (active preset IDs + per-channel volumes).
|
|
235
|
+
Save and pass back to `load()` to reproduce the same instrument mapping.
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
const setup = await player.getSongSetup();
|
|
239
|
+
localStorage.setItem('setup', JSON.stringify(setup));
|
|
240
|
+
|
|
241
|
+
// Later:
|
|
242
|
+
await player.load('song.mid', JSON.parse(localStorage.getItem('setup')));
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
#### `getTrainingPresets() → Promise<string[]>`
|
|
246
|
+
|
|
247
|
+
Returns the preset IDs currently registered in the internal program-to-preset override map.
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
### Playback Control
|
|
252
|
+
|
|
253
|
+
#### `play(content?) → Promise<boolean>`
|
|
254
|
+
|
|
255
|
+
Starts or resumes playback. If `content` is provided, loads it first.
|
|
256
|
+
|
|
257
|
+
Returns `true` on success, `false` if the Web Audio context could not be resumed (browser autoplay restriction).
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
await player.play(); // Resume
|
|
261
|
+
await player.play('https://example.com/song.mid'); // Load and play
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
#### `pause() → Promise<void>`
|
|
265
|
+
|
|
266
|
+
Pauses at the current position. Kills the reverb tail. Resume with `play()`.
|
|
267
|
+
|
|
268
|
+
#### `stop() → Promise<MidiAudioPlayer>`
|
|
269
|
+
|
|
270
|
+
Stops playback and resets to the beginning. Returns the player instance for chaining.
|
|
271
|
+
|
|
272
|
+
#### `skipToSeconds(seconds: number) → Promise<MidiAudioPlayer>`
|
|
273
|
+
|
|
274
|
+
Seeks to the specified position in seconds. Works during playback and while paused.
|
|
275
|
+
Resumes playback automatically if it was active.
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
await player.skipToSeconds(45.0);
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
### Metering & Visualization
|
|
284
|
+
|
|
285
|
+
#### `getRealTimeVolume() → number`
|
|
286
|
+
|
|
287
|
+
Returns the current normalized output amplitude `[0, 1]`, computed from the `AnalyserNode`.
|
|
288
|
+
Useful for driving VU meters or visualizers in a `requestAnimationFrame` loop.
|
|
289
|
+
|
|
290
|
+
```ts
|
|
291
|
+
function tick() {
|
|
292
|
+
meter.style.width = `${player.getRealTimeVolume() * 100}%`;
|
|
293
|
+
requestAnimationFrame(tick);
|
|
294
|
+
}
|
|
295
|
+
tick();
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
#### `getSongTimeRemaining() → number`
|
|
299
|
+
|
|
300
|
+
Returns the remaining playback time in seconds.
|
|
301
|
+
|
|
302
|
+
#### `generateWaveformSVG(samples?) → Promise<string>`
|
|
303
|
+
|
|
304
|
+
Generates an SVG waveform for the currently loaded song, based on note velocities
|
|
305
|
+
and expression/volume controller events.
|
|
306
|
+
|
|
307
|
+
The returned `<svg>` element has the class `midiaudioplayer-waveform`. Its `<path>` carries no default stroke or fill — apply via CSS.
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
const svg = await player.generateWaveformSVG(800);
|
|
311
|
+
document.getElementById('waveform').innerHTML = svg;
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
```css
|
|
315
|
+
.midiaudioplayer-waveform path {
|
|
316
|
+
stroke: #6ee7b7;
|
|
317
|
+
stroke-width: 1.5;
|
|
318
|
+
fill: rgba(110, 231, 183, 0.15);
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
| Parameter | Type | Default | Description |
|
|
323
|
+
|---|---|---|---|
|
|
324
|
+
| `samples` | `number` | `1000` | Number of horizontal data points. Higher = finer detail. |
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
### Lifecycle
|
|
329
|
+
|
|
330
|
+
#### `close() → Promise<void>`
|
|
331
|
+
|
|
332
|
+
Closes all channel players and the Web Audio context, releasing hardware resources.
|
|
333
|
+
The instance cannot be reused after this call.
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
### Events — `player.on(event, handler)`
|
|
338
|
+
|
|
339
|
+
#### `logs`
|
|
340
|
+
|
|
341
|
+
Emitted throughout the loading and playback lifecycle with internal status messages.
|
|
342
|
+
|
|
343
|
+
```ts
|
|
344
|
+
player.on('logs', (message) => console.log('[midi]', message));
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
#### `computed`
|
|
348
|
+
|
|
349
|
+
Emitted after MIDI parsing completes, before presets finish downloading.
|
|
350
|
+
Use this to update your UI with song metadata immediately.
|
|
351
|
+
|
|
352
|
+
```ts
|
|
353
|
+
player.on('computed', ({ title, duration, channels, karaoke }) => {
|
|
354
|
+
titleEl.textContent = title || 'Untitled';
|
|
355
|
+
durationEl.textContent = `${duration.toFixed(1)}s`;
|
|
356
|
+
});
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
**Payload:**
|
|
360
|
+
|
|
361
|
+
| Field | Type | Description |
|
|
362
|
+
|---|---|---|
|
|
363
|
+
| `title` | `string` | Song title from MIDI metadata, or empty string. |
|
|
364
|
+
| `karaoke` | `boolean` | Whether the file contains parseable lyric data. |
|
|
365
|
+
| `tempo` | `number` | Initial tempo in BPM. |
|
|
366
|
+
| `division` | `number` | Ticks per quarter-note (PPQ). |
|
|
367
|
+
| `duration` | `number` | Total duration in seconds. |
|
|
368
|
+
| `sampleRate` | `number` | Web Audio context sample rate. |
|
|
369
|
+
| `totalTicks` | `number` | Total MIDI ticks in the song. |
|
|
370
|
+
| `totalEvents` | `number` | Total MIDI events across all tracks. |
|
|
371
|
+
| `channels` | `Record<string, number>` | Map of channel number → GM program number. |
|
|
372
|
+
|
|
373
|
+
#### `presetsLoaded`
|
|
374
|
+
|
|
375
|
+
Emitted once all instrument presets have been downloaded and the player is fully ready.
|
|
376
|
+
|
|
377
|
+
```ts
|
|
378
|
+
player.on('presetsLoaded', () => playButton.disabled = false);
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
#### `endOfFile`
|
|
382
|
+
|
|
383
|
+
Emitted when playback reaches the end of the MIDI file.
|
|
384
|
+
|
|
385
|
+
```ts
|
|
386
|
+
player.on('endOfFile', () => playNextSong());
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
#### `karaoke`
|
|
390
|
+
|
|
391
|
+
Emitted for each timed lyric frame when karaoke mode is enabled.
|
|
392
|
+
Inject `html` directly into a DOM element. The HTML uses predictable CSS classes:
|
|
393
|
+
|
|
394
|
+
| Class | Description |
|
|
395
|
+
|---|---|
|
|
396
|
+
| `.karaoke-playing` | Syllable currently being sung |
|
|
397
|
+
| `.karaoke-played` | Syllables already past |
|
|
398
|
+
| `.karaoke-coming` | Upcoming syllables |
|
|
399
|
+
| `.karaoke-clear` | Empty frame — silence between paragraphs |
|
|
400
|
+
| `.karaoke-intro` | Emitted before song start or after `stop()` |
|
|
401
|
+
| `.karaoke-title` | Emitted when a title is detected in the MIDI metadata |
|
|
402
|
+
|
|
403
|
+
```ts
|
|
404
|
+
const lyricsEl = document.getElementById('lyrics');
|
|
405
|
+
player.on('karaoke', ({ html }) => { lyricsEl.innerHTML = html; });
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
**Payload:**
|
|
409
|
+
|
|
410
|
+
| Field | Type | Description |
|
|
411
|
+
|---|---|---|
|
|
412
|
+
| `type` | `'lyric' \| 'clear' \| 'intro' \| 'title'` | Frame type. |
|
|
413
|
+
| `tick` | `number` | MIDI tick at which this frame should appear. |
|
|
414
|
+
| `html` | `string` | HTML string ready for DOM injection. |
|
|
415
|
+
| `title` | `string?` | Present only when `type === "title"`. |
|
|
416
|
+
|
|
417
|
+
#### `channelState`
|
|
418
|
+
|
|
419
|
+
Emitted whenever any MIDI channel transitions between active (sustaining notes) and idle.
|
|
420
|
+
Fires only on actual state changes.
|
|
421
|
+
|
|
422
|
+
```ts
|
|
423
|
+
player.on('channelState', (states) => {
|
|
424
|
+
// { "1": true, "2": false, "10": true }
|
|
425
|
+
Object.entries(states).forEach(([ch, active]) => {
|
|
426
|
+
document.getElementById(`ch-${ch}`)?.classList.toggle('active', active);
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## Custom Preset Endpoint
|
|
434
|
+
|
|
435
|
+
By default, presets are served from the official WebAudioFont CDN.
|
|
436
|
+
If you want to self-host — to reduce latency, restrict the available preset set,
|
|
437
|
+
or curate your own instrument library — you can deploy the
|
|
438
|
+
[sf2-json template](https://github.com/WebAudioFonts/sf2-json) and
|
|
439
|
+
point the player at your own server:
|
|
440
|
+
|
|
441
|
+
```ts
|
|
442
|
+
const player = new MidiAudioPlayer({
|
|
443
|
+
endpoint: 'https://my-cdn.example.com/presets/',
|
|
444
|
+
});
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
The endpoint must expose:
|
|
448
|
+
- `catalog.json` — the full instrument catalog
|
|
449
|
+
- `{presetId}.json` — one file per preset
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
## Browser Compatibility
|
|
454
|
+
|
|
455
|
+
Requires a modern browser with:
|
|
456
|
+
- [Web Audio API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API)
|
|
457
|
+
- [ES Modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules)
|
|
458
|
+
- [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) (optional, for preset caching)
|
|
459
|
+
|
|
460
|
+
---
|
|
461
|
+
|
|
462
|
+
## License
|
|
463
|
+
|
|
464
|
+
[MIT](LICENSE)
|
|
465
|
+
|
|
466
|
+
## Author
|
|
467
|
+
|
|
468
|
+
Maxime Larrivée-Roy
|
|
469
|
+
|
|
470
|
+
## Repository
|
|
471
|
+
|
|
472
|
+
[https://github.com/WebAudioFonts/midi-audio-player](https://github.com/WebAudioFonts/midi-audio-player)
|