@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-GMLite.js
CHANGED
|
@@ -3,60 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.MidyGMLite = 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,
|
|
@@ -130,7 +84,7 @@ const defaultControllerState = {
|
|
|
130
84
|
modulationDepth: { type: 128 + 1, defaultValue: 0 },
|
|
131
85
|
// dataMSB: { type: 128 + 6, defaultValue: 0, },
|
|
132
86
|
volume: { type: 128 + 7, defaultValue: 100 / 127 },
|
|
133
|
-
pan: { type: 128 + 10, defaultValue:
|
|
87
|
+
pan: { type: 128 + 10, defaultValue: 64 / 127 },
|
|
134
88
|
expression: { type: 128 + 11, defaultValue: 1 },
|
|
135
89
|
// bankLSB: { type: 128 + 32, defaultValue: 0, },
|
|
136
90
|
// dataLSB: { type: 128 + 38, defaultValue: 0, },
|
|
@@ -341,7 +295,7 @@ class MidyGMLite {
|
|
|
341
295
|
initSoundFontTable() {
|
|
342
296
|
const table = new Array(128);
|
|
343
297
|
for (let i = 0; i < 128; i++) {
|
|
344
|
-
table[i] = new
|
|
298
|
+
table[i] = new Map();
|
|
345
299
|
}
|
|
346
300
|
return table;
|
|
347
301
|
}
|
|
@@ -393,10 +347,10 @@ class MidyGMLite {
|
|
|
393
347
|
return {
|
|
394
348
|
currentBufferSource: null,
|
|
395
349
|
isDrum: false,
|
|
396
|
-
...this.constructor.channelSettings,
|
|
397
350
|
state: new ControllerState(),
|
|
351
|
+
...this.constructor.channelSettings,
|
|
398
352
|
...this.setChannelAudioNodes(audioContext),
|
|
399
|
-
scheduledNotes:
|
|
353
|
+
scheduledNotes: [],
|
|
400
354
|
sustainNotes: [],
|
|
401
355
|
};
|
|
402
356
|
});
|
|
@@ -441,24 +395,20 @@ class MidyGMLite {
|
|
|
441
395
|
}
|
|
442
396
|
return bufferSource;
|
|
443
397
|
}
|
|
444
|
-
async scheduleTimelineEvents(t,
|
|
398
|
+
async scheduleTimelineEvents(t, resumeTime, queueIndex) {
|
|
445
399
|
while (queueIndex < this.timeline.length) {
|
|
446
400
|
const event = this.timeline[queueIndex];
|
|
447
401
|
if (event.startTime > t + this.lookAhead)
|
|
448
402
|
break;
|
|
449
|
-
const
|
|
403
|
+
const delay = this.startDelay - resumeTime;
|
|
404
|
+
const startTime = event.startTime + delay;
|
|
450
405
|
switch (event.type) {
|
|
451
|
-
case "noteOn":
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
case "noteOff": {
|
|
458
|
-
const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
|
|
459
|
-
if (notePromise) {
|
|
460
|
-
this.notePromises.push(notePromise);
|
|
461
|
-
}
|
|
406
|
+
case "noteOn": {
|
|
407
|
+
const noteOffEvent = {
|
|
408
|
+
...event.noteOffEvent,
|
|
409
|
+
startTime: event.noteOffEvent.startTime + delay,
|
|
410
|
+
};
|
|
411
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, noteOffEvent);
|
|
462
412
|
break;
|
|
463
413
|
}
|
|
464
414
|
case "controller":
|
|
@@ -491,44 +441,53 @@ class MidyGMLite {
|
|
|
491
441
|
this.isPaused = false;
|
|
492
442
|
this.startTime = this.audioContext.currentTime;
|
|
493
443
|
let queueIndex = this.getQueueIndex(this.resumeTime);
|
|
494
|
-
let
|
|
444
|
+
let resumeTime = this.resumeTime - this.startTime;
|
|
495
445
|
this.notePromises = [];
|
|
496
446
|
const schedulePlayback = async () => {
|
|
497
447
|
if (queueIndex >= this.timeline.length) {
|
|
498
448
|
await Promise.all(this.notePromises);
|
|
499
449
|
this.notePromises = [];
|
|
500
450
|
this.exclusiveClassNotes.fill(undefined);
|
|
451
|
+
this.drumExclusiveClassNotes.fill(undefined);
|
|
501
452
|
this.audioBufferCache.clear();
|
|
453
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
454
|
+
this.resetAllStates(i);
|
|
455
|
+
}
|
|
502
456
|
resolve();
|
|
503
457
|
return;
|
|
504
458
|
}
|
|
505
459
|
const now = this.audioContext.currentTime;
|
|
506
|
-
const t = now +
|
|
507
|
-
queueIndex = await this.scheduleTimelineEvents(t,
|
|
460
|
+
const t = now + resumeTime;
|
|
461
|
+
queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
|
|
508
462
|
if (this.isPausing) {
|
|
509
463
|
await this.stopNotes(0, true, now);
|
|
510
464
|
this.notePromises = [];
|
|
511
|
-
resolve();
|
|
512
465
|
this.isPausing = false;
|
|
513
466
|
this.isPaused = true;
|
|
467
|
+
resolve();
|
|
514
468
|
return;
|
|
515
469
|
}
|
|
516
470
|
else if (this.isStopping) {
|
|
517
471
|
await this.stopNotes(0, true, now);
|
|
518
472
|
this.notePromises = [];
|
|
519
473
|
this.exclusiveClassNotes.fill(undefined);
|
|
474
|
+
this.drumExclusiveClassNotes.fill(undefined);
|
|
520
475
|
this.audioBufferCache.clear();
|
|
521
|
-
|
|
476
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
477
|
+
this.resetAllStates(i);
|
|
478
|
+
}
|
|
522
479
|
this.isStopping = false;
|
|
523
480
|
this.isPaused = false;
|
|
481
|
+
resolve();
|
|
524
482
|
return;
|
|
525
483
|
}
|
|
526
484
|
else if (this.isSeeking) {
|
|
527
485
|
this.stopNotes(0, true, now);
|
|
528
486
|
this.exclusiveClassNotes.fill(undefined);
|
|
487
|
+
this.drumExclusiveClassNotes.fill(undefined);
|
|
529
488
|
this.startTime = this.audioContext.currentTime;
|
|
530
489
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
531
|
-
|
|
490
|
+
resumeTime = this.resumeTime - this.startTime;
|
|
532
491
|
this.isSeeking = false;
|
|
533
492
|
await schedulePlayback();
|
|
534
493
|
}
|
|
@@ -614,13 +573,37 @@ class MidyGMLite {
|
|
|
614
573
|
prevTempoTicks = event.ticks;
|
|
615
574
|
}
|
|
616
575
|
}
|
|
576
|
+
const activeNotes = new Array(this.channels.length * 128);
|
|
577
|
+
for (let i = 0; i < activeNotes.length; i++) {
|
|
578
|
+
activeNotes[i] = [];
|
|
579
|
+
}
|
|
580
|
+
for (let i = 0; i < timeline.length; i++) {
|
|
581
|
+
const event = timeline[i];
|
|
582
|
+
switch (event.type) {
|
|
583
|
+
case "noteOn": {
|
|
584
|
+
const index = event.channel * 128 + event.noteNumber;
|
|
585
|
+
activeNotes[index].push(event);
|
|
586
|
+
break;
|
|
587
|
+
}
|
|
588
|
+
case "noteOff": {
|
|
589
|
+
const index = event.channel * 128 + event.noteNumber;
|
|
590
|
+
const noteOn = activeNotes[index].pop();
|
|
591
|
+
if (noteOn) {
|
|
592
|
+
noteOn.noteOffEvent = event;
|
|
593
|
+
}
|
|
594
|
+
else {
|
|
595
|
+
const eventString = JSON.stringify(event, null, 2);
|
|
596
|
+
console.warn(`noteOff without matching noteOn: ${eventString}`);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
617
601
|
return { instruments, timeline };
|
|
618
602
|
}
|
|
619
603
|
stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
|
|
620
604
|
const channel = this.channels[channelNumber];
|
|
621
605
|
const promises = [];
|
|
622
|
-
|
|
623
|
-
activeNotes.forEach((note) => {
|
|
606
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
624
607
|
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
|
|
625
608
|
this.notePromises.push(promise);
|
|
626
609
|
promises.push(promise);
|
|
@@ -631,11 +614,11 @@ class MidyGMLite {
|
|
|
631
614
|
const channel = this.channels[channelNumber];
|
|
632
615
|
const promises = [];
|
|
633
616
|
this.processScheduledNotes(channel, (note) => {
|
|
634
|
-
const promise = this.scheduleNoteOff(channelNumber, note
|
|
617
|
+
const promise = this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, force);
|
|
635
618
|
this.notePromises.push(promise);
|
|
636
619
|
promises.push(promise);
|
|
637
620
|
});
|
|
638
|
-
channel.scheduledNotes
|
|
621
|
+
channel.scheduledNotes = [];
|
|
639
622
|
return Promise.all(promises);
|
|
640
623
|
}
|
|
641
624
|
stopNotes(velocity, force, scheduleTime) {
|
|
@@ -656,9 +639,6 @@ class MidyGMLite {
|
|
|
656
639
|
if (!this.isPlaying)
|
|
657
640
|
return;
|
|
658
641
|
this.isStopping = true;
|
|
659
|
-
for (let i = 0; i < this.channels.length; i++) {
|
|
660
|
-
this.resetAllStates(i);
|
|
661
|
-
}
|
|
662
642
|
}
|
|
663
643
|
pause() {
|
|
664
644
|
if (!this.isPlaying || this.isPaused)
|
|
@@ -693,37 +673,31 @@ class MidyGMLite {
|
|
|
693
673
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
694
674
|
}
|
|
695
675
|
processScheduledNotes(channel, callback) {
|
|
696
|
-
channel.scheduledNotes
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
});
|
|
706
|
-
}
|
|
707
|
-
getActiveNotes(channel, scheduleTime) {
|
|
708
|
-
const activeNotes = new SparseMap(128);
|
|
709
|
-
channel.scheduledNotes.forEach((noteList) => {
|
|
710
|
-
const activeNote = this.getActiveNote(noteList, scheduleTime);
|
|
711
|
-
if (activeNote) {
|
|
712
|
-
activeNotes.set(activeNote.noteNumber, activeNote);
|
|
713
|
-
}
|
|
714
|
-
});
|
|
715
|
-
return activeNotes;
|
|
676
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
677
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
678
|
+
const note = scheduledNotes[i];
|
|
679
|
+
if (!note)
|
|
680
|
+
continue;
|
|
681
|
+
if (note.ending)
|
|
682
|
+
continue;
|
|
683
|
+
callback(note);
|
|
684
|
+
}
|
|
716
685
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
686
|
+
processActiveNotes(channel, scheduleTime, callback) {
|
|
687
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
688
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
689
|
+
const note = scheduledNotes[i];
|
|
720
690
|
if (!note)
|
|
721
|
-
|
|
691
|
+
continue;
|
|
692
|
+
if (note.ending)
|
|
693
|
+
continue;
|
|
694
|
+
const noteOffEvent = note.noteOffEvent;
|
|
695
|
+
if (noteOffEvent && noteOffEvent.startTime < scheduleTime)
|
|
696
|
+
continue;
|
|
722
697
|
if (scheduleTime < note.startTime)
|
|
723
698
|
continue;
|
|
724
|
-
|
|
699
|
+
callback(note);
|
|
725
700
|
}
|
|
726
|
-
return noteList[0];
|
|
727
701
|
}
|
|
728
702
|
cbToRatio(cb) {
|
|
729
703
|
return Math.pow(10, cb / 200);
|
|
@@ -870,6 +844,7 @@ class MidyGMLite {
|
|
|
870
844
|
this.setVolumeEnvelope(note, now);
|
|
871
845
|
this.setFilterEnvelope(note, now);
|
|
872
846
|
this.setPitchEnvelope(note, now);
|
|
847
|
+
this.updateDetune(channel, note, now);
|
|
873
848
|
if (0 < state.modulationDepth) {
|
|
874
849
|
this.startModulation(channel, note, now);
|
|
875
850
|
}
|
|
@@ -886,7 +861,7 @@ class MidyGMLite {
|
|
|
886
861
|
if (prev) {
|
|
887
862
|
const [prevNote, prevChannelNumber] = prev;
|
|
888
863
|
if (prevNote && !prevNote.ending) {
|
|
889
|
-
this.scheduleNoteOff(prevChannelNumber, prevNote
|
|
864
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote, 0, // velocity,
|
|
890
865
|
startTime, true);
|
|
891
866
|
}
|
|
892
867
|
}
|
|
@@ -902,12 +877,12 @@ class MidyGMLite {
|
|
|
902
877
|
const index = drumExclusiveClass * this.channels.length + channelNumber;
|
|
903
878
|
const prevNote = this.drumExclusiveClassNotes[index];
|
|
904
879
|
if (prevNote && !prevNote.ending) {
|
|
905
|
-
this.scheduleNoteOff(channelNumber, prevNote
|
|
880
|
+
this.scheduleNoteOff(channelNumber, prevNote, 0, // velocity,
|
|
906
881
|
startTime, true);
|
|
907
882
|
}
|
|
908
883
|
this.drumExclusiveClassNotes[index] = note;
|
|
909
884
|
}
|
|
910
|
-
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
885
|
+
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, noteOffEvent) {
|
|
911
886
|
const channel = this.channels[channelNumber];
|
|
912
887
|
const bankNumber = channel.bank;
|
|
913
888
|
const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
|
|
@@ -919,6 +894,7 @@ class MidyGMLite {
|
|
|
919
894
|
return;
|
|
920
895
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
921
896
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
897
|
+
note.noteOffEvent = noteOffEvent;
|
|
922
898
|
note.volumeEnvelopeNode.connect(channel.gainL);
|
|
923
899
|
note.volumeEnvelopeNode.connect(channel.gainR);
|
|
924
900
|
if (0.5 <= channel.state.sustainPedal) {
|
|
@@ -927,20 +903,13 @@ class MidyGMLite {
|
|
|
927
903
|
this.handleExclusiveClass(note, channelNumber, startTime);
|
|
928
904
|
this.handleDrumExclusiveClass(note, channelNumber, startTime);
|
|
929
905
|
const scheduledNotes = channel.scheduledNotes;
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
noteList.push(note);
|
|
933
|
-
}
|
|
934
|
-
else {
|
|
935
|
-
noteList = [note];
|
|
936
|
-
scheduledNotes.set(noteNumber, noteList);
|
|
937
|
-
}
|
|
906
|
+
note.index = scheduledNotes.length;
|
|
907
|
+
scheduledNotes.push(note);
|
|
938
908
|
if (channel.isDrum) {
|
|
939
909
|
const stopTime = startTime + note.bufferSource.buffer.duration;
|
|
940
|
-
const index = noteList.length - 1;
|
|
941
910
|
const promise = new Promise((resolve) => {
|
|
942
911
|
note.bufferSource.onended = () => {
|
|
943
|
-
|
|
912
|
+
scheduledNotes[note.index] = undefined;
|
|
944
913
|
this.disconnectNote(note);
|
|
945
914
|
resolve();
|
|
946
915
|
};
|
|
@@ -948,10 +917,16 @@ class MidyGMLite {
|
|
|
948
917
|
});
|
|
949
918
|
this.notePromises.push(promise);
|
|
950
919
|
}
|
|
920
|
+
else if (noteOffEvent) {
|
|
921
|
+
const notePromise = this.scheduleNoteOff(channelNumber, note, noteOffEvent.velocity, noteOffEvent.startTime, false);
|
|
922
|
+
if (notePromise) {
|
|
923
|
+
this.notePromises.push(notePromise);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
951
926
|
}
|
|
952
927
|
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
953
928
|
scheduleTime ??= this.audioContext.currentTime;
|
|
954
|
-
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime);
|
|
929
|
+
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, undefined);
|
|
955
930
|
}
|
|
956
931
|
disconnectNote(note) {
|
|
957
932
|
note.bufferSource.disconnect();
|
|
@@ -963,8 +938,7 @@ class MidyGMLite {
|
|
|
963
938
|
note.modulationLFO.stop();
|
|
964
939
|
}
|
|
965
940
|
}
|
|
966
|
-
stopNote(
|
|
967
|
-
const note = noteList[index];
|
|
941
|
+
stopNote(channel, note, endTime, stopTime) {
|
|
968
942
|
note.volumeEnvelopeNode.gain
|
|
969
943
|
.cancelScheduledValues(endTime)
|
|
970
944
|
.linearRampToValueAtTime(0, stopTime);
|
|
@@ -974,56 +948,52 @@ class MidyGMLite {
|
|
|
974
948
|
}, stopTime);
|
|
975
949
|
return new Promise((resolve) => {
|
|
976
950
|
note.bufferSource.onended = () => {
|
|
977
|
-
|
|
951
|
+
channel.scheduledNotes[note.index] = undefined;
|
|
978
952
|
this.disconnectNote(note);
|
|
979
953
|
resolve();
|
|
980
954
|
};
|
|
981
955
|
note.bufferSource.stop(stopTime);
|
|
982
956
|
});
|
|
983
957
|
}
|
|
984
|
-
|
|
985
|
-
for (let i = 0; i < noteList.length; i++) {
|
|
986
|
-
const note = noteList[i];
|
|
987
|
-
if (!note)
|
|
988
|
-
continue;
|
|
989
|
-
if (note.ending)
|
|
990
|
-
continue;
|
|
991
|
-
return [note, i];
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
|
|
958
|
+
scheduleNoteOff(channelNumber, note, _velocity, endTime, force) {
|
|
995
959
|
const channel = this.channels[channelNumber];
|
|
996
960
|
if (channel.isDrum)
|
|
997
961
|
return;
|
|
998
962
|
if (!force && 0.5 <= channel.state.sustainPedal)
|
|
999
963
|
return;
|
|
1000
|
-
if (!channel.scheduledNotes.has(noteNumber))
|
|
1001
|
-
return;
|
|
1002
|
-
const noteList = channel.scheduledNotes.get(noteNumber);
|
|
1003
|
-
if (!noteList)
|
|
1004
|
-
return; // be careful with drum channel
|
|
1005
|
-
const noteOffTarget = this.findNoteOffTarget(noteList, endTime);
|
|
1006
|
-
if (!noteOffTarget)
|
|
1007
|
-
return;
|
|
1008
|
-
const [note, i] = noteOffTarget;
|
|
1009
964
|
const volRelease = endTime + note.voiceParams.volRelease;
|
|
1010
965
|
const modRelease = endTime + note.voiceParams.modRelease;
|
|
1011
966
|
note.filterNode.frequency
|
|
1012
967
|
.cancelScheduledValues(endTime)
|
|
1013
968
|
.linearRampToValueAtTime(0, modRelease);
|
|
1014
969
|
const stopTime = Math.min(volRelease, modRelease);
|
|
1015
|
-
return this.stopNote(
|
|
970
|
+
return this.stopNote(channel, note, endTime, stopTime);
|
|
971
|
+
}
|
|
972
|
+
findNoteOffTarget(channel, noteNumber) {
|
|
973
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
974
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
975
|
+
const note = scheduledNotes[i];
|
|
976
|
+
if (!note)
|
|
977
|
+
continue;
|
|
978
|
+
if (note.ending)
|
|
979
|
+
continue;
|
|
980
|
+
if (note.noteNumber !== noteNumber)
|
|
981
|
+
continue;
|
|
982
|
+
return note;
|
|
983
|
+
}
|
|
1016
984
|
}
|
|
1017
985
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1018
986
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1019
|
-
|
|
987
|
+
const channel = this.channels[channelNumber];
|
|
988
|
+
const note = this.findNoteOffTarget(channel, noteNumber);
|
|
989
|
+
return this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, false);
|
|
1020
990
|
}
|
|
1021
991
|
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
1022
992
|
const velocity = halfVelocity * 2;
|
|
1023
993
|
const channel = this.channels[channelNumber];
|
|
1024
994
|
const promises = [];
|
|
1025
995
|
for (let i = 0; i < channel.sustainNotes.length; i++) {
|
|
1026
|
-
const promise = this.
|
|
996
|
+
const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i], velocity, scheduleTime);
|
|
1027
997
|
promises.push(promise);
|
|
1028
998
|
}
|
|
1029
999
|
channel.sustainNotes = [];
|
|
@@ -1179,20 +1149,20 @@ class MidyGMLite {
|
|
|
1179
1149
|
});
|
|
1180
1150
|
}
|
|
1181
1151
|
createControlChangeHandlers() {
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1152
|
+
const handlers = new Array(128);
|
|
1153
|
+
handlers[1] = this.setModulationDepth;
|
|
1154
|
+
handlers[6] = this.dataEntryMSB;
|
|
1155
|
+
handlers[7] = this.setVolume;
|
|
1156
|
+
handlers[10] = this.setPan;
|
|
1157
|
+
handlers[11] = this.setExpression;
|
|
1158
|
+
handlers[38] = this.dataEntryLSB;
|
|
1159
|
+
handlers[64] = this.setSustainPedal;
|
|
1160
|
+
handlers[100] = this.setRPNLSB;
|
|
1161
|
+
handlers[101] = this.setRPNMSB;
|
|
1162
|
+
handlers[120] = this.allSoundOff;
|
|
1163
|
+
handlers[121] = this.resetAllControllers;
|
|
1164
|
+
handlers[123] = this.allNotesOff;
|
|
1165
|
+
return handlers;
|
|
1196
1166
|
}
|
|
1197
1167
|
handleControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1198
1168
|
const handler = this.controlChangeHandlers[controllerType];
|
|
@@ -1337,19 +1307,26 @@ class MidyGMLite {
|
|
|
1337
1307
|
return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
|
|
1338
1308
|
}
|
|
1339
1309
|
resetAllStates(channelNumber) {
|
|
1310
|
+
const scheduleTime = this.audioContext.currentTime;
|
|
1340
1311
|
const channel = this.channels[channelNumber];
|
|
1341
1312
|
const state = channel.state;
|
|
1342
|
-
|
|
1343
|
-
|
|
1313
|
+
const entries = Object.entries(defaultControllerState);
|
|
1314
|
+
for (const [key, { type, defaultValue }] of entries) {
|
|
1315
|
+
if (128 <= type) {
|
|
1316
|
+
this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
1317
|
+
}
|
|
1318
|
+
else {
|
|
1319
|
+
state[key] = defaultValue;
|
|
1320
|
+
}
|
|
1344
1321
|
}
|
|
1345
|
-
for (const
|
|
1346
|
-
channel[
|
|
1322
|
+
for (const key of Object.keys(this.constructor.channelSettings)) {
|
|
1323
|
+
channel[key] = this.constructor.channelSettings[key];
|
|
1347
1324
|
}
|
|
1348
1325
|
this.mode = "GM1";
|
|
1349
1326
|
}
|
|
1350
1327
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
|
|
1351
|
-
resetAllControllers(channelNumber) {
|
|
1352
|
-
const
|
|
1328
|
+
resetAllControllers(channelNumber, _value, scheduleTime) {
|
|
1329
|
+
const keys = [
|
|
1353
1330
|
"pitchWheel",
|
|
1354
1331
|
"expression",
|
|
1355
1332
|
"modulationDepth",
|
|
@@ -1357,10 +1334,17 @@ class MidyGMLite {
|
|
|
1357
1334
|
];
|
|
1358
1335
|
const channel = this.channels[channelNumber];
|
|
1359
1336
|
const state = channel.state;
|
|
1360
|
-
for (let i = 0; i <
|
|
1361
|
-
const
|
|
1362
|
-
|
|
1337
|
+
for (let i = 0; i < keys.length; i++) {
|
|
1338
|
+
const key = keys[i];
|
|
1339
|
+
const { type, defaultValue } = defaultControllerState[key];
|
|
1340
|
+
if (128 <= type) {
|
|
1341
|
+
this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
1342
|
+
}
|
|
1343
|
+
else {
|
|
1344
|
+
state[key] = defaultValue;
|
|
1345
|
+
}
|
|
1363
1346
|
}
|
|
1347
|
+
this.setPitchBend(channelNumber, 8192, scheduleTime);
|
|
1364
1348
|
const settingTypes = [
|
|
1365
1349
|
"rpnMSB",
|
|
1366
1350
|
"rpnLSB",
|