@marmooo/midy 0.3.0 → 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/README.md +2 -1
- package/esm/midy-GM1.d.ts +13 -50
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +164 -160
- package/esm/midy-GM2.d.ts +27 -83
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +390 -275
- package/esm/midy-GMLite.d.ts +12 -49
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +170 -163
- package/esm/midy.d.ts +31 -107
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +423 -306
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +13 -50
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +164 -160
- package/script/midy-GM2.d.ts +27 -83
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +390 -275
- package/script/midy-GMLite.d.ts +12 -49
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +170 -163
- package/script/midy.d.ts +31 -107
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +423 -306
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
|
-
this.exclusiveClassNotes.
|
|
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,17 +573,52 @@ 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
|
}
|
|
603
|
+
stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
|
|
604
|
+
const channel = this.channels[channelNumber];
|
|
605
|
+
const promises = [];
|
|
606
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
607
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
|
|
608
|
+
this.notePromises.push(promise);
|
|
609
|
+
promises.push(promise);
|
|
610
|
+
});
|
|
611
|
+
return Promise.all(promises);
|
|
612
|
+
}
|
|
619
613
|
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
620
614
|
const channel = this.channels[channelNumber];
|
|
621
615
|
const promises = [];
|
|
622
616
|
this.processScheduledNotes(channel, (note) => {
|
|
623
|
-
const promise = this.scheduleNoteOff(channelNumber, note
|
|
617
|
+
const promise = this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, force);
|
|
624
618
|
this.notePromises.push(promise);
|
|
625
619
|
promises.push(promise);
|
|
626
620
|
});
|
|
627
|
-
channel.scheduledNotes
|
|
621
|
+
channel.scheduledNotes = [];
|
|
628
622
|
return Promise.all(promises);
|
|
629
623
|
}
|
|
630
624
|
stopNotes(velocity, force, scheduleTime) {
|
|
@@ -645,9 +639,6 @@ class MidyGMLite {
|
|
|
645
639
|
if (!this.isPlaying)
|
|
646
640
|
return;
|
|
647
641
|
this.isStopping = true;
|
|
648
|
-
for (let i = 0; i < this.channels.length; i++) {
|
|
649
|
-
this.resetAllStates(i);
|
|
650
|
-
}
|
|
651
642
|
}
|
|
652
643
|
pause() {
|
|
653
644
|
if (!this.isPlaying || this.isPaused)
|
|
@@ -682,35 +673,31 @@ class MidyGMLite {
|
|
|
682
673
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
683
674
|
}
|
|
684
675
|
processScheduledNotes(channel, callback) {
|
|
685
|
-
channel.scheduledNotes
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
getActiveNotes(channel, scheduleTime) {
|
|
695
|
-
const activeNotes = new SparseMap(128);
|
|
696
|
-
channel.scheduledNotes.forEach((noteList) => {
|
|
697
|
-
const activeNote = this.getActiveNote(noteList, scheduleTime);
|
|
698
|
-
if (activeNote) {
|
|
699
|
-
activeNotes.set(activeNote.noteNumber, activeNote);
|
|
700
|
-
}
|
|
701
|
-
});
|
|
702
|
-
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
|
+
}
|
|
703
685
|
}
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
686
|
+
processActiveNotes(channel, scheduleTime, callback) {
|
|
687
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
688
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
689
|
+
const note = scheduledNotes[i];
|
|
707
690
|
if (!note)
|
|
708
|
-
|
|
691
|
+
continue;
|
|
692
|
+
if (note.ending)
|
|
693
|
+
continue;
|
|
694
|
+
const noteOffEvent = note.noteOffEvent;
|
|
695
|
+
if (noteOffEvent && noteOffEvent.startTime < scheduleTime)
|
|
696
|
+
continue;
|
|
709
697
|
if (scheduleTime < note.startTime)
|
|
710
698
|
continue;
|
|
711
|
-
|
|
699
|
+
callback(note);
|
|
712
700
|
}
|
|
713
|
-
return noteList[0];
|
|
714
701
|
}
|
|
715
702
|
cbToRatio(cb) {
|
|
716
703
|
return Math.pow(10, cb / 200);
|
|
@@ -857,6 +844,7 @@ class MidyGMLite {
|
|
|
857
844
|
this.setVolumeEnvelope(note, now);
|
|
858
845
|
this.setFilterEnvelope(note, now);
|
|
859
846
|
this.setPitchEnvelope(note, now);
|
|
847
|
+
this.updateDetune(channel, note, now);
|
|
860
848
|
if (0 < state.modulationDepth) {
|
|
861
849
|
this.startModulation(channel, note, now);
|
|
862
850
|
}
|
|
@@ -873,7 +861,7 @@ class MidyGMLite {
|
|
|
873
861
|
if (prev) {
|
|
874
862
|
const [prevNote, prevChannelNumber] = prev;
|
|
875
863
|
if (prevNote && !prevNote.ending) {
|
|
876
|
-
this.scheduleNoteOff(prevChannelNumber, prevNote
|
|
864
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote, 0, // velocity,
|
|
877
865
|
startTime, true);
|
|
878
866
|
}
|
|
879
867
|
}
|
|
@@ -889,12 +877,12 @@ class MidyGMLite {
|
|
|
889
877
|
const index = drumExclusiveClass * this.channels.length + channelNumber;
|
|
890
878
|
const prevNote = this.drumExclusiveClassNotes[index];
|
|
891
879
|
if (prevNote && !prevNote.ending) {
|
|
892
|
-
this.scheduleNoteOff(channelNumber, prevNote
|
|
880
|
+
this.scheduleNoteOff(channelNumber, prevNote, 0, // velocity,
|
|
893
881
|
startTime, true);
|
|
894
882
|
}
|
|
895
883
|
this.drumExclusiveClassNotes[index] = note;
|
|
896
884
|
}
|
|
897
|
-
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
885
|
+
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, noteOffEvent) {
|
|
898
886
|
const channel = this.channels[channelNumber];
|
|
899
887
|
const bankNumber = channel.bank;
|
|
900
888
|
const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
|
|
@@ -906,6 +894,7 @@ class MidyGMLite {
|
|
|
906
894
|
return;
|
|
907
895
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
908
896
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
897
|
+
note.noteOffEvent = noteOffEvent;
|
|
909
898
|
note.volumeEnvelopeNode.connect(channel.gainL);
|
|
910
899
|
note.volumeEnvelopeNode.connect(channel.gainR);
|
|
911
900
|
if (0.5 <= channel.state.sustainPedal) {
|
|
@@ -914,33 +903,32 @@ class MidyGMLite {
|
|
|
914
903
|
this.handleExclusiveClass(note, channelNumber, startTime);
|
|
915
904
|
this.handleDrumExclusiveClass(note, channelNumber, startTime);
|
|
916
905
|
const scheduledNotes = channel.scheduledNotes;
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
}
|
|
921
|
-
else {
|
|
922
|
-
notes = [note];
|
|
923
|
-
scheduledNotes.set(noteNumber, notes);
|
|
924
|
-
}
|
|
925
|
-
if (this.isDrumNoteOffException(channel, noteNumber)) {
|
|
906
|
+
note.index = scheduledNotes.length;
|
|
907
|
+
scheduledNotes.push(note);
|
|
908
|
+
if (channel.isDrum) {
|
|
926
909
|
const stopTime = startTime + note.bufferSource.buffer.duration;
|
|
927
|
-
const index = notes.length - 1;
|
|
928
910
|
const promise = new Promise((resolve) => {
|
|
929
911
|
note.bufferSource.onended = () => {
|
|
930
|
-
|
|
912
|
+
scheduledNotes[note.index] = undefined;
|
|
913
|
+
this.disconnectNote(note);
|
|
931
914
|
resolve();
|
|
932
915
|
};
|
|
933
916
|
note.bufferSource.stop(stopTime);
|
|
934
917
|
});
|
|
935
918
|
this.notePromises.push(promise);
|
|
936
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
|
+
}
|
|
937
926
|
}
|
|
938
927
|
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
939
928
|
scheduleTime ??= this.audioContext.currentTime;
|
|
940
|
-
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime);
|
|
929
|
+
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, undefined);
|
|
941
930
|
}
|
|
942
|
-
disconnectNote(note
|
|
943
|
-
scheduledNotes[index] = null;
|
|
931
|
+
disconnectNote(note) {
|
|
944
932
|
note.bufferSource.disconnect();
|
|
945
933
|
note.filterNode.disconnect();
|
|
946
934
|
note.volumeEnvelopeNode.disconnect();
|
|
@@ -950,8 +938,7 @@ class MidyGMLite {
|
|
|
950
938
|
note.modulationLFO.stop();
|
|
951
939
|
}
|
|
952
940
|
}
|
|
953
|
-
stopNote(
|
|
954
|
-
const note = scheduledNotes[index];
|
|
941
|
+
stopNote(channel, note, endTime, stopTime) {
|
|
955
942
|
note.volumeEnvelopeNode.gain
|
|
956
943
|
.cancelScheduledValues(endTime)
|
|
957
944
|
.linearRampToValueAtTime(0, stopTime);
|
|
@@ -961,46 +948,52 @@ class MidyGMLite {
|
|
|
961
948
|
}, stopTime);
|
|
962
949
|
return new Promise((resolve) => {
|
|
963
950
|
note.bufferSource.onended = () => {
|
|
964
|
-
|
|
951
|
+
channel.scheduledNotes[note.index] = undefined;
|
|
952
|
+
this.disconnectNote(note);
|
|
965
953
|
resolve();
|
|
966
954
|
};
|
|
967
955
|
note.bufferSource.stop(stopTime);
|
|
968
956
|
});
|
|
969
957
|
}
|
|
970
|
-
scheduleNoteOff(channelNumber,
|
|
958
|
+
scheduleNoteOff(channelNumber, note, _velocity, endTime, force) {
|
|
971
959
|
const channel = this.channels[channelNumber];
|
|
972
960
|
if (channel.isDrum)
|
|
973
961
|
return;
|
|
974
962
|
if (!force && 0.5 <= channel.state.sustainPedal)
|
|
975
963
|
return;
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
964
|
+
const volRelease = endTime + note.voiceParams.volRelease;
|
|
965
|
+
const modRelease = endTime + note.voiceParams.modRelease;
|
|
966
|
+
note.filterNode.frequency
|
|
967
|
+
.cancelScheduledValues(endTime)
|
|
968
|
+
.linearRampToValueAtTime(0, modRelease);
|
|
969
|
+
const stopTime = Math.min(volRelease, modRelease);
|
|
970
|
+
return this.stopNote(channel, note, endTime, stopTime);
|
|
971
|
+
}
|
|
972
|
+
findNoteOffTarget(channel, noteNumber) {
|
|
973
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
979
974
|
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
980
975
|
const note = scheduledNotes[i];
|
|
981
976
|
if (!note)
|
|
982
977
|
continue;
|
|
983
978
|
if (note.ending)
|
|
984
979
|
continue;
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
note
|
|
988
|
-
.cancelScheduledValues(endTime)
|
|
989
|
-
.linearRampToValueAtTime(0, modRelease);
|
|
990
|
-
const stopTime = Math.min(volRelease, modRelease);
|
|
991
|
-
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
980
|
+
if (note.noteNumber !== noteNumber)
|
|
981
|
+
continue;
|
|
982
|
+
return note;
|
|
992
983
|
}
|
|
993
984
|
}
|
|
994
985
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
995
986
|
scheduleTime ??= this.audioContext.currentTime;
|
|
996
|
-
|
|
987
|
+
const channel = this.channels[channelNumber];
|
|
988
|
+
const note = this.findNoteOffTarget(channel, noteNumber);
|
|
989
|
+
return this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, false);
|
|
997
990
|
}
|
|
998
991
|
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
999
992
|
const velocity = halfVelocity * 2;
|
|
1000
993
|
const channel = this.channels[channelNumber];
|
|
1001
994
|
const promises = [];
|
|
1002
995
|
for (let i = 0; i < channel.sustainNotes.length; i++) {
|
|
1003
|
-
const promise = this.
|
|
996
|
+
const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i], velocity, scheduleTime);
|
|
1004
997
|
promises.push(promise);
|
|
1005
998
|
}
|
|
1006
999
|
channel.sustainNotes = [];
|
|
@@ -1156,20 +1149,20 @@ class MidyGMLite {
|
|
|
1156
1149
|
});
|
|
1157
1150
|
}
|
|
1158
1151
|
createControlChangeHandlers() {
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
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;
|
|
1173
1166
|
}
|
|
1174
1167
|
handleControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1175
1168
|
const handler = this.controlChangeHandlers[controllerType];
|
|
@@ -1311,22 +1304,29 @@ class MidyGMLite {
|
|
|
1311
1304
|
}
|
|
1312
1305
|
allSoundOff(channelNumber, _value, scheduleTime) {
|
|
1313
1306
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1314
|
-
return this.
|
|
1307
|
+
return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
|
|
1315
1308
|
}
|
|
1316
1309
|
resetAllStates(channelNumber) {
|
|
1310
|
+
const scheduleTime = this.audioContext.currentTime;
|
|
1317
1311
|
const channel = this.channels[channelNumber];
|
|
1318
1312
|
const state = channel.state;
|
|
1319
|
-
|
|
1320
|
-
|
|
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
|
+
}
|
|
1321
1321
|
}
|
|
1322
|
-
for (const
|
|
1323
|
-
channel[
|
|
1322
|
+
for (const key of Object.keys(this.constructor.channelSettings)) {
|
|
1323
|
+
channel[key] = this.constructor.channelSettings[key];
|
|
1324
1324
|
}
|
|
1325
1325
|
this.mode = "GM1";
|
|
1326
1326
|
}
|
|
1327
1327
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
|
|
1328
|
-
resetAllControllers(channelNumber) {
|
|
1329
|
-
const
|
|
1328
|
+
resetAllControllers(channelNumber, _value, scheduleTime) {
|
|
1329
|
+
const keys = [
|
|
1330
1330
|
"pitchWheel",
|
|
1331
1331
|
"expression",
|
|
1332
1332
|
"modulationDepth",
|
|
@@ -1334,10 +1334,17 @@ class MidyGMLite {
|
|
|
1334
1334
|
];
|
|
1335
1335
|
const channel = this.channels[channelNumber];
|
|
1336
1336
|
const state = channel.state;
|
|
1337
|
-
for (let i = 0; i <
|
|
1338
|
-
const
|
|
1339
|
-
|
|
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
|
+
}
|
|
1340
1346
|
}
|
|
1347
|
+
this.setPitchBend(channelNumber, 8192, scheduleTime);
|
|
1341
1348
|
const settingTypes = [
|
|
1342
1349
|
"rpnMSB",
|
|
1343
1350
|
"rpnLSB",
|
|
@@ -1349,7 +1356,7 @@ class MidyGMLite {
|
|
|
1349
1356
|
}
|
|
1350
1357
|
allNotesOff(channelNumber, _value, scheduleTime) {
|
|
1351
1358
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1352
|
-
return this.
|
|
1359
|
+
return this.stopActiveNotes(channelNumber, 0, false, scheduleTime);
|
|
1353
1360
|
}
|
|
1354
1361
|
handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime) {
|
|
1355
1362
|
switch (data[2]) {
|