@marmooo/midy 0.2.4 → 0.2.6
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 +44 -28
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +159 -101
- package/esm/midy-GM2.d.ts +54 -33
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +260 -156
- package/esm/midy-GMLite.d.ts +38 -19
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +157 -49
- package/esm/midy.d.ts +57 -35
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +312 -183
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +44 -28
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +159 -101
- package/script/midy-GM2.d.ts +54 -33
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +260 -156
- package/script/midy-GMLite.d.ts +38 -19
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +157 -49
- package/script/midy.d.ts +57 -35
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +312 -183
package/script/midy-GM2.js
CHANGED
|
@@ -3,6 +3,58 @@ 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
|
+
}
|
|
6
58
|
class Note {
|
|
7
59
|
constructor(noteNumber, velocity, startTime, voice, voiceParams) {
|
|
8
60
|
Object.defineProperty(this, "bufferSource", {
|
|
@@ -17,31 +69,37 @@ class Note {
|
|
|
17
69
|
writable: true,
|
|
18
70
|
value: void 0
|
|
19
71
|
});
|
|
72
|
+
Object.defineProperty(this, "filterDepth", {
|
|
73
|
+
enumerable: true,
|
|
74
|
+
configurable: true,
|
|
75
|
+
writable: true,
|
|
76
|
+
value: void 0
|
|
77
|
+
});
|
|
20
78
|
Object.defineProperty(this, "volumeEnvelopeNode", {
|
|
21
79
|
enumerable: true,
|
|
22
80
|
configurable: true,
|
|
23
81
|
writable: true,
|
|
24
82
|
value: void 0
|
|
25
83
|
});
|
|
26
|
-
Object.defineProperty(this, "
|
|
84
|
+
Object.defineProperty(this, "volumeDepth", {
|
|
27
85
|
enumerable: true,
|
|
28
86
|
configurable: true,
|
|
29
87
|
writable: true,
|
|
30
88
|
value: void 0
|
|
31
89
|
});
|
|
32
|
-
Object.defineProperty(this, "
|
|
90
|
+
Object.defineProperty(this, "volumeNode", {
|
|
33
91
|
enumerable: true,
|
|
34
92
|
configurable: true,
|
|
35
93
|
writable: true,
|
|
36
94
|
value: void 0
|
|
37
95
|
});
|
|
38
|
-
Object.defineProperty(this, "
|
|
96
|
+
Object.defineProperty(this, "gainL", {
|
|
39
97
|
enumerable: true,
|
|
40
98
|
configurable: true,
|
|
41
99
|
writable: true,
|
|
42
100
|
value: void 0
|
|
43
101
|
});
|
|
44
|
-
Object.defineProperty(this, "
|
|
102
|
+
Object.defineProperty(this, "gainR", {
|
|
45
103
|
enumerable: true,
|
|
46
104
|
configurable: true,
|
|
47
105
|
writable: true,
|
|
@@ -283,6 +341,18 @@ class MidyGM2 {
|
|
|
283
341
|
writable: true,
|
|
284
342
|
value: this.initSoundFontTable()
|
|
285
343
|
});
|
|
344
|
+
Object.defineProperty(this, "audioBufferCounter", {
|
|
345
|
+
enumerable: true,
|
|
346
|
+
configurable: true,
|
|
347
|
+
writable: true,
|
|
348
|
+
value: new Map()
|
|
349
|
+
});
|
|
350
|
+
Object.defineProperty(this, "audioBufferCache", {
|
|
351
|
+
enumerable: true,
|
|
352
|
+
configurable: true,
|
|
353
|
+
writable: true,
|
|
354
|
+
value: new Map()
|
|
355
|
+
});
|
|
286
356
|
Object.defineProperty(this, "isPlaying", {
|
|
287
357
|
enumerable: true,
|
|
288
358
|
configurable: true,
|
|
@@ -335,7 +405,7 @@ class MidyGM2 {
|
|
|
335
405
|
enumerable: true,
|
|
336
406
|
configurable: true,
|
|
337
407
|
writable: true,
|
|
338
|
-
value: new
|
|
408
|
+
value: new SparseMap(128)
|
|
339
409
|
});
|
|
340
410
|
Object.defineProperty(this, "defaultOptions", {
|
|
341
411
|
enumerable: true,
|
|
@@ -375,7 +445,7 @@ class MidyGM2 {
|
|
|
375
445
|
initSoundFontTable() {
|
|
376
446
|
const table = new Array(128);
|
|
377
447
|
for (let i = 0; i < 128; i++) {
|
|
378
|
-
table[i] = new
|
|
448
|
+
table[i] = new SparseMap(128);
|
|
379
449
|
}
|
|
380
450
|
return table;
|
|
381
451
|
}
|
|
@@ -429,8 +499,11 @@ class MidyGM2 {
|
|
|
429
499
|
state: new ControllerState(),
|
|
430
500
|
controlTable: this.initControlTable(),
|
|
431
501
|
...this.setChannelAudioNodes(audioContext),
|
|
432
|
-
scheduledNotes: new
|
|
433
|
-
sostenutoNotes: new
|
|
502
|
+
scheduledNotes: new SparseMap(128),
|
|
503
|
+
sostenutoNotes: new SparseMap(128),
|
|
504
|
+
scaleOctaveTuningTable: new Int8Array(12), // [-64, 63] cent
|
|
505
|
+
channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
506
|
+
keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
|
|
434
507
|
};
|
|
435
508
|
});
|
|
436
509
|
return channels;
|
|
@@ -464,9 +537,8 @@ class MidyGM2 {
|
|
|
464
537
|
return audioBuffer;
|
|
465
538
|
}
|
|
466
539
|
}
|
|
467
|
-
|
|
540
|
+
createNoteBufferNode(audioBuffer, voiceParams) {
|
|
468
541
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
469
|
-
const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
|
|
470
542
|
bufferSource.buffer = audioBuffer;
|
|
471
543
|
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
472
544
|
if (bufferSource.loop) {
|
|
@@ -498,10 +570,11 @@ class MidyGM2 {
|
|
|
498
570
|
const event = this.timeline[queueIndex];
|
|
499
571
|
if (event.startTime > t + this.lookAhead)
|
|
500
572
|
break;
|
|
573
|
+
const startTime = event.startTime + this.startDelay - offset;
|
|
501
574
|
switch (event.type) {
|
|
502
575
|
case "noteOn":
|
|
503
576
|
if (event.velocity !== 0) {
|
|
504
|
-
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity,
|
|
577
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, event.portamento);
|
|
505
578
|
break;
|
|
506
579
|
}
|
|
507
580
|
/* falls through */
|
|
@@ -509,26 +582,26 @@ class MidyGM2 {
|
|
|
509
582
|
const portamentoTarget = this.findPortamentoTarget(queueIndex);
|
|
510
583
|
if (portamentoTarget)
|
|
511
584
|
portamentoTarget.portamento = true;
|
|
512
|
-
const notePromise = this.scheduleNoteRelease(this.omni ? 0 : event.channel, event.noteNumber, event.velocity,
|
|
585
|
+
const notePromise = this.scheduleNoteRelease(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, startTime, portamentoTarget?.noteNumber, false);
|
|
513
586
|
if (notePromise) {
|
|
514
587
|
this.notePromises.push(notePromise);
|
|
515
588
|
}
|
|
516
589
|
break;
|
|
517
590
|
}
|
|
518
591
|
case "controller":
|
|
519
|
-
this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
|
|
592
|
+
this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value, startTime);
|
|
520
593
|
break;
|
|
521
594
|
case "programChange":
|
|
522
|
-
this.handleProgramChange(event.channel, event.programNumber);
|
|
595
|
+
this.handleProgramChange(event.channel, event.programNumber, startTime);
|
|
523
596
|
break;
|
|
524
597
|
case "channelAftertouch":
|
|
525
|
-
this.handleChannelPressure(event.channel, event.amount);
|
|
598
|
+
this.handleChannelPressure(event.channel, event.amount, startTime);
|
|
526
599
|
break;
|
|
527
600
|
case "pitchBend":
|
|
528
|
-
this.setPitchBend(event.channel, event.value + 8192);
|
|
601
|
+
this.setPitchBend(event.channel, event.value + 8192, startTime);
|
|
529
602
|
break;
|
|
530
603
|
case "sysEx":
|
|
531
|
-
this.handleSysEx(event.data);
|
|
604
|
+
this.handleSysEx(event.data, startTime);
|
|
532
605
|
}
|
|
533
606
|
queueIndex++;
|
|
534
607
|
}
|
|
@@ -555,6 +628,7 @@ class MidyGM2 {
|
|
|
555
628
|
await Promise.all(this.notePromises);
|
|
556
629
|
this.notePromises = [];
|
|
557
630
|
this.exclusiveClassMap.clear();
|
|
631
|
+
this.audioBufferCache.clear();
|
|
558
632
|
resolve();
|
|
559
633
|
return;
|
|
560
634
|
}
|
|
@@ -570,8 +644,9 @@ class MidyGM2 {
|
|
|
570
644
|
}
|
|
571
645
|
else if (this.isStopping) {
|
|
572
646
|
await this.stopNotes(0, true);
|
|
573
|
-
this.exclusiveClassMap.clear();
|
|
574
647
|
this.notePromises = [];
|
|
648
|
+
this.exclusiveClassMap.clear();
|
|
649
|
+
this.audioBufferCache.clear();
|
|
575
650
|
resolve();
|
|
576
651
|
this.isStopping = false;
|
|
577
652
|
this.isPaused = false;
|
|
@@ -602,6 +677,9 @@ class MidyGM2 {
|
|
|
602
677
|
secondToTicks(second, secondsPerBeat) {
|
|
603
678
|
return second * this.ticksPerBeat / secondsPerBeat;
|
|
604
679
|
}
|
|
680
|
+
getAudioBufferId(programNumber, noteNumber, velocity) {
|
|
681
|
+
return `${programNumber}:${noteNumber}:${velocity}`;
|
|
682
|
+
}
|
|
605
683
|
extractMidiData(midi) {
|
|
606
684
|
const instruments = new Set();
|
|
607
685
|
const timeline = [];
|
|
@@ -623,6 +701,8 @@ class MidyGM2 {
|
|
|
623
701
|
switch (event.type) {
|
|
624
702
|
case "noteOn": {
|
|
625
703
|
const channel = tmpChannels[event.channel];
|
|
704
|
+
const audioBufferId = this.getAudioBufferId(channel.programNumber, event.noteNumber, event.velocity);
|
|
705
|
+
this.audioBufferCounter.set(audioBufferId, (this.audioBufferCounter.get(audioBufferId) ?? 0) + 1);
|
|
626
706
|
if (channel.programNumber < 0) {
|
|
627
707
|
channel.programNumber = event.programNumber;
|
|
628
708
|
switch (channel.bankMSB) {
|
|
@@ -672,6 +752,10 @@ class MidyGM2 {
|
|
|
672
752
|
timeline.push(event);
|
|
673
753
|
}
|
|
674
754
|
}
|
|
755
|
+
for (const [audioBufferId, count] of this.audioBufferCounter) {
|
|
756
|
+
if (count === 1)
|
|
757
|
+
this.audioBufferCounter.delete(audioBufferId);
|
|
758
|
+
}
|
|
675
759
|
const priority = {
|
|
676
760
|
controller: 0,
|
|
677
761
|
sysEx: 1,
|
|
@@ -764,8 +848,20 @@ class MidyGM2 {
|
|
|
764
848
|
const now = this.audioContext.currentTime;
|
|
765
849
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
766
850
|
}
|
|
851
|
+
processScheduledNotes(channel, scheduleTime, callback) {
|
|
852
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
853
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
854
|
+
const note = noteList[i];
|
|
855
|
+
if (!note)
|
|
856
|
+
continue;
|
|
857
|
+
if (scheduleTime < note.startTime)
|
|
858
|
+
continue;
|
|
859
|
+
callback(note);
|
|
860
|
+
}
|
|
861
|
+
});
|
|
862
|
+
}
|
|
767
863
|
getActiveNotes(channel, time) {
|
|
768
|
-
const activeNotes = new
|
|
864
|
+
const activeNotes = new SparseMap(128);
|
|
769
865
|
channel.scheduledNotes.forEach((noteList) => {
|
|
770
866
|
const activeNote = this.getActiveNote(noteList, time);
|
|
771
867
|
if (activeNote) {
|
|
@@ -945,14 +1041,14 @@ class MidyGM2 {
|
|
|
945
1041
|
const note = noteList[i];
|
|
946
1042
|
if (!note)
|
|
947
1043
|
continue;
|
|
948
|
-
this.updateDetune(channel, note
|
|
1044
|
+
this.updateDetune(channel, note);
|
|
949
1045
|
}
|
|
950
1046
|
});
|
|
951
1047
|
}
|
|
952
|
-
updateDetune(channel, note
|
|
1048
|
+
updateDetune(channel, note) {
|
|
953
1049
|
const now = this.audioContext.currentTime;
|
|
954
1050
|
const noteDetune = this.calcNoteDetune(channel, note);
|
|
955
|
-
const detune = channel.detune + noteDetune
|
|
1051
|
+
const detune = channel.detune + noteDetune;
|
|
956
1052
|
note.bufferSource.detune
|
|
957
1053
|
.cancelScheduledValues(now)
|
|
958
1054
|
.setValueAtTime(detune, now);
|
|
@@ -974,11 +1070,11 @@ class MidyGM2 {
|
|
|
974
1070
|
.setValueAtTime(0, volDelay)
|
|
975
1071
|
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
976
1072
|
}
|
|
977
|
-
setVolumeEnvelope(
|
|
1073
|
+
setVolumeEnvelope(channel, note) {
|
|
978
1074
|
const now = this.audioContext.currentTime;
|
|
979
1075
|
const { voiceParams, startTime } = note;
|
|
980
1076
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
|
|
981
|
-
(1 +
|
|
1077
|
+
(1 + this.getAmplitudeControl(channel));
|
|
982
1078
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
983
1079
|
const volDelay = startTime + voiceParams.volDelay;
|
|
984
1080
|
const volAttack = volDelay + voiceParams.volAttack;
|
|
@@ -992,20 +1088,20 @@ class MidyGM2 {
|
|
|
992
1088
|
.setValueAtTime(attackVolume, volHold)
|
|
993
1089
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
994
1090
|
}
|
|
995
|
-
setPitchEnvelope(note) {
|
|
996
|
-
|
|
1091
|
+
setPitchEnvelope(note, scheduleTime) {
|
|
1092
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
997
1093
|
const { voiceParams } = note;
|
|
998
1094
|
const baseRate = voiceParams.playbackRate;
|
|
999
1095
|
note.bufferSource.playbackRate
|
|
1000
|
-
.cancelScheduledValues(
|
|
1001
|
-
.setValueAtTime(baseRate,
|
|
1096
|
+
.cancelScheduledValues(scheduleTime)
|
|
1097
|
+
.setValueAtTime(baseRate, scheduleTime);
|
|
1002
1098
|
const modEnvToPitch = voiceParams.modEnvToPitch;
|
|
1003
1099
|
if (modEnvToPitch === 0)
|
|
1004
1100
|
return;
|
|
1005
1101
|
const basePitch = this.rateToCent(baseRate);
|
|
1006
1102
|
const peekPitch = basePitch + modEnvToPitch;
|
|
1007
1103
|
const peekRate = this.centToRate(peekPitch);
|
|
1008
|
-
const modDelay = startTime + voiceParams.modDelay;
|
|
1104
|
+
const modDelay = note.startTime + voiceParams.modDelay;
|
|
1009
1105
|
const modAttack = modDelay + voiceParams.modAttack;
|
|
1010
1106
|
const modHold = modAttack + voiceParams.modHold;
|
|
1011
1107
|
const modDecay = modHold + voiceParams.modDecay;
|
|
@@ -1041,13 +1137,14 @@ class MidyGM2 {
|
|
|
1041
1137
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
1042
1138
|
.linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
|
|
1043
1139
|
}
|
|
1044
|
-
setFilterEnvelope(channel, note
|
|
1140
|
+
setFilterEnvelope(channel, note) {
|
|
1045
1141
|
const now = this.audioContext.currentTime;
|
|
1046
1142
|
const state = channel.state;
|
|
1047
1143
|
const { voiceParams, noteNumber, startTime } = note;
|
|
1048
1144
|
const softPedalFactor = 1 -
|
|
1049
1145
|
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1050
|
-
const baseCent = voiceParams.initialFilterFc +
|
|
1146
|
+
const baseCent = voiceParams.initialFilterFc +
|
|
1147
|
+
this.getFilterCutoffControl(channel);
|
|
1051
1148
|
const baseFreq = this.centToHz(baseCent) * softPedalFactor;
|
|
1052
1149
|
const peekFreq = this.centToHz(baseCent + voiceParams.modEnvToFilterFc) *
|
|
1053
1150
|
softPedalFactor;
|
|
@@ -1077,9 +1174,9 @@ class MidyGM2 {
|
|
|
1077
1174
|
gain: voiceParams.modLfoToFilterFc,
|
|
1078
1175
|
});
|
|
1079
1176
|
note.modulationDepth = new GainNode(this.audioContext);
|
|
1080
|
-
this.setModLfoToPitch(channel, note
|
|
1177
|
+
this.setModLfoToPitch(channel, note);
|
|
1081
1178
|
note.volumeDepth = new GainNode(this.audioContext);
|
|
1082
|
-
this.setModLfoToVolume(
|
|
1179
|
+
this.setModLfoToVolume(channel, note);
|
|
1083
1180
|
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
1084
1181
|
note.modulationLFO.connect(note.filterDepth);
|
|
1085
1182
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
@@ -1100,12 +1197,31 @@ class MidyGM2 {
|
|
|
1100
1197
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
1101
1198
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
1102
1199
|
}
|
|
1200
|
+
async getAudioBuffer(program, noteNumber, velocity, voiceParams, isSF3) {
|
|
1201
|
+
const audioBufferId = this.getAudioBufferId(program, noteNumber, velocity);
|
|
1202
|
+
const cache = this.audioBufferCache.get(audioBufferId);
|
|
1203
|
+
if (cache) {
|
|
1204
|
+
cache.counter += 1;
|
|
1205
|
+
if (cache.maxCount <= cache.counter) {
|
|
1206
|
+
this.audioBufferCache.delete(audioBufferId);
|
|
1207
|
+
}
|
|
1208
|
+
return cache.audioBuffer;
|
|
1209
|
+
}
|
|
1210
|
+
else {
|
|
1211
|
+
const maxCount = this.audioBufferCounter.get(audioBufferId) ?? 0;
|
|
1212
|
+
const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
|
|
1213
|
+
const cache = { audioBuffer, maxCount, counter: 1 };
|
|
1214
|
+
this.audioBufferCache.set(audioBufferId, cache);
|
|
1215
|
+
return audioBuffer;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1103
1218
|
async createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3) {
|
|
1104
1219
|
const state = channel.state;
|
|
1105
1220
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1106
1221
|
const voiceParams = voice.getAllParams(controllerState);
|
|
1107
1222
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1108
|
-
|
|
1223
|
+
const audioBuffer = await this.getAudioBuffer(channel.program, noteNumber, velocity, voiceParams, isSF3);
|
|
1224
|
+
note.bufferSource = this.createNoteBufferNode(audioBuffer, voiceParams);
|
|
1109
1225
|
note.volumeNode = new GainNode(this.audioContext);
|
|
1110
1226
|
note.gainL = new GainNode(this.audioContext);
|
|
1111
1227
|
note.gainR = new GainNode(this.audioContext);
|
|
@@ -1121,8 +1237,8 @@ class MidyGM2 {
|
|
|
1121
1237
|
}
|
|
1122
1238
|
else {
|
|
1123
1239
|
note.portamento = false;
|
|
1124
|
-
this.setVolumeEnvelope(
|
|
1125
|
-
this.setFilterEnvelope(channel, note
|
|
1240
|
+
this.setVolumeEnvelope(channel, note);
|
|
1241
|
+
this.setFilterEnvelope(channel, note);
|
|
1126
1242
|
}
|
|
1127
1243
|
if (0 < state.vibratoDepth) {
|
|
1128
1244
|
this.startVibrato(channel, note, startTime);
|
|
@@ -1165,10 +1281,10 @@ class MidyGM2 {
|
|
|
1165
1281
|
if (soundFontIndex === undefined)
|
|
1166
1282
|
return;
|
|
1167
1283
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
1168
|
-
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
1169
1284
|
const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
|
|
1170
1285
|
if (!voice)
|
|
1171
1286
|
return;
|
|
1287
|
+
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
1172
1288
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
|
|
1173
1289
|
note.gainL.connect(channel.gainL);
|
|
1174
1290
|
note.gainR.connect(channel.gainR);
|
|
@@ -1336,7 +1452,9 @@ class MidyGM2 {
|
|
|
1336
1452
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1337
1453
|
channel.program = program;
|
|
1338
1454
|
}
|
|
1339
|
-
handleChannelPressure(channelNumber, value) {
|
|
1455
|
+
handleChannelPressure(channelNumber, value, startTime) {
|
|
1456
|
+
if (!startTime)
|
|
1457
|
+
startTime = this.audioContext.currentTime;
|
|
1340
1458
|
const channel = this.channels[channelNumber];
|
|
1341
1459
|
const prev = channel.state.channelPressure;
|
|
1342
1460
|
const next = value / 127;
|
|
@@ -1346,13 +1464,8 @@ class MidyGM2 {
|
|
|
1346
1464
|
channel.detune += pressureDepth * (next - prev);
|
|
1347
1465
|
}
|
|
1348
1466
|
const table = channel.channelPressureTable;
|
|
1349
|
-
channel.
|
|
1350
|
-
|
|
1351
|
-
const note = noteList[i];
|
|
1352
|
-
if (!note)
|
|
1353
|
-
continue;
|
|
1354
|
-
this.applyDestinationSettings(channel, note, table);
|
|
1355
|
-
}
|
|
1467
|
+
this.getActiveNotes(channel, startTime).forEach((note) => {
|
|
1468
|
+
this.setControllerParameters(channel, note, table);
|
|
1356
1469
|
});
|
|
1357
1470
|
// this.applyVoiceParams(channel, 13);
|
|
1358
1471
|
}
|
|
@@ -1370,9 +1483,10 @@ class MidyGM2 {
|
|
|
1370
1483
|
this.updateChannelDetune(channel);
|
|
1371
1484
|
this.applyVoiceParams(channel, 14);
|
|
1372
1485
|
}
|
|
1373
|
-
setModLfoToPitch(channel, note
|
|
1486
|
+
setModLfoToPitch(channel, note) {
|
|
1374
1487
|
const now = this.audioContext.currentTime;
|
|
1375
|
-
const modLfoToPitch = note.voiceParams.modLfoToPitch +
|
|
1488
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch +
|
|
1489
|
+
this.getLFOPitchDepth(channel);
|
|
1376
1490
|
const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
|
|
1377
1491
|
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
1378
1492
|
note.modulationDepth.gain
|
|
@@ -1389,18 +1503,20 @@ class MidyGM2 {
|
|
|
1389
1503
|
.cancelScheduledValues(now)
|
|
1390
1504
|
.setValueAtTime(vibratoDepth * vibratoDepthSign, now);
|
|
1391
1505
|
}
|
|
1392
|
-
setModLfoToFilterFc(
|
|
1506
|
+
setModLfoToFilterFc(channel, note) {
|
|
1393
1507
|
const now = this.audioContext.currentTime;
|
|
1394
|
-
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc +
|
|
1508
|
+
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc +
|
|
1509
|
+
this.getLFOFilterDepth(channel);
|
|
1395
1510
|
note.filterDepth.gain
|
|
1396
1511
|
.cancelScheduledValues(now)
|
|
1397
1512
|
.setValueAtTime(modLfoToFilterFc, now);
|
|
1398
1513
|
}
|
|
1399
|
-
setModLfoToVolume(
|
|
1514
|
+
setModLfoToVolume(channel, note) {
|
|
1400
1515
|
const now = this.audioContext.currentTime;
|
|
1401
1516
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1402
1517
|
const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1403
|
-
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) *
|
|
1518
|
+
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) *
|
|
1519
|
+
(1 + this.getLFOAmplitudeDepth(channel));
|
|
1404
1520
|
note.volumeDepth.gain
|
|
1405
1521
|
.cancelScheduledValues(now)
|
|
1406
1522
|
.setValueAtTime(volumeDepth, now);
|
|
@@ -1484,7 +1600,7 @@ class MidyGM2 {
|
|
|
1484
1600
|
return {
|
|
1485
1601
|
modLfoToPitch: (channel, note, _prevValue) => {
|
|
1486
1602
|
if (0 < channel.state.modulationDepth) {
|
|
1487
|
-
this.setModLfoToPitch(channel, note
|
|
1603
|
+
this.setModLfoToPitch(channel, note);
|
|
1488
1604
|
}
|
|
1489
1605
|
},
|
|
1490
1606
|
vibLfoToPitch: (channel, note, _prevValue) => {
|
|
@@ -1494,12 +1610,12 @@ class MidyGM2 {
|
|
|
1494
1610
|
},
|
|
1495
1611
|
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1496
1612
|
if (0 < channel.state.modulationDepth) {
|
|
1497
|
-
this.setModLfoToFilterFc(
|
|
1613
|
+
this.setModLfoToFilterFc(channel, note);
|
|
1498
1614
|
}
|
|
1499
1615
|
},
|
|
1500
1616
|
modLfoToVolume: (channel, note, _prevValue) => {
|
|
1501
1617
|
if (0 < channel.state.modulationDepth) {
|
|
1502
|
-
this.setModLfoToVolume(
|
|
1618
|
+
this.setModLfoToVolume(channel, note);
|
|
1503
1619
|
}
|
|
1504
1620
|
},
|
|
1505
1621
|
chorusEffectsSend: (channel, note, prevValue) => {
|
|
@@ -1569,7 +1685,7 @@ class MidyGM2 {
|
|
|
1569
1685
|
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
1570
1686
|
}
|
|
1571
1687
|
else {
|
|
1572
|
-
this.setFilterEnvelope(channel, note
|
|
1688
|
+
this.setFilterEnvelope(channel, note);
|
|
1573
1689
|
}
|
|
1574
1690
|
this.setPitchEnvelope(note);
|
|
1575
1691
|
}
|
|
@@ -1583,7 +1699,7 @@ class MidyGM2 {
|
|
|
1583
1699
|
if (key in voiceParams)
|
|
1584
1700
|
noteVoiceParams[key] = voiceParams[key];
|
|
1585
1701
|
}
|
|
1586
|
-
this.setVolumeEnvelope(
|
|
1702
|
+
this.setVolumeEnvelope(channel, note);
|
|
1587
1703
|
}
|
|
1588
1704
|
}
|
|
1589
1705
|
}
|
|
@@ -1617,10 +1733,10 @@ class MidyGM2 {
|
|
|
1617
1733
|
127: this.polyOn,
|
|
1618
1734
|
};
|
|
1619
1735
|
}
|
|
1620
|
-
handleControlChange(channelNumber, controllerType, value) {
|
|
1736
|
+
handleControlChange(channelNumber, controllerType, value, startTime) {
|
|
1621
1737
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1622
1738
|
if (handler) {
|
|
1623
|
-
handler.call(this, channelNumber, value);
|
|
1739
|
+
handler.call(this, channelNumber, value, startTime);
|
|
1624
1740
|
const channel = this.channels[channelNumber];
|
|
1625
1741
|
this.applyVoiceParams(channel, controllerType + 128);
|
|
1626
1742
|
this.applyControlTable(channel, controllerType);
|
|
@@ -1632,55 +1748,45 @@ class MidyGM2 {
|
|
|
1632
1748
|
setBankMSB(channelNumber, msb) {
|
|
1633
1749
|
this.channels[channelNumber].bankMSB = msb;
|
|
1634
1750
|
}
|
|
1635
|
-
updateModulation(channel) {
|
|
1636
|
-
|
|
1751
|
+
updateModulation(channel, scheduleTime) {
|
|
1752
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1637
1753
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
}
|
|
1646
|
-
else {
|
|
1647
|
-
this.setPitchEnvelope(note);
|
|
1648
|
-
this.startModulation(channel, note, now);
|
|
1649
|
-
}
|
|
1754
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1755
|
+
if (note.modulationDepth) {
|
|
1756
|
+
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1757
|
+
}
|
|
1758
|
+
else {
|
|
1759
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1760
|
+
this.startModulation(channel, note, scheduleTime);
|
|
1650
1761
|
}
|
|
1651
1762
|
});
|
|
1652
1763
|
}
|
|
1653
|
-
setModulationDepth(channelNumber, modulation) {
|
|
1764
|
+
setModulationDepth(channelNumber, modulation, scheduleTime) {
|
|
1654
1765
|
const channel = this.channels[channelNumber];
|
|
1655
1766
|
channel.state.modulationDepth = modulation / 127;
|
|
1656
|
-
this.updateModulation(channel);
|
|
1767
|
+
this.updateModulation(channel, scheduleTime);
|
|
1657
1768
|
}
|
|
1658
1769
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
1659
1770
|
const channel = this.channels[channelNumber];
|
|
1660
1771
|
const factor = 5 * Math.log(10) / 127;
|
|
1661
1772
|
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1662
1773
|
}
|
|
1663
|
-
setKeyBasedVolume(channel) {
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
if (!note)
|
|
1669
|
-
continue;
|
|
1670
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1671
|
-
if (keyBasedValue === 0)
|
|
1672
|
-
continue;
|
|
1774
|
+
setKeyBasedVolume(channel, scheduleTime) {
|
|
1775
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1776
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1777
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1778
|
+
if (keyBasedValue !== 0) {
|
|
1673
1779
|
note.volumeNode.gain
|
|
1674
|
-
.cancelScheduledValues(
|
|
1675
|
-
.setValueAtTime(1 + keyBasedValue,
|
|
1780
|
+
.cancelScheduledValues(scheduleTime)
|
|
1781
|
+
.setValueAtTime(1 + keyBasedValue, scheduleTime);
|
|
1676
1782
|
}
|
|
1677
1783
|
});
|
|
1678
1784
|
}
|
|
1679
|
-
setVolume(channelNumber, volume) {
|
|
1785
|
+
setVolume(channelNumber, volume, scheduleTime) {
|
|
1680
1786
|
const channel = this.channels[channelNumber];
|
|
1681
1787
|
channel.state.volume = volume / 127;
|
|
1682
|
-
this.updateChannelVolume(channel);
|
|
1683
|
-
this.setKeyBasedVolume(channel);
|
|
1788
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1789
|
+
this.setKeyBasedVolume(channel, scheduleTime);
|
|
1684
1790
|
}
|
|
1685
1791
|
panToGain(pan) {
|
|
1686
1792
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1689,36 +1795,31 @@ class MidyGM2 {
|
|
|
1689
1795
|
gainRight: Math.sin(theta),
|
|
1690
1796
|
};
|
|
1691
1797
|
}
|
|
1692
|
-
setKeyBasedPan(channel) {
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
if (!note)
|
|
1698
|
-
continue;
|
|
1699
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1700
|
-
if (keyBasedValue === 0)
|
|
1701
|
-
continue;
|
|
1798
|
+
setKeyBasedPan(channel, scheduleTime) {
|
|
1799
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1800
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1801
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1802
|
+
if (keyBasedValue !== 0) {
|
|
1702
1803
|
const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
|
|
1703
1804
|
note.gainL.gain
|
|
1704
|
-
.cancelScheduledValues(
|
|
1705
|
-
.setValueAtTime(gainLeft,
|
|
1805
|
+
.cancelScheduledValues(scheduleTime)
|
|
1806
|
+
.setValueAtTime(gainLeft, scheduleTime);
|
|
1706
1807
|
note.gainR.gain
|
|
1707
|
-
.cancelScheduledValues(
|
|
1708
|
-
.setValueAtTime(gainRight,
|
|
1808
|
+
.cancelScheduledValues(scheduleTime)
|
|
1809
|
+
.setValueAtTime(gainRight, scheduleTime);
|
|
1709
1810
|
}
|
|
1710
1811
|
});
|
|
1711
1812
|
}
|
|
1712
|
-
setPan(channelNumber, pan) {
|
|
1813
|
+
setPan(channelNumber, pan, scheduleTime) {
|
|
1713
1814
|
const channel = this.channels[channelNumber];
|
|
1714
1815
|
channel.state.pan = pan / 127;
|
|
1715
|
-
this.updateChannelVolume(channel);
|
|
1716
|
-
this.setKeyBasedPan(channel);
|
|
1816
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1817
|
+
this.setKeyBasedPan(channel, scheduleTime);
|
|
1717
1818
|
}
|
|
1718
|
-
setExpression(channelNumber, expression) {
|
|
1819
|
+
setExpression(channelNumber, expression, scheduleTime) {
|
|
1719
1820
|
const channel = this.channels[channelNumber];
|
|
1720
1821
|
channel.state.expression = expression / 127;
|
|
1721
|
-
this.updateChannelVolume(channel);
|
|
1822
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1722
1823
|
}
|
|
1723
1824
|
setBankLSB(channelNumber, lsb) {
|
|
1724
1825
|
this.channels[channelNumber].bankLSB = lsb;
|
|
@@ -1753,8 +1854,7 @@ class MidyGM2 {
|
|
|
1753
1854
|
channel.state.sostenutoPedal = value / 127;
|
|
1754
1855
|
if (64 <= value) {
|
|
1755
1856
|
const now = this.audioContext.currentTime;
|
|
1756
|
-
|
|
1757
|
-
channel.sostenutoNotes = new Map(activeNotes);
|
|
1857
|
+
channel.sostenutoNotes = this.getActiveNotes(channel, now);
|
|
1758
1858
|
}
|
|
1759
1859
|
else {
|
|
1760
1860
|
this.releaseSostenutoPedal(channelNumber, value);
|
|
@@ -2007,7 +2107,7 @@ class MidyGM2 {
|
|
|
2007
2107
|
switch (data[3]) {
|
|
2008
2108
|
case 8:
|
|
2009
2109
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2010
|
-
return this.handleScaleOctaveTuning1ByteFormatSysEx(data);
|
|
2110
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data, false);
|
|
2011
2111
|
default:
|
|
2012
2112
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2013
2113
|
}
|
|
@@ -2329,8 +2429,8 @@ class MidyGM2 {
|
|
|
2329
2429
|
}
|
|
2330
2430
|
return bitmap;
|
|
2331
2431
|
}
|
|
2332
|
-
handleScaleOctaveTuning1ByteFormatSysEx(data) {
|
|
2333
|
-
if (data.length <
|
|
2432
|
+
handleScaleOctaveTuning1ByteFormatSysEx(data, realtime) {
|
|
2433
|
+
if (data.length < 19) {
|
|
2334
2434
|
console.error("Data length is too short");
|
|
2335
2435
|
return;
|
|
2336
2436
|
}
|
|
@@ -2338,48 +2438,55 @@ class MidyGM2 {
|
|
|
2338
2438
|
for (let i = 0; i < channelBitmap.length; i++) {
|
|
2339
2439
|
if (!channelBitmap[i])
|
|
2340
2440
|
continue;
|
|
2441
|
+
const channel = this.channels[i];
|
|
2341
2442
|
for (let j = 0; j < 12; j++) {
|
|
2342
|
-
const
|
|
2343
|
-
|
|
2443
|
+
const centValue = data[j + 7] - 64;
|
|
2444
|
+
channel.scaleOctaveTuningTable[j] = centValue;
|
|
2344
2445
|
}
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2446
|
+
if (realtime)
|
|
2447
|
+
this.updateChannelDetune(channel);
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
getFilterCutoffControl(channel) {
|
|
2451
|
+
const channelPressure = (channel.channelPressureTable[1] - 64) *
|
|
2452
|
+
channel.state.channelPressure;
|
|
2453
|
+
return channelPressure * 15;
|
|
2454
|
+
}
|
|
2455
|
+
getAmplitudeControl(channel) {
|
|
2456
|
+
const channelPressure = channel.channelPressureTable[2] *
|
|
2457
|
+
channel.state.channelPressure;
|
|
2458
|
+
return channelPressure / 64;
|
|
2459
|
+
}
|
|
2460
|
+
getLFOPitchDepth(channel) {
|
|
2461
|
+
const channelPressure = channel.channelPressureTable[3] *
|
|
2462
|
+
channel.state.channelPressure;
|
|
2463
|
+
return channelPressure / 127 * 600;
|
|
2464
|
+
}
|
|
2465
|
+
getLFOFilterDepth(channel) {
|
|
2466
|
+
const channelPressure = channel.channelPressureTable[4] *
|
|
2467
|
+
channel.state.channelPressure;
|
|
2468
|
+
return channelPressure / 127 * 2400;
|
|
2469
|
+
}
|
|
2470
|
+
getLFOAmplitudeDepth(channel) {
|
|
2471
|
+
const channelPressure = channel.channelPressureTable[5] *
|
|
2472
|
+
channel.state.channelPressure;
|
|
2473
|
+
return channelPressure / 127;
|
|
2474
|
+
}
|
|
2475
|
+
setControllerParameters(channel, note, table) {
|
|
2476
|
+
if (table[0] !== 64)
|
|
2477
|
+
this.updateDetune(channel, note);
|
|
2351
2478
|
if (!note.portamento) {
|
|
2352
|
-
if (table[1] !== 64)
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
}
|
|
2364
|
-
}
|
|
2365
|
-
if (table[3] !== 0) {
|
|
2366
|
-
const channelPressure = channel.channelPressureTable[3] *
|
|
2367
|
-
channel.state.channelPressure;
|
|
2368
|
-
const pressure = channelPressure / 127 * 600;
|
|
2369
|
-
this.setModLfoToPitch(channel, note, pressure);
|
|
2370
|
-
}
|
|
2371
|
-
if (table[4] !== 0) {
|
|
2372
|
-
const channelPressure = channel.channelPressureTable[4] *
|
|
2373
|
-
channel.state.channelPressure;
|
|
2374
|
-
const pressure = channelPressure / 127 * 2400;
|
|
2375
|
-
this.setModLfoToFilterFc(note, pressure);
|
|
2376
|
-
}
|
|
2377
|
-
if (table[5] !== 0) {
|
|
2378
|
-
const channelPressure = channel.channelPressureTable[5] *
|
|
2379
|
-
channel.state.channelPressure;
|
|
2380
|
-
const pressure = channelPressure / 127;
|
|
2381
|
-
this.setModLfoToVolume(note, pressure);
|
|
2382
|
-
}
|
|
2479
|
+
if (table[1] !== 64)
|
|
2480
|
+
this.setFilterEnvelope(channel, note);
|
|
2481
|
+
if (table[2] !== 64)
|
|
2482
|
+
this.setVolumeEnvelope(channel, note);
|
|
2483
|
+
}
|
|
2484
|
+
if (table[3] !== 0)
|
|
2485
|
+
this.setModLfoToPitch(channel, note);
|
|
2486
|
+
if (table[4] !== 0)
|
|
2487
|
+
this.setModLfoToFilterFc(channel, note);
|
|
2488
|
+
if (table[5] !== 0)
|
|
2489
|
+
this.setModLfoToVolume(channel, note);
|
|
2383
2490
|
}
|
|
2384
2491
|
handleChannelPressureSysEx(data, tableName) {
|
|
2385
2492
|
const channelNumber = data[4];
|
|
@@ -2410,7 +2517,7 @@ class MidyGM2 {
|
|
|
2410
2517
|
const note = noteList[i];
|
|
2411
2518
|
if (!note)
|
|
2412
2519
|
continue;
|
|
2413
|
-
this.
|
|
2520
|
+
this.setControllerParameters(channel, note, table);
|
|
2414
2521
|
}
|
|
2415
2522
|
});
|
|
2416
2523
|
}
|
|
@@ -2474,9 +2581,6 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
2474
2581
|
value: {
|
|
2475
2582
|
currentBufferSource: null,
|
|
2476
2583
|
detune: 0,
|
|
2477
|
-
scaleOctaveTuningTable: new Array(12).fill(0), // cent
|
|
2478
|
-
channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
2479
|
-
keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
|
|
2480
2584
|
program: 0,
|
|
2481
2585
|
bank: 121 * 128,
|
|
2482
2586
|
bankMSB: 121,
|