@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/esm/midy-GM2.js
CHANGED
|
@@ -1,5 +1,57 @@
|
|
|
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
|
+
}
|
|
3
55
|
class Note {
|
|
4
56
|
constructor(noteNumber, velocity, startTime, voice, voiceParams) {
|
|
5
57
|
Object.defineProperty(this, "bufferSource", {
|
|
@@ -14,31 +66,37 @@ class Note {
|
|
|
14
66
|
writable: true,
|
|
15
67
|
value: void 0
|
|
16
68
|
});
|
|
69
|
+
Object.defineProperty(this, "filterDepth", {
|
|
70
|
+
enumerable: true,
|
|
71
|
+
configurable: true,
|
|
72
|
+
writable: true,
|
|
73
|
+
value: void 0
|
|
74
|
+
});
|
|
17
75
|
Object.defineProperty(this, "volumeEnvelopeNode", {
|
|
18
76
|
enumerable: true,
|
|
19
77
|
configurable: true,
|
|
20
78
|
writable: true,
|
|
21
79
|
value: void 0
|
|
22
80
|
});
|
|
23
|
-
Object.defineProperty(this, "
|
|
81
|
+
Object.defineProperty(this, "volumeDepth", {
|
|
24
82
|
enumerable: true,
|
|
25
83
|
configurable: true,
|
|
26
84
|
writable: true,
|
|
27
85
|
value: void 0
|
|
28
86
|
});
|
|
29
|
-
Object.defineProperty(this, "
|
|
87
|
+
Object.defineProperty(this, "volumeNode", {
|
|
30
88
|
enumerable: true,
|
|
31
89
|
configurable: true,
|
|
32
90
|
writable: true,
|
|
33
91
|
value: void 0
|
|
34
92
|
});
|
|
35
|
-
Object.defineProperty(this, "
|
|
93
|
+
Object.defineProperty(this, "gainL", {
|
|
36
94
|
enumerable: true,
|
|
37
95
|
configurable: true,
|
|
38
96
|
writable: true,
|
|
39
97
|
value: void 0
|
|
40
98
|
});
|
|
41
|
-
Object.defineProperty(this, "
|
|
99
|
+
Object.defineProperty(this, "gainR", {
|
|
42
100
|
enumerable: true,
|
|
43
101
|
configurable: true,
|
|
44
102
|
writable: true,
|
|
@@ -280,6 +338,18 @@ export class MidyGM2 {
|
|
|
280
338
|
writable: true,
|
|
281
339
|
value: this.initSoundFontTable()
|
|
282
340
|
});
|
|
341
|
+
Object.defineProperty(this, "audioBufferCounter", {
|
|
342
|
+
enumerable: true,
|
|
343
|
+
configurable: true,
|
|
344
|
+
writable: true,
|
|
345
|
+
value: new Map()
|
|
346
|
+
});
|
|
347
|
+
Object.defineProperty(this, "audioBufferCache", {
|
|
348
|
+
enumerable: true,
|
|
349
|
+
configurable: true,
|
|
350
|
+
writable: true,
|
|
351
|
+
value: new Map()
|
|
352
|
+
});
|
|
283
353
|
Object.defineProperty(this, "isPlaying", {
|
|
284
354
|
enumerable: true,
|
|
285
355
|
configurable: true,
|
|
@@ -332,7 +402,7 @@ export class MidyGM2 {
|
|
|
332
402
|
enumerable: true,
|
|
333
403
|
configurable: true,
|
|
334
404
|
writable: true,
|
|
335
|
-
value: new
|
|
405
|
+
value: new SparseMap(128)
|
|
336
406
|
});
|
|
337
407
|
Object.defineProperty(this, "defaultOptions", {
|
|
338
408
|
enumerable: true,
|
|
@@ -372,7 +442,7 @@ export class MidyGM2 {
|
|
|
372
442
|
initSoundFontTable() {
|
|
373
443
|
const table = new Array(128);
|
|
374
444
|
for (let i = 0; i < 128; i++) {
|
|
375
|
-
table[i] = new
|
|
445
|
+
table[i] = new SparseMap(128);
|
|
376
446
|
}
|
|
377
447
|
return table;
|
|
378
448
|
}
|
|
@@ -426,8 +496,11 @@ export class MidyGM2 {
|
|
|
426
496
|
state: new ControllerState(),
|
|
427
497
|
controlTable: this.initControlTable(),
|
|
428
498
|
...this.setChannelAudioNodes(audioContext),
|
|
429
|
-
scheduledNotes: new
|
|
430
|
-
sostenutoNotes: new
|
|
499
|
+
scheduledNotes: new SparseMap(128),
|
|
500
|
+
sostenutoNotes: new SparseMap(128),
|
|
501
|
+
scaleOctaveTuningTable: new Int8Array(12), // [-64, 63] cent
|
|
502
|
+
channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
503
|
+
keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
|
|
431
504
|
};
|
|
432
505
|
});
|
|
433
506
|
return channels;
|
|
@@ -461,9 +534,8 @@ export class MidyGM2 {
|
|
|
461
534
|
return audioBuffer;
|
|
462
535
|
}
|
|
463
536
|
}
|
|
464
|
-
|
|
537
|
+
createNoteBufferNode(audioBuffer, voiceParams) {
|
|
465
538
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
466
|
-
const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
|
|
467
539
|
bufferSource.buffer = audioBuffer;
|
|
468
540
|
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
469
541
|
if (bufferSource.loop) {
|
|
@@ -495,10 +567,11 @@ export class MidyGM2 {
|
|
|
495
567
|
const event = this.timeline[queueIndex];
|
|
496
568
|
if (event.startTime > t + this.lookAhead)
|
|
497
569
|
break;
|
|
570
|
+
const startTime = event.startTime + this.startDelay - offset;
|
|
498
571
|
switch (event.type) {
|
|
499
572
|
case "noteOn":
|
|
500
573
|
if (event.velocity !== 0) {
|
|
501
|
-
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity,
|
|
574
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, event.portamento);
|
|
502
575
|
break;
|
|
503
576
|
}
|
|
504
577
|
/* falls through */
|
|
@@ -506,26 +579,26 @@ export class MidyGM2 {
|
|
|
506
579
|
const portamentoTarget = this.findPortamentoTarget(queueIndex);
|
|
507
580
|
if (portamentoTarget)
|
|
508
581
|
portamentoTarget.portamento = true;
|
|
509
|
-
const notePromise = this.scheduleNoteRelease(this.omni ? 0 : event.channel, event.noteNumber, event.velocity,
|
|
582
|
+
const notePromise = this.scheduleNoteRelease(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, startTime, portamentoTarget?.noteNumber, false);
|
|
510
583
|
if (notePromise) {
|
|
511
584
|
this.notePromises.push(notePromise);
|
|
512
585
|
}
|
|
513
586
|
break;
|
|
514
587
|
}
|
|
515
588
|
case "controller":
|
|
516
|
-
this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
|
|
589
|
+
this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value, startTime);
|
|
517
590
|
break;
|
|
518
591
|
case "programChange":
|
|
519
|
-
this.handleProgramChange(event.channel, event.programNumber);
|
|
592
|
+
this.handleProgramChange(event.channel, event.programNumber, startTime);
|
|
520
593
|
break;
|
|
521
594
|
case "channelAftertouch":
|
|
522
|
-
this.handleChannelPressure(event.channel, event.amount);
|
|
595
|
+
this.handleChannelPressure(event.channel, event.amount, startTime);
|
|
523
596
|
break;
|
|
524
597
|
case "pitchBend":
|
|
525
|
-
this.setPitchBend(event.channel, event.value + 8192);
|
|
598
|
+
this.setPitchBend(event.channel, event.value + 8192, startTime);
|
|
526
599
|
break;
|
|
527
600
|
case "sysEx":
|
|
528
|
-
this.handleSysEx(event.data);
|
|
601
|
+
this.handleSysEx(event.data, startTime);
|
|
529
602
|
}
|
|
530
603
|
queueIndex++;
|
|
531
604
|
}
|
|
@@ -552,6 +625,7 @@ export class MidyGM2 {
|
|
|
552
625
|
await Promise.all(this.notePromises);
|
|
553
626
|
this.notePromises = [];
|
|
554
627
|
this.exclusiveClassMap.clear();
|
|
628
|
+
this.audioBufferCache.clear();
|
|
555
629
|
resolve();
|
|
556
630
|
return;
|
|
557
631
|
}
|
|
@@ -567,8 +641,9 @@ export class MidyGM2 {
|
|
|
567
641
|
}
|
|
568
642
|
else if (this.isStopping) {
|
|
569
643
|
await this.stopNotes(0, true);
|
|
570
|
-
this.exclusiveClassMap.clear();
|
|
571
644
|
this.notePromises = [];
|
|
645
|
+
this.exclusiveClassMap.clear();
|
|
646
|
+
this.audioBufferCache.clear();
|
|
572
647
|
resolve();
|
|
573
648
|
this.isStopping = false;
|
|
574
649
|
this.isPaused = false;
|
|
@@ -599,6 +674,9 @@ export class MidyGM2 {
|
|
|
599
674
|
secondToTicks(second, secondsPerBeat) {
|
|
600
675
|
return second * this.ticksPerBeat / secondsPerBeat;
|
|
601
676
|
}
|
|
677
|
+
getAudioBufferId(programNumber, noteNumber, velocity) {
|
|
678
|
+
return `${programNumber}:${noteNumber}:${velocity}`;
|
|
679
|
+
}
|
|
602
680
|
extractMidiData(midi) {
|
|
603
681
|
const instruments = new Set();
|
|
604
682
|
const timeline = [];
|
|
@@ -620,6 +698,8 @@ export class MidyGM2 {
|
|
|
620
698
|
switch (event.type) {
|
|
621
699
|
case "noteOn": {
|
|
622
700
|
const channel = tmpChannels[event.channel];
|
|
701
|
+
const audioBufferId = this.getAudioBufferId(channel.programNumber, event.noteNumber, event.velocity);
|
|
702
|
+
this.audioBufferCounter.set(audioBufferId, (this.audioBufferCounter.get(audioBufferId) ?? 0) + 1);
|
|
623
703
|
if (channel.programNumber < 0) {
|
|
624
704
|
channel.programNumber = event.programNumber;
|
|
625
705
|
switch (channel.bankMSB) {
|
|
@@ -669,6 +749,10 @@ export class MidyGM2 {
|
|
|
669
749
|
timeline.push(event);
|
|
670
750
|
}
|
|
671
751
|
}
|
|
752
|
+
for (const [audioBufferId, count] of this.audioBufferCounter) {
|
|
753
|
+
if (count === 1)
|
|
754
|
+
this.audioBufferCounter.delete(audioBufferId);
|
|
755
|
+
}
|
|
672
756
|
const priority = {
|
|
673
757
|
controller: 0,
|
|
674
758
|
sysEx: 1,
|
|
@@ -761,8 +845,20 @@ export class MidyGM2 {
|
|
|
761
845
|
const now = this.audioContext.currentTime;
|
|
762
846
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
763
847
|
}
|
|
848
|
+
processScheduledNotes(channel, scheduleTime, callback) {
|
|
849
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
850
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
851
|
+
const note = noteList[i];
|
|
852
|
+
if (!note)
|
|
853
|
+
continue;
|
|
854
|
+
if (scheduleTime < note.startTime)
|
|
855
|
+
continue;
|
|
856
|
+
callback(note);
|
|
857
|
+
}
|
|
858
|
+
});
|
|
859
|
+
}
|
|
764
860
|
getActiveNotes(channel, time) {
|
|
765
|
-
const activeNotes = new
|
|
861
|
+
const activeNotes = new SparseMap(128);
|
|
766
862
|
channel.scheduledNotes.forEach((noteList) => {
|
|
767
863
|
const activeNote = this.getActiveNote(noteList, time);
|
|
768
864
|
if (activeNote) {
|
|
@@ -942,14 +1038,14 @@ export class MidyGM2 {
|
|
|
942
1038
|
const note = noteList[i];
|
|
943
1039
|
if (!note)
|
|
944
1040
|
continue;
|
|
945
|
-
this.updateDetune(channel, note
|
|
1041
|
+
this.updateDetune(channel, note);
|
|
946
1042
|
}
|
|
947
1043
|
});
|
|
948
1044
|
}
|
|
949
|
-
updateDetune(channel, note
|
|
1045
|
+
updateDetune(channel, note) {
|
|
950
1046
|
const now = this.audioContext.currentTime;
|
|
951
1047
|
const noteDetune = this.calcNoteDetune(channel, note);
|
|
952
|
-
const detune = channel.detune + noteDetune
|
|
1048
|
+
const detune = channel.detune + noteDetune;
|
|
953
1049
|
note.bufferSource.detune
|
|
954
1050
|
.cancelScheduledValues(now)
|
|
955
1051
|
.setValueAtTime(detune, now);
|
|
@@ -971,11 +1067,11 @@ export class MidyGM2 {
|
|
|
971
1067
|
.setValueAtTime(0, volDelay)
|
|
972
1068
|
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
973
1069
|
}
|
|
974
|
-
setVolumeEnvelope(
|
|
1070
|
+
setVolumeEnvelope(channel, note) {
|
|
975
1071
|
const now = this.audioContext.currentTime;
|
|
976
1072
|
const { voiceParams, startTime } = note;
|
|
977
1073
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
|
|
978
|
-
(1 +
|
|
1074
|
+
(1 + this.getAmplitudeControl(channel));
|
|
979
1075
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
980
1076
|
const volDelay = startTime + voiceParams.volDelay;
|
|
981
1077
|
const volAttack = volDelay + voiceParams.volAttack;
|
|
@@ -989,20 +1085,20 @@ export class MidyGM2 {
|
|
|
989
1085
|
.setValueAtTime(attackVolume, volHold)
|
|
990
1086
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
991
1087
|
}
|
|
992
|
-
setPitchEnvelope(note) {
|
|
993
|
-
|
|
1088
|
+
setPitchEnvelope(note, scheduleTime) {
|
|
1089
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
994
1090
|
const { voiceParams } = note;
|
|
995
1091
|
const baseRate = voiceParams.playbackRate;
|
|
996
1092
|
note.bufferSource.playbackRate
|
|
997
|
-
.cancelScheduledValues(
|
|
998
|
-
.setValueAtTime(baseRate,
|
|
1093
|
+
.cancelScheduledValues(scheduleTime)
|
|
1094
|
+
.setValueAtTime(baseRate, scheduleTime);
|
|
999
1095
|
const modEnvToPitch = voiceParams.modEnvToPitch;
|
|
1000
1096
|
if (modEnvToPitch === 0)
|
|
1001
1097
|
return;
|
|
1002
1098
|
const basePitch = this.rateToCent(baseRate);
|
|
1003
1099
|
const peekPitch = basePitch + modEnvToPitch;
|
|
1004
1100
|
const peekRate = this.centToRate(peekPitch);
|
|
1005
|
-
const modDelay = startTime + voiceParams.modDelay;
|
|
1101
|
+
const modDelay = note.startTime + voiceParams.modDelay;
|
|
1006
1102
|
const modAttack = modDelay + voiceParams.modAttack;
|
|
1007
1103
|
const modHold = modAttack + voiceParams.modHold;
|
|
1008
1104
|
const modDecay = modHold + voiceParams.modDecay;
|
|
@@ -1038,13 +1134,14 @@ export class MidyGM2 {
|
|
|
1038
1134
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
1039
1135
|
.linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
|
|
1040
1136
|
}
|
|
1041
|
-
setFilterEnvelope(channel, note
|
|
1137
|
+
setFilterEnvelope(channel, note) {
|
|
1042
1138
|
const now = this.audioContext.currentTime;
|
|
1043
1139
|
const state = channel.state;
|
|
1044
1140
|
const { voiceParams, noteNumber, startTime } = note;
|
|
1045
1141
|
const softPedalFactor = 1 -
|
|
1046
1142
|
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1047
|
-
const baseCent = voiceParams.initialFilterFc +
|
|
1143
|
+
const baseCent = voiceParams.initialFilterFc +
|
|
1144
|
+
this.getFilterCutoffControl(channel);
|
|
1048
1145
|
const baseFreq = this.centToHz(baseCent) * softPedalFactor;
|
|
1049
1146
|
const peekFreq = this.centToHz(baseCent + voiceParams.modEnvToFilterFc) *
|
|
1050
1147
|
softPedalFactor;
|
|
@@ -1074,9 +1171,9 @@ export class MidyGM2 {
|
|
|
1074
1171
|
gain: voiceParams.modLfoToFilterFc,
|
|
1075
1172
|
});
|
|
1076
1173
|
note.modulationDepth = new GainNode(this.audioContext);
|
|
1077
|
-
this.setModLfoToPitch(channel, note
|
|
1174
|
+
this.setModLfoToPitch(channel, note);
|
|
1078
1175
|
note.volumeDepth = new GainNode(this.audioContext);
|
|
1079
|
-
this.setModLfoToVolume(
|
|
1176
|
+
this.setModLfoToVolume(channel, note);
|
|
1080
1177
|
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
1081
1178
|
note.modulationLFO.connect(note.filterDepth);
|
|
1082
1179
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
@@ -1097,12 +1194,31 @@ export class MidyGM2 {
|
|
|
1097
1194
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
1098
1195
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
1099
1196
|
}
|
|
1197
|
+
async getAudioBuffer(program, noteNumber, velocity, voiceParams, isSF3) {
|
|
1198
|
+
const audioBufferId = this.getAudioBufferId(program, noteNumber, velocity);
|
|
1199
|
+
const cache = this.audioBufferCache.get(audioBufferId);
|
|
1200
|
+
if (cache) {
|
|
1201
|
+
cache.counter += 1;
|
|
1202
|
+
if (cache.maxCount <= cache.counter) {
|
|
1203
|
+
this.audioBufferCache.delete(audioBufferId);
|
|
1204
|
+
}
|
|
1205
|
+
return cache.audioBuffer;
|
|
1206
|
+
}
|
|
1207
|
+
else {
|
|
1208
|
+
const maxCount = this.audioBufferCounter.get(audioBufferId) ?? 0;
|
|
1209
|
+
const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
|
|
1210
|
+
const cache = { audioBuffer, maxCount, counter: 1 };
|
|
1211
|
+
this.audioBufferCache.set(audioBufferId, cache);
|
|
1212
|
+
return audioBuffer;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1100
1215
|
async createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3) {
|
|
1101
1216
|
const state = channel.state;
|
|
1102
1217
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1103
1218
|
const voiceParams = voice.getAllParams(controllerState);
|
|
1104
1219
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1105
|
-
|
|
1220
|
+
const audioBuffer = await this.getAudioBuffer(channel.program, noteNumber, velocity, voiceParams, isSF3);
|
|
1221
|
+
note.bufferSource = this.createNoteBufferNode(audioBuffer, voiceParams);
|
|
1106
1222
|
note.volumeNode = new GainNode(this.audioContext);
|
|
1107
1223
|
note.gainL = new GainNode(this.audioContext);
|
|
1108
1224
|
note.gainR = new GainNode(this.audioContext);
|
|
@@ -1118,8 +1234,8 @@ export class MidyGM2 {
|
|
|
1118
1234
|
}
|
|
1119
1235
|
else {
|
|
1120
1236
|
note.portamento = false;
|
|
1121
|
-
this.setVolumeEnvelope(
|
|
1122
|
-
this.setFilterEnvelope(channel, note
|
|
1237
|
+
this.setVolumeEnvelope(channel, note);
|
|
1238
|
+
this.setFilterEnvelope(channel, note);
|
|
1123
1239
|
}
|
|
1124
1240
|
if (0 < state.vibratoDepth) {
|
|
1125
1241
|
this.startVibrato(channel, note, startTime);
|
|
@@ -1162,10 +1278,10 @@ export class MidyGM2 {
|
|
|
1162
1278
|
if (soundFontIndex === undefined)
|
|
1163
1279
|
return;
|
|
1164
1280
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
1165
|
-
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
1166
1281
|
const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
|
|
1167
1282
|
if (!voice)
|
|
1168
1283
|
return;
|
|
1284
|
+
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
1169
1285
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
|
|
1170
1286
|
note.gainL.connect(channel.gainL);
|
|
1171
1287
|
note.gainR.connect(channel.gainR);
|
|
@@ -1333,7 +1449,9 @@ export class MidyGM2 {
|
|
|
1333
1449
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1334
1450
|
channel.program = program;
|
|
1335
1451
|
}
|
|
1336
|
-
handleChannelPressure(channelNumber, value) {
|
|
1452
|
+
handleChannelPressure(channelNumber, value, startTime) {
|
|
1453
|
+
if (!startTime)
|
|
1454
|
+
startTime = this.audioContext.currentTime;
|
|
1337
1455
|
const channel = this.channels[channelNumber];
|
|
1338
1456
|
const prev = channel.state.channelPressure;
|
|
1339
1457
|
const next = value / 127;
|
|
@@ -1343,13 +1461,8 @@ export class MidyGM2 {
|
|
|
1343
1461
|
channel.detune += pressureDepth * (next - prev);
|
|
1344
1462
|
}
|
|
1345
1463
|
const table = channel.channelPressureTable;
|
|
1346
|
-
channel.
|
|
1347
|
-
|
|
1348
|
-
const note = noteList[i];
|
|
1349
|
-
if (!note)
|
|
1350
|
-
continue;
|
|
1351
|
-
this.applyDestinationSettings(channel, note, table);
|
|
1352
|
-
}
|
|
1464
|
+
this.getActiveNotes(channel, startTime).forEach((note) => {
|
|
1465
|
+
this.setControllerParameters(channel, note, table);
|
|
1353
1466
|
});
|
|
1354
1467
|
// this.applyVoiceParams(channel, 13);
|
|
1355
1468
|
}
|
|
@@ -1367,9 +1480,10 @@ export class MidyGM2 {
|
|
|
1367
1480
|
this.updateChannelDetune(channel);
|
|
1368
1481
|
this.applyVoiceParams(channel, 14);
|
|
1369
1482
|
}
|
|
1370
|
-
setModLfoToPitch(channel, note
|
|
1483
|
+
setModLfoToPitch(channel, note) {
|
|
1371
1484
|
const now = this.audioContext.currentTime;
|
|
1372
|
-
const modLfoToPitch = note.voiceParams.modLfoToPitch +
|
|
1485
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch +
|
|
1486
|
+
this.getLFOPitchDepth(channel);
|
|
1373
1487
|
const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
|
|
1374
1488
|
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
1375
1489
|
note.modulationDepth.gain
|
|
@@ -1386,18 +1500,20 @@ export class MidyGM2 {
|
|
|
1386
1500
|
.cancelScheduledValues(now)
|
|
1387
1501
|
.setValueAtTime(vibratoDepth * vibratoDepthSign, now);
|
|
1388
1502
|
}
|
|
1389
|
-
setModLfoToFilterFc(
|
|
1503
|
+
setModLfoToFilterFc(channel, note) {
|
|
1390
1504
|
const now = this.audioContext.currentTime;
|
|
1391
|
-
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc +
|
|
1505
|
+
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc +
|
|
1506
|
+
this.getLFOFilterDepth(channel);
|
|
1392
1507
|
note.filterDepth.gain
|
|
1393
1508
|
.cancelScheduledValues(now)
|
|
1394
1509
|
.setValueAtTime(modLfoToFilterFc, now);
|
|
1395
1510
|
}
|
|
1396
|
-
setModLfoToVolume(
|
|
1511
|
+
setModLfoToVolume(channel, note) {
|
|
1397
1512
|
const now = this.audioContext.currentTime;
|
|
1398
1513
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1399
1514
|
const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1400
|
-
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) *
|
|
1515
|
+
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) *
|
|
1516
|
+
(1 + this.getLFOAmplitudeDepth(channel));
|
|
1401
1517
|
note.volumeDepth.gain
|
|
1402
1518
|
.cancelScheduledValues(now)
|
|
1403
1519
|
.setValueAtTime(volumeDepth, now);
|
|
@@ -1481,7 +1597,7 @@ export class MidyGM2 {
|
|
|
1481
1597
|
return {
|
|
1482
1598
|
modLfoToPitch: (channel, note, _prevValue) => {
|
|
1483
1599
|
if (0 < channel.state.modulationDepth) {
|
|
1484
|
-
this.setModLfoToPitch(channel, note
|
|
1600
|
+
this.setModLfoToPitch(channel, note);
|
|
1485
1601
|
}
|
|
1486
1602
|
},
|
|
1487
1603
|
vibLfoToPitch: (channel, note, _prevValue) => {
|
|
@@ -1491,12 +1607,12 @@ export class MidyGM2 {
|
|
|
1491
1607
|
},
|
|
1492
1608
|
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1493
1609
|
if (0 < channel.state.modulationDepth) {
|
|
1494
|
-
this.setModLfoToFilterFc(
|
|
1610
|
+
this.setModLfoToFilterFc(channel, note);
|
|
1495
1611
|
}
|
|
1496
1612
|
},
|
|
1497
1613
|
modLfoToVolume: (channel, note, _prevValue) => {
|
|
1498
1614
|
if (0 < channel.state.modulationDepth) {
|
|
1499
|
-
this.setModLfoToVolume(
|
|
1615
|
+
this.setModLfoToVolume(channel, note);
|
|
1500
1616
|
}
|
|
1501
1617
|
},
|
|
1502
1618
|
chorusEffectsSend: (channel, note, prevValue) => {
|
|
@@ -1566,7 +1682,7 @@ export class MidyGM2 {
|
|
|
1566
1682
|
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
1567
1683
|
}
|
|
1568
1684
|
else {
|
|
1569
|
-
this.setFilterEnvelope(channel, note
|
|
1685
|
+
this.setFilterEnvelope(channel, note);
|
|
1570
1686
|
}
|
|
1571
1687
|
this.setPitchEnvelope(note);
|
|
1572
1688
|
}
|
|
@@ -1580,7 +1696,7 @@ export class MidyGM2 {
|
|
|
1580
1696
|
if (key in voiceParams)
|
|
1581
1697
|
noteVoiceParams[key] = voiceParams[key];
|
|
1582
1698
|
}
|
|
1583
|
-
this.setVolumeEnvelope(
|
|
1699
|
+
this.setVolumeEnvelope(channel, note);
|
|
1584
1700
|
}
|
|
1585
1701
|
}
|
|
1586
1702
|
}
|
|
@@ -1614,10 +1730,10 @@ export class MidyGM2 {
|
|
|
1614
1730
|
127: this.polyOn,
|
|
1615
1731
|
};
|
|
1616
1732
|
}
|
|
1617
|
-
handleControlChange(channelNumber, controllerType, value) {
|
|
1733
|
+
handleControlChange(channelNumber, controllerType, value, startTime) {
|
|
1618
1734
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1619
1735
|
if (handler) {
|
|
1620
|
-
handler.call(this, channelNumber, value);
|
|
1736
|
+
handler.call(this, channelNumber, value, startTime);
|
|
1621
1737
|
const channel = this.channels[channelNumber];
|
|
1622
1738
|
this.applyVoiceParams(channel, controllerType + 128);
|
|
1623
1739
|
this.applyControlTable(channel, controllerType);
|
|
@@ -1629,55 +1745,45 @@ export class MidyGM2 {
|
|
|
1629
1745
|
setBankMSB(channelNumber, msb) {
|
|
1630
1746
|
this.channels[channelNumber].bankMSB = msb;
|
|
1631
1747
|
}
|
|
1632
|
-
updateModulation(channel) {
|
|
1633
|
-
|
|
1748
|
+
updateModulation(channel, scheduleTime) {
|
|
1749
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1634
1750
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
}
|
|
1643
|
-
else {
|
|
1644
|
-
this.setPitchEnvelope(note);
|
|
1645
|
-
this.startModulation(channel, note, now);
|
|
1646
|
-
}
|
|
1751
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1752
|
+
if (note.modulationDepth) {
|
|
1753
|
+
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1754
|
+
}
|
|
1755
|
+
else {
|
|
1756
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1757
|
+
this.startModulation(channel, note, scheduleTime);
|
|
1647
1758
|
}
|
|
1648
1759
|
});
|
|
1649
1760
|
}
|
|
1650
|
-
setModulationDepth(channelNumber, modulation) {
|
|
1761
|
+
setModulationDepth(channelNumber, modulation, scheduleTime) {
|
|
1651
1762
|
const channel = this.channels[channelNumber];
|
|
1652
1763
|
channel.state.modulationDepth = modulation / 127;
|
|
1653
|
-
this.updateModulation(channel);
|
|
1764
|
+
this.updateModulation(channel, scheduleTime);
|
|
1654
1765
|
}
|
|
1655
1766
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
1656
1767
|
const channel = this.channels[channelNumber];
|
|
1657
1768
|
const factor = 5 * Math.log(10) / 127;
|
|
1658
1769
|
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1659
1770
|
}
|
|
1660
|
-
setKeyBasedVolume(channel) {
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
if (!note)
|
|
1666
|
-
continue;
|
|
1667
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1668
|
-
if (keyBasedValue === 0)
|
|
1669
|
-
continue;
|
|
1771
|
+
setKeyBasedVolume(channel, scheduleTime) {
|
|
1772
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1773
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1774
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1775
|
+
if (keyBasedValue !== 0) {
|
|
1670
1776
|
note.volumeNode.gain
|
|
1671
|
-
.cancelScheduledValues(
|
|
1672
|
-
.setValueAtTime(1 + keyBasedValue,
|
|
1777
|
+
.cancelScheduledValues(scheduleTime)
|
|
1778
|
+
.setValueAtTime(1 + keyBasedValue, scheduleTime);
|
|
1673
1779
|
}
|
|
1674
1780
|
});
|
|
1675
1781
|
}
|
|
1676
|
-
setVolume(channelNumber, volume) {
|
|
1782
|
+
setVolume(channelNumber, volume, scheduleTime) {
|
|
1677
1783
|
const channel = this.channels[channelNumber];
|
|
1678
1784
|
channel.state.volume = volume / 127;
|
|
1679
|
-
this.updateChannelVolume(channel);
|
|
1680
|
-
this.setKeyBasedVolume(channel);
|
|
1785
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1786
|
+
this.setKeyBasedVolume(channel, scheduleTime);
|
|
1681
1787
|
}
|
|
1682
1788
|
panToGain(pan) {
|
|
1683
1789
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1686,36 +1792,31 @@ export class MidyGM2 {
|
|
|
1686
1792
|
gainRight: Math.sin(theta),
|
|
1687
1793
|
};
|
|
1688
1794
|
}
|
|
1689
|
-
setKeyBasedPan(channel) {
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
if (!note)
|
|
1695
|
-
continue;
|
|
1696
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1697
|
-
if (keyBasedValue === 0)
|
|
1698
|
-
continue;
|
|
1795
|
+
setKeyBasedPan(channel, scheduleTime) {
|
|
1796
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1797
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1798
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1799
|
+
if (keyBasedValue !== 0) {
|
|
1699
1800
|
const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
|
|
1700
1801
|
note.gainL.gain
|
|
1701
|
-
.cancelScheduledValues(
|
|
1702
|
-
.setValueAtTime(gainLeft,
|
|
1802
|
+
.cancelScheduledValues(scheduleTime)
|
|
1803
|
+
.setValueAtTime(gainLeft, scheduleTime);
|
|
1703
1804
|
note.gainR.gain
|
|
1704
|
-
.cancelScheduledValues(
|
|
1705
|
-
.setValueAtTime(gainRight,
|
|
1805
|
+
.cancelScheduledValues(scheduleTime)
|
|
1806
|
+
.setValueAtTime(gainRight, scheduleTime);
|
|
1706
1807
|
}
|
|
1707
1808
|
});
|
|
1708
1809
|
}
|
|
1709
|
-
setPan(channelNumber, pan) {
|
|
1810
|
+
setPan(channelNumber, pan, scheduleTime) {
|
|
1710
1811
|
const channel = this.channels[channelNumber];
|
|
1711
1812
|
channel.state.pan = pan / 127;
|
|
1712
|
-
this.updateChannelVolume(channel);
|
|
1713
|
-
this.setKeyBasedPan(channel);
|
|
1813
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1814
|
+
this.setKeyBasedPan(channel, scheduleTime);
|
|
1714
1815
|
}
|
|
1715
|
-
setExpression(channelNumber, expression) {
|
|
1816
|
+
setExpression(channelNumber, expression, scheduleTime) {
|
|
1716
1817
|
const channel = this.channels[channelNumber];
|
|
1717
1818
|
channel.state.expression = expression / 127;
|
|
1718
|
-
this.updateChannelVolume(channel);
|
|
1819
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1719
1820
|
}
|
|
1720
1821
|
setBankLSB(channelNumber, lsb) {
|
|
1721
1822
|
this.channels[channelNumber].bankLSB = lsb;
|
|
@@ -1750,8 +1851,7 @@ export class MidyGM2 {
|
|
|
1750
1851
|
channel.state.sostenutoPedal = value / 127;
|
|
1751
1852
|
if (64 <= value) {
|
|
1752
1853
|
const now = this.audioContext.currentTime;
|
|
1753
|
-
|
|
1754
|
-
channel.sostenutoNotes = new Map(activeNotes);
|
|
1854
|
+
channel.sostenutoNotes = this.getActiveNotes(channel, now);
|
|
1755
1855
|
}
|
|
1756
1856
|
else {
|
|
1757
1857
|
this.releaseSostenutoPedal(channelNumber, value);
|
|
@@ -2004,7 +2104,7 @@ export class MidyGM2 {
|
|
|
2004
2104
|
switch (data[3]) {
|
|
2005
2105
|
case 8:
|
|
2006
2106
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2007
|
-
return this.handleScaleOctaveTuning1ByteFormatSysEx(data);
|
|
2107
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data, false);
|
|
2008
2108
|
default:
|
|
2009
2109
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2010
2110
|
}
|
|
@@ -2326,8 +2426,8 @@ export class MidyGM2 {
|
|
|
2326
2426
|
}
|
|
2327
2427
|
return bitmap;
|
|
2328
2428
|
}
|
|
2329
|
-
handleScaleOctaveTuning1ByteFormatSysEx(data) {
|
|
2330
|
-
if (data.length <
|
|
2429
|
+
handleScaleOctaveTuning1ByteFormatSysEx(data, realtime) {
|
|
2430
|
+
if (data.length < 19) {
|
|
2331
2431
|
console.error("Data length is too short");
|
|
2332
2432
|
return;
|
|
2333
2433
|
}
|
|
@@ -2335,48 +2435,55 @@ export class MidyGM2 {
|
|
|
2335
2435
|
for (let i = 0; i < channelBitmap.length; i++) {
|
|
2336
2436
|
if (!channelBitmap[i])
|
|
2337
2437
|
continue;
|
|
2438
|
+
const channel = this.channels[i];
|
|
2338
2439
|
for (let j = 0; j < 12; j++) {
|
|
2339
|
-
const
|
|
2340
|
-
|
|
2440
|
+
const centValue = data[j + 7] - 64;
|
|
2441
|
+
channel.scaleOctaveTuningTable[j] = centValue;
|
|
2341
2442
|
}
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2443
|
+
if (realtime)
|
|
2444
|
+
this.updateChannelDetune(channel);
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
getFilterCutoffControl(channel) {
|
|
2448
|
+
const channelPressure = (channel.channelPressureTable[1] - 64) *
|
|
2449
|
+
channel.state.channelPressure;
|
|
2450
|
+
return channelPressure * 15;
|
|
2451
|
+
}
|
|
2452
|
+
getAmplitudeControl(channel) {
|
|
2453
|
+
const channelPressure = channel.channelPressureTable[2] *
|
|
2454
|
+
channel.state.channelPressure;
|
|
2455
|
+
return channelPressure / 64;
|
|
2456
|
+
}
|
|
2457
|
+
getLFOPitchDepth(channel) {
|
|
2458
|
+
const channelPressure = channel.channelPressureTable[3] *
|
|
2459
|
+
channel.state.channelPressure;
|
|
2460
|
+
return channelPressure / 127 * 600;
|
|
2461
|
+
}
|
|
2462
|
+
getLFOFilterDepth(channel) {
|
|
2463
|
+
const channelPressure = channel.channelPressureTable[4] *
|
|
2464
|
+
channel.state.channelPressure;
|
|
2465
|
+
return channelPressure / 127 * 2400;
|
|
2466
|
+
}
|
|
2467
|
+
getLFOAmplitudeDepth(channel) {
|
|
2468
|
+
const channelPressure = channel.channelPressureTable[5] *
|
|
2469
|
+
channel.state.channelPressure;
|
|
2470
|
+
return channelPressure / 127;
|
|
2471
|
+
}
|
|
2472
|
+
setControllerParameters(channel, note, table) {
|
|
2473
|
+
if (table[0] !== 64)
|
|
2474
|
+
this.updateDetune(channel, note);
|
|
2348
2475
|
if (!note.portamento) {
|
|
2349
|
-
if (table[1] !== 64)
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
}
|
|
2361
|
-
}
|
|
2362
|
-
if (table[3] !== 0) {
|
|
2363
|
-
const channelPressure = channel.channelPressureTable[3] *
|
|
2364
|
-
channel.state.channelPressure;
|
|
2365
|
-
const pressure = channelPressure / 127 * 600;
|
|
2366
|
-
this.setModLfoToPitch(channel, note, pressure);
|
|
2367
|
-
}
|
|
2368
|
-
if (table[4] !== 0) {
|
|
2369
|
-
const channelPressure = channel.channelPressureTable[4] *
|
|
2370
|
-
channel.state.channelPressure;
|
|
2371
|
-
const pressure = channelPressure / 127 * 2400;
|
|
2372
|
-
this.setModLfoToFilterFc(note, pressure);
|
|
2373
|
-
}
|
|
2374
|
-
if (table[5] !== 0) {
|
|
2375
|
-
const channelPressure = channel.channelPressureTable[5] *
|
|
2376
|
-
channel.state.channelPressure;
|
|
2377
|
-
const pressure = channelPressure / 127;
|
|
2378
|
-
this.setModLfoToVolume(note, pressure);
|
|
2379
|
-
}
|
|
2476
|
+
if (table[1] !== 64)
|
|
2477
|
+
this.setFilterEnvelope(channel, note);
|
|
2478
|
+
if (table[2] !== 64)
|
|
2479
|
+
this.setVolumeEnvelope(channel, note);
|
|
2480
|
+
}
|
|
2481
|
+
if (table[3] !== 0)
|
|
2482
|
+
this.setModLfoToPitch(channel, note);
|
|
2483
|
+
if (table[4] !== 0)
|
|
2484
|
+
this.setModLfoToFilterFc(channel, note);
|
|
2485
|
+
if (table[5] !== 0)
|
|
2486
|
+
this.setModLfoToVolume(channel, note);
|
|
2380
2487
|
}
|
|
2381
2488
|
handleChannelPressureSysEx(data, tableName) {
|
|
2382
2489
|
const channelNumber = data[4];
|
|
@@ -2407,7 +2514,7 @@ export class MidyGM2 {
|
|
|
2407
2514
|
const note = noteList[i];
|
|
2408
2515
|
if (!note)
|
|
2409
2516
|
continue;
|
|
2410
|
-
this.
|
|
2517
|
+
this.setControllerParameters(channel, note, table);
|
|
2411
2518
|
}
|
|
2412
2519
|
});
|
|
2413
2520
|
}
|
|
@@ -2470,9 +2577,6 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
2470
2577
|
value: {
|
|
2471
2578
|
currentBufferSource: null,
|
|
2472
2579
|
detune: 0,
|
|
2473
|
-
scaleOctaveTuningTable: new Array(12).fill(0), // cent
|
|
2474
|
-
channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
2475
|
-
keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
|
|
2476
2580
|
program: 0,
|
|
2477
2581
|
bank: 121 * 128,
|
|
2478
2582
|
bankMSB: 121,
|