@marmooo/midy 0.3.1 → 0.3.3
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/esm/midy-GM1.d.ts +17 -55
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +159 -184
- package/esm/midy-GM2.d.ts +34 -90
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +421 -342
- package/esm/midy-GMLite.d.ts +17 -55
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +169 -199
- package/esm/midy.d.ts +37 -114
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +451 -376
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +17 -55
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +159 -184
- package/script/midy-GM2.d.ts +34 -90
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +421 -342
- package/script/midy-GMLite.d.ts +17 -55
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +169 -199
- package/script/midy.d.ts +37 -114
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +451 -376
package/script/midy-GM1.js
CHANGED
|
@@ -3,60 +3,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.MidyGM1 = void 0;
|
|
4
4
|
const midi_file_1 = require("midi-file");
|
|
5
5
|
const soundfont_parser_1 = require("@marmooo/soundfont-parser");
|
|
6
|
-
// 2-3 times faster than Map
|
|
7
|
-
class SparseMap {
|
|
8
|
-
constructor(size) {
|
|
9
|
-
this.data = new Array(size);
|
|
10
|
-
this.activeIndices = [];
|
|
11
|
-
}
|
|
12
|
-
set(key, value) {
|
|
13
|
-
if (this.data[key] === undefined) {
|
|
14
|
-
this.activeIndices.push(key);
|
|
15
|
-
}
|
|
16
|
-
this.data[key] = value;
|
|
17
|
-
}
|
|
18
|
-
get(key) {
|
|
19
|
-
return this.data[key];
|
|
20
|
-
}
|
|
21
|
-
delete(key) {
|
|
22
|
-
if (this.data[key] !== undefined) {
|
|
23
|
-
this.data[key] = undefined;
|
|
24
|
-
const index = this.activeIndices.indexOf(key);
|
|
25
|
-
if (index !== -1) {
|
|
26
|
-
this.activeIndices.splice(index, 1);
|
|
27
|
-
}
|
|
28
|
-
return true;
|
|
29
|
-
}
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
has(key) {
|
|
33
|
-
return this.data[key] !== undefined;
|
|
34
|
-
}
|
|
35
|
-
get size() {
|
|
36
|
-
return this.activeIndices.length;
|
|
37
|
-
}
|
|
38
|
-
clear() {
|
|
39
|
-
for (let i = 0; i < this.activeIndices.length; i++) {
|
|
40
|
-
const key = this.activeIndices[i];
|
|
41
|
-
this.data[key] = undefined;
|
|
42
|
-
}
|
|
43
|
-
this.activeIndices = [];
|
|
44
|
-
}
|
|
45
|
-
*[Symbol.iterator]() {
|
|
46
|
-
for (let i = 0; i < this.activeIndices.length; i++) {
|
|
47
|
-
const key = this.activeIndices[i];
|
|
48
|
-
yield [key, this.data[key]];
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
forEach(callback) {
|
|
52
|
-
for (let i = 0; i < this.activeIndices.length; i++) {
|
|
53
|
-
const key = this.activeIndices[i];
|
|
54
|
-
callback(this.data[key], key, this);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
6
|
class Note {
|
|
59
7
|
constructor(noteNumber, velocity, startTime, voice, voiceParams) {
|
|
8
|
+
Object.defineProperty(this, "index", {
|
|
9
|
+
enumerable: true,
|
|
10
|
+
configurable: true,
|
|
11
|
+
writable: true,
|
|
12
|
+
value: -1
|
|
13
|
+
});
|
|
14
|
+
Object.defineProperty(this, "ending", {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
configurable: true,
|
|
17
|
+
writable: true,
|
|
18
|
+
value: false
|
|
19
|
+
});
|
|
60
20
|
Object.defineProperty(this, "bufferSource", {
|
|
61
21
|
enumerable: true,
|
|
62
22
|
configurable: true,
|
|
@@ -117,7 +77,7 @@ const defaultControllerState = {
|
|
|
117
77
|
modulationDepth: { type: 128 + 1, defaultValue: 0 },
|
|
118
78
|
// dataMSB: { type: 128 + 6, defaultValue: 0, },
|
|
119
79
|
volume: { type: 128 + 7, defaultValue: 100 / 127 },
|
|
120
|
-
pan: { type: 128 + 10, defaultValue:
|
|
80
|
+
pan: { type: 128 + 10, defaultValue: 64 / 127 },
|
|
121
81
|
expression: { type: 128 + 11, defaultValue: 1 },
|
|
122
82
|
// bankLSB: { type: 128 + 32, defaultValue: 0, },
|
|
123
83
|
// dataLSB: { type: 128 + 38, defaultValue: 0, },
|
|
@@ -322,7 +282,7 @@ class MidyGM1 {
|
|
|
322
282
|
initSoundFontTable() {
|
|
323
283
|
const table = new Array(128);
|
|
324
284
|
for (let i = 0; i < 128; i++) {
|
|
325
|
-
table[i] = new
|
|
285
|
+
table[i] = new Map();
|
|
326
286
|
}
|
|
327
287
|
return table;
|
|
328
288
|
}
|
|
@@ -338,17 +298,37 @@ class MidyGM1 {
|
|
|
338
298
|
}
|
|
339
299
|
}
|
|
340
300
|
}
|
|
341
|
-
async loadSoundFont(
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
301
|
+
async loadSoundFont(input) {
|
|
302
|
+
let uint8Array;
|
|
303
|
+
if (typeof input === "string") {
|
|
304
|
+
const response = await fetch(input);
|
|
305
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
306
|
+
uint8Array = new Uint8Array(arrayBuffer);
|
|
307
|
+
}
|
|
308
|
+
else if (input instanceof Uint8Array) {
|
|
309
|
+
uint8Array = input;
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
throw new TypeError("input must be a URL string or Uint8Array");
|
|
313
|
+
}
|
|
314
|
+
const parsed = (0, soundfont_parser_1.parse)(uint8Array);
|
|
345
315
|
const soundFont = new soundfont_parser_1.SoundFont(parsed);
|
|
346
316
|
this.addSoundFont(soundFont);
|
|
347
317
|
}
|
|
348
|
-
async loadMIDI(
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
318
|
+
async loadMIDI(input) {
|
|
319
|
+
let uint8Array;
|
|
320
|
+
if (typeof input === "string") {
|
|
321
|
+
const response = await fetch(input);
|
|
322
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
323
|
+
uint8Array = new Uint8Array(arrayBuffer);
|
|
324
|
+
}
|
|
325
|
+
else if (input instanceof Uint8Array) {
|
|
326
|
+
uint8Array = input;
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
throw new TypeError("input must be a URL string or Uint8Array");
|
|
330
|
+
}
|
|
331
|
+
const midi = (0, midi_file_1.parseMidi)(uint8Array);
|
|
352
332
|
this.ticksPerBeat = midi.header.ticksPerBeat;
|
|
353
333
|
const midiData = this.extractMidiData(midi);
|
|
354
334
|
this.instruments = midiData.instruments;
|
|
@@ -374,10 +354,10 @@ class MidyGM1 {
|
|
|
374
354
|
return {
|
|
375
355
|
currentBufferSource: null,
|
|
376
356
|
isDrum: false,
|
|
377
|
-
...this.constructor.channelSettings,
|
|
378
357
|
state: new ControllerState(),
|
|
358
|
+
...this.constructor.channelSettings,
|
|
379
359
|
...this.setChannelAudioNodes(audioContext),
|
|
380
|
-
scheduledNotes:
|
|
360
|
+
scheduledNotes: [],
|
|
381
361
|
sustainNotes: [],
|
|
382
362
|
};
|
|
383
363
|
});
|
|
@@ -412,7 +392,7 @@ class MidyGM1 {
|
|
|
412
392
|
return audioBuffer;
|
|
413
393
|
}
|
|
414
394
|
}
|
|
415
|
-
createBufferSource(
|
|
395
|
+
createBufferSource(voiceParams, audioBuffer) {
|
|
416
396
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
417
397
|
bufferSource.buffer = audioBuffer;
|
|
418
398
|
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
@@ -422,24 +402,21 @@ class MidyGM1 {
|
|
|
422
402
|
}
|
|
423
403
|
return bufferSource;
|
|
424
404
|
}
|
|
425
|
-
async scheduleTimelineEvents(t,
|
|
405
|
+
async scheduleTimelineEvents(t, resumeTime, queueIndex) {
|
|
426
406
|
while (queueIndex < this.timeline.length) {
|
|
427
407
|
const event = this.timeline[queueIndex];
|
|
428
408
|
if (event.startTime > t + this.lookAhead)
|
|
429
409
|
break;
|
|
430
|
-
const
|
|
410
|
+
const delay = this.startDelay - resumeTime;
|
|
411
|
+
const startTime = event.startTime + delay;
|
|
431
412
|
switch (event.type) {
|
|
432
413
|
case "noteOn":
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
break;
|
|
436
|
-
}
|
|
437
|
-
/* falls through */
|
|
414
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
|
|
415
|
+
break;
|
|
438
416
|
case "noteOff": {
|
|
439
417
|
const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
|
|
440
|
-
if (notePromise)
|
|
418
|
+
if (notePromise)
|
|
441
419
|
this.notePromises.push(notePromise);
|
|
442
|
-
}
|
|
443
420
|
break;
|
|
444
421
|
}
|
|
445
422
|
case "controller":
|
|
@@ -472,7 +449,7 @@ class MidyGM1 {
|
|
|
472
449
|
this.isPaused = false;
|
|
473
450
|
this.startTime = this.audioContext.currentTime;
|
|
474
451
|
let queueIndex = this.getQueueIndex(this.resumeTime);
|
|
475
|
-
let
|
|
452
|
+
let resumeTime = this.resumeTime - this.startTime;
|
|
476
453
|
this.notePromises = [];
|
|
477
454
|
const schedulePlayback = async () => {
|
|
478
455
|
if (queueIndex >= this.timeline.length) {
|
|
@@ -480,18 +457,21 @@ class MidyGM1 {
|
|
|
480
457
|
this.notePromises = [];
|
|
481
458
|
this.exclusiveClassNotes.fill(undefined);
|
|
482
459
|
this.audioBufferCache.clear();
|
|
460
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
461
|
+
this.resetAllStates(i);
|
|
462
|
+
}
|
|
483
463
|
resolve();
|
|
484
464
|
return;
|
|
485
465
|
}
|
|
486
466
|
const now = this.audioContext.currentTime;
|
|
487
|
-
const t = now +
|
|
488
|
-
queueIndex = await this.scheduleTimelineEvents(t,
|
|
467
|
+
const t = now + resumeTime;
|
|
468
|
+
queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
|
|
489
469
|
if (this.isPausing) {
|
|
490
470
|
await this.stopNotes(0, true, now);
|
|
491
471
|
this.notePromises = [];
|
|
492
|
-
resolve();
|
|
493
472
|
this.isPausing = false;
|
|
494
473
|
this.isPaused = true;
|
|
474
|
+
resolve();
|
|
495
475
|
return;
|
|
496
476
|
}
|
|
497
477
|
else if (this.isStopping) {
|
|
@@ -499,9 +479,12 @@ class MidyGM1 {
|
|
|
499
479
|
this.notePromises = [];
|
|
500
480
|
this.exclusiveClassNotes.fill(undefined);
|
|
501
481
|
this.audioBufferCache.clear();
|
|
502
|
-
|
|
482
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
483
|
+
this.resetAllStates(i);
|
|
484
|
+
}
|
|
503
485
|
this.isStopping = false;
|
|
504
486
|
this.isPaused = false;
|
|
487
|
+
resolve();
|
|
505
488
|
return;
|
|
506
489
|
}
|
|
507
490
|
else if (this.isSeeking) {
|
|
@@ -509,7 +492,7 @@ class MidyGM1 {
|
|
|
509
492
|
this.exclusiveClassNotes.fill(undefined);
|
|
510
493
|
this.startTime = this.audioContext.currentTime;
|
|
511
494
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
512
|
-
|
|
495
|
+
resumeTime = this.resumeTime - this.startTime;
|
|
513
496
|
this.isSeeking = false;
|
|
514
497
|
await schedulePlayback();
|
|
515
498
|
}
|
|
@@ -532,6 +515,7 @@ class MidyGM1 {
|
|
|
532
515
|
return `${programNumber}:${noteNumber}:${velocity}`;
|
|
533
516
|
}
|
|
534
517
|
extractMidiData(midi) {
|
|
518
|
+
this.audioBufferCounter.clear();
|
|
535
519
|
const instruments = new Set();
|
|
536
520
|
const timeline = [];
|
|
537
521
|
const tmpChannels = new Array(this.channels.length);
|
|
@@ -600,9 +584,8 @@ class MidyGM1 {
|
|
|
600
584
|
stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
|
|
601
585
|
const channel = this.channels[channelNumber];
|
|
602
586
|
const promises = [];
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
|
|
587
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
588
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
606
589
|
this.notePromises.push(promise);
|
|
607
590
|
promises.push(promise);
|
|
608
591
|
});
|
|
@@ -616,7 +599,7 @@ class MidyGM1 {
|
|
|
616
599
|
this.notePromises.push(promise);
|
|
617
600
|
promises.push(promise);
|
|
618
601
|
});
|
|
619
|
-
channel.scheduledNotes
|
|
602
|
+
channel.scheduledNotes = [];
|
|
620
603
|
return Promise.all(promises);
|
|
621
604
|
}
|
|
622
605
|
stopNotes(velocity, force, scheduleTime) {
|
|
@@ -637,9 +620,6 @@ class MidyGM1 {
|
|
|
637
620
|
if (!this.isPlaying)
|
|
638
621
|
return;
|
|
639
622
|
this.isStopping = true;
|
|
640
|
-
for (let i = 0; i < this.channels.length; i++) {
|
|
641
|
-
this.resetAllStates(i);
|
|
642
|
-
}
|
|
643
623
|
}
|
|
644
624
|
pause() {
|
|
645
625
|
if (!this.isPlaying || this.isPaused)
|
|
@@ -674,37 +654,28 @@ class MidyGM1 {
|
|
|
674
654
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
675
655
|
}
|
|
676
656
|
processScheduledNotes(channel, callback) {
|
|
677
|
-
channel.scheduledNotes
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
});
|
|
687
|
-
}
|
|
688
|
-
getActiveNotes(channel, scheduleTime) {
|
|
689
|
-
const activeNotes = new SparseMap(128);
|
|
690
|
-
channel.scheduledNotes.forEach((noteList) => {
|
|
691
|
-
const activeNote = this.getActiveNote(noteList, scheduleTime);
|
|
692
|
-
if (activeNote) {
|
|
693
|
-
activeNotes.set(activeNote.noteNumber, activeNote);
|
|
694
|
-
}
|
|
695
|
-
});
|
|
696
|
-
return activeNotes;
|
|
657
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
658
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
659
|
+
const note = scheduledNotes[i];
|
|
660
|
+
if (!note)
|
|
661
|
+
continue;
|
|
662
|
+
if (note.ending)
|
|
663
|
+
continue;
|
|
664
|
+
callback(note);
|
|
665
|
+
}
|
|
697
666
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
667
|
+
processActiveNotes(channel, scheduleTime, callback) {
|
|
668
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
669
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
670
|
+
const note = scheduledNotes[i];
|
|
701
671
|
if (!note)
|
|
702
|
-
|
|
672
|
+
continue;
|
|
673
|
+
if (note.ending)
|
|
674
|
+
continue;
|
|
703
675
|
if (scheduleTime < note.startTime)
|
|
704
676
|
continue;
|
|
705
|
-
|
|
677
|
+
callback(note);
|
|
706
678
|
}
|
|
707
|
-
return noteList[0];
|
|
708
679
|
}
|
|
709
680
|
cbToRatio(cb) {
|
|
710
681
|
return Math.pow(10, cb / 200);
|
|
@@ -844,7 +815,7 @@ class MidyGM1 {
|
|
|
844
815
|
const voiceParams = voice.getAllParams(controllerState);
|
|
845
816
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
846
817
|
const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
|
|
847
|
-
note.bufferSource = this.createBufferSource(
|
|
818
|
+
note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
|
|
848
819
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
849
820
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
850
821
|
type: "lowpass",
|
|
@@ -853,6 +824,7 @@ class MidyGM1 {
|
|
|
853
824
|
this.setVolumeEnvelope(note, now);
|
|
854
825
|
this.setFilterEnvelope(note, now);
|
|
855
826
|
this.setPitchEnvelope(note, now);
|
|
827
|
+
this.updateDetune(channel, note, now);
|
|
856
828
|
if (0 < state.modulationDepth) {
|
|
857
829
|
this.startModulation(channel, note, now);
|
|
858
830
|
}
|
|
@@ -875,7 +847,7 @@ class MidyGM1 {
|
|
|
875
847
|
}
|
|
876
848
|
this.exclusiveClassNotes[exclusiveClass] = [note, channelNumber];
|
|
877
849
|
}
|
|
878
|
-
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
850
|
+
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, noteOffEvent) {
|
|
879
851
|
const channel = this.channels[channelNumber];
|
|
880
852
|
const bankNumber = channel.bank;
|
|
881
853
|
const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
|
|
@@ -887,6 +859,7 @@ class MidyGM1 {
|
|
|
887
859
|
return;
|
|
888
860
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
889
861
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
862
|
+
note.noteOffEvent = noteOffEvent;
|
|
890
863
|
note.volumeEnvelopeNode.connect(channel.gainL);
|
|
891
864
|
note.volumeEnvelopeNode.connect(channel.gainR);
|
|
892
865
|
if (0.5 <= channel.state.sustainPedal) {
|
|
@@ -894,18 +867,12 @@ class MidyGM1 {
|
|
|
894
867
|
}
|
|
895
868
|
this.handleExclusiveClass(note, channelNumber, startTime);
|
|
896
869
|
const scheduledNotes = channel.scheduledNotes;
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
noteList.push(note);
|
|
900
|
-
}
|
|
901
|
-
else {
|
|
902
|
-
noteList = [note];
|
|
903
|
-
scheduledNotes.set(noteNumber, noteList);
|
|
904
|
-
}
|
|
870
|
+
note.index = scheduledNotes.length;
|
|
871
|
+
scheduledNotes.push(note);
|
|
905
872
|
}
|
|
906
873
|
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
907
874
|
scheduleTime ??= this.audioContext.currentTime;
|
|
908
|
-
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime);
|
|
875
|
+
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, undefined);
|
|
909
876
|
}
|
|
910
877
|
disconnectNote(note) {
|
|
911
878
|
note.bufferSource.disconnect();
|
|
@@ -917,54 +884,49 @@ class MidyGM1 {
|
|
|
917
884
|
note.modulationLFO.stop();
|
|
918
885
|
}
|
|
919
886
|
}
|
|
920
|
-
|
|
921
|
-
const
|
|
887
|
+
releaseNote(channel, note, endTime) {
|
|
888
|
+
const volRelease = endTime + note.voiceParams.volRelease;
|
|
889
|
+
const modRelease = endTime + note.voiceParams.modRelease;
|
|
890
|
+
const stopTime = Math.min(volRelease, modRelease);
|
|
891
|
+
note.filterNode.frequency
|
|
892
|
+
.cancelScheduledValues(endTime)
|
|
893
|
+
.linearRampToValueAtTime(0, modRelease);
|
|
922
894
|
note.volumeEnvelopeNode.gain
|
|
923
895
|
.cancelScheduledValues(endTime)
|
|
924
|
-
.linearRampToValueAtTime(0,
|
|
925
|
-
note.ending = true;
|
|
926
|
-
this.scheduleTask(() => {
|
|
927
|
-
note.bufferSource.loop = false;
|
|
928
|
-
}, stopTime);
|
|
896
|
+
.linearRampToValueAtTime(0, volRelease);
|
|
929
897
|
return new Promise((resolve) => {
|
|
930
|
-
|
|
931
|
-
|
|
898
|
+
this.scheduleTask(() => {
|
|
899
|
+
const bufferSource = note.bufferSource;
|
|
900
|
+
bufferSource.loop = false;
|
|
901
|
+
bufferSource.stop(stopTime);
|
|
932
902
|
this.disconnectNote(note);
|
|
903
|
+
channel.scheduledNotes[note.index] = undefined;
|
|
933
904
|
resolve();
|
|
934
|
-
};
|
|
935
|
-
note.bufferSource.stop(stopTime);
|
|
905
|
+
}, stopTime);
|
|
936
906
|
});
|
|
937
907
|
}
|
|
938
|
-
findNoteOffTarget(noteList) {
|
|
939
|
-
for (let i = 0; i < noteList.length; i++) {
|
|
940
|
-
const note = noteList[i];
|
|
941
|
-
if (!note)
|
|
942
|
-
continue;
|
|
943
|
-
if (note.ending)
|
|
944
|
-
continue;
|
|
945
|
-
return [note, i];
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
908
|
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
|
|
949
909
|
const channel = this.channels[channelNumber];
|
|
950
910
|
if (!force && 0.5 <= channel.state.sustainPedal)
|
|
951
911
|
return;
|
|
952
|
-
|
|
912
|
+
const note = this.findNoteOffTarget(channel, noteNumber);
|
|
913
|
+
if (!note)
|
|
953
914
|
return;
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
.
|
|
966
|
-
|
|
967
|
-
|
|
915
|
+
note.ending = true;
|
|
916
|
+
this.releaseNote(channel, note, endTime);
|
|
917
|
+
}
|
|
918
|
+
findNoteOffTarget(channel, noteNumber) {
|
|
919
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
920
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
921
|
+
const note = scheduledNotes[i];
|
|
922
|
+
if (!note)
|
|
923
|
+
continue;
|
|
924
|
+
if (note.ending)
|
|
925
|
+
continue;
|
|
926
|
+
if (note.noteNumber !== noteNumber)
|
|
927
|
+
continue;
|
|
928
|
+
return note;
|
|
929
|
+
}
|
|
968
930
|
}
|
|
969
931
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
970
932
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -975,7 +937,7 @@ class MidyGM1 {
|
|
|
975
937
|
const channel = this.channels[channelNumber];
|
|
976
938
|
const promises = [];
|
|
977
939
|
for (let i = 0; i < channel.sustainNotes.length; i++) {
|
|
978
|
-
const promise = this.
|
|
940
|
+
const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
|
|
979
941
|
promises.push(promise);
|
|
980
942
|
}
|
|
981
943
|
channel.sustainNotes = [];
|
|
@@ -1131,20 +1093,19 @@ class MidyGM1 {
|
|
|
1131
1093
|
});
|
|
1132
1094
|
}
|
|
1133
1095
|
createControlChangeHandlers() {
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
};
|
|
1096
|
+
const handlers = new Array(128);
|
|
1097
|
+
handlers[1] = this.setModulationDepth;
|
|
1098
|
+
handlers[6] = this.dataEntryMSB;
|
|
1099
|
+
handlers[7] = this.setVolume;
|
|
1100
|
+
handlers[10] = this.setPan;
|
|
1101
|
+
handlers[11] = this.setExpression;
|
|
1102
|
+
handlers[38] = this.dataEntryLSB;
|
|
1103
|
+
handlers[64] = this.setSustainPedal;
|
|
1104
|
+
handlers[100] = this.setRPNLSB;
|
|
1105
|
+
handlers[101] = this.setRPNMSB;
|
|
1106
|
+
handlers[120] = this.allSoundOff;
|
|
1107
|
+
handlers[121] = this.resetAllControllers;
|
|
1108
|
+
return handlers;
|
|
1148
1109
|
}
|
|
1149
1110
|
handleControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1150
1111
|
const handler = this.controlChangeHandlers[controllerType];
|
|
@@ -1333,19 +1294,26 @@ class MidyGM1 {
|
|
|
1333
1294
|
return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
|
|
1334
1295
|
}
|
|
1335
1296
|
resetAllStates(channelNumber) {
|
|
1297
|
+
const scheduleTime = this.audioContext.currentTime;
|
|
1336
1298
|
const channel = this.channels[channelNumber];
|
|
1337
1299
|
const state = channel.state;
|
|
1338
|
-
|
|
1339
|
-
|
|
1300
|
+
const entries = Object.entries(defaultControllerState);
|
|
1301
|
+
for (const [key, { type, defaultValue }] of entries) {
|
|
1302
|
+
if (128 <= type) {
|
|
1303
|
+
this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
1304
|
+
}
|
|
1305
|
+
else {
|
|
1306
|
+
state[key] = defaultValue;
|
|
1307
|
+
}
|
|
1340
1308
|
}
|
|
1341
|
-
for (const
|
|
1342
|
-
channel[
|
|
1309
|
+
for (const key of Object.keys(this.constructor.channelSettings)) {
|
|
1310
|
+
channel[key] = this.constructor.channelSettings[key];
|
|
1343
1311
|
}
|
|
1344
1312
|
this.mode = "GM1";
|
|
1345
1313
|
}
|
|
1346
1314
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
|
|
1347
|
-
resetAllControllers(channelNumber) {
|
|
1348
|
-
const
|
|
1315
|
+
resetAllControllers(channelNumber, _value, scheduleTime) {
|
|
1316
|
+
const keys = [
|
|
1349
1317
|
"pitchWheel",
|
|
1350
1318
|
"expression",
|
|
1351
1319
|
"modulationDepth",
|
|
@@ -1353,10 +1321,17 @@ class MidyGM1 {
|
|
|
1353
1321
|
];
|
|
1354
1322
|
const channel = this.channels[channelNumber];
|
|
1355
1323
|
const state = channel.state;
|
|
1356
|
-
for (let i = 0; i <
|
|
1357
|
-
const
|
|
1358
|
-
|
|
1324
|
+
for (let i = 0; i < keys.length; i++) {
|
|
1325
|
+
const key = keys[i];
|
|
1326
|
+
const { type, defaultValue } = defaultControllerState[key];
|
|
1327
|
+
if (128 <= type) {
|
|
1328
|
+
this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
1329
|
+
}
|
|
1330
|
+
else {
|
|
1331
|
+
state[key] = defaultValue;
|
|
1332
|
+
}
|
|
1359
1333
|
}
|
|
1334
|
+
this.setPitchBend(channelNumber, 8192, scheduleTime);
|
|
1360
1335
|
const settingTypes = [
|
|
1361
1336
|
"rpnMSB",
|
|
1362
1337
|
"rpnLSB",
|