@marmooo/midy 0.2.3 → 0.2.5
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 +22 -4
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +120 -23
- package/esm/midy-GM2.d.ts +34 -24
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +194 -133
- package/esm/midy-GMLite.d.ts +23 -5
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +122 -23
- package/esm/midy.d.ts +37 -23
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +303 -132
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +22 -4
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +120 -23
- package/script/midy-GM2.d.ts +34 -24
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +194 -133
- package/script/midy-GMLite.d.ts +23 -5
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +122 -23
- package/script/midy.d.ts +37 -23
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +303 -132
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", {
|
|
@@ -100,7 +152,6 @@ class Note {
|
|
|
100
152
|
const defaultControllerState = {
|
|
101
153
|
noteOnVelocity: { type: 2, defaultValue: 0 },
|
|
102
154
|
noteOnKeyNumber: { type: 3, defaultValue: 0 },
|
|
103
|
-
polyPressure: { type: 10, defaultValue: 0 },
|
|
104
155
|
channelPressure: { type: 13, defaultValue: 0 },
|
|
105
156
|
pitchWheel: { type: 14, defaultValue: 8192 / 16383 },
|
|
106
157
|
pitchWheelSensitivity: { type: 16, defaultValue: 2 / 128 },
|
|
@@ -284,6 +335,18 @@ class MidyGM2 {
|
|
|
284
335
|
writable: true,
|
|
285
336
|
value: this.initSoundFontTable()
|
|
286
337
|
});
|
|
338
|
+
Object.defineProperty(this, "audioBufferCounter", {
|
|
339
|
+
enumerable: true,
|
|
340
|
+
configurable: true,
|
|
341
|
+
writable: true,
|
|
342
|
+
value: new Map()
|
|
343
|
+
});
|
|
344
|
+
Object.defineProperty(this, "audioBufferCache", {
|
|
345
|
+
enumerable: true,
|
|
346
|
+
configurable: true,
|
|
347
|
+
writable: true,
|
|
348
|
+
value: new Map()
|
|
349
|
+
});
|
|
287
350
|
Object.defineProperty(this, "isPlaying", {
|
|
288
351
|
enumerable: true,
|
|
289
352
|
configurable: true,
|
|
@@ -336,7 +399,7 @@ class MidyGM2 {
|
|
|
336
399
|
enumerable: true,
|
|
337
400
|
configurable: true,
|
|
338
401
|
writable: true,
|
|
339
|
-
value: new
|
|
402
|
+
value: new SparseMap(128)
|
|
340
403
|
});
|
|
341
404
|
Object.defineProperty(this, "defaultOptions", {
|
|
342
405
|
enumerable: true,
|
|
@@ -376,7 +439,7 @@ class MidyGM2 {
|
|
|
376
439
|
initSoundFontTable() {
|
|
377
440
|
const table = new Array(128);
|
|
378
441
|
for (let i = 0; i < 128; i++) {
|
|
379
|
-
table[i] = new
|
|
442
|
+
table[i] = new SparseMap(128);
|
|
380
443
|
}
|
|
381
444
|
return table;
|
|
382
445
|
}
|
|
@@ -430,11 +493,8 @@ class MidyGM2 {
|
|
|
430
493
|
state: new ControllerState(),
|
|
431
494
|
controlTable: this.initControlTable(),
|
|
432
495
|
...this.setChannelAudioNodes(audioContext),
|
|
433
|
-
scheduledNotes: new
|
|
434
|
-
sostenutoNotes: new
|
|
435
|
-
channelPressure: {
|
|
436
|
-
...this.constructor.controllerDestinationSettings,
|
|
437
|
-
},
|
|
496
|
+
scheduledNotes: new SparseMap(128),
|
|
497
|
+
sostenutoNotes: new SparseMap(128),
|
|
438
498
|
};
|
|
439
499
|
});
|
|
440
500
|
return channels;
|
|
@@ -468,9 +528,8 @@ class MidyGM2 {
|
|
|
468
528
|
return audioBuffer;
|
|
469
529
|
}
|
|
470
530
|
}
|
|
471
|
-
|
|
531
|
+
createNoteBufferNode(audioBuffer, voiceParams) {
|
|
472
532
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
473
|
-
const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
|
|
474
533
|
bufferSource.buffer = audioBuffer;
|
|
475
534
|
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
476
535
|
if (bufferSource.loop) {
|
|
@@ -559,6 +618,7 @@ class MidyGM2 {
|
|
|
559
618
|
await Promise.all(this.notePromises);
|
|
560
619
|
this.notePromises = [];
|
|
561
620
|
this.exclusiveClassMap.clear();
|
|
621
|
+
this.audioBufferCache.clear();
|
|
562
622
|
resolve();
|
|
563
623
|
return;
|
|
564
624
|
}
|
|
@@ -574,8 +634,9 @@ class MidyGM2 {
|
|
|
574
634
|
}
|
|
575
635
|
else if (this.isStopping) {
|
|
576
636
|
await this.stopNotes(0, true);
|
|
577
|
-
this.exclusiveClassMap.clear();
|
|
578
637
|
this.notePromises = [];
|
|
638
|
+
this.exclusiveClassMap.clear();
|
|
639
|
+
this.audioBufferCache.clear();
|
|
579
640
|
resolve();
|
|
580
641
|
this.isStopping = false;
|
|
581
642
|
this.isPaused = false;
|
|
@@ -606,6 +667,9 @@ class MidyGM2 {
|
|
|
606
667
|
secondToTicks(second, secondsPerBeat) {
|
|
607
668
|
return second * this.ticksPerBeat / secondsPerBeat;
|
|
608
669
|
}
|
|
670
|
+
getAudioBufferId(programNumber, noteNumber, velocity) {
|
|
671
|
+
return `${programNumber}:${noteNumber}:${velocity}`;
|
|
672
|
+
}
|
|
609
673
|
extractMidiData(midi) {
|
|
610
674
|
const instruments = new Set();
|
|
611
675
|
const timeline = [];
|
|
@@ -627,6 +691,8 @@ class MidyGM2 {
|
|
|
627
691
|
switch (event.type) {
|
|
628
692
|
case "noteOn": {
|
|
629
693
|
const channel = tmpChannels[event.channel];
|
|
694
|
+
const audioBufferId = this.getAudioBufferId(channel.programNumber, event.noteNumber, event.velocity);
|
|
695
|
+
this.audioBufferCounter.set(audioBufferId, (this.audioBufferCounter.get(audioBufferId) ?? 0) + 1);
|
|
630
696
|
if (channel.programNumber < 0) {
|
|
631
697
|
channel.programNumber = event.programNumber;
|
|
632
698
|
switch (channel.bankMSB) {
|
|
@@ -676,6 +742,10 @@ class MidyGM2 {
|
|
|
676
742
|
timeline.push(event);
|
|
677
743
|
}
|
|
678
744
|
}
|
|
745
|
+
for (const [audioBufferId, count] of this.audioBufferCounter) {
|
|
746
|
+
if (count === 1)
|
|
747
|
+
this.audioBufferCounter.delete(audioBufferId);
|
|
748
|
+
}
|
|
679
749
|
const priority = {
|
|
680
750
|
controller: 0,
|
|
681
751
|
sysEx: 1,
|
|
@@ -769,7 +839,7 @@ class MidyGM2 {
|
|
|
769
839
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
770
840
|
}
|
|
771
841
|
getActiveNotes(channel, time) {
|
|
772
|
-
const activeNotes = new
|
|
842
|
+
const activeNotes = new SparseMap(128);
|
|
773
843
|
channel.scheduledNotes.forEach((noteList) => {
|
|
774
844
|
const activeNote = this.getActiveNote(noteList, time);
|
|
775
845
|
if (activeNote) {
|
|
@@ -936,28 +1006,31 @@ class MidyGM2 {
|
|
|
936
1006
|
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
937
1007
|
const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
|
|
938
1008
|
const pitch = pitchWheel * pitchWheelSensitivity;
|
|
939
|
-
const pressureDepth = (channel.
|
|
1009
|
+
const pressureDepth = (channel.channelPressureTable[0] - 64) / 37.5; // 2400 / 64;
|
|
940
1010
|
const pressure = pressureDepth * channel.state.channelPressure;
|
|
941
1011
|
return tuning + pitch + pressure;
|
|
942
1012
|
}
|
|
943
1013
|
calcNoteDetune(channel, note) {
|
|
944
1014
|
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
945
1015
|
}
|
|
946
|
-
|
|
947
|
-
const now = this.audioContext.currentTime;
|
|
1016
|
+
updateChannelDetune(channel) {
|
|
948
1017
|
channel.scheduledNotes.forEach((noteList) => {
|
|
949
1018
|
for (let i = 0; i < noteList.length; i++) {
|
|
950
1019
|
const note = noteList[i];
|
|
951
1020
|
if (!note)
|
|
952
1021
|
continue;
|
|
953
|
-
|
|
954
|
-
const detune = channel.detune + noteDetune;
|
|
955
|
-
note.bufferSource.detune
|
|
956
|
-
.cancelScheduledValues(now)
|
|
957
|
-
.setValueAtTime(detune, now);
|
|
1022
|
+
this.updateDetune(channel, note, 0);
|
|
958
1023
|
}
|
|
959
1024
|
});
|
|
960
1025
|
}
|
|
1026
|
+
updateDetune(channel, note, pressure) {
|
|
1027
|
+
const now = this.audioContext.currentTime;
|
|
1028
|
+
const noteDetune = this.calcNoteDetune(channel, note);
|
|
1029
|
+
const detune = channel.detune + noteDetune + pressure;
|
|
1030
|
+
note.bufferSource.detune
|
|
1031
|
+
.cancelScheduledValues(now)
|
|
1032
|
+
.setValueAtTime(detune, now);
|
|
1033
|
+
}
|
|
961
1034
|
getPortamentoTime(channel) {
|
|
962
1035
|
const factor = 5 * Math.log(10) / 127;
|
|
963
1036
|
const time = channel.state.portamentoTime;
|
|
@@ -975,14 +1048,11 @@ class MidyGM2 {
|
|
|
975
1048
|
.setValueAtTime(0, volDelay)
|
|
976
1049
|
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
977
1050
|
}
|
|
978
|
-
setVolumeEnvelope(
|
|
1051
|
+
setVolumeEnvelope(note, pressure) {
|
|
979
1052
|
const now = this.audioContext.currentTime;
|
|
980
|
-
const state = channel.state;
|
|
981
1053
|
const { voiceParams, startTime } = note;
|
|
982
|
-
const pressureDepth = channel.pressureTable[2] / 64;
|
|
983
|
-
const pressure = 1 + pressureDepth * channel.state.channelPressure;
|
|
984
1054
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
|
|
985
|
-
pressure;
|
|
1055
|
+
(1 + pressure);
|
|
986
1056
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
987
1057
|
const volDelay = startTime + voiceParams.volDelay;
|
|
988
1058
|
const volAttack = volDelay + voiceParams.volAttack;
|
|
@@ -1030,10 +1100,8 @@ class MidyGM2 {
|
|
|
1030
1100
|
const { voiceParams, noteNumber, startTime } = note;
|
|
1031
1101
|
const softPedalFactor = 1 -
|
|
1032
1102
|
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1033
|
-
const
|
|
1034
|
-
|
|
1035
|
-
const baseCent = voiceParams.initialFilterFc + pressure;
|
|
1036
|
-
const baseFreq = this.centToHz(baseCent) * softPedalFactor;
|
|
1103
|
+
const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
|
|
1104
|
+
softPedalFactor;
|
|
1037
1105
|
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor;
|
|
1038
1106
|
const sustainFreq = baseFreq +
|
|
1039
1107
|
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
@@ -1047,15 +1115,16 @@ class MidyGM2 {
|
|
|
1047
1115
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
1048
1116
|
.linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
|
|
1049
1117
|
}
|
|
1050
|
-
setFilterEnvelope(channel, note) {
|
|
1118
|
+
setFilterEnvelope(channel, note, pressure) {
|
|
1051
1119
|
const now = this.audioContext.currentTime;
|
|
1052
1120
|
const state = channel.state;
|
|
1053
1121
|
const { voiceParams, noteNumber, startTime } = note;
|
|
1054
1122
|
const softPedalFactor = 1 -
|
|
1055
1123
|
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1056
|
-
const
|
|
1124
|
+
const baseCent = voiceParams.initialFilterFc + pressure;
|
|
1125
|
+
const baseFreq = this.centToHz(baseCent) * softPedalFactor;
|
|
1126
|
+
const peekFreq = this.centToHz(baseCent + voiceParams.modEnvToFilterFc) *
|
|
1057
1127
|
softPedalFactor;
|
|
1058
|
-
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor;
|
|
1059
1128
|
const sustainFreq = baseFreq +
|
|
1060
1129
|
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
1061
1130
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
@@ -1082,9 +1151,9 @@ class MidyGM2 {
|
|
|
1082
1151
|
gain: voiceParams.modLfoToFilterFc,
|
|
1083
1152
|
});
|
|
1084
1153
|
note.modulationDepth = new GainNode(this.audioContext);
|
|
1085
|
-
this.setModLfoToPitch(channel, note);
|
|
1154
|
+
this.setModLfoToPitch(channel, note, 0);
|
|
1086
1155
|
note.volumeDepth = new GainNode(this.audioContext);
|
|
1087
|
-
this.setModLfoToVolume(
|
|
1156
|
+
this.setModLfoToVolume(note, 0);
|
|
1088
1157
|
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
1089
1158
|
note.modulationLFO.connect(note.filterDepth);
|
|
1090
1159
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
@@ -1097,8 +1166,7 @@ class MidyGM2 {
|
|
|
1097
1166
|
const { voiceParams } = note;
|
|
1098
1167
|
const state = channel.state;
|
|
1099
1168
|
note.vibratoLFO = new OscillatorNode(this.audioContext, {
|
|
1100
|
-
frequency: this.centToHz(voiceParams.freqVibLFO) *
|
|
1101
|
-
state.vibratoRate,
|
|
1169
|
+
frequency: this.centToHz(voiceParams.freqVibLFO) * state.vibratoRate * 2,
|
|
1102
1170
|
});
|
|
1103
1171
|
note.vibratoLFO.start(startTime + voiceParams.delayVibLFO * state.vibratoDelay * 2);
|
|
1104
1172
|
note.vibratoDepth = new GainNode(this.audioContext);
|
|
@@ -1106,12 +1174,31 @@ class MidyGM2 {
|
|
|
1106
1174
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
1107
1175
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
1108
1176
|
}
|
|
1177
|
+
async getAudioBuffer(program, noteNumber, velocity, voiceParams, isSF3) {
|
|
1178
|
+
const audioBufferId = this.getAudioBufferId(program, noteNumber, velocity);
|
|
1179
|
+
const cache = this.audioBufferCache.get(audioBufferId);
|
|
1180
|
+
if (cache) {
|
|
1181
|
+
cache.counter += 1;
|
|
1182
|
+
if (cache.maxCount <= cache.counter) {
|
|
1183
|
+
this.audioBufferCache.delete(audioBufferId);
|
|
1184
|
+
}
|
|
1185
|
+
return cache.audioBuffer;
|
|
1186
|
+
}
|
|
1187
|
+
else {
|
|
1188
|
+
const maxCount = this.audioBufferCounter.get(audioBufferId) ?? 0;
|
|
1189
|
+
const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
|
|
1190
|
+
const cache = { audioBuffer, maxCount, counter: 1 };
|
|
1191
|
+
this.audioBufferCache.set(audioBufferId, cache);
|
|
1192
|
+
return audioBuffer;
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1109
1195
|
async createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3) {
|
|
1110
1196
|
const state = channel.state;
|
|
1111
1197
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1112
1198
|
const voiceParams = voice.getAllParams(controllerState);
|
|
1113
1199
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1114
|
-
|
|
1200
|
+
const audioBuffer = await this.getAudioBuffer(channel.program, noteNumber, velocity, voiceParams, isSF3);
|
|
1201
|
+
note.bufferSource = this.createNoteBufferNode(audioBuffer, voiceParams);
|
|
1115
1202
|
note.volumeNode = new GainNode(this.audioContext);
|
|
1116
1203
|
note.gainL = new GainNode(this.audioContext);
|
|
1117
1204
|
note.gainR = new GainNode(this.audioContext);
|
|
@@ -1127,8 +1214,8 @@ class MidyGM2 {
|
|
|
1127
1214
|
}
|
|
1128
1215
|
else {
|
|
1129
1216
|
note.portamento = false;
|
|
1130
|
-
this.setVolumeEnvelope(
|
|
1131
|
-
this.setFilterEnvelope(channel, note);
|
|
1217
|
+
this.setVolumeEnvelope(note, 0);
|
|
1218
|
+
this.setFilterEnvelope(channel, note, 0);
|
|
1132
1219
|
}
|
|
1133
1220
|
if (0 < state.vibratoDepth) {
|
|
1134
1221
|
this.startVibrato(channel, note, startTime);
|
|
@@ -1171,10 +1258,10 @@ class MidyGM2 {
|
|
|
1171
1258
|
if (soundFontIndex === undefined)
|
|
1172
1259
|
return;
|
|
1173
1260
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
1174
|
-
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
1175
1261
|
const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
|
|
1176
1262
|
if (!voice)
|
|
1177
1263
|
return;
|
|
1264
|
+
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
1178
1265
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
|
|
1179
1266
|
note.gainL.connect(channel.gainL);
|
|
1180
1267
|
note.gainR.connect(channel.gainR);
|
|
@@ -1343,47 +1430,21 @@ class MidyGM2 {
|
|
|
1343
1430
|
channel.program = program;
|
|
1344
1431
|
}
|
|
1345
1432
|
handleChannelPressure(channelNumber, value) {
|
|
1433
|
+
const now = this.audioContext.currentTime;
|
|
1346
1434
|
const channel = this.channels[channelNumber];
|
|
1347
1435
|
const prev = channel.state.channelPressure;
|
|
1348
1436
|
const next = value / 127;
|
|
1349
1437
|
channel.state.channelPressure = next;
|
|
1350
|
-
if (channel.
|
|
1351
|
-
const pressureDepth = (channel.
|
|
1438
|
+
if (channel.channelPressureTable[0] !== 64) {
|
|
1439
|
+
const pressureDepth = (channel.channelPressureTable[0] - 64) / 37.5; // 2400 / 64;
|
|
1352
1440
|
channel.detune += pressureDepth * (next - prev);
|
|
1353
1441
|
}
|
|
1354
|
-
const table = channel.
|
|
1355
|
-
channel.
|
|
1356
|
-
|
|
1357
|
-
const note = noteList[i];
|
|
1358
|
-
if (!note)
|
|
1359
|
-
continue;
|
|
1360
|
-
this.applyDestinationSettings(channel, note, table);
|
|
1361
|
-
}
|
|
1442
|
+
const table = channel.channelPressureTable;
|
|
1443
|
+
this.getActiveNotes(channel, now).forEach((note) => {
|
|
1444
|
+
this.applyDestinationSettings(channel, note, table);
|
|
1362
1445
|
});
|
|
1363
1446
|
// this.applyVoiceParams(channel, 13);
|
|
1364
1447
|
}
|
|
1365
|
-
setChannelPressure(channel, note) {
|
|
1366
|
-
if (channel.pressureTable[0] !== 64) {
|
|
1367
|
-
this.updateDetune(channel);
|
|
1368
|
-
}
|
|
1369
|
-
if (!note.portamento) {
|
|
1370
|
-
if (channel.pressureTable[1] !== 64) {
|
|
1371
|
-
this.setFilterEnvelope(channel, note);
|
|
1372
|
-
}
|
|
1373
|
-
if (channel.pressureTable[2] !== 64) {
|
|
1374
|
-
this.setVolumeEnvelope(channel, note);
|
|
1375
|
-
}
|
|
1376
|
-
}
|
|
1377
|
-
if (channel.pressureTable[3] !== 0) {
|
|
1378
|
-
this.setModLfoToPitch(channel, note);
|
|
1379
|
-
}
|
|
1380
|
-
if (channel.pressureTable[4] !== 0) {
|
|
1381
|
-
this.setModLfoToFilterFc(channel, note);
|
|
1382
|
-
}
|
|
1383
|
-
if (channel.pressureTable[5] !== 0) {
|
|
1384
|
-
this.setModLfoToVolume(channel, note);
|
|
1385
|
-
}
|
|
1386
|
-
}
|
|
1387
1448
|
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
1388
1449
|
const pitchBend = msb * 128 + lsb;
|
|
1389
1450
|
this.setPitchBend(channelNumber, pitchBend);
|
|
@@ -1395,16 +1456,13 @@ class MidyGM2 {
|
|
|
1395
1456
|
const next = (value - 8192) / 8192;
|
|
1396
1457
|
state.pitchWheel = value / 16383;
|
|
1397
1458
|
channel.detune += (next - prev) * state.pitchWheelSensitivity * 12800;
|
|
1398
|
-
this.
|
|
1459
|
+
this.updateChannelDetune(channel);
|
|
1399
1460
|
this.applyVoiceParams(channel, 14);
|
|
1400
1461
|
}
|
|
1401
|
-
setModLfoToPitch(channel, note) {
|
|
1462
|
+
setModLfoToPitch(channel, note, pressure) {
|
|
1402
1463
|
const now = this.audioContext.currentTime;
|
|
1403
|
-
const pressureDepth = channel.pressureTable[3] / 127 * 600;
|
|
1404
|
-
const pressure = pressureDepth * channel.state.channelPressure;
|
|
1405
1464
|
const modLfoToPitch = note.voiceParams.modLfoToPitch + pressure;
|
|
1406
|
-
const baseDepth = Math.abs(modLfoToPitch) +
|
|
1407
|
-
channel.state.modulationDepth;
|
|
1465
|
+
const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
|
|
1408
1466
|
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
1409
1467
|
note.modulationDepth.gain
|
|
1410
1468
|
.cancelScheduledValues(now)
|
|
@@ -1420,22 +1478,18 @@ class MidyGM2 {
|
|
|
1420
1478
|
.cancelScheduledValues(now)
|
|
1421
1479
|
.setValueAtTime(vibratoDepth * vibratoDepthSign, now);
|
|
1422
1480
|
}
|
|
1423
|
-
setModLfoToFilterFc(
|
|
1481
|
+
setModLfoToFilterFc(note, pressure) {
|
|
1424
1482
|
const now = this.audioContext.currentTime;
|
|
1425
|
-
const pressureDepth = channel.pressureTable[4] / 127 * 2400;
|
|
1426
|
-
const pressure = pressureDepth * channel.state.channelPressure;
|
|
1427
1483
|
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc + pressure;
|
|
1428
1484
|
note.filterDepth.gain
|
|
1429
1485
|
.cancelScheduledValues(now)
|
|
1430
1486
|
.setValueAtTime(modLfoToFilterFc, now);
|
|
1431
1487
|
}
|
|
1432
|
-
setModLfoToVolume(
|
|
1488
|
+
setModLfoToVolume(note, pressure) {
|
|
1433
1489
|
const now = this.audioContext.currentTime;
|
|
1434
1490
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1435
1491
|
const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1436
|
-
const
|
|
1437
|
-
const pressure = 1 + pressureDepth * channel.state.channelPressure;
|
|
1438
|
-
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) * pressure;
|
|
1492
|
+
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) * (1 + pressure);
|
|
1439
1493
|
note.volumeDepth.gain
|
|
1440
1494
|
.cancelScheduledValues(now)
|
|
1441
1495
|
.setValueAtTime(volumeDepth, now);
|
|
@@ -1508,11 +1562,18 @@ class MidyGM2 {
|
|
|
1508
1562
|
.cancelScheduledValues(now)
|
|
1509
1563
|
.setValueAtTime(freqModLFO, now);
|
|
1510
1564
|
}
|
|
1565
|
+
setFreqVibLFO(channel, note) {
|
|
1566
|
+
const now = this.audioContext.currentTime;
|
|
1567
|
+
const freqVibLFO = note.voiceParams.freqVibLFO;
|
|
1568
|
+
note.vibratoLFO.frequency
|
|
1569
|
+
.cancelScheduledValues(now)
|
|
1570
|
+
.setValueAtTime(freqVibLFO * channel.state.vibratoRate * 2, now);
|
|
1571
|
+
}
|
|
1511
1572
|
createVoiceParamsHandlers() {
|
|
1512
1573
|
return {
|
|
1513
1574
|
modLfoToPitch: (channel, note, _prevValue) => {
|
|
1514
1575
|
if (0 < channel.state.modulationDepth) {
|
|
1515
|
-
this.setModLfoToPitch(channel, note);
|
|
1576
|
+
this.setModLfoToPitch(channel, note, 0);
|
|
1516
1577
|
}
|
|
1517
1578
|
},
|
|
1518
1579
|
vibLfoToPitch: (channel, note, _prevValue) => {
|
|
@@ -1522,12 +1583,12 @@ class MidyGM2 {
|
|
|
1522
1583
|
},
|
|
1523
1584
|
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1524
1585
|
if (0 < channel.state.modulationDepth) {
|
|
1525
|
-
this.setModLfoToFilterFc(
|
|
1586
|
+
this.setModLfoToFilterFc(note, 0);
|
|
1526
1587
|
}
|
|
1527
1588
|
},
|
|
1528
1589
|
modLfoToVolume: (channel, note, _prevValue) => {
|
|
1529
1590
|
if (0 < channel.state.modulationDepth) {
|
|
1530
|
-
this.setModLfoToVolume(
|
|
1591
|
+
this.setModLfoToVolume(note, 0);
|
|
1531
1592
|
}
|
|
1532
1593
|
},
|
|
1533
1594
|
chorusEffectsSend: (channel, note, prevValue) => {
|
|
@@ -1553,11 +1614,7 @@ class MidyGM2 {
|
|
|
1553
1614
|
},
|
|
1554
1615
|
freqVibLFO: (channel, note, _prevValue) => {
|
|
1555
1616
|
if (0 < channel.state.vibratoDepth) {
|
|
1556
|
-
|
|
1557
|
-
const freqVibLFO = note.voiceParams.freqVibLFO;
|
|
1558
|
-
note.vibratoLFO.frequency
|
|
1559
|
-
.cancelScheduledValues(now)
|
|
1560
|
-
.setValueAtTime(freqVibLFO * channel.state.vibratoRate, now);
|
|
1617
|
+
this.setFreqVibLFO(channel, note);
|
|
1561
1618
|
}
|
|
1562
1619
|
},
|
|
1563
1620
|
};
|
|
@@ -1601,7 +1658,7 @@ class MidyGM2 {
|
|
|
1601
1658
|
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
1602
1659
|
}
|
|
1603
1660
|
else {
|
|
1604
|
-
this.setFilterEnvelope(channel, note);
|
|
1661
|
+
this.setFilterEnvelope(channel, note, 0);
|
|
1605
1662
|
}
|
|
1606
1663
|
this.setPitchEnvelope(note);
|
|
1607
1664
|
}
|
|
@@ -1615,7 +1672,7 @@ class MidyGM2 {
|
|
|
1615
1672
|
if (key in voiceParams)
|
|
1616
1673
|
noteVoiceParams[key] = voiceParams[key];
|
|
1617
1674
|
}
|
|
1618
|
-
this.setVolumeEnvelope(
|
|
1675
|
+
this.setVolumeEnvelope(note, 0);
|
|
1619
1676
|
}
|
|
1620
1677
|
}
|
|
1621
1678
|
}
|
|
@@ -1654,7 +1711,7 @@ class MidyGM2 {
|
|
|
1654
1711
|
if (handler) {
|
|
1655
1712
|
handler.call(this, channelNumber, value);
|
|
1656
1713
|
const channel = this.channels[channelNumber];
|
|
1657
|
-
this.applyVoiceParams(channel,
|
|
1714
|
+
this.applyVoiceParams(channel, controllerType + 128);
|
|
1658
1715
|
this.applyControlTable(channel, controllerType);
|
|
1659
1716
|
}
|
|
1660
1717
|
else {
|
|
@@ -1785,8 +1842,7 @@ class MidyGM2 {
|
|
|
1785
1842
|
channel.state.sostenutoPedal = value / 127;
|
|
1786
1843
|
if (64 <= value) {
|
|
1787
1844
|
const now = this.audioContext.currentTime;
|
|
1788
|
-
|
|
1789
|
-
channel.sostenutoNotes = new Map(activeNotes);
|
|
1845
|
+
channel.sostenutoNotes = this.getActiveNotes(channel, now);
|
|
1790
1846
|
}
|
|
1791
1847
|
else {
|
|
1792
1848
|
this.releaseSostenutoPedal(channelNumber, value);
|
|
@@ -1947,7 +2003,7 @@ class MidyGM2 {
|
|
|
1947
2003
|
const next = value / 128;
|
|
1948
2004
|
state.pitchWheelSensitivity = next;
|
|
1949
2005
|
channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
|
|
1950
|
-
this.
|
|
2006
|
+
this.updateChannelDetune(channel);
|
|
1951
2007
|
this.applyVoiceParams(channel, 16);
|
|
1952
2008
|
}
|
|
1953
2009
|
handleFineTuningRPN(channelNumber) {
|
|
@@ -1962,7 +2018,7 @@ class MidyGM2 {
|
|
|
1962
2018
|
const next = (value - 8192) / 8.192; // cent
|
|
1963
2019
|
channel.fineTuning = next;
|
|
1964
2020
|
channel.detune += next - prev;
|
|
1965
|
-
this.
|
|
2021
|
+
this.updateChannelDetune(channel);
|
|
1966
2022
|
}
|
|
1967
2023
|
handleCoarseTuningRPN(channelNumber) {
|
|
1968
2024
|
const channel = this.channels[channelNumber];
|
|
@@ -1976,7 +2032,7 @@ class MidyGM2 {
|
|
|
1976
2032
|
const next = (value - 64) * 100; // cent
|
|
1977
2033
|
channel.coarseTuning = next;
|
|
1978
2034
|
channel.detune += next - prev;
|
|
1979
|
-
this.
|
|
2035
|
+
this.updateChannelDetune(channel);
|
|
1980
2036
|
}
|
|
1981
2037
|
handleModulationDepthRangeRPN(channelNumber) {
|
|
1982
2038
|
const channel = this.channels[channelNumber];
|
|
@@ -2007,7 +2063,7 @@ class MidyGM2 {
|
|
|
2007
2063
|
const state = channel.state;
|
|
2008
2064
|
for (let i = 0; i < stateTypes.length; i++) {
|
|
2009
2065
|
const type = stateTypes[i];
|
|
2010
|
-
state[type] = defaultControllerState[type];
|
|
2066
|
+
state[type] = defaultControllerState[type].defaultValue;
|
|
2011
2067
|
}
|
|
2012
2068
|
const settingTypes = [
|
|
2013
2069
|
"rpnMSB",
|
|
@@ -2039,7 +2095,7 @@ class MidyGM2 {
|
|
|
2039
2095
|
switch (data[3]) {
|
|
2040
2096
|
case 8:
|
|
2041
2097
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2042
|
-
return this.handleScaleOctaveTuning1ByteFormatSysEx(data);
|
|
2098
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data, false);
|
|
2043
2099
|
default:
|
|
2044
2100
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2045
2101
|
}
|
|
@@ -2101,7 +2157,7 @@ class MidyGM2 {
|
|
|
2101
2157
|
case 9:
|
|
2102
2158
|
switch (data[3]) {
|
|
2103
2159
|
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
|
|
2104
|
-
return this.
|
|
2160
|
+
return this.handlePressureSysEx(data, "channelPressureTable");
|
|
2105
2161
|
case 3: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
|
|
2106
2162
|
return this.handleControlChangeSysEx(data);
|
|
2107
2163
|
default:
|
|
@@ -2143,7 +2199,7 @@ class MidyGM2 {
|
|
|
2143
2199
|
const next = (value - 8192) / 8.192; // cent
|
|
2144
2200
|
this.masterFineTuning = next;
|
|
2145
2201
|
channel.detune += next - prev;
|
|
2146
|
-
this.
|
|
2202
|
+
this.updateChannelDetune(channel);
|
|
2147
2203
|
}
|
|
2148
2204
|
handleMasterCoarseTuningSysEx(data) {
|
|
2149
2205
|
const coarseTuning = data[4];
|
|
@@ -2154,7 +2210,7 @@ class MidyGM2 {
|
|
|
2154
2210
|
const next = (value - 64) * 100; // cent
|
|
2155
2211
|
this.masterCoarseTuning = next;
|
|
2156
2212
|
channel.detune += next - prev;
|
|
2157
|
-
this.
|
|
2213
|
+
this.updateChannelDetune(channel);
|
|
2158
2214
|
}
|
|
2159
2215
|
handleGlobalParameterControlSysEx(data) {
|
|
2160
2216
|
if (data[7] === 1) {
|
|
@@ -2361,8 +2417,8 @@ class MidyGM2 {
|
|
|
2361
2417
|
}
|
|
2362
2418
|
return bitmap;
|
|
2363
2419
|
}
|
|
2364
|
-
handleScaleOctaveTuning1ByteFormatSysEx(data) {
|
|
2365
|
-
if (data.length <
|
|
2420
|
+
handleScaleOctaveTuning1ByteFormatSysEx(data, realtime) {
|
|
2421
|
+
if (data.length < 19) {
|
|
2366
2422
|
console.error("Data length is too short");
|
|
2367
2423
|
return;
|
|
2368
2424
|
}
|
|
@@ -2370,37 +2426,55 @@ class MidyGM2 {
|
|
|
2370
2426
|
for (let i = 0; i < channelBitmap.length; i++) {
|
|
2371
2427
|
if (!channelBitmap[i])
|
|
2372
2428
|
continue;
|
|
2429
|
+
const channel = this.channels[i];
|
|
2373
2430
|
for (let j = 0; j < 12; j++) {
|
|
2374
|
-
const
|
|
2375
|
-
|
|
2431
|
+
const centValue = data[j + 7] - 64;
|
|
2432
|
+
channel.scaleOctaveTuningTable[j] = centValue;
|
|
2376
2433
|
}
|
|
2434
|
+
if (realtime)
|
|
2435
|
+
this.updateChannelDetune(channel);
|
|
2377
2436
|
}
|
|
2378
2437
|
}
|
|
2379
2438
|
applyDestinationSettings(channel, note, table) {
|
|
2380
2439
|
if (table[0] !== 64) {
|
|
2381
|
-
this.updateDetune(channel);
|
|
2440
|
+
this.updateDetune(channel, note, 0);
|
|
2382
2441
|
}
|
|
2383
2442
|
if (!note.portamento) {
|
|
2384
2443
|
if (table[1] !== 64) {
|
|
2385
|
-
|
|
2444
|
+
const channelPressure = channel.channelPressureTable[1] *
|
|
2445
|
+
channel.state.channelPressure;
|
|
2446
|
+
const pressure = (channelPressure - 64) * 15;
|
|
2447
|
+
this.setFilterEnvelope(channel, note, pressure);
|
|
2386
2448
|
}
|
|
2387
2449
|
if (table[2] !== 64) {
|
|
2388
|
-
|
|
2450
|
+
const channelPressure = channel.channelPressureTable[2] *
|
|
2451
|
+
channel.state.channelPressure;
|
|
2452
|
+
const pressure = channelPressure / 64;
|
|
2453
|
+
this.setVolumeEnvelope(note, pressure);
|
|
2389
2454
|
}
|
|
2390
2455
|
}
|
|
2391
2456
|
if (table[3] !== 0) {
|
|
2392
|
-
|
|
2457
|
+
const channelPressure = channel.channelPressureTable[3] *
|
|
2458
|
+
channel.state.channelPressure;
|
|
2459
|
+
const pressure = channelPressure / 127 * 600;
|
|
2460
|
+
this.setModLfoToPitch(channel, note, pressure);
|
|
2393
2461
|
}
|
|
2394
2462
|
if (table[4] !== 0) {
|
|
2395
|
-
|
|
2463
|
+
const channelPressure = channel.channelPressureTable[4] *
|
|
2464
|
+
channel.state.channelPressure;
|
|
2465
|
+
const pressure = channelPressure / 127 * 2400;
|
|
2466
|
+
this.setModLfoToFilterFc(note, pressure);
|
|
2396
2467
|
}
|
|
2397
2468
|
if (table[5] !== 0) {
|
|
2398
|
-
|
|
2469
|
+
const channelPressure = channel.channelPressureTable[5] *
|
|
2470
|
+
channel.state.channelPressure;
|
|
2471
|
+
const pressure = channelPressure / 127;
|
|
2472
|
+
this.setModLfoToVolume(note, pressure);
|
|
2399
2473
|
}
|
|
2400
2474
|
}
|
|
2401
|
-
handleChannelPressureSysEx(data) {
|
|
2475
|
+
handleChannelPressureSysEx(data, tableName) {
|
|
2402
2476
|
const channelNumber = data[4];
|
|
2403
|
-
const table = this.channels[channelNumber]
|
|
2477
|
+
const table = this.channels[channelNumber][tableName];
|
|
2404
2478
|
for (let i = 5; i < data.length - 1; i += 2) {
|
|
2405
2479
|
const pp = data[i];
|
|
2406
2480
|
const rr = data[i + 1];
|
|
@@ -2491,8 +2565,8 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
2491
2565
|
value: {
|
|
2492
2566
|
currentBufferSource: null,
|
|
2493
2567
|
detune: 0,
|
|
2494
|
-
scaleOctaveTuningTable: new
|
|
2495
|
-
|
|
2568
|
+
scaleOctaveTuningTable: new Int8Array(12), // [-64, 63] cent
|
|
2569
|
+
channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
2496
2570
|
keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
|
|
2497
2571
|
program: 0,
|
|
2498
2572
|
bank: 121 * 128,
|
|
@@ -2507,16 +2581,3 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
2507
2581
|
modulationDepthRange: 50, // cent
|
|
2508
2582
|
}
|
|
2509
2583
|
});
|
|
2510
|
-
Object.defineProperty(MidyGM2, "controllerDestinationSettings", {
|
|
2511
|
-
enumerable: true,
|
|
2512
|
-
configurable: true,
|
|
2513
|
-
writable: true,
|
|
2514
|
-
value: {
|
|
2515
|
-
pitchControl: 0,
|
|
2516
|
-
filterCutoffControl: 0,
|
|
2517
|
-
amplitudeControl: 1,
|
|
2518
|
-
lfoPitchDepth: 0,
|
|
2519
|
-
lfoFilterDepth: 0,
|
|
2520
|
-
lfoAmplitudeDepth: 0,
|
|
2521
|
-
}
|
|
2522
|
-
});
|