@marmooo/midy 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/midy-GM1.d.ts +17 -55
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +159 -184
- package/esm/midy-GM2.d.ts +34 -90
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +421 -342
- package/esm/midy-GMLite.d.ts +17 -55
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +169 -199
- package/esm/midy.d.ts +37 -114
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +451 -376
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +17 -55
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +159 -184
- package/script/midy-GM2.d.ts +34 -90
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +421 -342
- package/script/midy-GMLite.d.ts +17 -55
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +169 -199
- package/script/midy.d.ts +37 -114
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +451 -376
package/script/midy-GM2.js
CHANGED
|
@@ -3,60 +3,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.MidyGM2 = void 0;
|
|
4
4
|
const midi_file_1 = require("midi-file");
|
|
5
5
|
const soundfont_parser_1 = require("@marmooo/soundfont-parser");
|
|
6
|
-
// 2-3 times faster than Map
|
|
7
|
-
class SparseMap {
|
|
8
|
-
constructor(size) {
|
|
9
|
-
this.data = new Array(size);
|
|
10
|
-
this.activeIndices = [];
|
|
11
|
-
}
|
|
12
|
-
set(key, value) {
|
|
13
|
-
if (this.data[key] === undefined) {
|
|
14
|
-
this.activeIndices.push(key);
|
|
15
|
-
}
|
|
16
|
-
this.data[key] = value;
|
|
17
|
-
}
|
|
18
|
-
get(key) {
|
|
19
|
-
return this.data[key];
|
|
20
|
-
}
|
|
21
|
-
delete(key) {
|
|
22
|
-
if (this.data[key] !== undefined) {
|
|
23
|
-
this.data[key] = undefined;
|
|
24
|
-
const index = this.activeIndices.indexOf(key);
|
|
25
|
-
if (index !== -1) {
|
|
26
|
-
this.activeIndices.splice(index, 1);
|
|
27
|
-
}
|
|
28
|
-
return true;
|
|
29
|
-
}
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
has(key) {
|
|
33
|
-
return this.data[key] !== undefined;
|
|
34
|
-
}
|
|
35
|
-
get size() {
|
|
36
|
-
return this.activeIndices.length;
|
|
37
|
-
}
|
|
38
|
-
clear() {
|
|
39
|
-
for (let i = 0; i < this.activeIndices.length; i++) {
|
|
40
|
-
const key = this.activeIndices[i];
|
|
41
|
-
this.data[key] = undefined;
|
|
42
|
-
}
|
|
43
|
-
this.activeIndices = [];
|
|
44
|
-
}
|
|
45
|
-
*[Symbol.iterator]() {
|
|
46
|
-
for (let i = 0; i < this.activeIndices.length; i++) {
|
|
47
|
-
const key = this.activeIndices[i];
|
|
48
|
-
yield [key, this.data[key]];
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
forEach(callback) {
|
|
52
|
-
for (let i = 0; i < this.activeIndices.length; i++) {
|
|
53
|
-
const key = this.activeIndices[i];
|
|
54
|
-
callback(this.data[key], key, this);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
6
|
class Note {
|
|
59
7
|
constructor(noteNumber, velocity, startTime, voice, voiceParams) {
|
|
8
|
+
Object.defineProperty(this, "index", {
|
|
9
|
+
enumerable: true,
|
|
10
|
+
configurable: true,
|
|
11
|
+
writable: true,
|
|
12
|
+
value: -1
|
|
13
|
+
});
|
|
14
|
+
Object.defineProperty(this, "ending", {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
configurable: true,
|
|
17
|
+
writable: true,
|
|
18
|
+
value: false
|
|
19
|
+
});
|
|
60
20
|
Object.defineProperty(this, "bufferSource", {
|
|
61
21
|
enumerable: true,
|
|
62
22
|
configurable: true,
|
|
@@ -141,11 +101,11 @@ class Note {
|
|
|
141
101
|
writable: true,
|
|
142
102
|
value: void 0
|
|
143
103
|
});
|
|
144
|
-
Object.defineProperty(this, "
|
|
104
|
+
Object.defineProperty(this, "portamentoNoteNumber", {
|
|
145
105
|
enumerable: true,
|
|
146
106
|
configurable: true,
|
|
147
107
|
writable: true,
|
|
148
|
-
value:
|
|
108
|
+
value: -1
|
|
149
109
|
});
|
|
150
110
|
this.noteNumber = noteNumber;
|
|
151
111
|
this.velocity = velocity;
|
|
@@ -200,7 +160,7 @@ const defaultControllerState = {
|
|
|
200
160
|
portamentoTime: { type: 128 + 5, defaultValue: 0 },
|
|
201
161
|
// dataMSB: { type: 128 + 6, defaultValue: 0, },
|
|
202
162
|
volume: { type: 128 + 7, defaultValue: 100 / 127 },
|
|
203
|
-
pan: { type: 128 + 10, defaultValue:
|
|
163
|
+
pan: { type: 128 + 10, defaultValue: 64 / 127 },
|
|
204
164
|
expression: { type: 128 + 11, defaultValue: 1 },
|
|
205
165
|
// bankLSB: { type: 128 + 32, defaultValue: 0, },
|
|
206
166
|
// dataLSB: { type: 128 + 38, defaultValue: 0, },
|
|
@@ -208,14 +168,6 @@ const defaultControllerState = {
|
|
|
208
168
|
portamento: { type: 128 + 65, defaultValue: 0 },
|
|
209
169
|
sostenutoPedal: { type: 128 + 66, defaultValue: 0 },
|
|
210
170
|
softPedal: { type: 128 + 67, defaultValue: 0 },
|
|
211
|
-
filterResonance: { type: 128 + 71, defaultValue: 0.5 },
|
|
212
|
-
releaseTime: { type: 128 + 72, defaultValue: 0.5 },
|
|
213
|
-
attackTime: { type: 128 + 73, defaultValue: 0.5 },
|
|
214
|
-
brightness: { type: 128 + 74, defaultValue: 0.5 },
|
|
215
|
-
decayTime: { type: 128 + 75, defaultValue: 0.5 },
|
|
216
|
-
vibratoRate: { type: 128 + 76, defaultValue: 0.5 },
|
|
217
|
-
vibratoDepth: { type: 128 + 77, defaultValue: 0.5 },
|
|
218
|
-
vibratoDelay: { type: 128 + 78, defaultValue: 0.5 },
|
|
219
171
|
reverbSendLevel: { type: 128 + 91, defaultValue: 0 },
|
|
220
172
|
chorusSendLevel: { type: 128 + 93, defaultValue: 0 },
|
|
221
173
|
// dataIncrement: { type: 128 + 96, defaultValue: 0 },
|
|
@@ -490,7 +442,7 @@ class MidyGM2 {
|
|
|
490
442
|
initSoundFontTable() {
|
|
491
443
|
const table = new Array(128);
|
|
492
444
|
for (let i = 0; i < 128; i++) {
|
|
493
|
-
table[i] = new
|
|
445
|
+
table[i] = new Map();
|
|
494
446
|
}
|
|
495
447
|
return table;
|
|
496
448
|
}
|
|
@@ -506,17 +458,37 @@ class MidyGM2 {
|
|
|
506
458
|
}
|
|
507
459
|
}
|
|
508
460
|
}
|
|
509
|
-
async loadSoundFont(
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
461
|
+
async loadSoundFont(input) {
|
|
462
|
+
let uint8Array;
|
|
463
|
+
if (typeof input === "string") {
|
|
464
|
+
const response = await fetch(input);
|
|
465
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
466
|
+
uint8Array = new Uint8Array(arrayBuffer);
|
|
467
|
+
}
|
|
468
|
+
else if (input instanceof Uint8Array) {
|
|
469
|
+
uint8Array = input;
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
throw new TypeError("input must be a URL string or Uint8Array");
|
|
473
|
+
}
|
|
474
|
+
const parsed = (0, soundfont_parser_1.parse)(uint8Array);
|
|
513
475
|
const soundFont = new soundfont_parser_1.SoundFont(parsed);
|
|
514
476
|
this.addSoundFont(soundFont);
|
|
515
477
|
}
|
|
516
|
-
async loadMIDI(
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
478
|
+
async loadMIDI(input) {
|
|
479
|
+
let uint8Array;
|
|
480
|
+
if (typeof input === "string") {
|
|
481
|
+
const response = await fetch(input);
|
|
482
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
483
|
+
uint8Array = new Uint8Array(arrayBuffer);
|
|
484
|
+
}
|
|
485
|
+
else if (input instanceof Uint8Array) {
|
|
486
|
+
uint8Array = input;
|
|
487
|
+
}
|
|
488
|
+
else {
|
|
489
|
+
throw new TypeError("input must be a URL string or Uint8Array");
|
|
490
|
+
}
|
|
491
|
+
const midi = (0, midi_file_1.parseMidi)(uint8Array);
|
|
520
492
|
this.ticksPerBeat = midi.header.ticksPerBeat;
|
|
521
493
|
const midiData = this.extractMidiData(midi);
|
|
522
494
|
this.instruments = midiData.instruments;
|
|
@@ -537,21 +509,28 @@ class MidyGM2 {
|
|
|
537
509
|
merger,
|
|
538
510
|
};
|
|
539
511
|
}
|
|
512
|
+
resetChannelTable(channel) {
|
|
513
|
+
this.resetControlTable(channel.controlTable);
|
|
514
|
+
channel.scaleOctaveTuningTable.fill(0); // [-100, 100] cent
|
|
515
|
+
channel.channelPressureTable.set([64, 64, 64, 0, 0, 0]);
|
|
516
|
+
channel.polyphonicKeyPressureTable.set([64, 64, 64, 0, 0, 0]);
|
|
517
|
+
channel.keyBasedInstrumentControlTable.fill(-1);
|
|
518
|
+
}
|
|
540
519
|
createChannels(audioContext) {
|
|
541
520
|
const channels = Array.from({ length: this.numChannels }, () => {
|
|
542
521
|
return {
|
|
543
522
|
currentBufferSource: null,
|
|
544
523
|
isDrum: false,
|
|
545
|
-
...this.constructor.channelSettings,
|
|
546
524
|
state: new ControllerState(),
|
|
547
|
-
|
|
525
|
+
...this.constructor.channelSettings,
|
|
548
526
|
...this.setChannelAudioNodes(audioContext),
|
|
549
|
-
scheduledNotes:
|
|
527
|
+
scheduledNotes: [],
|
|
550
528
|
sustainNotes: [],
|
|
551
|
-
sostenutoNotes:
|
|
529
|
+
sostenutoNotes: [],
|
|
530
|
+
controlTable: this.initControlTable(),
|
|
552
531
|
scaleOctaveTuningTable: new Int8Array(12), // [-64, 63] cent
|
|
553
532
|
channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
554
|
-
keyBasedInstrumentControlTable: new Int8Array(128 * 128)
|
|
533
|
+
keyBasedInstrumentControlTable: new Int8Array(128 * 128).fill(-1),
|
|
555
534
|
};
|
|
556
535
|
});
|
|
557
536
|
return channels;
|
|
@@ -585,56 +564,39 @@ class MidyGM2 {
|
|
|
585
564
|
return audioBuffer;
|
|
586
565
|
}
|
|
587
566
|
}
|
|
588
|
-
|
|
567
|
+
isLoopDrum(channel, noteNumber) {
|
|
568
|
+
const programNumber = channel.programNumber;
|
|
569
|
+
return ((programNumber === 48 && noteNumber === 88) ||
|
|
570
|
+
(programNumber === 56 && 47 <= noteNumber && noteNumber <= 84));
|
|
571
|
+
}
|
|
572
|
+
createBufferSource(channel, noteNumber, voiceParams, audioBuffer) {
|
|
589
573
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
590
574
|
bufferSource.buffer = audioBuffer;
|
|
591
575
|
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
576
|
+
if (channel.isDrum) {
|
|
577
|
+
bufferSource.loop = this.isLoopDrum(channel, noteNumber);
|
|
578
|
+
}
|
|
592
579
|
if (bufferSource.loop) {
|
|
593
580
|
bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
|
|
594
581
|
bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
|
|
595
582
|
}
|
|
596
583
|
return bufferSource;
|
|
597
584
|
}
|
|
598
|
-
|
|
599
|
-
const endEvent = this.timeline[queueIndex];
|
|
600
|
-
if (!this.channels[endEvent.channel].portamento)
|
|
601
|
-
return;
|
|
602
|
-
const endTime = endEvent.startTime;
|
|
603
|
-
let target;
|
|
604
|
-
while (++queueIndex < this.timeline.length) {
|
|
605
|
-
const event = this.timeline[queueIndex];
|
|
606
|
-
if (endTime !== event.startTime)
|
|
607
|
-
break;
|
|
608
|
-
if (event.type !== "noteOn")
|
|
609
|
-
continue;
|
|
610
|
-
if (!target || event.noteNumber < target.noteNumber) {
|
|
611
|
-
target = event;
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
return target;
|
|
615
|
-
}
|
|
616
|
-
async scheduleTimelineEvents(t, offset, queueIndex) {
|
|
585
|
+
async scheduleTimelineEvents(t, resumeTime, queueIndex) {
|
|
617
586
|
while (queueIndex < this.timeline.length) {
|
|
618
587
|
const event = this.timeline[queueIndex];
|
|
619
588
|
if (event.startTime > t + this.lookAhead)
|
|
620
589
|
break;
|
|
621
|
-
const
|
|
590
|
+
const delay = this.startDelay - resumeTime;
|
|
591
|
+
const startTime = event.startTime + delay;
|
|
622
592
|
switch (event.type) {
|
|
623
593
|
case "noteOn":
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
break;
|
|
627
|
-
}
|
|
628
|
-
/* falls through */
|
|
594
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
|
|
595
|
+
break;
|
|
629
596
|
case "noteOff": {
|
|
630
|
-
const
|
|
631
|
-
if (
|
|
632
|
-
portamentoTarget.portamento = true;
|
|
633
|
-
const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false, // force
|
|
634
|
-
portamentoTarget?.noteNumber);
|
|
635
|
-
if (notePromise) {
|
|
597
|
+
const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
|
|
598
|
+
if (notePromise)
|
|
636
599
|
this.notePromises.push(notePromise);
|
|
637
|
-
}
|
|
638
600
|
break;
|
|
639
601
|
}
|
|
640
602
|
case "controller":
|
|
@@ -670,7 +632,7 @@ class MidyGM2 {
|
|
|
670
632
|
this.isPaused = false;
|
|
671
633
|
this.startTime = this.audioContext.currentTime;
|
|
672
634
|
let queueIndex = this.getQueueIndex(this.resumeTime);
|
|
673
|
-
let
|
|
635
|
+
let resumeTime = this.resumeTime - this.startTime;
|
|
674
636
|
this.notePromises = [];
|
|
675
637
|
const schedulePlayback = async () => {
|
|
676
638
|
if (queueIndex >= this.timeline.length) {
|
|
@@ -679,18 +641,21 @@ class MidyGM2 {
|
|
|
679
641
|
this.exclusiveClassNotes.fill(undefined);
|
|
680
642
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
681
643
|
this.audioBufferCache.clear();
|
|
644
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
645
|
+
this.resetAllStates(i);
|
|
646
|
+
}
|
|
682
647
|
resolve();
|
|
683
648
|
return;
|
|
684
649
|
}
|
|
685
650
|
const now = this.audioContext.currentTime;
|
|
686
|
-
const t = now +
|
|
687
|
-
queueIndex = await this.scheduleTimelineEvents(t,
|
|
651
|
+
const t = now + resumeTime;
|
|
652
|
+
queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
|
|
688
653
|
if (this.isPausing) {
|
|
689
654
|
await this.stopNotes(0, true, now);
|
|
690
655
|
this.notePromises = [];
|
|
691
|
-
resolve();
|
|
692
656
|
this.isPausing = false;
|
|
693
657
|
this.isPaused = true;
|
|
658
|
+
resolve();
|
|
694
659
|
return;
|
|
695
660
|
}
|
|
696
661
|
else if (this.isStopping) {
|
|
@@ -699,9 +664,12 @@ class MidyGM2 {
|
|
|
699
664
|
this.exclusiveClassNotes.fill(undefined);
|
|
700
665
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
701
666
|
this.audioBufferCache.clear();
|
|
702
|
-
|
|
667
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
668
|
+
this.resetAllStates(i);
|
|
669
|
+
}
|
|
703
670
|
this.isStopping = false;
|
|
704
671
|
this.isPaused = false;
|
|
672
|
+
resolve();
|
|
705
673
|
return;
|
|
706
674
|
}
|
|
707
675
|
else if (this.isSeeking) {
|
|
@@ -710,7 +678,7 @@ class MidyGM2 {
|
|
|
710
678
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
711
679
|
this.startTime = this.audioContext.currentTime;
|
|
712
680
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
713
|
-
|
|
681
|
+
resumeTime = this.resumeTime - this.startTime;
|
|
714
682
|
this.isSeeking = false;
|
|
715
683
|
await schedulePlayback();
|
|
716
684
|
}
|
|
@@ -733,6 +701,7 @@ class MidyGM2 {
|
|
|
733
701
|
return `${programNumber}:${noteNumber}:${velocity}`;
|
|
734
702
|
}
|
|
735
703
|
extractMidiData(midi) {
|
|
704
|
+
this.audioBufferCounter.clear();
|
|
736
705
|
const instruments = new Set();
|
|
737
706
|
const timeline = [];
|
|
738
707
|
const tmpChannels = new Array(this.channels.length);
|
|
@@ -837,9 +806,8 @@ class MidyGM2 {
|
|
|
837
806
|
stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
|
|
838
807
|
const channel = this.channels[channelNumber];
|
|
839
808
|
const promises = [];
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
|
|
809
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
810
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
843
811
|
this.notePromises.push(promise);
|
|
844
812
|
promises.push(promise);
|
|
845
813
|
});
|
|
@@ -849,11 +817,11 @@ class MidyGM2 {
|
|
|
849
817
|
const channel = this.channels[channelNumber];
|
|
850
818
|
const promises = [];
|
|
851
819
|
this.processScheduledNotes(channel, (note) => {
|
|
852
|
-
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force
|
|
820
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
853
821
|
this.notePromises.push(promise);
|
|
854
822
|
promises.push(promise);
|
|
855
823
|
});
|
|
856
|
-
channel.scheduledNotes
|
|
824
|
+
channel.scheduledNotes = [];
|
|
857
825
|
return Promise.all(promises);
|
|
858
826
|
}
|
|
859
827
|
stopNotes(velocity, force, scheduleTime) {
|
|
@@ -874,9 +842,6 @@ class MidyGM2 {
|
|
|
874
842
|
if (!this.isPlaying)
|
|
875
843
|
return;
|
|
876
844
|
this.isStopping = true;
|
|
877
|
-
for (let i = 0; i < this.channels.length; i++) {
|
|
878
|
-
this.resetAllStates(i);
|
|
879
|
-
}
|
|
880
845
|
}
|
|
881
846
|
pause() {
|
|
882
847
|
if (!this.isPlaying || this.isPaused)
|
|
@@ -911,37 +876,28 @@ class MidyGM2 {
|
|
|
911
876
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
912
877
|
}
|
|
913
878
|
processScheduledNotes(channel, callback) {
|
|
914
|
-
channel.scheduledNotes
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
});
|
|
924
|
-
}
|
|
925
|
-
getActiveNotes(channel, scheduleTime) {
|
|
926
|
-
const activeNotes = new SparseMap(128);
|
|
927
|
-
channel.scheduledNotes.forEach((noteList) => {
|
|
928
|
-
const activeNote = this.getActiveNote(noteList, scheduleTime);
|
|
929
|
-
if (activeNote) {
|
|
930
|
-
activeNotes.set(activeNote.noteNumber, activeNote);
|
|
931
|
-
}
|
|
932
|
-
});
|
|
933
|
-
return activeNotes;
|
|
879
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
880
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
881
|
+
const note = scheduledNotes[i];
|
|
882
|
+
if (!note)
|
|
883
|
+
continue;
|
|
884
|
+
if (note.ending)
|
|
885
|
+
continue;
|
|
886
|
+
callback(note);
|
|
887
|
+
}
|
|
934
888
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
889
|
+
processActiveNotes(channel, scheduleTime, callback) {
|
|
890
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
891
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
892
|
+
const note = scheduledNotes[i];
|
|
938
893
|
if (!note)
|
|
939
|
-
|
|
894
|
+
continue;
|
|
895
|
+
if (note.ending)
|
|
896
|
+
continue;
|
|
940
897
|
if (scheduleTime < note.startTime)
|
|
941
898
|
continue;
|
|
942
|
-
|
|
899
|
+
callback(note);
|
|
943
900
|
}
|
|
944
|
-
return noteList[0];
|
|
945
901
|
}
|
|
946
902
|
createConvolutionReverbImpulse(audioContext, decay, preDecay) {
|
|
947
903
|
const sampleRate = audioContext.sampleRate;
|
|
@@ -1107,24 +1063,93 @@ class MidyGM2 {
|
|
|
1107
1063
|
updateDetune(channel, note, scheduleTime) {
|
|
1108
1064
|
const noteDetune = this.calcNoteDetune(channel, note);
|
|
1109
1065
|
const detune = channel.detune + noteDetune;
|
|
1110
|
-
note.
|
|
1111
|
-
.
|
|
1112
|
-
.
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1066
|
+
if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
1067
|
+
const startTime = note.startTime;
|
|
1068
|
+
const deltaCent = (note.noteNumber - note.portamentoNoteNumber) * 100;
|
|
1069
|
+
const portamentoTime = startTime + this.getPortamentoTime(channel, note);
|
|
1070
|
+
note.bufferSource.detune
|
|
1071
|
+
.cancelScheduledValues(scheduleTime)
|
|
1072
|
+
.setValueAtTime(detune - deltaCent, scheduleTime)
|
|
1073
|
+
.linearRampToValueAtTime(detune, portamentoTime);
|
|
1074
|
+
}
|
|
1075
|
+
else {
|
|
1076
|
+
note.bufferSource.detune
|
|
1077
|
+
.cancelScheduledValues(scheduleTime)
|
|
1078
|
+
.setValueAtTime(detune, scheduleTime);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
getPortamentoTime(channel, note) {
|
|
1082
|
+
const deltaSemitone = Math.abs(note.noteNumber - note.portamentoNoteNumber);
|
|
1083
|
+
const value = Math.ceil(channel.state.portamentoTime * 127);
|
|
1084
|
+
return deltaSemitone / this.getPitchIncrementSpeed(value) / 10;
|
|
1085
|
+
}
|
|
1086
|
+
getPitchIncrementSpeed(value) {
|
|
1087
|
+
const points = [
|
|
1088
|
+
[0, 1000],
|
|
1089
|
+
[6, 100],
|
|
1090
|
+
[16, 20],
|
|
1091
|
+
[32, 10],
|
|
1092
|
+
[48, 5],
|
|
1093
|
+
[64, 2.5],
|
|
1094
|
+
[80, 1],
|
|
1095
|
+
[96, 0.4],
|
|
1096
|
+
[112, 0.15],
|
|
1097
|
+
[127, 0.01],
|
|
1098
|
+
];
|
|
1099
|
+
const logPoints = new Array(points.length);
|
|
1100
|
+
for (let i = 0; i < points.length; i++) {
|
|
1101
|
+
const [x, y] = points[i];
|
|
1102
|
+
if (value === x)
|
|
1103
|
+
return y;
|
|
1104
|
+
logPoints[i] = [x, Math.log(y)];
|
|
1105
|
+
}
|
|
1106
|
+
let startIndex = 0;
|
|
1107
|
+
for (let i = 1; i < logPoints.length; i++) {
|
|
1108
|
+
if (value <= logPoints[i][0]) {
|
|
1109
|
+
startIndex = i - 1;
|
|
1110
|
+
break;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
const [x0, y0] = logPoints[startIndex];
|
|
1114
|
+
const [x1, y1] = logPoints[startIndex + 1];
|
|
1115
|
+
const h = x1 - x0;
|
|
1116
|
+
const t = (value - x0) / h;
|
|
1117
|
+
let m0, m1;
|
|
1118
|
+
if (startIndex === 0) {
|
|
1119
|
+
m0 = (y1 - y0) / h;
|
|
1120
|
+
}
|
|
1121
|
+
else {
|
|
1122
|
+
const [xPrev, yPrev] = logPoints[startIndex - 1];
|
|
1123
|
+
m0 = (y1 - yPrev) / (x1 - xPrev);
|
|
1124
|
+
}
|
|
1125
|
+
if (startIndex === logPoints.length - 2) {
|
|
1126
|
+
m1 = (y1 - y0) / h;
|
|
1127
|
+
}
|
|
1128
|
+
else {
|
|
1129
|
+
const [xNext, yNext] = logPoints[startIndex + 2];
|
|
1130
|
+
m1 = (yNext - y0) / (xNext - x0);
|
|
1131
|
+
}
|
|
1132
|
+
// Cubic Hermite Spline
|
|
1133
|
+
const t2 = t * t;
|
|
1134
|
+
const t3 = t2 * t;
|
|
1135
|
+
const h00 = 2 * t3 - 3 * t2 + 1;
|
|
1136
|
+
const h10 = t3 - 2 * t2 + t;
|
|
1137
|
+
const h01 = -2 * t3 + 3 * t2;
|
|
1138
|
+
const h11 = t3 - t2;
|
|
1139
|
+
const y = h00 * y0 + h01 * y1 + h * (h10 * m0 + h11 * m1);
|
|
1140
|
+
return Math.exp(y);
|
|
1141
|
+
}
|
|
1142
|
+
setPortamentoVolumeEnvelope(channel, note, scheduleTime) {
|
|
1119
1143
|
const { voiceParams, startTime } = note;
|
|
1120
|
-
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation)
|
|
1144
|
+
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
|
|
1145
|
+
(1 + this.getAmplitudeControl(channel));
|
|
1121
1146
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
1122
1147
|
const volDelay = startTime + voiceParams.volDelay;
|
|
1123
|
-
const
|
|
1148
|
+
const volAttack = volDelay + voiceParams.volAttack;
|
|
1149
|
+
const volHold = volAttack + voiceParams.volHold;
|
|
1124
1150
|
note.volumeEnvelopeNode.gain
|
|
1125
1151
|
.cancelScheduledValues(scheduleTime)
|
|
1126
|
-
.setValueAtTime(
|
|
1127
|
-
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
1152
|
+
.setValueAtTime(sustainVolume, volHold);
|
|
1128
1153
|
}
|
|
1129
1154
|
setVolumeEnvelope(channel, note, scheduleTime) {
|
|
1130
1155
|
const { voiceParams, startTime } = note;
|
|
@@ -1143,6 +1168,12 @@ class MidyGM2 {
|
|
|
1143
1168
|
.setValueAtTime(attackVolume, volHold)
|
|
1144
1169
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
1145
1170
|
}
|
|
1171
|
+
setPortamentoPitchEnvelope(note, scheduleTime) {
|
|
1172
|
+
const baseRate = note.voiceParams.playbackRate;
|
|
1173
|
+
note.bufferSource.playbackRate
|
|
1174
|
+
.cancelScheduledValues(scheduleTime)
|
|
1175
|
+
.setValueAtTime(baseRate, scheduleTime);
|
|
1176
|
+
}
|
|
1146
1177
|
setPitchEnvelope(note, scheduleTime) {
|
|
1147
1178
|
const { voiceParams } = note;
|
|
1148
1179
|
const baseRate = voiceParams.playbackRate;
|
|
@@ -1170,19 +1201,18 @@ class MidyGM2 {
|
|
|
1170
1201
|
const maxFrequency = 20000; // max Hz of initialFilterFc
|
|
1171
1202
|
return Math.max(minFrequency, Math.min(frequency, maxFrequency));
|
|
1172
1203
|
}
|
|
1173
|
-
|
|
1174
|
-
const
|
|
1175
|
-
const
|
|
1176
|
-
const
|
|
1177
|
-
|
|
1178
|
-
const baseFreq = this.centToHz(
|
|
1179
|
-
softPedalFactor;
|
|
1204
|
+
setPortamentoFilterEnvelope(channel, note, scheduleTime) {
|
|
1205
|
+
const { voiceParams, startTime } = note;
|
|
1206
|
+
const softPedalFactor = this.getSoftPedalFactor(channel, note);
|
|
1207
|
+
const baseCent = voiceParams.initialFilterFc +
|
|
1208
|
+
this.getFilterCutoffControl(channel);
|
|
1209
|
+
const baseFreq = this.centToHz(baseCent) * softPedalFactor;
|
|
1180
1210
|
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor;
|
|
1181
1211
|
const sustainFreq = baseFreq +
|
|
1182
1212
|
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
1183
1213
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
1184
1214
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
1185
|
-
const portamentoTime = startTime + this.getPortamentoTime(channel);
|
|
1215
|
+
const portamentoTime = startTime + this.getPortamentoTime(channel, note);
|
|
1186
1216
|
const modDelay = startTime + voiceParams.modDelay;
|
|
1187
1217
|
note.filterNode.frequency
|
|
1188
1218
|
.cancelScheduledValues(scheduleTime)
|
|
@@ -1191,10 +1221,8 @@ class MidyGM2 {
|
|
|
1191
1221
|
.linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
|
|
1192
1222
|
}
|
|
1193
1223
|
setFilterEnvelope(channel, note, scheduleTime) {
|
|
1194
|
-
const
|
|
1195
|
-
const
|
|
1196
|
-
const softPedalFactor = 1 -
|
|
1197
|
-
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1224
|
+
const { voiceParams, startTime } = note;
|
|
1225
|
+
const softPedalFactor = this.getSoftPedalFactor(channel, note);
|
|
1198
1226
|
const baseCent = voiceParams.initialFilterFc +
|
|
1199
1227
|
this.getFilterCutoffControl(channel);
|
|
1200
1228
|
const baseFreq = this.centToHz(baseCent) * softPedalFactor;
|
|
@@ -1267,14 +1295,14 @@ class MidyGM2 {
|
|
|
1267
1295
|
return audioBuffer;
|
|
1268
1296
|
}
|
|
1269
1297
|
}
|
|
1270
|
-
async createNote(channel, voice, noteNumber, velocity, startTime,
|
|
1298
|
+
async createNote(channel, voice, noteNumber, velocity, startTime, isSF3) {
|
|
1271
1299
|
const now = this.audioContext.currentTime;
|
|
1272
1300
|
const state = channel.state;
|
|
1273
1301
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1274
1302
|
const voiceParams = voice.getAllParams(controllerState);
|
|
1275
1303
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1276
1304
|
const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
|
|
1277
|
-
note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
|
|
1305
|
+
note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
|
|
1278
1306
|
note.volumeNode = new GainNode(this.audioContext);
|
|
1279
1307
|
note.gainL = new GainNode(this.audioContext);
|
|
1280
1308
|
note.gainR = new GainNode(this.audioContext);
|
|
@@ -1283,20 +1311,24 @@ class MidyGM2 {
|
|
|
1283
1311
|
type: "lowpass",
|
|
1284
1312
|
Q: voiceParams.initialFilterQ / 10, // dB
|
|
1285
1313
|
});
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1314
|
+
const prevNote = channel.scheduledNotes.at(-1);
|
|
1315
|
+
if (prevNote && prevNote.noteNumber !== noteNumber) {
|
|
1316
|
+
note.portamentoNoteNumber = prevNote.noteNumber;
|
|
1317
|
+
}
|
|
1318
|
+
if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
1319
|
+
this.setPortamentoVolumeEnvelope(channel, note, now);
|
|
1320
|
+
this.setPortamentoFilterEnvelope(channel, note, now);
|
|
1321
|
+
this.setPortamentoPitchEnvelope(note, now);
|
|
1290
1322
|
}
|
|
1291
1323
|
else {
|
|
1292
|
-
note.portamento = false;
|
|
1293
1324
|
this.setVolumeEnvelope(channel, note, now);
|
|
1294
1325
|
this.setFilterEnvelope(channel, note, now);
|
|
1326
|
+
this.setPitchEnvelope(note, now);
|
|
1295
1327
|
}
|
|
1328
|
+
this.updateDetune(channel, note, now);
|
|
1296
1329
|
if (0 < state.vibratoDepth) {
|
|
1297
1330
|
this.startVibrato(channel, note, now);
|
|
1298
1331
|
}
|
|
1299
|
-
this.setPitchEnvelope(note, now);
|
|
1300
1332
|
if (0 < state.modulationDepth) {
|
|
1301
1333
|
this.startModulation(channel, note, now);
|
|
1302
1334
|
}
|
|
@@ -1309,10 +1341,10 @@ class MidyGM2 {
|
|
|
1309
1341
|
note.volumeEnvelopeNode.connect(note.volumeNode);
|
|
1310
1342
|
note.volumeNode.connect(note.gainL);
|
|
1311
1343
|
note.volumeNode.connect(note.gainR);
|
|
1312
|
-
if (0 <
|
|
1344
|
+
if (0 < state.chorusSendLevel) {
|
|
1313
1345
|
this.setChorusEffectsSend(channel, note, 0, now);
|
|
1314
1346
|
}
|
|
1315
|
-
if (0 <
|
|
1347
|
+
if (0 < state.reverbSendLevel) {
|
|
1316
1348
|
this.setReverbEffectsSend(channel, note, 0, now);
|
|
1317
1349
|
}
|
|
1318
1350
|
note.bufferSource.start(startTime);
|
|
@@ -1343,8 +1375,7 @@ class MidyGM2 {
|
|
|
1343
1375
|
const [prevNote, prevChannelNumber] = prev;
|
|
1344
1376
|
if (prevNote && !prevNote.ending) {
|
|
1345
1377
|
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1346
|
-
startTime, true
|
|
1347
|
-
undefined);
|
|
1378
|
+
startTime, true);
|
|
1348
1379
|
}
|
|
1349
1380
|
}
|
|
1350
1381
|
this.exclusiveClassNotes[exclusiveClass] = [note, channelNumber];
|
|
@@ -1364,8 +1395,7 @@ class MidyGM2 {
|
|
|
1364
1395
|
const prevNote = this.drumExclusiveClassNotes[index];
|
|
1365
1396
|
if (prevNote && !prevNote.ending) {
|
|
1366
1397
|
this.scheduleNoteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1367
|
-
startTime, true
|
|
1368
|
-
undefined);
|
|
1398
|
+
startTime, true);
|
|
1369
1399
|
}
|
|
1370
1400
|
this.drumExclusiveClassNotes[index] = note;
|
|
1371
1401
|
}
|
|
@@ -1376,7 +1406,7 @@ class MidyGM2 {
|
|
|
1376
1406
|
return !((programNumber === 48 && noteNumber === 88) ||
|
|
1377
1407
|
(programNumber === 56 && 47 <= noteNumber && noteNumber <= 84));
|
|
1378
1408
|
}
|
|
1379
|
-
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime,
|
|
1409
|
+
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, noteOffEvent) {
|
|
1380
1410
|
const channel = this.channels[channelNumber];
|
|
1381
1411
|
const bankNumber = this.calcBank(channel, channelNumber);
|
|
1382
1412
|
const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
|
|
@@ -1387,7 +1417,8 @@ class MidyGM2 {
|
|
|
1387
1417
|
if (!voice)
|
|
1388
1418
|
return;
|
|
1389
1419
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
1390
|
-
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime,
|
|
1420
|
+
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
1421
|
+
note.noteOffEvent = noteOffEvent;
|
|
1391
1422
|
note.gainL.connect(channel.gainL);
|
|
1392
1423
|
note.gainR.connect(channel.gainR);
|
|
1393
1424
|
if (0.5 <= channel.state.sustainPedal) {
|
|
@@ -1396,31 +1427,12 @@ class MidyGM2 {
|
|
|
1396
1427
|
this.handleExclusiveClass(note, channelNumber, startTime);
|
|
1397
1428
|
this.handleDrumExclusiveClass(note, channelNumber, startTime);
|
|
1398
1429
|
const scheduledNotes = channel.scheduledNotes;
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
noteList.push(note);
|
|
1402
|
-
}
|
|
1403
|
-
else {
|
|
1404
|
-
noteList = [note];
|
|
1405
|
-
scheduledNotes.set(noteNumber, noteList);
|
|
1406
|
-
}
|
|
1407
|
-
if (this.isDrumNoteOffException(channel, noteNumber)) {
|
|
1408
|
-
const stopTime = startTime + note.bufferSource.buffer.duration;
|
|
1409
|
-
const index = noteList.length - 1;
|
|
1410
|
-
const promise = new Promise((resolve) => {
|
|
1411
|
-
note.bufferSource.onended = () => {
|
|
1412
|
-
noteList[index] = undefined;
|
|
1413
|
-
this.disconnectNote(note);
|
|
1414
|
-
resolve();
|
|
1415
|
-
};
|
|
1416
|
-
note.bufferSource.stop(stopTime);
|
|
1417
|
-
});
|
|
1418
|
-
this.notePromises.push(promise);
|
|
1419
|
-
}
|
|
1430
|
+
note.index = scheduledNotes.length;
|
|
1431
|
+
scheduledNotes.push(note);
|
|
1420
1432
|
}
|
|
1421
1433
|
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1422
1434
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1423
|
-
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime,
|
|
1435
|
+
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, undefined);
|
|
1424
1436
|
}
|
|
1425
1437
|
disconnectNote(note) {
|
|
1426
1438
|
note.bufferSource.disconnect();
|
|
@@ -1445,84 +1457,71 @@ class MidyGM2 {
|
|
|
1445
1457
|
note.chorusEffectsSend.disconnect();
|
|
1446
1458
|
}
|
|
1447
1459
|
}
|
|
1448
|
-
|
|
1449
|
-
const
|
|
1460
|
+
releaseNote(channel, note, endTime) {
|
|
1461
|
+
const volRelease = endTime + note.voiceParams.volRelease;
|
|
1462
|
+
const modRelease = endTime + note.voiceParams.modRelease;
|
|
1463
|
+
const stopTime = Math.min(volRelease, modRelease);
|
|
1464
|
+
note.filterNode.frequency
|
|
1465
|
+
.cancelScheduledValues(endTime)
|
|
1466
|
+
.linearRampToValueAtTime(0, modRelease);
|
|
1450
1467
|
note.volumeEnvelopeNode.gain
|
|
1451
1468
|
.cancelScheduledValues(endTime)
|
|
1452
|
-
.linearRampToValueAtTime(0,
|
|
1453
|
-
note.ending = true;
|
|
1454
|
-
this.scheduleTask(() => {
|
|
1455
|
-
note.bufferSource.loop = false;
|
|
1456
|
-
}, stopTime);
|
|
1469
|
+
.linearRampToValueAtTime(0, volRelease);
|
|
1457
1470
|
return new Promise((resolve) => {
|
|
1458
|
-
|
|
1459
|
-
|
|
1471
|
+
this.scheduleTask(() => {
|
|
1472
|
+
const bufferSource = note.bufferSource;
|
|
1473
|
+
bufferSource.loop = false;
|
|
1474
|
+
bufferSource.stop(stopTime);
|
|
1460
1475
|
this.disconnectNote(note);
|
|
1476
|
+
channel.scheduledNotes[note.index] = undefined;
|
|
1461
1477
|
resolve();
|
|
1462
|
-
};
|
|
1463
|
-
note.bufferSource.stop(stopTime);
|
|
1478
|
+
}, stopTime);
|
|
1464
1479
|
});
|
|
1465
1480
|
}
|
|
1466
|
-
|
|
1467
|
-
for (let i = 0; i < noteList.length; i++) {
|
|
1468
|
-
const note = noteList[i];
|
|
1469
|
-
if (!note)
|
|
1470
|
-
continue;
|
|
1471
|
-
if (note.ending)
|
|
1472
|
-
continue;
|
|
1473
|
-
return [note, i];
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1476
|
-
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force, portamentoNoteNumber) {
|
|
1481
|
+
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
|
|
1477
1482
|
const channel = this.channels[channelNumber];
|
|
1478
|
-
if (this.isDrumNoteOffException(channel, noteNumber))
|
|
1479
|
-
return;
|
|
1480
1483
|
const state = channel.state;
|
|
1481
1484
|
if (!force) {
|
|
1482
|
-
if (
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
return;
|
|
1493
|
-
const [note, i] = noteOffTarget;
|
|
1494
|
-
if (0.5 <= state.portamento && portamentoNoteNumber !== undefined) {
|
|
1495
|
-
const portamentoTime = endTime + this.getPortamentoTime(channel);
|
|
1496
|
-
const deltaNote = portamentoNoteNumber - noteNumber;
|
|
1497
|
-
const baseRate = note.voiceParams.playbackRate;
|
|
1498
|
-
const targetRate = baseRate * Math.pow(2, deltaNote / 12);
|
|
1499
|
-
note.bufferSource.playbackRate
|
|
1500
|
-
.cancelScheduledValues(endTime)
|
|
1501
|
-
.linearRampToValueAtTime(targetRate, portamentoTime);
|
|
1502
|
-
return this.stopNote(endTime, portamentoTime, noteList, i);
|
|
1485
|
+
if (channel.isDrum) {
|
|
1486
|
+
if (!this.isLoopDrum(channel, noteNumber))
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
else {
|
|
1490
|
+
if (0.5 <= state.sustainPedal)
|
|
1491
|
+
return;
|
|
1492
|
+
if (0.5 <= state.sostenutoPedal)
|
|
1493
|
+
return;
|
|
1494
|
+
}
|
|
1503
1495
|
}
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1496
|
+
const note = this.findNoteOffTarget(channel, noteNumber);
|
|
1497
|
+
if (!note)
|
|
1498
|
+
return;
|
|
1499
|
+
note.ending = true;
|
|
1500
|
+
this.releaseNote(channel, note, endTime);
|
|
1501
|
+
}
|
|
1502
|
+
findNoteOffTarget(channel, noteNumber) {
|
|
1503
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
1504
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
1505
|
+
const note = scheduledNotes[i];
|
|
1506
|
+
if (!note)
|
|
1507
|
+
continue;
|
|
1508
|
+
if (note.ending)
|
|
1509
|
+
continue;
|
|
1510
|
+
if (note.noteNumber !== noteNumber)
|
|
1511
|
+
continue;
|
|
1512
|
+
return note;
|
|
1513
1513
|
}
|
|
1514
1514
|
}
|
|
1515
1515
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1516
1516
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1517
|
-
return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false
|
|
1518
|
-
undefined);
|
|
1517
|
+
return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false);
|
|
1519
1518
|
}
|
|
1520
1519
|
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
1521
1520
|
const velocity = halfVelocity * 2;
|
|
1522
1521
|
const channel = this.channels[channelNumber];
|
|
1523
1522
|
const promises = [];
|
|
1524
1523
|
for (let i = 0; i < channel.sustainNotes.length; i++) {
|
|
1525
|
-
const promise = this.
|
|
1524
|
+
const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
|
|
1526
1525
|
promises.push(promise);
|
|
1527
1526
|
}
|
|
1528
1527
|
channel.sustainNotes = [];
|
|
@@ -1532,12 +1531,14 @@ class MidyGM2 {
|
|
|
1532
1531
|
const velocity = halfVelocity * 2;
|
|
1533
1532
|
const channel = this.channels[channelNumber];
|
|
1534
1533
|
const promises = [];
|
|
1534
|
+
const sostenutoNotes = channel.sostenutoNotes;
|
|
1535
1535
|
channel.state.sostenutoPedal = 0;
|
|
1536
|
-
|
|
1537
|
-
const
|
|
1536
|
+
for (let i = 0; i < sostenutoNotes.length; i++) {
|
|
1537
|
+
const note = sostenutoNotes[i];
|
|
1538
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime);
|
|
1538
1539
|
promises.push(promise);
|
|
1539
|
-
}
|
|
1540
|
-
channel.sostenutoNotes
|
|
1540
|
+
}
|
|
1541
|
+
channel.sostenutoNotes = [];
|
|
1541
1542
|
return promises;
|
|
1542
1543
|
}
|
|
1543
1544
|
handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
|
|
@@ -1587,7 +1588,7 @@ class MidyGM2 {
|
|
|
1587
1588
|
channel.detune += pressureDepth * (next - prev);
|
|
1588
1589
|
}
|
|
1589
1590
|
const table = channel.channelPressureTable;
|
|
1590
|
-
this.
|
|
1591
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
1591
1592
|
this.setControllerParameters(channel, note, table);
|
|
1592
1593
|
});
|
|
1593
1594
|
this.applyVoiceParams(channel, 13);
|
|
@@ -1644,10 +1645,13 @@ class MidyGM2 {
|
|
|
1644
1645
|
.setValueAtTime(volumeDepth, scheduleTime);
|
|
1645
1646
|
}
|
|
1646
1647
|
setReverbEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1648
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
|
|
1649
|
+
let value = note.voiceParams.reverbEffectsSend;
|
|
1650
|
+
if (0 <= keyBasedValue) {
|
|
1651
|
+
value *= keyBasedValue / 127 / channel.state.reverbSendLevel;
|
|
1652
|
+
}
|
|
1647
1653
|
if (0 < prevValue) {
|
|
1648
|
-
if (0 <
|
|
1649
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
|
|
1650
|
-
const value = note.voiceParams.reverbEffectsSend + keyBasedValue;
|
|
1654
|
+
if (0 < value) {
|
|
1651
1655
|
note.reverbEffectsSend.gain
|
|
1652
1656
|
.cancelScheduledValues(scheduleTime)
|
|
1653
1657
|
.setValueAtTime(value, scheduleTime);
|
|
@@ -1657,10 +1661,10 @@ class MidyGM2 {
|
|
|
1657
1661
|
}
|
|
1658
1662
|
}
|
|
1659
1663
|
else {
|
|
1660
|
-
if (0 <
|
|
1664
|
+
if (0 < value) {
|
|
1661
1665
|
if (!note.reverbEffectsSend) {
|
|
1662
1666
|
note.reverbEffectsSend = new GainNode(this.audioContext, {
|
|
1663
|
-
gain:
|
|
1667
|
+
gain: value,
|
|
1664
1668
|
});
|
|
1665
1669
|
note.volumeNode.connect(note.reverbEffectsSend);
|
|
1666
1670
|
}
|
|
@@ -1669,10 +1673,13 @@ class MidyGM2 {
|
|
|
1669
1673
|
}
|
|
1670
1674
|
}
|
|
1671
1675
|
setChorusEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1676
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
|
|
1677
|
+
let value = note.voiceParams.chorusEffectsSend;
|
|
1678
|
+
if (0 <= keyBasedValue) {
|
|
1679
|
+
value *= keyBasedValue / 127 / channel.state.chorusSendLevel;
|
|
1680
|
+
}
|
|
1672
1681
|
if (0 < prevValue) {
|
|
1673
|
-
if (0 <
|
|
1674
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
|
|
1675
|
-
const value = note.voiceParams.chorusEffectsSend + keyBasedValue;
|
|
1682
|
+
if (0 < vaule) {
|
|
1676
1683
|
note.chorusEffectsSend.gain
|
|
1677
1684
|
.cancelScheduledValues(scheduleTime)
|
|
1678
1685
|
.setValueAtTime(value, scheduleTime);
|
|
@@ -1682,10 +1689,10 @@ class MidyGM2 {
|
|
|
1682
1689
|
}
|
|
1683
1690
|
}
|
|
1684
1691
|
else {
|
|
1685
|
-
if (0 <
|
|
1692
|
+
if (0 < value) {
|
|
1686
1693
|
if (!note.chorusEffectsSend) {
|
|
1687
1694
|
note.chorusEffectsSend = new GainNode(this.audioContext, {
|
|
1688
|
-
gain:
|
|
1695
|
+
gain: value,
|
|
1689
1696
|
});
|
|
1690
1697
|
note.volumeNode.connect(note.chorusEffectsSend);
|
|
1691
1698
|
}
|
|
@@ -1794,8 +1801,8 @@ class MidyGM2 {
|
|
|
1794
1801
|
if (key in voiceParams)
|
|
1795
1802
|
noteVoiceParams[key] = voiceParams[key];
|
|
1796
1803
|
}
|
|
1797
|
-
if (0.5 <= channel.state.portamento && note.
|
|
1798
|
-
this.
|
|
1804
|
+
if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
1805
|
+
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
1799
1806
|
}
|
|
1800
1807
|
else {
|
|
1801
1808
|
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
@@ -1818,32 +1825,32 @@ class MidyGM2 {
|
|
|
1818
1825
|
});
|
|
1819
1826
|
}
|
|
1820
1827
|
createControlChangeHandlers() {
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1828
|
+
const handlers = new Array(128);
|
|
1829
|
+
handlers[0] = this.setBankMSB;
|
|
1830
|
+
handlers[1] = this.setModulationDepth;
|
|
1831
|
+
handlers[5] = this.setPortamentoTime;
|
|
1832
|
+
handlers[6] = this.dataEntryMSB;
|
|
1833
|
+
handlers[7] = this.setVolume;
|
|
1834
|
+
handlers[10] = this.setPan;
|
|
1835
|
+
handlers[11] = this.setExpression;
|
|
1836
|
+
handlers[32] = this.setBankLSB;
|
|
1837
|
+
handlers[38] = this.dataEntryLSB;
|
|
1838
|
+
handlers[64] = this.setSustainPedal;
|
|
1839
|
+
handlers[65] = this.setPortamento;
|
|
1840
|
+
handlers[66] = this.setSostenutoPedal;
|
|
1841
|
+
handlers[67] = this.setSoftPedal;
|
|
1842
|
+
handlers[91] = this.setReverbSendLevel;
|
|
1843
|
+
handlers[93] = this.setChorusSendLevel;
|
|
1844
|
+
handlers[100] = this.setRPNLSB;
|
|
1845
|
+
handlers[101] = this.setRPNMSB;
|
|
1846
|
+
handlers[120] = this.allSoundOff;
|
|
1847
|
+
handlers[121] = this.resetAllControllers;
|
|
1848
|
+
handlers[123] = this.allNotesOff;
|
|
1849
|
+
handlers[124] = this.omniOff;
|
|
1850
|
+
handlers[125] = this.omniOn;
|
|
1851
|
+
handlers[126] = this.monoOn;
|
|
1852
|
+
handlers[127] = this.polyOn;
|
|
1853
|
+
return handlers;
|
|
1847
1854
|
}
|
|
1848
1855
|
handleControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1849
1856
|
const handler = this.controlChangeHandlers[controllerType];
|
|
@@ -1880,17 +1887,41 @@ class MidyGM2 {
|
|
|
1880
1887
|
channel.state.modulationDepth = modulation / 127;
|
|
1881
1888
|
this.updateModulation(channel, scheduleTime);
|
|
1882
1889
|
}
|
|
1883
|
-
|
|
1890
|
+
updatePortamento(channel, scheduleTime) {
|
|
1891
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1892
|
+
if (0.5 <= channel.state.portamento) {
|
|
1893
|
+
if (0 <= note.portamentoNoteNumber) {
|
|
1894
|
+
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
1895
|
+
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
1896
|
+
this.setPortamentoPitchEnvelope(note, scheduleTime);
|
|
1897
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
else {
|
|
1901
|
+
if (0 <= note.portamentoNoteNumber) {
|
|
1902
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1903
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
1904
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1905
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
});
|
|
1909
|
+
}
|
|
1910
|
+
setPortamentoTime(channelNumber, portamentoTime, scheduleTime) {
|
|
1884
1911
|
const channel = this.channels[channelNumber];
|
|
1912
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1885
1913
|
channel.state.portamentoTime = portamentoTime / 127;
|
|
1914
|
+
if (channel.isDrum)
|
|
1915
|
+
return;
|
|
1916
|
+
this.updatePortamento(channel, scheduleTime);
|
|
1886
1917
|
}
|
|
1887
1918
|
setKeyBasedVolume(channel, scheduleTime) {
|
|
1888
1919
|
this.processScheduledNotes(channel, (note) => {
|
|
1889
1920
|
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1890
|
-
if (
|
|
1921
|
+
if (0 <= keyBasedValue) {
|
|
1891
1922
|
note.volumeNode.gain
|
|
1892
1923
|
.cancelScheduledValues(scheduleTime)
|
|
1893
|
-
.setValueAtTime(
|
|
1924
|
+
.setValueAtTime(keyBasedValue / 127, scheduleTime);
|
|
1894
1925
|
}
|
|
1895
1926
|
});
|
|
1896
1927
|
}
|
|
@@ -1911,8 +1942,8 @@ class MidyGM2 {
|
|
|
1911
1942
|
setKeyBasedPan(channel, scheduleTime) {
|
|
1912
1943
|
this.processScheduledNotes(channel, (note) => {
|
|
1913
1944
|
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1914
|
-
if (
|
|
1915
|
-
const { gainLeft, gainRight } = this.panToGain(
|
|
1945
|
+
if (0 <= keyBasedValue) {
|
|
1946
|
+
const { gainLeft, gainRight } = this.panToGain(keyBasedValue / 127);
|
|
1916
1947
|
note.gainL.gain
|
|
1917
1948
|
.cancelScheduledValues(scheduleTime)
|
|
1918
1949
|
.setValueAtTime(gainLeft, scheduleTime);
|
|
@@ -1968,11 +1999,13 @@ class MidyGM2 {
|
|
|
1968
1999
|
this.releaseSustainPedal(channelNumber, value, scheduleTime);
|
|
1969
2000
|
}
|
|
1970
2001
|
}
|
|
1971
|
-
setPortamento(channelNumber, value) {
|
|
2002
|
+
setPortamento(channelNumber, value, scheduleTime) {
|
|
1972
2003
|
const channel = this.channels[channelNumber];
|
|
1973
2004
|
if (channel.isDrum)
|
|
1974
2005
|
return;
|
|
2006
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1975
2007
|
channel.state.portamento = value / 127;
|
|
2008
|
+
this.updatePortamento(channel, scheduleTime);
|
|
1976
2009
|
}
|
|
1977
2010
|
setSostenutoPedal(channelNumber, value, scheduleTime) {
|
|
1978
2011
|
const channel = this.channels[channelNumber];
|
|
@@ -1981,12 +2014,19 @@ class MidyGM2 {
|
|
|
1981
2014
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1982
2015
|
channel.state.sostenutoPedal = value / 127;
|
|
1983
2016
|
if (64 <= value) {
|
|
1984
|
-
|
|
2017
|
+
const sostenutoNotes = [];
|
|
2018
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
2019
|
+
sostenutoNotes.push(note);
|
|
2020
|
+
});
|
|
2021
|
+
channel.sostenutoNotes = sostenutoNotes;
|
|
1985
2022
|
}
|
|
1986
2023
|
else {
|
|
1987
2024
|
this.releaseSostenutoPedal(channelNumber, value, scheduleTime);
|
|
1988
2025
|
}
|
|
1989
2026
|
}
|
|
2027
|
+
getSoftPedalFactor(channel, note) {
|
|
2028
|
+
return 1 - (0.1 + (note.noteNumber / 127) * 0.2) * channel.state.softPedal;
|
|
2029
|
+
}
|
|
1990
2030
|
setSoftPedal(channelNumber, softPedal, scheduleTime) {
|
|
1991
2031
|
const channel = this.channels[channelNumber];
|
|
1992
2032
|
if (channel.isDrum)
|
|
@@ -1995,9 +2035,9 @@ class MidyGM2 {
|
|
|
1995
2035
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1996
2036
|
state.softPedal = softPedal / 127;
|
|
1997
2037
|
this.processScheduledNotes(channel, (note) => {
|
|
1998
|
-
if (0.5 <= state.portamento && note.
|
|
1999
|
-
this.
|
|
2000
|
-
this.
|
|
2038
|
+
if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
2039
|
+
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2040
|
+
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2001
2041
|
}
|
|
2002
2042
|
else {
|
|
2003
2043
|
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
@@ -2021,7 +2061,8 @@ class MidyGM2 {
|
|
|
2021
2061
|
this.processScheduledNotes(channel, (note) => {
|
|
2022
2062
|
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
2023
2063
|
return false;
|
|
2024
|
-
note.reverbEffectsSend
|
|
2064
|
+
if (note.reverbEffectsSend)
|
|
2065
|
+
note.reverbEffectsSend.disconnect();
|
|
2025
2066
|
});
|
|
2026
2067
|
}
|
|
2027
2068
|
}
|
|
@@ -2053,7 +2094,8 @@ class MidyGM2 {
|
|
|
2053
2094
|
this.processScheduledNotes(channel, (note) => {
|
|
2054
2095
|
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
2055
2096
|
return false;
|
|
2056
|
-
note.chorusEffectsSend
|
|
2097
|
+
if (note.chorusEffectsSend)
|
|
2098
|
+
note.chorusEffectsSend.disconnect();
|
|
2057
2099
|
});
|
|
2058
2100
|
}
|
|
2059
2101
|
}
|
|
@@ -2197,21 +2239,29 @@ class MidyGM2 {
|
|
|
2197
2239
|
return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
|
|
2198
2240
|
}
|
|
2199
2241
|
resetAllStates(channelNumber) {
|
|
2242
|
+
const scheduleTime = this.audioContext.currentTime;
|
|
2200
2243
|
const channel = this.channels[channelNumber];
|
|
2201
2244
|
const state = channel.state;
|
|
2202
|
-
|
|
2203
|
-
|
|
2245
|
+
const entries = Object.entries(defaultControllerState);
|
|
2246
|
+
for (const [key, { type, defaultValue }] of entries) {
|
|
2247
|
+
if (128 <= type) {
|
|
2248
|
+
this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
2249
|
+
}
|
|
2250
|
+
else {
|
|
2251
|
+
state[key] = defaultValue;
|
|
2252
|
+
}
|
|
2204
2253
|
}
|
|
2205
|
-
for (const
|
|
2206
|
-
channel[
|
|
2254
|
+
for (const key of Object.keys(this.constructor.channelSettings)) {
|
|
2255
|
+
channel[key] = this.constructor.channelSettings[key];
|
|
2207
2256
|
}
|
|
2257
|
+
this.resetChannelTable(channel);
|
|
2208
2258
|
this.mode = "GM2";
|
|
2209
2259
|
this.masterFineTuning = 0; // cb
|
|
2210
2260
|
this.masterCoarseTuning = 0; // cb
|
|
2211
2261
|
}
|
|
2212
2262
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
|
|
2213
|
-
resetAllControllers(channelNumber) {
|
|
2214
|
-
const
|
|
2263
|
+
resetAllControllers(channelNumber, _value, scheduleTime) {
|
|
2264
|
+
const keys = [
|
|
2215
2265
|
"channelPressure",
|
|
2216
2266
|
"pitchWheel",
|
|
2217
2267
|
"expression",
|
|
@@ -2223,10 +2273,17 @@ class MidyGM2 {
|
|
|
2223
2273
|
];
|
|
2224
2274
|
const channel = this.channels[channelNumber];
|
|
2225
2275
|
const state = channel.state;
|
|
2226
|
-
for (let i = 0; i <
|
|
2227
|
-
const
|
|
2228
|
-
|
|
2276
|
+
for (let i = 0; i < keys.length; i++) {
|
|
2277
|
+
const key = keys[i];
|
|
2278
|
+
const { type, defaultValue } = defaultControllerState[key];
|
|
2279
|
+
if (128 <= type) {
|
|
2280
|
+
this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
2281
|
+
}
|
|
2282
|
+
else {
|
|
2283
|
+
state[key] = defaultValue;
|
|
2284
|
+
}
|
|
2229
2285
|
}
|
|
2286
|
+
this.setPitchBend(channelNumber, 8192, scheduleTime);
|
|
2230
2287
|
const settingTypes = [
|
|
2231
2288
|
"rpnMSB",
|
|
2232
2289
|
"rpnLSB",
|
|
@@ -2455,7 +2512,7 @@ class MidyGM2 {
|
|
|
2455
2512
|
this.reverbEffect = options.reverbAlgorithm(audioContext);
|
|
2456
2513
|
}
|
|
2457
2514
|
getReverbTime(value) {
|
|
2458
|
-
return Math.
|
|
2515
|
+
return Math.exp((value - 40) * 0.025);
|
|
2459
2516
|
}
|
|
2460
2517
|
// mean free path equation
|
|
2461
2518
|
// https://repository.dl.itc.u-tokyo.ac.jp/record/8550/files/A31912.pdf
|
|
@@ -2612,6 +2669,8 @@ class MidyGM2 {
|
|
|
2612
2669
|
if (!channelBitmap[i])
|
|
2613
2670
|
continue;
|
|
2614
2671
|
const channel = this.channels[i];
|
|
2672
|
+
if (channel.isDrum)
|
|
2673
|
+
continue;
|
|
2615
2674
|
for (let j = 0; j < 12; j++) {
|
|
2616
2675
|
const centValue = data[j + 7] - 64;
|
|
2617
2676
|
channel.scaleOctaveTuningTable[j] = centValue;
|
|
@@ -2648,7 +2707,13 @@ class MidyGM2 {
|
|
|
2648
2707
|
setControllerParameters(channel, note, table) {
|
|
2649
2708
|
if (table[0] !== 64)
|
|
2650
2709
|
this.updateDetune(channel, note);
|
|
2651
|
-
if (
|
|
2710
|
+
if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
|
|
2711
|
+
if (table[1] !== 64)
|
|
2712
|
+
this.setPortamentoFilterEnvelope(channel, note);
|
|
2713
|
+
if (table[2] !== 64)
|
|
2714
|
+
this.setPortamentoVolumeEnvelope(channel, note);
|
|
2715
|
+
}
|
|
2716
|
+
else {
|
|
2652
2717
|
if (table[1] !== 64)
|
|
2653
2718
|
this.setFilterEnvelope(channel, note);
|
|
2654
2719
|
if (table[2] !== 64)
|
|
@@ -2663,7 +2728,10 @@ class MidyGM2 {
|
|
|
2663
2728
|
}
|
|
2664
2729
|
handlePressureSysEx(data, tableName) {
|
|
2665
2730
|
const channelNumber = data[4];
|
|
2666
|
-
const
|
|
2731
|
+
const channel = this.channels[channelNumber];
|
|
2732
|
+
if (channel.isDrum)
|
|
2733
|
+
return;
|
|
2734
|
+
const table = channel[tableName];
|
|
2667
2735
|
for (let i = 5; i < data.length - 1; i += 2) {
|
|
2668
2736
|
const pp = data[i];
|
|
2669
2737
|
const rr = data[i + 1];
|
|
@@ -2673,8 +2741,13 @@ class MidyGM2 {
|
|
|
2673
2741
|
initControlTable() {
|
|
2674
2742
|
const channelCount = 128;
|
|
2675
2743
|
const slotSize = 6;
|
|
2676
|
-
const defaultValues = [64, 64, 64, 0, 0, 0];
|
|
2677
2744
|
const table = new Uint8Array(channelCount * slotSize);
|
|
2745
|
+
return this.resetControlTable(table);
|
|
2746
|
+
}
|
|
2747
|
+
resetControlTable(table) {
|
|
2748
|
+
const channelCount = 128;
|
|
2749
|
+
const slotSize = 6;
|
|
2750
|
+
const defaultValues = [64, 64, 64, 0, 0, 0];
|
|
2678
2751
|
for (let ch = 0; ch < channelCount; ch++) {
|
|
2679
2752
|
const offset = ch * slotSize;
|
|
2680
2753
|
table.set(defaultValues, offset);
|
|
@@ -2691,8 +2764,11 @@ class MidyGM2 {
|
|
|
2691
2764
|
}
|
|
2692
2765
|
handleControlChangeSysEx(data) {
|
|
2693
2766
|
const channelNumber = data[4];
|
|
2767
|
+
const channel = this.channels[channelNumber];
|
|
2768
|
+
if (channel.isDrum)
|
|
2769
|
+
return;
|
|
2694
2770
|
const controllerType = data[5];
|
|
2695
|
-
const table =
|
|
2771
|
+
const table = channel.controlTable[controllerType];
|
|
2696
2772
|
for (let i = 6; i < data.length - 1; i += 2) {
|
|
2697
2773
|
const pp = data[i];
|
|
2698
2774
|
const rr = data[i + 1];
|
|
@@ -2702,17 +2778,20 @@ class MidyGM2 {
|
|
|
2702
2778
|
getKeyBasedInstrumentControlValue(channel, keyNumber, controllerType) {
|
|
2703
2779
|
const index = keyNumber * 128 + controllerType;
|
|
2704
2780
|
const controlValue = channel.keyBasedInstrumentControlTable[index];
|
|
2705
|
-
return
|
|
2781
|
+
return controlValue;
|
|
2706
2782
|
}
|
|
2707
2783
|
handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
|
|
2708
2784
|
const channelNumber = data[4];
|
|
2785
|
+
const channel = this.channels[channelNumber];
|
|
2786
|
+
if (channel.isDrum)
|
|
2787
|
+
return;
|
|
2709
2788
|
const keyNumber = data[5];
|
|
2710
|
-
const table =
|
|
2789
|
+
const table = channel.keyBasedInstrumentControlTable;
|
|
2711
2790
|
for (let i = 6; i < data.length - 1; i += 2) {
|
|
2712
2791
|
const controllerType = data[i];
|
|
2713
2792
|
const value = data[i + 1];
|
|
2714
2793
|
const index = keyNumber * 128 + controllerType;
|
|
2715
|
-
table[index] = value
|
|
2794
|
+
table[index] = value;
|
|
2716
2795
|
}
|
|
2717
2796
|
this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
|
|
2718
2797
|
}
|