@datamoc/mw_games 0.17.0 → 0.18.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.
@@ -0,0 +1,456 @@
1
+ /**
2
+ * Buffered WebAudio voices for music and jingles: a `Channel` plays one looping track
3
+ * with volume, pitch, pan, a loop region and a seekable playhead, and an `AudioBus`
4
+ * wires channels into the four lifetimes a role-playing game needs from its audio.
5
+ * BGM and BGS are two independent looping streams, ME is an interrupting jingle that
6
+ * pauses the BGM and resumes it where it left off, SE is overlapping one-shots, each
7
+ * with its own volume, pitch and pan per play.
8
+ *
9
+ * The browser owns the clock and the nodes, so both classes take the pieces they
10
+ * cannot build themselves as injectables: the `AudioContext` in the constructor and,
11
+ * for the bus, a `load` function turning a kind and a name into a decoded buffer.
12
+ * Tests hand in fakes for both, the same way `Sound` and `Music` take `create()`.
13
+ */
14
+ /** one wired voice: a buffer source through a gain and an optional stereo panner */
15
+ function wireVoice(context, destination, buffer, volume, pitch, pan) {
16
+ const source = context.createBufferSource();
17
+ source.buffer = buffer;
18
+ source.playbackRate.value = pitch;
19
+ const gain = context.createGain();
20
+ gain.gain.value = volume;
21
+ let panner = null;
22
+ if (typeof context.createStereoPanner === 'function') {
23
+ panner = context.createStereoPanner();
24
+ panner.pan.value = pan;
25
+ source.connect(gain);
26
+ gain.connect(panner);
27
+ panner.connect(destination);
28
+ }
29
+ else {
30
+ source.connect(gain);
31
+ gain.connect(destination);
32
+ }
33
+ return { source, gain, panner };
34
+ }
35
+ /**
36
+ * One buffered voice: a decoded track with a loop region and a playhead the game can
37
+ * read and move. Replaying the same buffer restarts it; changing parameters mid-play
38
+ * never does.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * import { Channel } from '@datamoc/mw_games/audio';
43
+ *
44
+ * declare const context: AudioContext;
45
+ * declare const buffer: AudioBuffer;
46
+ *
47
+ * const channel = new Channel(context);
48
+ * channel.play(buffer, { volume: 0.8, pitch: 1, pan: 0, loop: { start: 1, end: 4 } });
49
+ * console.log(channel.position());
50
+ * channel.stop();
51
+ * ```
52
+ */
53
+ export class Channel {
54
+ constructor(context, destination) {
55
+ this.source = null;
56
+ this.gainNode = null;
57
+ this.pannerNode = null;
58
+ this.buffer = null;
59
+ this.params = { volume: 1, pitch: 1, pan: 0 };
60
+ this.loopRegion = null;
61
+ this.repeat = true;
62
+ this.startedAt = 0;
63
+ this.offset = 0;
64
+ this.context = context;
65
+ this.destination = destination ?? context.destination;
66
+ }
67
+ get isPlaying() {
68
+ return this.source !== null;
69
+ }
70
+ get duration() {
71
+ return this.buffer?.duration ?? 0;
72
+ }
73
+ /** starts the buffer with these parameters, stopping whatever played before */
74
+ play(buffer, options = {}) {
75
+ this.stop();
76
+ const volume = options.volume ?? 1;
77
+ const pitch = options.pitch ?? 1;
78
+ const pan = options.pan ?? 0;
79
+ const { source, gain, panner } = wireVoice(this.context, this.destination, buffer, volume, pitch, pan);
80
+ const loop = options.loop ?? null;
81
+ const repeat = options.repeat ?? true;
82
+ source.loop = repeat;
83
+ if (loop) {
84
+ source.loopStart = loop.start;
85
+ source.loopEnd = loop.end;
86
+ }
87
+ if (!repeat) {
88
+ const started = source;
89
+ source.onended = () => {
90
+ if (this.source === started)
91
+ this.clearNodes();
92
+ };
93
+ }
94
+ this.source = source;
95
+ this.gainNode = gain;
96
+ this.pannerNode = panner;
97
+ this.buffer = buffer;
98
+ this.params = { volume, pitch, pan };
99
+ this.loopRegion = loop;
100
+ this.repeat = repeat;
101
+ this.offset = Math.min(Math.max(0, options.pos ?? 0), Math.max(0, buffer.duration - 0.001));
102
+ this.startedAt = this.context.currentTime;
103
+ source.start(0, this.offset);
104
+ }
105
+ /** retunes the playing voice in place: no restart, no seek */
106
+ update(params) {
107
+ if (params.volume !== undefined)
108
+ this.params.volume = params.volume;
109
+ if (params.pitch !== undefined)
110
+ this.params.pitch = params.pitch;
111
+ if (params.pan !== undefined)
112
+ this.params.pan = params.pan;
113
+ if (!this.source || !this.gainNode)
114
+ return;
115
+ const now = this.context.currentTime;
116
+ this.gainNode.gain.setTargetAtTime(this.params.volume, now, 0.02);
117
+ this.source.playbackRate.value = this.params.pitch;
118
+ if (this.pannerNode)
119
+ this.pannerNode.pan.value = this.params.pan;
120
+ }
121
+ /** stops the voice, fading out over `fadeSeconds` first when positive */
122
+ stop(fadeSeconds = 0) {
123
+ const resumeAt = this.position();
124
+ const source = this.source;
125
+ const gain = this.gainNode;
126
+ this.clearNodes();
127
+ this.offset = resumeAt;
128
+ if (!source)
129
+ return;
130
+ try {
131
+ if (fadeSeconds > 0 && gain) {
132
+ const now = this.context.currentTime;
133
+ gain.gain.cancelScheduledValues(now);
134
+ gain.gain.setValueAtTime(gain.gain.value, now);
135
+ gain.gain.linearRampToValueAtTime(0, now + fadeSeconds);
136
+ source.stop(now + fadeSeconds + 0.02);
137
+ }
138
+ else {
139
+ source.stop();
140
+ }
141
+ }
142
+ catch {
143
+ //already stopped
144
+ }
145
+ }
146
+ /** seconds into the buffer, wrapping inside the loop region while it repeats */
147
+ position() {
148
+ if (!this.source || !this.buffer)
149
+ return this.offset;
150
+ const elapsed = Math.max(0, this.context.currentTime - this.startedAt) * this.params.pitch;
151
+ let pos = this.offset + elapsed;
152
+ if (this.repeat && this.duration > 0) {
153
+ const start = this.loopRegion?.start ?? 0;
154
+ const end = this.loopRegion?.end ?? this.buffer.duration;
155
+ if (end > start && pos >= end)
156
+ pos = start + ((pos - start) % (end - start));
157
+ }
158
+ return pos;
159
+ }
160
+ /** moves the playhead, restarting the voice when it plays */
161
+ seek(pos) {
162
+ this.restore({ ...this.state(), pos });
163
+ }
164
+ /** plain-data state for saves and transfers: parameters plus playhead */
165
+ state() {
166
+ return { ...this.params, pos: this.position() };
167
+ }
168
+ /** applies plain-data state, restarting the voice at its playhead when it plays */
169
+ restore(state) {
170
+ const buffer = this.buffer;
171
+ this.update({ volume: state.volume, pitch: state.pitch, pan: state.pan });
172
+ if (buffer && this.source) {
173
+ this.play(buffer, {
174
+ volume: state.volume,
175
+ pitch: state.pitch,
176
+ pan: state.pan,
177
+ pos: state.pos,
178
+ loop: this.loopRegion,
179
+ repeat: this.repeat,
180
+ });
181
+ }
182
+ else {
183
+ this.offset = Math.max(0, state.pos);
184
+ }
185
+ }
186
+ clearNodes() {
187
+ if (this.source)
188
+ this.source.onended = null;
189
+ this.source = null;
190
+ this.gainNode = null;
191
+ this.pannerNode = null;
192
+ }
193
+ }
194
+ function clampBus(value, low, high, fallback) {
195
+ const n = Number(value);
196
+ return Math.min(high, Math.max(low, Number.isFinite(n) ? n : fallback));
197
+ }
198
+ /**
199
+ * The four audio lifetimes of a role-playing game over two `Channel` voices: BGM and
200
+ * BGS loop independently, an ME jingle suspends the BGM and resumes it where it left
201
+ * off, SE plays overlapping one-shots. Replaying the current track only retunes it;
202
+ * save and replay memorize a track with its playhead for menus and transfers.
203
+ *
204
+ * @example
205
+ * ```ts
206
+ * import { AudioBus } from '@datamoc/mw_games/audio';
207
+ *
208
+ * declare const context: AudioContext;
209
+ *
210
+ * const bus = new AudioBus(context, { load: (_kind, _name) => Promise.resolve(null) });
211
+ * void bus.playBgm({ name: 'town', volume: 90, pitch: 100, pan: 0 });
212
+ * console.log(bus.snapshot());
213
+ * ```
214
+ */
215
+ export class AudioBus {
216
+ constructor(context, options = {}) {
217
+ this.tracks = { bgm: null, bgs: null };
218
+ this.saved = { bgm: null, bgs: null };
219
+ this.requests = { bgm: 0, bgs: 0 };
220
+ this.me = null;
221
+ this.meToken = 0;
222
+ this.suspendedBgm = null;
223
+ this.seNodes = new Set();
224
+ this.context = context;
225
+ this.destination = options.destination ?? context.destination;
226
+ this.load = options.load ?? (async () => null);
227
+ this.bgmChannel = new Channel(context, this.destination);
228
+ this.bgsChannel = new Channel(context, this.destination);
229
+ }
230
+ /** loops this BGM, or retunes it when it already loops */
231
+ playBgm(track) {
232
+ return this.playLoop('bgm', track);
233
+ }
234
+ /** loops this BGS, or retunes it when it already loops */
235
+ playBgs(track) {
236
+ return this.playLoop('bgs', track);
237
+ }
238
+ stopBgm() {
239
+ this.requests.bgm++;
240
+ this.tracks.bgm = null;
241
+ this.suspendedBgm = null;
242
+ this.bgmChannel.stop();
243
+ }
244
+ stopBgs() {
245
+ this.requests.bgs++;
246
+ this.tracks.bgs = null;
247
+ this.bgsChannel.stop();
248
+ }
249
+ fadeOutBgm(seconds = 1) {
250
+ this.requests.bgm++;
251
+ this.tracks.bgm = null;
252
+ this.suspendedBgm = null;
253
+ this.bgmChannel.stop(Math.max(0, seconds));
254
+ }
255
+ fadeOutBgs(seconds = 1) {
256
+ this.requests.bgs++;
257
+ this.tracks.bgs = null;
258
+ this.bgsChannel.stop(Math.max(0, seconds));
259
+ }
260
+ /** the intended track with its live playhead, or the one an ME suspended */
261
+ currentTrack(kind) {
262
+ const track = this.tracks[kind];
263
+ if (track) {
264
+ const channel = kind === 'bgm' ? this.bgmChannel : this.bgsChannel;
265
+ return { ...track, pos: channel.isPlaying ? channel.position() : track.pos };
266
+ }
267
+ return kind === 'bgm' ? this.suspendedBgm : null;
268
+ }
269
+ /** memorizes the playing BGM with its playhead; replay restores it */
270
+ saveBgm() {
271
+ this.saved.bgm = this.currentTrack('bgm');
272
+ return this.saved.bgm;
273
+ }
274
+ /** memorizes the playing BGS with its playhead; replay restores it */
275
+ saveBgs() {
276
+ this.saved.bgs = this.currentTrack('bgs');
277
+ return this.saved.bgs;
278
+ }
279
+ replayBgm() {
280
+ if (!this.saved.bgm)
281
+ return Promise.resolve();
282
+ return this.playBgm(this.saved.bgm);
283
+ }
284
+ replayBgs() {
285
+ if (!this.saved.bgs)
286
+ return Promise.resolve();
287
+ return this.playBgs(this.saved.bgs);
288
+ }
289
+ /**
290
+ * Plays a jingle once, suspending the BGM and resuming it where it left off when
291
+ * the jingle ends. A BGM started mid-jingle takes over instead of being resumed over.
292
+ */
293
+ async playMe(track) {
294
+ const me = AudioBus.normalize(track);
295
+ const token = ++this.meToken;
296
+ this.stopMeNodes();
297
+ if (!me.name)
298
+ return;
299
+ const loaded = await this.load('me', me.name);
300
+ if (token !== this.meToken || !loaded)
301
+ return;
302
+ const remembered = this.currentTrack('bgm');
303
+ if (remembered) {
304
+ this.suspendedBgm = remembered;
305
+ this.requests.bgm++;
306
+ this.bgmChannel.stop();
307
+ this.tracks.bgm = null;
308
+ }
309
+ const { source } = wireVoice(this.context, this.destination, loaded.buffer, me.volume / 100, me.pitch / 100, me.pan / 100);
310
+ this.me = { source, token };
311
+ source.onended = () => {
312
+ if (this.me?.token === token)
313
+ this.finishMe();
314
+ };
315
+ source.start(0);
316
+ }
317
+ /** stops the jingle, resuming the suspended BGM unless told not to */
318
+ stopMe(resume = true) {
319
+ this.stopMeNodes();
320
+ if (resume)
321
+ this.finishMe();
322
+ }
323
+ /** plays one overlapping sound effect */
324
+ async playSe(track) {
325
+ const se = AudioBus.normalize(track);
326
+ if (!se.name)
327
+ return;
328
+ const loaded = await this.load('se', se.name);
329
+ if (!loaded)
330
+ return;
331
+ const { source } = wireVoice(this.context, this.destination, loaded.buffer, se.volume / 100, se.pitch / 100, se.pan / 100);
332
+ const node = { source };
333
+ this.seNodes.add(node);
334
+ source.onended = () => {
335
+ this.seNodes.delete(node);
336
+ };
337
+ source.start(0);
338
+ }
339
+ stopSe() {
340
+ for (const node of this.seNodes) {
341
+ node.source.onended = null;
342
+ try {
343
+ node.source.stop();
344
+ }
345
+ catch {
346
+ //already ended
347
+ }
348
+ }
349
+ this.seNodes.clear();
350
+ }
351
+ stopAll() {
352
+ this.requests.bgm++;
353
+ this.requests.bgs++;
354
+ this.tracks.bgm = null;
355
+ this.tracks.bgs = null;
356
+ this.suspendedBgm = null;
357
+ this.bgmChannel.stop();
358
+ this.bgsChannel.stop();
359
+ this.stopMeNodes();
360
+ this.stopSe();
361
+ }
362
+ /** plain-data bus state for saves and transfers */
363
+ snapshot() {
364
+ return {
365
+ bgm: this.currentTrack('bgm'),
366
+ bgs: this.currentTrack('bgs'),
367
+ savedBgm: this.saved.bgm,
368
+ savedBgs: this.saved.bgs,
369
+ };
370
+ }
371
+ /** replays plain-data bus state, reloading every named track */
372
+ restore(state) {
373
+ if (!state || typeof state !== 'object')
374
+ return;
375
+ this.saved.bgm = state.savedBgm ?? null;
376
+ this.saved.bgs = state.savedBgs ?? null;
377
+ if (state.bgm)
378
+ void this.playBgm(state.bgm);
379
+ if (state.bgs)
380
+ void this.playBgs(state.bgs);
381
+ }
382
+ static normalize(track) {
383
+ return {
384
+ name: String(track?.name || ''),
385
+ volume: clampBus(track?.volume, 0, 100, 90),
386
+ pitch: clampBus(track?.pitch, 10, 400, 100),
387
+ pan: clampBus(track?.pan, -100, 100, 0),
388
+ pos: Math.max(0, Number(track?.pos) || 0),
389
+ };
390
+ }
391
+ async playLoop(kind, input) {
392
+ const channel = kind === 'bgm' ? this.bgmChannel : this.bgsChannel;
393
+ const wanted = AudioBus.normalize(input);
394
+ if (!wanted.name) {
395
+ this.requests[kind]++;
396
+ this.tracks[kind] = null;
397
+ if (kind === 'bgm')
398
+ this.suspendedBgm = null;
399
+ channel.stop();
400
+ return;
401
+ }
402
+ const current = this.tracks[kind];
403
+ //Replaying the current track only retunes it, even while it still loads.
404
+ if (current && current.name === wanted.name && !(wanted.pos > 0)) {
405
+ this.tracks[kind] = { ...wanted, pos: 0 };
406
+ channel.update({ volume: wanted.volume / 100, pitch: wanted.pitch / 100, pan: wanted.pan / 100 });
407
+ return;
408
+ }
409
+ //A BGM requested mid-jingle replaces the suspended one instead of racing it.
410
+ if (kind === 'bgm' && this.me) {
411
+ this.requests.bgm++;
412
+ this.suspendedBgm = wanted;
413
+ this.tracks.bgm = null;
414
+ channel.stop();
415
+ return;
416
+ }
417
+ const request = ++this.requests[kind];
418
+ channel.stop();
419
+ this.tracks[kind] = { ...wanted, pos: 0 };
420
+ const loaded = await this.load(kind, wanted.name);
421
+ if (request !== this.requests[kind])
422
+ return;
423
+ if (!loaded) {
424
+ this.tracks[kind] = null;
425
+ return;
426
+ }
427
+ channel.play(loaded.buffer, {
428
+ volume: wanted.volume / 100,
429
+ pitch: wanted.pitch / 100,
430
+ pan: wanted.pan / 100,
431
+ pos: wanted.pos,
432
+ loop: loaded.loop ?? null,
433
+ });
434
+ }
435
+ stopMeNodes() {
436
+ const me = this.me;
437
+ this.me = null;
438
+ if (!me)
439
+ return;
440
+ me.source.onended = null;
441
+ try {
442
+ me.source.stop();
443
+ }
444
+ catch {
445
+ //already ended
446
+ }
447
+ }
448
+ finishMe() {
449
+ this.me = null;
450
+ const resume = this.suspendedBgm;
451
+ this.suspendedBgm = null;
452
+ if (resume && !this.tracks.bgm)
453
+ void this.playBgm(resume);
454
+ }
455
+ }
456
+ //# sourceMappingURL=Channels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Channels.js","sourceRoot":"","sources":["../../src/audio/Channels.ts"],"names":[],"mappings":"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"}
@@ -2,11 +2,16 @@ import { type ToneOptions, type Waveform } from './Synth.ts';
2
2
  import type { Playable } from './Playable.ts';
