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