@marmooo/midy 0.3.1 → 0.3.2
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 +11 -50
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +150 -169
- package/esm/midy-GM2.d.ts +25 -83
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +376 -285
- package/esm/midy-GMLite.d.ts +10 -49
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +155 -171
- package/esm/midy.d.ts +28 -106
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +405 -313
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +11 -50
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +150 -169
- package/script/midy-GM2.d.ts +25 -83
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +376 -285
- package/script/midy-GMLite.d.ts +10 -49
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +155 -171
- package/script/midy.d.ts +28 -106
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +405 -313
package/script/midy-GM1.js
CHANGED
|
@@ -3,60 +3,14 @@ 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
|
+
});
|
|
60
14
|
Object.defineProperty(this, "bufferSource", {
|
|
61
15
|
enumerable: true,
|
|
62
16
|
configurable: true,
|
|
@@ -117,7 +71,7 @@ const defaultControllerState = {
|
|
|
117
71
|
modulationDepth: { type: 128 + 1, defaultValue: 0 },
|
|
118
72
|
// dataMSB: { type: 128 + 6, defaultValue: 0, },
|
|
119
73
|
volume: { type: 128 + 7, defaultValue: 100 / 127 },
|
|
120
|
-
pan: { type: 128 + 10, defaultValue:
|
|
74
|
+
pan: { type: 128 + 10, defaultValue: 64 / 127 },
|
|
121
75
|
expression: { type: 128 + 11, defaultValue: 1 },
|
|
122
76
|
// bankLSB: { type: 128 + 32, defaultValue: 0, },
|
|
123
77
|
// dataLSB: { type: 128 + 38, defaultValue: 0, },
|
|
@@ -322,7 +276,7 @@ class MidyGM1 {
|
|
|
322
276
|
initSoundFontTable() {
|
|
323
277
|
const table = new Array(128);
|
|
324
278
|
for (let i = 0; i < 128; i++) {
|
|
325
|
-
table[i] = new
|
|
279
|
+
table[i] = new Map();
|
|
326
280
|
}
|
|
327
281
|
return table;
|
|
328
282
|
}
|
|
@@ -374,10 +328,10 @@ class MidyGM1 {
|
|
|
374
328
|
return {
|
|
375
329
|
currentBufferSource: null,
|
|
376
330
|
isDrum: false,
|
|
377
|
-
...this.constructor.channelSettings,
|
|
378
331
|
state: new ControllerState(),
|
|
332
|
+
...this.constructor.channelSettings,
|
|
379
333
|
...this.setChannelAudioNodes(audioContext),
|
|
380
|
-
scheduledNotes:
|
|
334
|
+
scheduledNotes: [],
|
|
381
335
|
sustainNotes: [],
|
|
382
336
|
};
|
|
383
337
|
});
|
|
@@ -412,7 +366,7 @@ class MidyGM1 {
|
|
|
412
366
|
return audioBuffer;
|
|
413
367
|
}
|
|
414
368
|
}
|
|
415
|
-
createBufferSource(
|
|
369
|
+
createBufferSource(voiceParams, audioBuffer) {
|
|
416
370
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
417
371
|
bufferSource.buffer = audioBuffer;
|
|
418
372
|
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
@@ -422,24 +376,20 @@ class MidyGM1 {
|
|
|
422
376
|
}
|
|
423
377
|
return bufferSource;
|
|
424
378
|
}
|
|
425
|
-
async scheduleTimelineEvents(t,
|
|
379
|
+
async scheduleTimelineEvents(t, resumeTime, queueIndex) {
|
|
426
380
|
while (queueIndex < this.timeline.length) {
|
|
427
381
|
const event = this.timeline[queueIndex];
|
|
428
382
|
if (event.startTime > t + this.lookAhead)
|
|
429
383
|
break;
|
|
430
|
-
const
|
|
384
|
+
const delay = this.startDelay - resumeTime;
|
|
385
|
+
const startTime = event.startTime + delay;
|
|
431
386
|
switch (event.type) {
|
|
432
|
-
case "noteOn":
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
case "noteOff": {
|
|
439
|
-
const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
|
|
440
|
-
if (notePromise) {
|
|
441
|
-
this.notePromises.push(notePromise);
|
|
442
|
-
}
|
|
387
|
+
case "noteOn": {
|
|
388
|
+
const noteOffEvent = {
|
|
389
|
+
...event.noteOffEvent,
|
|
390
|
+
startTime: event.noteOffEvent.startTime + delay,
|
|
391
|
+
};
|
|
392
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, noteOffEvent);
|
|
443
393
|
break;
|
|
444
394
|
}
|
|
445
395
|
case "controller":
|
|
@@ -472,7 +422,7 @@ class MidyGM1 {
|
|
|
472
422
|
this.isPaused = false;
|
|
473
423
|
this.startTime = this.audioContext.currentTime;
|
|
474
424
|
let queueIndex = this.getQueueIndex(this.resumeTime);
|
|
475
|
-
let
|
|
425
|
+
let resumeTime = this.resumeTime - this.startTime;
|
|
476
426
|
this.notePromises = [];
|
|
477
427
|
const schedulePlayback = async () => {
|
|
478
428
|
if (queueIndex >= this.timeline.length) {
|
|
@@ -480,18 +430,21 @@ class MidyGM1 {
|
|
|
480
430
|
this.notePromises = [];
|
|
481
431
|
this.exclusiveClassNotes.fill(undefined);
|
|
482
432
|
this.audioBufferCache.clear();
|
|
433
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
434
|
+
this.resetAllStates(i);
|
|
435
|
+
}
|
|
483
436
|
resolve();
|
|
484
437
|
return;
|
|
485
438
|
}
|
|
486
439
|
const now = this.audioContext.currentTime;
|
|
487
|
-
const t = now +
|
|
488
|
-
queueIndex = await this.scheduleTimelineEvents(t,
|
|
440
|
+
const t = now + resumeTime;
|
|
441
|
+
queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
|
|
489
442
|
if (this.isPausing) {
|
|
490
443
|
await this.stopNotes(0, true, now);
|
|
491
444
|
this.notePromises = [];
|
|
492
|
-
resolve();
|
|
493
445
|
this.isPausing = false;
|
|
494
446
|
this.isPaused = true;
|
|
447
|
+
resolve();
|
|
495
448
|
return;
|
|
496
449
|
}
|
|
497
450
|
else if (this.isStopping) {
|
|
@@ -499,9 +452,12 @@ class MidyGM1 {
|
|
|
499
452
|
this.notePromises = [];
|
|
500
453
|
this.exclusiveClassNotes.fill(undefined);
|
|
501
454
|
this.audioBufferCache.clear();
|
|
502
|
-
|
|
455
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
456
|
+
this.resetAllStates(i);
|
|
457
|
+
}
|
|
503
458
|
this.isStopping = false;
|
|
504
459
|
this.isPaused = false;
|
|
460
|
+
resolve();
|
|
505
461
|
return;
|
|
506
462
|
}
|
|
507
463
|
else if (this.isSeeking) {
|
|
@@ -509,7 +465,7 @@ class MidyGM1 {
|
|
|
509
465
|
this.exclusiveClassNotes.fill(undefined);
|
|
510
466
|
this.startTime = this.audioContext.currentTime;
|
|
511
467
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
512
|
-
|
|
468
|
+
resumeTime = this.resumeTime - this.startTime;
|
|
513
469
|
this.isSeeking = false;
|
|
514
470
|
await schedulePlayback();
|
|
515
471
|
}
|
|
@@ -595,13 +551,37 @@ class MidyGM1 {
|
|
|
595
551
|
prevTempoTicks = event.ticks;
|
|
596
552
|
}
|
|
597
553
|
}
|
|
554
|
+
const activeNotes = new Array(this.channels.length * 128);
|
|
555
|
+
for (let i = 0; i < activeNotes.length; i++) {
|
|
556
|
+
activeNotes[i] = [];
|
|
557
|
+
}
|
|
558
|
+
for (let i = 0; i < timeline.length; i++) {
|
|
559
|
+
const event = timeline[i];
|
|
560
|
+
switch (event.type) {
|
|
561
|
+
case "noteOn": {
|
|
562
|
+
const index = event.channel * 128 + event.noteNumber;
|
|
563
|
+
activeNotes[index].push(event);
|
|
564
|
+
break;
|
|
565
|
+
}
|
|
566
|
+
case "noteOff": {
|
|
567
|
+
const index = event.channel * 128 + event.noteNumber;
|
|
568
|
+
const noteOn = activeNotes[index].pop();
|
|
569
|
+
if (noteOn) {
|
|
570
|
+
noteOn.noteOffEvent = event;
|
|
571
|
+
}
|
|
572
|
+
else {
|
|
573
|
+
const eventString = JSON.stringify(event, null, 2);
|
|
574
|
+
console.warn(`noteOff without matching noteOn: ${eventString}`);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
598
579
|
return { instruments, timeline };
|
|
599
580
|
}
|
|
600
581
|
stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
|
|
601
582
|
const channel = this.channels[channelNumber];
|
|
602
583
|
const promises = [];
|
|
603
|
-
|
|
604
|
-
activeNotes.forEach((note) => {
|
|
584
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
605
585
|
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
|
|
606
586
|
this.notePromises.push(promise);
|
|
607
587
|
promises.push(promise);
|
|
@@ -612,11 +592,11 @@ class MidyGM1 {
|
|
|
612
592
|
const channel = this.channels[channelNumber];
|
|
613
593
|
const promises = [];
|
|
614
594
|
this.processScheduledNotes(channel, (note) => {
|
|
615
|
-
const promise = this.scheduleNoteOff(channelNumber, note
|
|
595
|
+
const promise = this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, force);
|
|
616
596
|
this.notePromises.push(promise);
|
|
617
597
|
promises.push(promise);
|
|
618
598
|
});
|
|
619
|
-
channel.scheduledNotes
|
|
599
|
+
channel.scheduledNotes = [];
|
|
620
600
|
return Promise.all(promises);
|
|
621
601
|
}
|
|
622
602
|
stopNotes(velocity, force, scheduleTime) {
|
|
@@ -637,9 +617,6 @@ class MidyGM1 {
|
|
|
637
617
|
if (!this.isPlaying)
|
|
638
618
|
return;
|
|
639
619
|
this.isStopping = true;
|
|
640
|
-
for (let i = 0; i < this.channels.length; i++) {
|
|
641
|
-
this.resetAllStates(i);
|
|
642
|
-
}
|
|
643
620
|
}
|
|
644
621
|
pause() {
|
|
645
622
|
if (!this.isPlaying || this.isPaused)
|
|
@@ -674,37 +651,31 @@ class MidyGM1 {
|
|
|
674
651
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
675
652
|
}
|
|
676
653
|
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;
|
|
654
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
655
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
656
|
+
const note = scheduledNotes[i];
|
|
657
|
+
if (!note)
|
|
658
|
+
continue;
|
|
659
|
+
if (note.ending)
|
|
660
|
+
continue;
|
|
661
|
+
callback(note);
|
|
662
|
+
}
|
|
697
663
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
664
|
+
processActiveNotes(channel, scheduleTime, callback) {
|
|
665
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
666
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
667
|
+
const note = scheduledNotes[i];
|
|
701
668
|
if (!note)
|
|
702
|
-
|
|
669
|
+
continue;
|
|
670
|
+
if (note.ending)
|
|
671
|
+
continue;
|
|
672
|
+
const noteOffEvent = note.noteOffEvent;
|
|
673
|
+
if (noteOffEvent && noteOffEvent.startTime < scheduleTime)
|
|
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
|
}
|
|
@@ -869,13 +841,13 @@ class MidyGM1 {
|
|
|
869
841
|
if (prev) {
|
|
870
842
|
const [prevNote, prevChannelNumber] = prev;
|
|
871
843
|
if (prevNote && !prevNote.ending) {
|
|
872
|
-
this.scheduleNoteOff(prevChannelNumber, prevNote
|
|
844
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote, 0, // velocity,
|
|
873
845
|
startTime, true);
|
|
874
846
|
}
|
|
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,18 @@ class MidyGM1 {
|
|
|
894
867
|
}
|
|
895
868
|
this.handleExclusiveClass(note, channelNumber, startTime);
|
|
896
869
|
const scheduledNotes = channel.scheduledNotes;
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
870
|
+
note.index = scheduledNotes.length;
|
|
871
|
+
scheduledNotes.push(note);
|
|
872
|
+
if (noteOffEvent) {
|
|
873
|
+
const notePromise = this.scheduleNoteOff(channelNumber, note, noteOffEvent.velocity, noteOffEvent.startTime, false);
|
|
874
|
+
if (notePromise) {
|
|
875
|
+
this.notePromises.push(notePromise);
|
|
876
|
+
}
|
|
904
877
|
}
|
|
905
878
|
}
|
|
906
879
|
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
907
880
|
scheduleTime ??= this.audioContext.currentTime;
|
|
908
|
-
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime);
|
|
881
|
+
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, undefined);
|
|
909
882
|
}
|
|
910
883
|
disconnectNote(note) {
|
|
911
884
|
note.bufferSource.disconnect();
|
|
@@ -917,8 +890,7 @@ class MidyGM1 {
|
|
|
917
890
|
note.modulationLFO.stop();
|
|
918
891
|
}
|
|
919
892
|
}
|
|
920
|
-
stopNote(
|
|
921
|
-
const note = noteList[index];
|
|
893
|
+
stopNote(channel, note, endTime, stopTime) {
|
|
922
894
|
note.volumeEnvelopeNode.gain
|
|
923
895
|
.cancelScheduledValues(endTime)
|
|
924
896
|
.linearRampToValueAtTime(0, stopTime);
|
|
@@ -928,54 +900,50 @@ class MidyGM1 {
|
|
|
928
900
|
}, stopTime);
|
|
929
901
|
return new Promise((resolve) => {
|
|
930
902
|
note.bufferSource.onended = () => {
|
|
931
|
-
|
|
903
|
+
channel.scheduledNotes[note.index] = undefined;
|
|
932
904
|
this.disconnectNote(note);
|
|
933
905
|
resolve();
|
|
934
906
|
};
|
|
935
907
|
note.bufferSource.stop(stopTime);
|
|
936
908
|
});
|
|
937
909
|
}
|
|
938
|
-
|
|
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
|
-
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
|
|
910
|
+
scheduleNoteOff(channelNumber, note, _velocity, endTime, force) {
|
|
949
911
|
const channel = this.channels[channelNumber];
|
|
950
912
|
if (!force && 0.5 <= channel.state.sustainPedal)
|
|
951
913
|
return;
|
|
952
|
-
if (!channel.scheduledNotes.has(noteNumber))
|
|
953
|
-
return;
|
|
954
|
-
const noteList = channel.scheduledNotes.get(noteNumber);
|
|
955
|
-
if (!noteList)
|
|
956
|
-
return; // be careful with drum channel
|
|
957
|
-
const noteOffTarget = this.findNoteOffTarget(noteList, endTime);
|
|
958
|
-
if (!noteOffTarget)
|
|
959
|
-
return;
|
|
960
|
-
const [note, i] = noteOffTarget;
|
|
961
914
|
const volRelease = endTime + note.voiceParams.volRelease;
|
|
962
915
|
const modRelease = endTime + note.voiceParams.modRelease;
|
|
963
916
|
note.filterNode.frequency
|
|
964
917
|
.cancelScheduledValues(endTime)
|
|
965
918
|
.linearRampToValueAtTime(0, modRelease);
|
|
966
919
|
const stopTime = Math.min(volRelease, modRelease);
|
|
967
|
-
return this.stopNote(
|
|
920
|
+
return this.stopNote(channel, note, endTime, stopTime);
|
|
921
|
+
}
|
|
922
|
+
findNoteOffTarget(channel, noteNumber) {
|
|
923
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
924
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
925
|
+
const note = scheduledNotes[i];
|
|
926
|
+
if (!note)
|
|
927
|
+
continue;
|
|
928
|
+
if (note.ending)
|
|
929
|
+
continue;
|
|
930
|
+
if (note.noteNumber !== noteNumber)
|
|
931
|
+
continue;
|
|
932
|
+
return note;
|
|
933
|
+
}
|
|
968
934
|
}
|
|
969
935
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
970
936
|
scheduleTime ??= this.audioContext.currentTime;
|
|
971
|
-
|
|
937
|
+
const channel = this.channels[channelNumber];
|
|
938
|
+
const note = this.findNoteOffTarget(channel, noteNumber);
|
|
939
|
+
return this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, false);
|
|
972
940
|
}
|
|
973
941
|
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
974
942
|
const velocity = halfVelocity * 2;
|
|
975
943
|
const channel = this.channels[channelNumber];
|
|
976
944
|
const promises = [];
|
|
977
945
|
for (let i = 0; i < channel.sustainNotes.length; i++) {
|
|
978
|
-
const promise = this.
|
|
946
|
+
const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i], velocity, scheduleTime);
|
|
979
947
|
promises.push(promise);
|
|
980
948
|
}
|
|
981
949
|
channel.sustainNotes = [];
|
|
@@ -1131,20 +1099,19 @@ class MidyGM1 {
|
|
|
1131
1099
|
});
|
|
1132
1100
|
}
|
|
1133
1101
|
createControlChangeHandlers() {
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
};
|
|
1102
|
+
const handlers = new Array(128);
|
|
1103
|
+
handlers[1] = this.setModulationDepth;
|
|
1104
|
+
handlers[6] = this.dataEntryMSB;
|
|
1105
|
+
handlers[7] = this.setVolume;
|
|
1106
|
+
handlers[10] = this.setPan;
|
|
1107
|
+
handlers[11] = this.setExpression;
|
|
1108
|
+
handlers[38] = this.dataEntryLSB;
|
|
1109
|
+
handlers[64] = this.setSustainPedal;
|
|
1110
|
+
handlers[100] = this.setRPNLSB;
|
|
1111
|
+
handlers[101] = this.setRPNMSB;
|
|
1112
|
+
handlers[120] = this.allSoundOff;
|
|
1113
|
+
handlers[121] = this.resetAllControllers;
|
|
1114
|
+
return handlers;
|
|
1148
1115
|
}
|
|
1149
1116
|
handleControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1150
1117
|
const handler = this.controlChangeHandlers[controllerType];
|
|
@@ -1333,19 +1300,26 @@ class MidyGM1 {
|
|
|
1333
1300
|
return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
|
|
1334
1301
|
}
|
|
1335
1302
|
resetAllStates(channelNumber) {
|
|
1303
|
+
const scheduleTime = this.audioContext.currentTime;
|
|
1336
1304
|
const channel = this.channels[channelNumber];
|
|
1337
1305
|
const state = channel.state;
|
|
1338
|
-
|
|
1339
|
-
|
|
1306
|
+
const entries = Object.entries(defaultControllerState);
|
|
1307
|
+
for (const [key, { type, defaultValue }] of entries) {
|
|
1308
|
+
if (128 <= type) {
|
|
1309
|
+
this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
1310
|
+
}
|
|
1311
|
+
else {
|
|
1312
|
+
state[key] = defaultValue;
|
|
1313
|
+
}
|
|
1340
1314
|
}
|
|
1341
|
-
for (const
|
|
1342
|
-
channel[
|
|
1315
|
+
for (const key of Object.keys(this.constructor.channelSettings)) {
|
|
1316
|
+
channel[key] = this.constructor.channelSettings[key];
|
|
1343
1317
|
}
|
|
1344
1318
|
this.mode = "GM1";
|
|
1345
1319
|
}
|
|
1346
1320
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
|
|
1347
|
-
resetAllControllers(channelNumber) {
|
|
1348
|
-
const
|
|
1321
|
+
resetAllControllers(channelNumber, _value, scheduleTime) {
|
|
1322
|
+
const keys = [
|
|
1349
1323
|
"pitchWheel",
|
|
1350
1324
|
"expression",
|
|
1351
1325
|
"modulationDepth",
|
|
@@ -1353,10 +1327,17 @@ class MidyGM1 {
|
|
|
1353
1327
|
];
|
|
1354
1328
|
const channel = this.channels[channelNumber];
|
|
1355
1329
|
const state = channel.state;
|
|
1356
|
-
for (let i = 0; i <
|
|
1357
|
-
const
|
|
1358
|
-
|
|
1330
|
+
for (let i = 0; i < keys.length; i++) {
|
|
1331
|
+
const key = keys[i];
|
|
1332
|
+
const { type, defaultValue } = defaultControllerState[key];
|
|
1333
|
+
if (128 <= type) {
|
|
1334
|
+
this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
1335
|
+
}
|
|
1336
|
+
else {
|
|
1337
|
+
state[key] = defaultValue;
|
|
1338
|
+
}
|
|
1359
1339
|
}
|
|
1340
|
+
this.setPitchBend(channelNumber, 8192, scheduleTime);
|
|
1360
1341
|
const settingTypes = [
|
|
1361
1342
|
"rpnMSB",
|
|
1362
1343
|
"rpnLSB",
|