3
3
  /**
4
4
  * A Standard MIDI File (SMF) reader plus a player that voices each note through
5
- * `audio.synthesizeTone` - `.mid` is a small, patent-free, well-documented event format
6
- * (timing and note-on/note-off, not audio), so reading one is ordinary format engineering,
7
- * and playback needed exactly the instrument item 120's waveform synth already supplies: a
8
- * crude but real, entirely generated-not-borrowed one, closer to a chiptune cover than a
9
- * sampled orchestra.
5
+ * `audio.synthesizeTone` - `.mid` is a small, patent-free, well-documented event format,
6
+ * so reading one is ordinary format engineering, and playback needed exactly the
7
+ * instrument item 120's waveform synth already supplies: a crude but real,
8
+ * entirely generated-not-borrowed one, closer to a chiptune cover than a sampled
9
+ * orchestra.
10
+ *
11
+ * Beyond notes and tempo the reader keeps program changes, bank select and channel
12
+ * controllers (volume, expression, pan, sustain, the RPG Maker loop marker) and pitch
13
+ * bend, and each scheduled note carries the channel voice in effect when it starts,
14
+ * so a sampled renderer can pick the right instrument at the right level.
10
15
  */
11
16
  export interface MidiNoteEvent {
12
17
  tick: number;
@@ -21,11 +26,50 @@ export interface MidiTempoEvent {
21
26
  /** microseconds per quarter note */
22
27
  microsecondsPerQuarter: number;
23
28
  }
24
- export type MidiEvent = MidiNoteEvent | MidiTempoEvent;
29
+ export interface MidiProgramEvent {
30
+ tick: number;
31
+ type: 'program';
32
+ channel: number;
33
+ /** General MIDI program number, 0 to 127 */
34
+ program: number;
35
+ }
36
+ export interface MidiControlEvent {
37
+ tick: number;
38
+ type: 'control';
39
+ channel: number;
40
+ /** controller number, 0 to 127: 0 is bank select, 7 volume, 10 pan, 11 expression, 64 sustain, 111 the RPG Maker loop marker */
41
+ controller: number;
42
+ /** controller value, 0 to 127 */
43
+ value: number;
44
+ }
45
+ export interface MidiPitchBendEvent {
46
+ tick: number;
47
+ type: 'pitchBend';
48
+ channel: number;
49
+ /** signed bend amount, -8192 to 8191, 0 is centered */
50
+ value: number;
51
+ }
52
+ /** the channel voice in effect when a scheduled note starts */
53
+ export interface MidiVoice {
54
+ /** General MIDI program number, 0 to 127 */
55
+ program: number;
56
+ /** bank select (CC0) */
57
+ bank: number;
58
+ /** combined channel volume (CC7) and expression (CC11), 0 to 1 */
59
+ gain: number;
60
+ /** channel pan (CC10), -1 (left) to 1 (right), 0 centered */
61
+ pan: number;
62
+ }
63
+ export type MidiEvent = MidiNoteEvent | MidiTempoEvent | MidiProgramEvent | MidiControlEvent | MidiPitchBendEvent;
25
64
  export interface MidiFile {
26
65
  ticksPerQuarter: number;
27
- /** every note-on/note-off and tempo-change event from every track, merged and sorted by tick */
66
+ /** every kept event from every track, merged and sorted by tick */
28
67
  events: readonly MidiEvent[];
68
+ /**
69
+ * Tick of the first controller-111 event (the RPG Maker XP/VX/Ace loop marker),
70
+ * or null when the file carries none and loops from the start instead.
71
+ */
72
+ loopStartTick: number | null;
29
73
  }
30
74
  /**
31
75
  * Reads format 0/1 Standard MIDI File bytes into a flat, tick-ordered event list.
@@ -47,7 +91,7 @@ export interface MidiFile {
47
91
  * ```
48
92
  */
49
93
  export declare function parseMidi(data: ArrayBuffer | ArrayBufferView): MidiFile;
50
- export interface ScheduledNote {
94
+ export interface ScheduledNote extends MidiVoice {
51
95
  /** seconds from the start of playback */
52
96
  time: number;
53
97
  duration: number;
@@ -55,11 +99,29 @@ export interface ScheduledNote {
55
99
  velocity: number;
56
100
  channel: number;
57
101
  }
102
+ /**
103
+ * Seconds from the start of playback at which the file loops (its controller-111
104
+ * marker through the file's own tempo map), or null when the file carries no marker
105
+ * and loops from the start instead.
106
+ *
107
+ * @example
108
+ * ```ts
109
+ * import { parseMidi, midiLoopStart } from '@datamoc/mw_games/audio';
110
+ *
111
+ * declare const midiBytes: ArrayBuffer;
112
+ *
113
+ * const file = parseMidi(midiBytes);
114
+ * console.log(midiLoopStart(file)); // seconds, or null with no loop marker
115
+ * ```
116
+ */
117
+ export declare function midiLoopStart(file: MidiFile): number | null;
58
118
  /**
59
119
  * Resolves a parsed file's tick-based events into real-time seconds, honouring tempo
60
120
  * changes and pairing each note-on with its matching note-off to find a duration. A
61
121
  * note-on with no matching note-off (a malformed or truncated file) still sounds, for a
62
- * short default duration, rather than being dropped silently.
122
+ * short default duration, rather than being dropped silently. Each note carries the
123
+ * channel voice in effect when it starts (program, bank, combined volume/expression
124
+ * gain, pan), so a sampled renderer can voice it without re-reading the events.
63
125
  */
64
126
  export declare function scheduleMidi(file: MidiFile): ScheduledNote[];
65
127
  /** MIDI note 69 (A4) is 440 Hz; each semitone is a twelfth root of two step */
@@ -1 +1 @@
1
- {"version":3,"file":"Midi.d.ts","sourceRoot":"","sources":["../../src/audio/Midi.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,WAAW,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AACvE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C;;;;;;;GAOG;AAEH,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,oCAAoC;IACpC,sBAAsB,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,MAAM,SAAS,GAAG,aAAa,GAAG,cAAc,CAAC;AAEvD,MAAM,WAAW,QAAQ;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,gGAAgG;IAChG,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC;CAC7B;AAID;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,WAAW,GAAG,eAAe,GAAG,QAAQ,CA8FvE;AAED,MAAM,WAAW,aAAa;IAC7B,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,aAAa,EAAE,CAyC5D;AAED,+EAA+E;AAC/E,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,QAAQ,CAAC;CAC1C;AAED,qKAAqK;AACrK,qBAAa,UAAU;IACtB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA2B;IACjD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqC;IAC5D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,OAAO,CAAS;IAExB,YAAY,IAAI,EAAE,QAAQ,EAAE,OAAO,GAAE,iBAAsB,EAK1D;IAED,IAAI,IAAI,IAAI,CAEX;IAED,KAAK,IAAI,IAAI,CAEZ;IAED,yCAAyC;IACzC,IAAI,IAAI,IAAI,CAIX;IAED,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAgBvB;IAED,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,+EAA+E;IAC/E,IAAI,QAAQ,IAAI,MAAM,CAGrB;CACD"}
1
+ {"version":3,"file":"Midi.d.ts","sourceRoot":"","sources":["../../src/audio/Midi.ts"],"names":[],"mappings":"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"}