@marmooo/midy 0.1.7 → 0.2.1
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/LICENSE +201 -0
- package/README.md +118 -0
- package/esm/midy-GM1.d.ts +53 -26
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +397 -167
- package/esm/midy-GM2.d.ts +61 -38
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +617 -278
- package/esm/midy-GMLite.d.ts +49 -23
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +361 -156
- package/esm/midy.d.ts +63 -45
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +699 -354
- package/package.json +2 -2
- package/script/midy-GM1.d.ts +53 -26
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +397 -167
- package/script/midy-GM2.d.ts +61 -38
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +617 -278
- package/script/midy-GMLite.d.ts +49 -23
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +361 -156
- package/script/midy.d.ts +63 -45
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +699 -354
package/script/midy-GM2.js
CHANGED
|
@@ -4,7 +4,7 @@ exports.MidyGM2 = void 0;
|
|
|
4
4
|
const midi_file_1 = require("midi-file");
|
|
5
5
|
const soundfont_parser_1 = require("@marmooo/soundfont-parser");
|
|
6
6
|
class Note {
|
|
7
|
-
constructor(noteNumber, velocity, startTime,
|
|
7
|
+
constructor(noteNumber, velocity, startTime, voice, voiceParams) {
|
|
8
8
|
Object.defineProperty(this, "bufferSource", {
|
|
9
9
|
enumerable: true,
|
|
10
10
|
configurable: true,
|
|
@@ -65,12 +65,106 @@ class Note {
|
|
|
65
65
|
writable: true,
|
|
66
66
|
value: void 0
|
|
67
67
|
});
|
|
68
|
+
Object.defineProperty(this, "portamento", {
|
|
69
|
+
enumerable: true,
|
|
70
|
+
configurable: true,
|
|
71
|
+
writable: true,
|
|
72
|
+
value: void 0
|
|
73
|
+
});
|
|
68
74
|
this.noteNumber = noteNumber;
|
|
69
75
|
this.velocity = velocity;
|
|
70
76
|
this.startTime = startTime;
|
|
71
|
-
this.
|
|
77
|
+
this.voice = voice;
|
|
78
|
+
this.voiceParams = voiceParams;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// normalized to 0-1 for use with the SF2 modulator model
|
|
82
|
+
const defaultControllerState = {
|
|
83
|
+
noteOnVelocity: { type: 2, defaultValue: 0 },
|
|
84
|
+
noteOnKeyNumber: { type: 3, defaultValue: 0 },
|
|
85
|
+
polyPressure: { type: 10, defaultValue: 0 },
|
|
86
|
+
channelPressure: { type: 13, defaultValue: 0 },
|
|
87
|
+
pitchWheel: { type: 14, defaultValue: 8192 / 16383 },
|
|
88
|
+
pitchWheelSensitivity: { type: 16, defaultValue: 2 / 128 },
|
|
89
|
+
link: { type: 127, defaultValue: 0 },
|
|
90
|
+
// bankMSB: { type: 128 + 0, defaultValue: 121, },
|
|
91
|
+
modulationDepth: { type: 128 + 1, defaultValue: 0 },
|
|
92
|
+
portamentoTime: { type: 128 + 5, defaultValue: 0 },
|
|
93
|
+
// dataMSB: { type: 128 + 6, defaultValue: 0, },
|
|
94
|
+
volume: { type: 128 + 7, defaultValue: 100 / 127 },
|
|
95
|
+
pan: { type: 128 + 10, defaultValue: 0.5 },
|
|
96
|
+
expression: { type: 128 + 11, defaultValue: 1 },
|
|
97
|
+
// bankLSB: { type: 128 + 32, defaultValue: 0, },
|
|
98
|
+
// dataLSB: { type: 128 + 38, defaultValue: 0, },
|
|
99
|
+
sustainPedal: { type: 128 + 64, defaultValue: 0 },
|
|
100
|
+
portamento: { type: 128 + 65, defaultValue: 0 },
|
|
101
|
+
sostenutoPedal: { type: 128 + 66, defaultValue: 0 },
|
|
102
|
+
softPedal: { type: 128 + 67, defaultValue: 0 },
|
|
103
|
+
filterResonance: { type: 128 + 71, defaultValue: 0.5 },
|
|
104
|
+
releaseTime: { type: 128 + 72, defaultValue: 0.5 },
|
|
105
|
+
attackTime: { type: 128 + 73, defaultValue: 0.5 },
|
|
106
|
+
brightness: { type: 128 + 74, defaultValue: 0.5 },
|
|
107
|
+
decayTime: { type: 128 + 75, defaultValue: 0.5 },
|
|
108
|
+
vibratoRate: { type: 128 + 76, defaultValue: 0.5 },
|
|
109
|
+
vibratoDepth: { type: 128 + 77, defaultValue: 0.5 },
|
|
110
|
+
vibratoDelay: { type: 128 + 78, defaultValue: 0.5 },
|
|
111
|
+
reverbSendLevel: { type: 128 + 91, defaultValue: 0 },
|
|
112
|
+
chorusSendLevel: { type: 128 + 93, defaultValue: 0 },
|
|
113
|
+
// dataIncrement: { type: 128 + 96, defaultValue: 0 },
|
|
114
|
+
// dataDecrement: { type: 128 + 97, defaultValue: 0 },
|
|
115
|
+
// rpnLSB: { type: 128 + 100, defaultValue: 127 },
|
|
116
|
+
// rpnMSB: { type: 128 + 101, defaultValue: 127 },
|
|
117
|
+
// allSoundOff: { type: 128 + 120, defaultValue: 0 },
|
|
118
|
+
// resetAllControllers: { type: 128 + 121, defaultValue: 0 },
|
|
119
|
+
// allNotesOff: { type: 128 + 123, defaultValue: 0 },
|
|
120
|
+
// omniOff: { type: 128 + 124, defaultValue: 0 },
|
|
121
|
+
// omniOn: { type: 128 + 125, defaultValue: 0 },
|
|
122
|
+
// monoOn: { type: 128 + 126, defaultValue: 0 },
|
|
123
|
+
// polyOn: { type: 128 + 127, defaultValue: 0 },
|
|
124
|
+
};
|
|
125
|
+
class ControllerState {
|
|
126
|
+
constructor() {
|
|
127
|
+
Object.defineProperty(this, "array", {
|
|
128
|
+
enumerable: true,
|
|
129
|
+
configurable: true,
|
|
130
|
+
writable: true,
|
|
131
|
+
value: new Float32Array(256)
|
|
132
|
+
});
|
|
133
|
+
const entries = Object.entries(defaultControllerState);
|
|
134
|
+
for (const [name, { type, defaultValue }] of entries) {
|
|
135
|
+
this.array[type] = defaultValue;
|
|
136
|
+
Object.defineProperty(this, name, {
|
|
137
|
+
get: () => this.array[type],
|
|
138
|
+
set: (value) => this.array[type] = value,
|
|
139
|
+
enumerable: true,
|
|
140
|
+
configurable: true,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
72
143
|
}
|
|
73
144
|
}
|
|
145
|
+
const filterEnvelopeKeys = [
|
|
146
|
+
"modEnvToPitch",
|
|
147
|
+
"initialFilterFc",
|
|
148
|
+
"modEnvToFilterFc",
|
|
149
|
+
"modDelay",
|
|
150
|
+
"modAttack",
|
|
151
|
+
"modHold",
|
|
152
|
+
"modDecay",
|
|
153
|
+
"modSustain",
|
|
154
|
+
"modRelease",
|
|
155
|
+
"playbackRate",
|
|
156
|
+
];
|
|
157
|
+
const filterEnvelopeKeySet = new Set(filterEnvelopeKeys);
|
|
158
|
+
const volumeEnvelopeKeys = [
|
|
159
|
+
"volDelay",
|
|
160
|
+
"volAttack",
|
|
161
|
+
"volHold",
|
|
162
|
+
"volDecay",
|
|
163
|
+
"volSustain",
|
|
164
|
+
"volRelease",
|
|
165
|
+
"initialAttenuation",
|
|
166
|
+
];
|
|
167
|
+
const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
|
|
74
168
|
class MidyGM2 {
|
|
75
169
|
constructor(audioContext, options = this.defaultOptions) {
|
|
76
170
|
Object.defineProperty(this, "ticksPerBeat", {
|
|
@@ -251,6 +345,7 @@ class MidyGM2 {
|
|
|
251
345
|
this.audioContext = audioContext;
|
|
252
346
|
this.options = { ...this.defaultOptions, ...options };
|
|
253
347
|
this.masterGain = new GainNode(audioContext);
|
|
348
|
+
this.voiceParamsHandlers = this.createVoiceParamsHandlers();
|
|
254
349
|
this.controlChangeHandlers = this.createControlChangeHandlers();
|
|
255
350
|
this.channels = this.createChannels(audioContext);
|
|
256
351
|
this.reverbEffect = this.options.reverbAlgorithm(audioContext);
|
|
@@ -297,7 +392,7 @@ class MidyGM2 {
|
|
|
297
392
|
this.totalTime = this.calcTotalTime();
|
|
298
393
|
}
|
|
299
394
|
setChannelAudioNodes(audioContext) {
|
|
300
|
-
const { gainLeft, gainRight } = this.panToGain(
|
|
395
|
+
const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
|
|
301
396
|
const gainL = new GainNode(audioContext, { gain: gainLeft });
|
|
302
397
|
const gainR = new GainNode(audioContext, { gain: gainRight });
|
|
303
398
|
const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
|
|
@@ -314,7 +409,7 @@ class MidyGM2 {
|
|
|
314
409
|
const channels = Array.from({ length: 16 }, () => {
|
|
315
410
|
return {
|
|
316
411
|
...this.constructor.channelSettings,
|
|
317
|
-
|
|
412
|
+
state: new ControllerState(),
|
|
318
413
|
...this.setChannelAudioNodes(audioContext),
|
|
319
414
|
scheduledNotes: new Map(),
|
|
320
415
|
sostenutoNotes: new Map(),
|
|
@@ -325,11 +420,11 @@ class MidyGM2 {
|
|
|
325
420
|
});
|
|
326
421
|
return channels;
|
|
327
422
|
}
|
|
328
|
-
async createNoteBuffer(
|
|
329
|
-
const sampleStart =
|
|
330
|
-
const sampleEnd =
|
|
423
|
+
async createNoteBuffer(voiceParams, isSF3) {
|
|
424
|
+
const sampleStart = voiceParams.start;
|
|
425
|
+
const sampleEnd = voiceParams.sample.length + voiceParams.end;
|
|
331
426
|
if (isSF3) {
|
|
332
|
-
const sample =
|
|
427
|
+
const sample = voiceParams.sample;
|
|
333
428
|
const start = sample.byteOffset + sampleStart;
|
|
334
429
|
const end = sample.byteOffset + sampleEnd;
|
|
335
430
|
const buffer = sample.buffer.slice(start, end);
|
|
@@ -337,14 +432,14 @@ class MidyGM2 {
|
|
|
337
432
|
return audioBuffer;
|
|
338
433
|
}
|
|
339
434
|
else {
|
|
340
|
-
const sample =
|
|
435
|
+
const sample = voiceParams.sample;
|
|
341
436
|
const start = sample.byteOffset + sampleStart;
|
|
342
437
|
const end = sample.byteOffset + sampleEnd;
|
|
343
438
|
const buffer = sample.buffer.slice(start, end);
|
|
344
439
|
const audioBuffer = new AudioBuffer({
|
|
345
440
|
numberOfChannels: 1,
|
|
346
441
|
length: sample.length,
|
|
347
|
-
sampleRate:
|
|
442
|
+
sampleRate: voiceParams.sampleRate,
|
|
348
443
|
});
|
|
349
444
|
const channelData = audioBuffer.getChannelData(0);
|
|
350
445
|
const int16Array = new Int16Array(buffer);
|
|
@@ -354,15 +449,14 @@ class MidyGM2 {
|
|
|
354
449
|
return audioBuffer;
|
|
355
450
|
}
|
|
356
451
|
}
|
|
357
|
-
async createNoteBufferNode(
|
|
452
|
+
async createNoteBufferNode(voiceParams, isSF3) {
|
|
358
453
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
359
|
-
const audioBuffer = await this.createNoteBuffer(
|
|
454
|
+
const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
|
|
360
455
|
bufferSource.buffer = audioBuffer;
|
|
361
|
-
bufferSource.loop =
|
|
456
|
+
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
362
457
|
if (bufferSource.loop) {
|
|
363
|
-
bufferSource.loopStart =
|
|
364
|
-
|
|
365
|
-
bufferSource.loopEnd = instrumentKey.loopEnd / instrumentKey.sampleRate;
|
|
458
|
+
bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
|
|
459
|
+
bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
|
|
366
460
|
}
|
|
367
461
|
return bufferSource;
|
|
368
462
|
}
|
|
@@ -416,7 +510,7 @@ class MidyGM2 {
|
|
|
416
510
|
this.handleChannelPressure(event.channel, event.amount);
|
|
417
511
|
break;
|
|
418
512
|
case "pitchBend":
|
|
419
|
-
this.setPitchBend(event.channel, event.value);
|
|
513
|
+
this.setPitchBend(event.channel, event.value + 8192);
|
|
420
514
|
break;
|
|
421
515
|
case "sysEx":
|
|
422
516
|
this.handleSysEx(event.data);
|
|
@@ -807,63 +901,94 @@ class MidyGM2 {
|
|
|
807
901
|
cbToRatio(cb) {
|
|
808
902
|
return Math.pow(10, cb / 200);
|
|
809
903
|
}
|
|
904
|
+
rateToCent(rate) {
|
|
905
|
+
return 1200 * Math.log2(rate);
|
|
906
|
+
}
|
|
907
|
+
centToRate(cent) {
|
|
908
|
+
return Math.pow(2, cent / 1200);
|
|
909
|
+
}
|
|
810
910
|
centToHz(cent) {
|
|
811
|
-
return 8.176 *
|
|
911
|
+
return 8.176 * this.centToRate(cent);
|
|
812
912
|
}
|
|
813
|
-
|
|
913
|
+
calcDetune(channel, note) {
|
|
814
914
|
const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
|
|
815
915
|
const channelTuning = channel.coarseTuning + channel.fineTuning;
|
|
816
|
-
const
|
|
817
|
-
|
|
916
|
+
const scaleOctaveTuning = channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
917
|
+
const tuning = masterTuning + channelTuning + scaleOctaveTuning;
|
|
918
|
+
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
919
|
+
const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
|
|
920
|
+
const pitch = pitchWheel * pitchWheelSensitivity;
|
|
921
|
+
return tuning + pitch;
|
|
922
|
+
}
|
|
923
|
+
calcNoteDetune(channel, note) {
|
|
924
|
+
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
818
925
|
}
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
926
|
+
updateDetune(channel) {
|
|
927
|
+
const now = this.audioContext.currentTime;
|
|
928
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
929
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
930
|
+
const note = noteList[i];
|
|
931
|
+
if (!note)
|
|
932
|
+
continue;
|
|
933
|
+
const noteDetune = this.calcNoteDetune(channel, note);
|
|
934
|
+
const detune = channel.detune + noteDetune;
|
|
935
|
+
note.bufferSource.detune
|
|
936
|
+
.cancelScheduledValues(now)
|
|
937
|
+
.setValueAtTime(detune, now);
|
|
938
|
+
}
|
|
939
|
+
});
|
|
822
940
|
}
|
|
823
941
|
setPortamentoStartVolumeEnvelope(channel, note) {
|
|
824
|
-
const
|
|
825
|
-
const
|
|
826
|
-
const
|
|
827
|
-
const
|
|
828
|
-
const
|
|
942
|
+
const now = this.audioContext.currentTime;
|
|
943
|
+
const { voiceParams, startTime } = note;
|
|
944
|
+
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
945
|
+
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
946
|
+
const volDelay = startTime + voiceParams.volDelay;
|
|
947
|
+
const portamentoTime = volDelay + channel.state.portamentoTime;
|
|
829
948
|
note.volumeNode.gain
|
|
830
|
-
.cancelScheduledValues(
|
|
949
|
+
.cancelScheduledValues(now)
|
|
831
950
|
.setValueAtTime(0, volDelay)
|
|
832
951
|
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
833
952
|
}
|
|
834
953
|
setVolumeEnvelope(note) {
|
|
835
|
-
const
|
|
836
|
-
const
|
|
837
|
-
const
|
|
838
|
-
const
|
|
839
|
-
const
|
|
840
|
-
const
|
|
841
|
-
const
|
|
954
|
+
const now = this.audioContext.currentTime;
|
|
955
|
+
const { voiceParams, startTime } = note;
|
|
956
|
+
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
957
|
+
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
958
|
+
const volDelay = startTime + voiceParams.volDelay;
|
|
959
|
+
const volAttack = volDelay + voiceParams.volAttack;
|
|
960
|
+
const volHold = volAttack + voiceParams.volHold;
|
|
961
|
+
const volDecay = volHold + voiceParams.volDecay;
|
|
842
962
|
note.volumeNode.gain
|
|
843
|
-
.cancelScheduledValues(
|
|
963
|
+
.cancelScheduledValues(now)
|
|
844
964
|
.setValueAtTime(0, startTime)
|
|
845
965
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
846
966
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
847
967
|
.setValueAtTime(attackVolume, volHold)
|
|
848
968
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
849
969
|
}
|
|
850
|
-
|
|
851
|
-
const
|
|
852
|
-
const
|
|
853
|
-
|
|
970
|
+
setPitchEnvelope(note) {
|
|
971
|
+
const now = this.audioContext.currentTime;
|
|
972
|
+
const { voiceParams } = note;
|
|
973
|
+
const baseRate = voiceParams.playbackRate;
|
|
974
|
+
note.bufferSource.playbackRate
|
|
975
|
+
.cancelScheduledValues(now)
|
|
976
|
+
.setValueAtTime(baseRate, now);
|
|
977
|
+
const modEnvToPitch = voiceParams.modEnvToPitch;
|
|
854
978
|
if (modEnvToPitch === 0)
|
|
855
979
|
return;
|
|
856
|
-
const basePitch =
|
|
857
|
-
const peekPitch =
|
|
858
|
-
const
|
|
859
|
-
const
|
|
860
|
-
const
|
|
861
|
-
const
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
.
|
|
865
|
-
.
|
|
866
|
-
.
|
|
980
|
+
const basePitch = this.rateToCent(baseRate);
|
|
981
|
+
const peekPitch = basePitch + modEnvToPitch;
|
|
982
|
+
const peekRate = this.centToRate(peekPitch);
|
|
983
|
+
const modDelay = startTime + voiceParams.modDelay;
|
|
984
|
+
const modAttack = modDelay + voiceParams.modAttack;
|
|
985
|
+
const modHold = modAttack + voiceParams.modHold;
|
|
986
|
+
const modDecay = modHold + voiceParams.modDecay;
|
|
987
|
+
note.bufferSource.playbackRate
|
|
988
|
+
.setValueAtTime(baseRate, modDelay)
|
|
989
|
+
.exponentialRampToValueAtTime(peekRate, modAttack)
|
|
990
|
+
.setValueAtTime(peekRate, modHold)
|
|
991
|
+
.linearRampToValueAtTime(baseRate, modDecay);
|
|
867
992
|
}
|
|
868
993
|
clampCutoffFrequency(frequency) {
|
|
869
994
|
const minFrequency = 20; // min Hz of initialFilterFc
|
|
@@ -871,42 +996,46 @@ class MidyGM2 {
|
|
|
871
996
|
return Math.max(minFrequency, Math.min(frequency, maxFrequency));
|
|
872
997
|
}
|
|
873
998
|
setPortamentoStartFilterEnvelope(channel, note) {
|
|
874
|
-
const
|
|
999
|
+
const now = this.audioContext.currentTime;
|
|
1000
|
+
const state = channel.state;
|
|
1001
|
+
const { voiceParams, noteNumber, startTime } = note;
|
|
875
1002
|
const softPedalFactor = 1 -
|
|
876
|
-
(0.1 + (noteNumber / 127) * 0.2) *
|
|
877
|
-
const baseFreq = this.centToHz(
|
|
1003
|
+
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1004
|
+
const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
|
|
878
1005
|
softPedalFactor;
|
|
879
|
-
const peekFreq = this.centToHz(
|
|
1006
|
+
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor;
|
|
880
1007
|
const sustainFreq = baseFreq +
|
|
881
|
-
(peekFreq - baseFreq) * (1 -
|
|
1008
|
+
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
882
1009
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
883
1010
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
884
|
-
const portamentoTime = startTime + channel.portamentoTime;
|
|
885
|
-
const modDelay = startTime +
|
|
1011
|
+
const portamentoTime = startTime + channel.state.portamentoTime;
|
|
1012
|
+
const modDelay = startTime + voiceParams.modDelay;
|
|
886
1013
|
note.filterNode.frequency
|
|
887
|
-
.cancelScheduledValues(
|
|
1014
|
+
.cancelScheduledValues(now)
|
|
888
1015
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
889
1016
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
890
1017
|
.linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
|
|
891
1018
|
}
|
|
892
1019
|
setFilterEnvelope(channel, note) {
|
|
893
|
-
const
|
|
1020
|
+
const now = this.audioContext.currentTime;
|
|
1021
|
+
const state = channel.state;
|
|
1022
|
+
const { voiceParams, noteNumber, startTime } = note;
|
|
894
1023
|
const softPedalFactor = 1 -
|
|
895
|
-
(0.1 + (noteNumber / 127) * 0.2) *
|
|
896
|
-
const baseFreq = this.centToHz(
|
|
1024
|
+
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1025
|
+
const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
|
|
897
1026
|
softPedalFactor;
|
|
898
|
-
const peekFreq = this.centToHz(
|
|
1027
|
+
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor;
|
|
899
1028
|
const sustainFreq = baseFreq +
|
|
900
|
-
(peekFreq - baseFreq) * (1 -
|
|
1029
|
+
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
901
1030
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
902
1031
|
const adjustedPeekFreq = this.clampCutoffFrequency(peekFreq);
|
|
903
1032
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
904
|
-
const modDelay = startTime +
|
|
905
|
-
const modAttack = modDelay +
|
|
906
|
-
const modHold = modAttack +
|
|
907
|
-
const modDecay = modHold +
|
|
1033
|
+
const modDelay = startTime + voiceParams.modDelay;
|
|
1034
|
+
const modAttack = modDelay + voiceParams.modAttack;
|
|
1035
|
+
const modHold = modAttack + voiceParams.modHold;
|
|
1036
|
+
const modDecay = modHold + voiceParams.modDecay;
|
|
908
1037
|
note.filterNode.frequency
|
|
909
|
-
.cancelScheduledValues(
|
|
1038
|
+
.cancelScheduledValues(now)
|
|
910
1039
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
911
1040
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
912
1041
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
@@ -914,25 +1043,18 @@ class MidyGM2 {
|
|
|
914
1043
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
915
1044
|
}
|
|
916
1045
|
startModulation(channel, note, startTime) {
|
|
917
|
-
const {
|
|
918
|
-
const { modLfoToPitch, modLfoToVolume } = instrumentKey;
|
|
1046
|
+
const { voiceParams } = note;
|
|
919
1047
|
note.modulationLFO = new OscillatorNode(this.audioContext, {
|
|
920
|
-
frequency: this.centToHz(
|
|
1048
|
+
frequency: this.centToHz(voiceParams.freqModLFO),
|
|
921
1049
|
});
|
|
922
1050
|
note.filterDepth = new GainNode(this.audioContext, {
|
|
923
|
-
gain:
|
|
924
|
-
});
|
|
925
|
-
const modulationDepth = Math.abs(modLfoToPitch) + channel.modulationDepth;
|
|
926
|
-
const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
|
|
927
|
-
note.modulationDepth = new GainNode(this.audioContext, {
|
|
928
|
-
gain: modulationDepth * modulationDepthSign,
|
|
1051
|
+
gain: voiceParams.modLfoToFilterFc,
|
|
929
1052
|
});
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
note.volumeDepth = new GainNode(this.audioContext
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
note.modulationLFO.start(startTime + instrumentKey.delayModLFO);
|
|
1053
|
+
note.modulationDepth = new GainNode(this.audioContext);
|
|
1054
|
+
this.setModLfoToPitch(channel, note);
|
|
1055
|
+
note.volumeDepth = new GainNode(this.audioContext);
|
|
1056
|
+
this.setModLfoToVolume(note);
|
|
1057
|
+
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
936
1058
|
note.modulationLFO.connect(note.filterDepth);
|
|
937
1059
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
938
1060
|
note.modulationLFO.connect(note.modulationDepth);
|
|
@@ -941,67 +1063,57 @@ class MidyGM2 {
|
|
|
941
1063
|
note.volumeDepth.connect(note.volumeNode.gain);
|
|
942
1064
|
}
|
|
943
1065
|
startVibrato(channel, note, startTime) {
|
|
944
|
-
const {
|
|
945
|
-
const
|
|
1066
|
+
const { voiceParams } = note;
|
|
1067
|
+
const state = channel.state;
|
|
946
1068
|
note.vibratoLFO = new OscillatorNode(this.audioContext, {
|
|
947
|
-
frequency: this.centToHz(
|
|
948
|
-
|
|
949
|
-
});
|
|
950
|
-
const vibratoDepth = Math.abs(vibLfoToPitch) * channel.vibratoDepth;
|
|
951
|
-
const vibratoDepthSign = 0 < vibLfoToPitch;
|
|
952
|
-
note.vibratoDepth = new GainNode(this.audioContext, {
|
|
953
|
-
gain: vibratoDepth * vibratoDepthSign,
|
|
1069
|
+
frequency: this.centToHz(voiceParams.freqVibLFO) *
|
|
1070
|
+
state.vibratoRate,
|
|
954
1071
|
});
|
|
955
|
-
note.vibratoLFO.start(startTime +
|
|
1072
|
+
note.vibratoLFO.start(startTime + voiceParams.delayVibLFO * state.vibratoDelay * 2);
|
|
1073
|
+
note.vibratoDepth = new GainNode(this.audioContext);
|
|
1074
|
+
this.setVibLfoToPitch(channel, note);
|
|
956
1075
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
957
1076
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
958
1077
|
}
|
|
959
|
-
async createNote(channel,
|
|
960
|
-
const
|
|
961
|
-
const
|
|
962
|
-
|
|
1078
|
+
async createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3) {
|
|
1079
|
+
const state = channel.state;
|
|
1080
|
+
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1081
|
+
const voiceParams = voice.getAllParams(controllerState);
|
|
1082
|
+
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1083
|
+
note.bufferSource = await this.createNoteBufferNode(voiceParams, isSF3);
|
|
963
1084
|
note.volumeNode = new GainNode(this.audioContext);
|
|
964
1085
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
965
1086
|
type: "lowpass",
|
|
966
|
-
Q:
|
|
1087
|
+
Q: voiceParams.initialFilterQ / 10, // dB
|
|
967
1088
|
});
|
|
968
1089
|
if (portamento) {
|
|
1090
|
+
note.portamento = true;
|
|
969
1091
|
this.setPortamentoStartVolumeEnvelope(channel, note);
|
|
970
1092
|
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
971
1093
|
}
|
|
972
1094
|
else {
|
|
973
|
-
|
|
1095
|
+
note.portamento = false;
|
|
1096
|
+
this.setVolumeEnvelope(channel, note);
|
|
974
1097
|
this.setFilterEnvelope(channel, note);
|
|
975
1098
|
}
|
|
976
|
-
if (0 <
|
|
1099
|
+
if (0 < state.vibratoDepth) {
|
|
977
1100
|
this.startVibrato(channel, note, startTime);
|
|
978
1101
|
}
|
|
979
|
-
|
|
980
|
-
|
|
1102
|
+
this.setPitchEnvelope(note);
|
|
1103
|
+
if (0 < state.modulationDepth) {
|
|
981
1104
|
this.startModulation(channel, note, startTime);
|
|
982
1105
|
}
|
|
983
|
-
else {
|
|
984
|
-
note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
|
|
985
|
-
}
|
|
986
1106
|
if (this.mono && channel.currentBufferSource) {
|
|
987
1107
|
channel.currentBufferSource.stop(startTime);
|
|
988
1108
|
channel.currentBufferSource = note.bufferSource;
|
|
989
1109
|
}
|
|
990
1110
|
note.bufferSource.connect(note.filterNode);
|
|
991
1111
|
note.filterNode.connect(note.volumeNode);
|
|
992
|
-
if (0 < channel.
|
|
993
|
-
|
|
994
|
-
gain: instrumentKey.reverbEffectsSend,
|
|
995
|
-
});
|
|
996
|
-
note.volumeNode.connect(note.reverbEffectsSend);
|
|
997
|
-
note.reverbEffectsSend.connect(this.reverbEffect.input);
|
|
1112
|
+
if (0 < channel.chorusSendLevel) {
|
|
1113
|
+
this.setChorusEffectsSend(channel, note, 0);
|
|
998
1114
|
}
|
|
999
|
-
if (0 < channel.
|
|
1000
|
-
|
|
1001
|
-
gain: instrumentKey.chorusEffectsSend,
|
|
1002
|
-
});
|
|
1003
|
-
note.volumeNode.connect(note.chorusEffectsSend);
|
|
1004
|
-
note.chorusEffectsSend.connect(this.chorusEffect.input);
|
|
1115
|
+
if (0 < channel.reverbSendLevel) {
|
|
1116
|
+
this.setReverbEffectsSend(channel, note, 0);
|
|
1005
1117
|
}
|
|
1006
1118
|
note.bufferSource.start(startTime);
|
|
1007
1119
|
return note;
|
|
@@ -1023,16 +1135,16 @@ class MidyGM2 {
|
|
|
1023
1135
|
return;
|
|
1024
1136
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
1025
1137
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
1026
|
-
const
|
|
1027
|
-
if (!
|
|
1138
|
+
const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
|
|
1139
|
+
if (!voice)
|
|
1028
1140
|
return;
|
|
1029
|
-
const note = await this.createNote(channel,
|
|
1141
|
+
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
|
|
1030
1142
|
note.volumeNode.connect(channel.gainL);
|
|
1031
1143
|
note.volumeNode.connect(channel.gainR);
|
|
1032
|
-
if (channel.sostenutoPedal) {
|
|
1144
|
+
if (channel.state.sostenutoPedal) {
|
|
1033
1145
|
channel.sostenutoNotes.set(noteNumber, note);
|
|
1034
1146
|
}
|
|
1035
|
-
const exclusiveClass =
|
|
1147
|
+
const exclusiveClass = note.voiceParams.exclusiveClass;
|
|
1036
1148
|
if (exclusiveClass !== 0) {
|
|
1037
1149
|
if (this.exclusiveClassMap.has(exclusiveClass)) {
|
|
1038
1150
|
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
@@ -1094,8 +1206,9 @@ class MidyGM2 {
|
|
|
1094
1206
|
}
|
|
1095
1207
|
scheduleNoteRelease(channelNumber, noteNumber, _velocity, endTime, portamentoNoteNumber, force) {
|
|
1096
1208
|
const channel = this.channels[channelNumber];
|
|
1209
|
+
const state = channel.state;
|
|
1097
1210
|
if (!force) {
|
|
1098
|
-
if (
|
|
1211
|
+
if (0.5 < state.sustainPedal)
|
|
1099
1212
|
return;
|
|
1100
1213
|
if (channel.sostenutoNotes.has(noteNumber))
|
|
1101
1214
|
return;
|
|
@@ -1110,8 +1223,8 @@ class MidyGM2 {
|
|
|
1110
1223
|
if (note.ending)
|
|
1111
1224
|
continue;
|
|
1112
1225
|
if (portamentoNoteNumber === undefined) {
|
|
1113
|
-
const volRelease = endTime + note.
|
|
1114
|
-
const modRelease = endTime + note.
|
|
1226
|
+
const volRelease = endTime + note.voiceParams.volRelease;
|
|
1227
|
+
const modRelease = endTime + note.voiceParams.modRelease;
|
|
1115
1228
|
note.filterNode.frequency
|
|
1116
1229
|
.cancelScheduledValues(endTime)
|
|
1117
1230
|
.linearRampToValueAtTime(0, modRelease);
|
|
@@ -1119,12 +1232,13 @@ class MidyGM2 {
|
|
|
1119
1232
|
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
1120
1233
|
}
|
|
1121
1234
|
else {
|
|
1122
|
-
const portamentoTime = endTime +
|
|
1123
|
-
const
|
|
1124
|
-
const
|
|
1125
|
-
|
|
1235
|
+
const portamentoTime = endTime + state.portamentoTime;
|
|
1236
|
+
const deltaNote = portamentoNoteNumber - noteNumber;
|
|
1237
|
+
const baseRate = note.voiceParams.playbackRate;
|
|
1238
|
+
const targetRate = baseRate * Math.pow(2, deltaNote / 12);
|
|
1239
|
+
note.bufferSource.playbackRate
|
|
1126
1240
|
.cancelScheduledValues(endTime)
|
|
1127
|
-
.linearRampToValueAtTime(
|
|
1241
|
+
.linearRampToValueAtTime(targetRate, portamentoTime);
|
|
1128
1242
|
return this.stopNote(endTime, portamentoTime, scheduledNotes, i);
|
|
1129
1243
|
}
|
|
1130
1244
|
}
|
|
@@ -1137,7 +1251,7 @@ class MidyGM2 {
|
|
|
1137
1251
|
const velocity = halfVelocity * 2;
|
|
1138
1252
|
const channel = this.channels[channelNumber];
|
|
1139
1253
|
const promises = [];
|
|
1140
|
-
channel.sustainPedal =
|
|
1254
|
+
channel.state.sustainPedal = halfVelocity;
|
|
1141
1255
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1142
1256
|
for (let i = 0; i < noteList.length; i++) {
|
|
1143
1257
|
const note = noteList[i];
|
|
@@ -1154,7 +1268,7 @@ class MidyGM2 {
|
|
|
1154
1268
|
const velocity = halfVelocity * 2;
|
|
1155
1269
|
const channel = this.channels[channelNumber];
|
|
1156
1270
|
const promises = [];
|
|
1157
|
-
channel.sostenutoPedal =
|
|
1271
|
+
channel.state.sostenutoPedal = 0;
|
|
1158
1272
|
channel.sostenutoNotes.forEach((activeNote) => {
|
|
1159
1273
|
const { noteNumber } = activeNote;
|
|
1160
1274
|
const promise = this.releaseNote(channelNumber, noteNumber, velocity);
|
|
@@ -1202,18 +1316,233 @@ class MidyGM2 {
|
|
|
1202
1316
|
.setValueAtTime(gain * pressure, now);
|
|
1203
1317
|
});
|
|
1204
1318
|
}
|
|
1319
|
+
// this.applyVoiceParams(channel, 13);
|
|
1205
1320
|
}
|
|
1206
1321
|
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
1207
|
-
const pitchBend = msb * 128 + lsb
|
|
1322
|
+
const pitchBend = msb * 128 + lsb;
|
|
1208
1323
|
this.setPitchBend(channelNumber, pitchBend);
|
|
1209
1324
|
}
|
|
1210
|
-
setPitchBend(channelNumber,
|
|
1325
|
+
setPitchBend(channelNumber, value) {
|
|
1211
1326
|
const channel = this.channels[channelNumber];
|
|
1212
|
-
const
|
|
1213
|
-
|
|
1214
|
-
const
|
|
1215
|
-
|
|
1216
|
-
|
|
1327
|
+
const state = channel.state;
|
|
1328
|
+
const prev = state.pitchWheel * 2 - 1;
|
|
1329
|
+
const next = (value - 8192) / 8192;
|
|
1330
|
+
state.pitchWheel = value / 16383;
|
|
1331
|
+
channel.detune += (next - prev) * state.pitchWheelSensitivity * 12800;
|
|
1332
|
+
this.updateDetune(channel);
|
|
1333
|
+
this.applyVoiceParams(channel, 14);
|
|
1334
|
+
}
|
|
1335
|
+
setModLfoToPitch(channel, note) {
|
|
1336
|
+
const now = this.audioContext.currentTime;
|
|
1337
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch;
|
|
1338
|
+
const modulationDepth = Math.abs(modLfoToPitch) +
|
|
1339
|
+
channel.state.modulationDepth;
|
|
1340
|
+
const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
|
|
1341
|
+
note.modulationDepth.gain
|
|
1342
|
+
.cancelScheduledValues(now)
|
|
1343
|
+
.setValueAtTime(modulationDepth * modulationDepthSign, now);
|
|
1344
|
+
}
|
|
1345
|
+
setVibLfoToPitch(channel, note) {
|
|
1346
|
+
const now = this.audioContext.currentTime;
|
|
1347
|
+
const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
|
|
1348
|
+
const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
|
|
1349
|
+
2;
|
|
1350
|
+
const vibratoDepthSign = 0 < vibLfoToPitch;
|
|
1351
|
+
note.vibratoDepth.gain
|
|
1352
|
+
.cancelScheduledValues(now)
|
|
1353
|
+
.setValueAtTime(vibratoDepth * vibratoDepthSign, now);
|
|
1354
|
+
}
|
|
1355
|
+
setModLfoToFilterFc(note) {
|
|
1356
|
+
const now = this.audioContext.currentTime;
|
|
1357
|
+
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
|
|
1358
|
+
note.filterDepth.gain
|
|
1359
|
+
.cancelScheduledValues(now)
|
|
1360
|
+
.setValueAtTime(modLfoToFilterFc, now);
|
|
1361
|
+
}
|
|
1362
|
+
setModLfoToVolume(note) {
|
|
1363
|
+
const now = this.audioContext.currentTime;
|
|
1364
|
+
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1365
|
+
const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1366
|
+
const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
|
|
1367
|
+
note.volumeDepth.gain
|
|
1368
|
+
.cancelScheduledValues(now)
|
|
1369
|
+
.setValueAtTime(volumeDepth * volumeDepthSign, now);
|
|
1370
|
+
}
|
|
1371
|
+
setChorusEffectsSend(note, prevValue) {
|
|
1372
|
+
if (0 < prevValue) {
|
|
1373
|
+
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1374
|
+
const now = this.audioContext.currentTime;
|
|
1375
|
+
const value = note.voiceParams.chorusEffectsSend;
|
|
1376
|
+
note.chorusEffectsSend.gain
|
|
1377
|
+
.cancelScheduledValues(now)
|
|
1378
|
+
.setValueAtTime(value, now);
|
|
1379
|
+
}
|
|
1380
|
+
else {
|
|
1381
|
+
note.chorusEffectsSend.disconnect();
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
else {
|
|
1385
|
+
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1386
|
+
if (!note.chorusEffectsSend) {
|
|
1387
|
+
note.chorusEffectsSend = new GainNode(this.audioContext, {
|
|
1388
|
+
gain: note.voiceParams.chorusEffectsSend,
|
|
1389
|
+
});
|
|
1390
|
+
note.volumeNode.connect(note.chorusEffectsSend);
|
|
1391
|
+
}
|
|
1392
|
+
note.chorusEffectsSend.connect(this.chorusEffect.input);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
setReverbEffectsSend(note, prevValue) {
|
|
1397
|
+
if (0 < prevValue) {
|
|
1398
|
+
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1399
|
+
const now = this.audioContext.currentTime;
|
|
1400
|
+
const value = note.voiceParams.reverbEffectsSend;
|
|
1401
|
+
note.reverbEffectsSend.gain
|
|
1402
|
+
.cancelScheduledValues(now)
|
|
1403
|
+
.setValueAtTime(value, now);
|
|
1404
|
+
}
|
|
1405
|
+
else {
|
|
1406
|
+
note.reverbEffectsSend.disconnect();
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
else {
|
|
1410
|
+
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1411
|
+
if (!note.reverbEffectsSend) {
|
|
1412
|
+
note.reverbEffectsSend = new GainNode(this.audioContext, {
|
|
1413
|
+
gain: note.voiceParams.reverbEffectsSend,
|
|
1414
|
+
});
|
|
1415
|
+
note.volumeNode.connect(note.reverbEffectsSend);
|
|
1416
|
+
}
|
|
1417
|
+
note.reverbEffectsSend.connect(this.reverbEffect.input);
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
setDelayModLFO(note) {
|
|
1422
|
+
const now = this.audioContext.currentTime;
|
|
1423
|
+
const startTime = note.startTime;
|
|
1424
|
+
if (startTime < now)
|
|
1425
|
+
return;
|
|
1426
|
+
note.modulationLFO.stop(now);
|
|
1427
|
+
note.modulationLFO.start(startTime + note.voiceParams.delayModLFO);
|
|
1428
|
+
note.modulationLFO.connect(note.filterDepth);
|
|
1429
|
+
}
|
|
1430
|
+
setFreqModLFO(note) {
|
|
1431
|
+
const now = this.audioContext.currentTime;
|
|
1432
|
+
const freqModLFO = note.voiceParams.freqModLFO;
|
|
1433
|
+
note.modulationLFO.frequency
|
|
1434
|
+
.cancelScheduledValues(now)
|
|
1435
|
+
.setValueAtTime(freqModLFO, now);
|
|
1436
|
+
}
|
|
1437
|
+
createVoiceParamsHandlers() {
|
|
1438
|
+
return {
|
|
1439
|
+
modLfoToPitch: (channel, note, _prevValue) => {
|
|
1440
|
+
if (0 < channel.state.modulationDepth) {
|
|
1441
|
+
this.setModLfoToPitch(channel, note);
|
|
1442
|
+
}
|
|
1443
|
+
},
|
|
1444
|
+
vibLfoToPitch: (channel, note, _prevValue) => {
|
|
1445
|
+
if (0 < channel.state.vibratoDepth) {
|
|
1446
|
+
this.setVibLfoToPitch(channel, note);
|
|
1447
|
+
}
|
|
1448
|
+
},
|
|
1449
|
+
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1450
|
+
if (0 < channel.state.modulationDepth)
|
|
1451
|
+
this.setModLfoToFilterFc(note);
|
|
1452
|
+
},
|
|
1453
|
+
modLfoToVolume: (channel, note) => {
|
|
1454
|
+
if (0 < channel.state.modulationDepth)
|
|
1455
|
+
this.setModLfoToVolume(note);
|
|
1456
|
+
},
|
|
1457
|
+
chorusEffectsSend: (_channel, note, prevValue) => {
|
|
1458
|
+
this.setChorusEffectsSend(note, prevValue);
|
|
1459
|
+
},
|
|
1460
|
+
reverbEffectsSend: (_channel, note, prevValue) => {
|
|
1461
|
+
this.setReverbEffectsSend(note, prevValue);
|
|
1462
|
+
},
|
|
1463
|
+
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
1464
|
+
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
1465
|
+
delayVibLFO: (channel, note, prevValue) => {
|
|
1466
|
+
if (0 < channel.state.vibratoDepth) {
|
|
1467
|
+
const now = this.audioContext.currentTime;
|
|
1468
|
+
const prevStartTime = note.startTime +
|
|
1469
|
+
prevValue * channel.state.vibratoDelay * 2;
|
|
1470
|
+
if (now < prevStartTime)
|
|
1471
|
+
return;
|
|
1472
|
+
const startTime = note.startTime +
|
|
1473
|
+
value * channel.state.vibratoDelay * 2;
|
|
1474
|
+
note.vibratoLFO.stop(now);
|
|
1475
|
+
note.vibratoLFO.start(startTime);
|
|
1476
|
+
}
|
|
1477
|
+
},
|
|
1478
|
+
freqVibLFO: (channel, note, _prevValue) => {
|
|
1479
|
+
if (0 < channel.state.vibratoDepth) {
|
|
1480
|
+
const now = this.audioContext.currentTime;
|
|
1481
|
+
note.vibratoLFO.frequency
|
|
1482
|
+
.cancelScheduledValues(now)
|
|
1483
|
+
.setValueAtTime(value * sate.vibratoRate, now);
|
|
1484
|
+
}
|
|
1485
|
+
},
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
getControllerState(channel, noteNumber, velocity) {
|
|
1489
|
+
const state = new Float32Array(channel.state.array.length);
|
|
1490
|
+
state.set(channel.state.array);
|
|
1491
|
+
state[2] = velocity / 127;
|
|
1492
|
+
state[3] = noteNumber / 127;
|
|
1493
|
+
return state;
|
|
1494
|
+
}
|
|
1495
|
+
applyVoiceParams(channel, controllerType) {
|
|
1496
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1497
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1498
|
+
const note = noteList[i];
|
|
1499
|
+
if (!note)
|
|
1500
|
+
continue;
|
|
1501
|
+
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
|
|
1502
|
+
const voiceParams = note.voice.getParams(controllerType, controllerState);
|
|
1503
|
+
let appliedFilterEnvelope = false;
|
|
1504
|
+
let appliedVolumeEnvelope = false;
|
|
1505
|
+
for (const [key, value] of Object.entries(voiceParams)) {
|
|
1506
|
+
const prevValue = note.voiceParams[key];
|
|
1507
|
+
if (value === prevValue)
|
|
1508
|
+
continue;
|
|
1509
|
+
note.voiceParams[key] = value;
|
|
1510
|
+
if (key in this.voiceParamsHandlers) {
|
|
1511
|
+
this.voiceParamsHandlers[key](channel, note, prevValue);
|
|
1512
|
+
}
|
|
1513
|
+
else if (filterEnvelopeKeySet.has(key)) {
|
|
1514
|
+
if (appliedFilterEnvelope)
|
|
1515
|
+
continue;
|
|
1516
|
+
appliedFilterEnvelope = true;
|
|
1517
|
+
const noteVoiceParams = note.voiceParams;
|
|
1518
|
+
for (let i = 0; i < filterEnvelopeKeys.length; i++) {
|
|
1519
|
+
const key = filterEnvelopeKeys[i];
|
|
1520
|
+
if (key in voiceParams)
|
|
1521
|
+
noteVoiceParams[key] = voiceParams[key];
|
|
1522
|
+
}
|
|
1523
|
+
if (note.portamento) {
|
|
1524
|
+
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
1525
|
+
}
|
|
1526
|
+
else {
|
|
1527
|
+
this.setFilterEnvelope(channel, note);
|
|
1528
|
+
}
|
|
1529
|
+
this.setPitchEnvelope(note);
|
|
1530
|
+
}
|
|
1531
|
+
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1532
|
+
if (appliedVolumeEnvelope)
|
|
1533
|
+
continue;
|
|
1534
|
+
appliedVolumeEnvelope = true;
|
|
1535
|
+
const noteVoiceParams = note.voiceParams;
|
|
1536
|
+
for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
|
|
1537
|
+
const key = volumeEnvelopeKeys[i];
|
|
1538
|
+
if (key in voiceParams)
|
|
1539
|
+
noteVoiceParams[key] = voiceParams[key];
|
|
1540
|
+
}
|
|
1541
|
+
this.setVolumeEnvelope(channel, note);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
});
|
|
1217
1546
|
}
|
|
1218
1547
|
createControlChangeHandlers() {
|
|
1219
1548
|
return {
|
|
@@ -1243,13 +1572,16 @@ class MidyGM2 {
|
|
|
1243
1572
|
127: this.polyOn,
|
|
1244
1573
|
};
|
|
1245
1574
|
}
|
|
1246
|
-
handleControlChange(channelNumber,
|
|
1247
|
-
const handler = this.controlChangeHandlers[
|
|
1575
|
+
handleControlChange(channelNumber, controllerType, value) {
|
|
1576
|
+
const handler = this.controlChangeHandlers[controllerType];
|
|
1248
1577
|
if (handler) {
|
|
1249
1578
|
handler.call(this, channelNumber, value);
|
|
1579
|
+
const channel = this.channels[channelNumber];
|
|
1580
|
+
const controller = 128 + controllerType;
|
|
1581
|
+
this.applyVoiceParams(channel, controller);
|
|
1250
1582
|
}
|
|
1251
1583
|
else {
|
|
1252
|
-
console.warn(`Unsupported Control change:
|
|
1584
|
+
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
1253
1585
|
}
|
|
1254
1586
|
}
|
|
1255
1587
|
setBankMSB(channelNumber, msb) {
|
|
@@ -1263,11 +1595,10 @@ class MidyGM2 {
|
|
|
1263
1595
|
if (!note)
|
|
1264
1596
|
continue;
|
|
1265
1597
|
if (note.modulationDepth) {
|
|
1266
|
-
note.modulationDepth.gain.setValueAtTime(channel.modulationDepth, now);
|
|
1598
|
+
note.modulationDepth.gain.setValueAtTime(channel.state.modulationDepth, now);
|
|
1267
1599
|
}
|
|
1268
1600
|
else {
|
|
1269
|
-
|
|
1270
|
-
this.setPitch(note, semitoneOffset);
|
|
1601
|
+
this.setPitchEnvelope(note);
|
|
1271
1602
|
this.startModulation(channel, note, now);
|
|
1272
1603
|
}
|
|
1273
1604
|
}
|
|
@@ -1275,21 +1606,22 @@ class MidyGM2 {
|
|
|
1275
1606
|
}
|
|
1276
1607
|
setModulationDepth(channelNumber, modulation) {
|
|
1277
1608
|
const channel = this.channels[channelNumber];
|
|
1278
|
-
channel.modulationDepth = (modulation / 127) *
|
|
1609
|
+
channel.state.modulationDepth = (modulation / 127) *
|
|
1610
|
+
channel.modulationDepthRange;
|
|
1279
1611
|
this.updateModulation(channel);
|
|
1280
1612
|
}
|
|
1281
1613
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
1282
1614
|
const channel = this.channels[channelNumber];
|
|
1283
1615
|
const factor = 5 * Math.log(10) / 127;
|
|
1284
|
-
channel.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1616
|
+
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1285
1617
|
}
|
|
1286
1618
|
setVolume(channelNumber, volume) {
|
|
1287
1619
|
const channel = this.channels[channelNumber];
|
|
1288
|
-
channel.volume = volume / 127;
|
|
1620
|
+
channel.state.volume = volume / 127;
|
|
1289
1621
|
this.updateChannelVolume(channel);
|
|
1290
1622
|
}
|
|
1291
1623
|
panToGain(pan) {
|
|
1292
|
-
const theta = Math.PI / 2 * Math.max(0, pan - 1) / 126;
|
|
1624
|
+
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
1293
1625
|
return {
|
|
1294
1626
|
gainLeft: Math.cos(theta),
|
|
1295
1627
|
gainRight: Math.sin(theta),
|
|
@@ -1297,12 +1629,12 @@ class MidyGM2 {
|
|
|
1297
1629
|
}
|
|
1298
1630
|
setPan(channelNumber, pan) {
|
|
1299
1631
|
const channel = this.channels[channelNumber];
|
|
1300
|
-
channel.pan = pan;
|
|
1632
|
+
channel.state.pan = pan / 127;
|
|
1301
1633
|
this.updateChannelVolume(channel);
|
|
1302
1634
|
}
|
|
1303
1635
|
setExpression(channelNumber, expression) {
|
|
1304
1636
|
const channel = this.channels[channelNumber];
|
|
1305
|
-
channel.expression = expression / 127;
|
|
1637
|
+
channel.state.expression = expression / 127;
|
|
1306
1638
|
this.updateChannelVolume(channel);
|
|
1307
1639
|
}
|
|
1308
1640
|
setBankLSB(channelNumber, lsb) {
|
|
@@ -1314,8 +1646,9 @@ class MidyGM2 {
|
|
|
1314
1646
|
}
|
|
1315
1647
|
updateChannelVolume(channel) {
|
|
1316
1648
|
const now = this.audioContext.currentTime;
|
|
1317
|
-
const
|
|
1318
|
-
const
|
|
1649
|
+
const state = channel.state;
|
|
1650
|
+
const volume = state.volume * state.expression;
|
|
1651
|
+
const { gainLeft, gainRight } = this.panToGain(state.pan);
|
|
1319
1652
|
channel.gainL.gain
|
|
1320
1653
|
.cancelScheduledValues(now)
|
|
1321
1654
|
.setValueAtTime(volume * gainLeft, now);
|
|
@@ -1324,24 +1657,24 @@ class MidyGM2 {
|
|
|
1324
1657
|
.setValueAtTime(volume * gainRight, now);
|
|
1325
1658
|
}
|
|
1326
1659
|
setSustainPedal(channelNumber, value) {
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
if (!isOn) {
|
|
1660
|
+
this.channels[channelNumber].state.sustainPedal = value / 127;
|
|
1661
|
+
if (value < 64) {
|
|
1330
1662
|
this.releaseSustainPedal(channelNumber, value);
|
|
1331
1663
|
}
|
|
1332
1664
|
}
|
|
1333
1665
|
setPortamento(channelNumber, value) {
|
|
1334
|
-
this.channels[channelNumber].portamento = value
|
|
1666
|
+
this.channels[channelNumber].state.portamento = value / 127;
|
|
1335
1667
|
}
|
|
1336
1668
|
setReverbSendLevel(channelNumber, reverbSendLevel) {
|
|
1337
1669
|
const channel = this.channels[channelNumber];
|
|
1670
|
+
const state = channel.state;
|
|
1338
1671
|
const reverbEffect = this.reverbEffect;
|
|
1339
|
-
if (0 <
|
|
1672
|
+
if (0 < state.reverbSendLevel) {
|
|
1340
1673
|
if (0 < reverbSendLevel) {
|
|
1341
1674
|
const now = this.audioContext.currentTime;
|
|
1342
|
-
|
|
1675
|
+
state.reverbSendLevel = reverbSendLevel / 127;
|
|
1343
1676
|
reverbEffect.input.gain.cancelScheduledValues(now);
|
|
1344
|
-
reverbEffect.input.gain.setValueAtTime(
|
|
1677
|
+
reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
|
|
1345
1678
|
}
|
|
1346
1679
|
else {
|
|
1347
1680
|
channel.scheduledNotes.forEach((noteList) => {
|
|
@@ -1349,7 +1682,7 @@ class MidyGM2 {
|
|
|
1349
1682
|
const note = noteList[i];
|
|
1350
1683
|
if (!note)
|
|
1351
1684
|
continue;
|
|
1352
|
-
if (note.
|
|
1685
|
+
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
1353
1686
|
continue;
|
|
1354
1687
|
note.reverbEffectsSend.disconnect();
|
|
1355
1688
|
}
|
|
@@ -1364,32 +1697,25 @@ class MidyGM2 {
|
|
|
1364
1697
|
const note = noteList[i];
|
|
1365
1698
|
if (!note)
|
|
1366
1699
|
continue;
|
|
1367
|
-
|
|
1368
|
-
continue;
|
|
1369
|
-
if (!note.reverbEffectsSend) {
|
|
1370
|
-
note.reverbEffectsSend = new GainNode(this.audioContext, {
|
|
1371
|
-
gain: note.instrumentKey.reverbEffectsSend,
|
|
1372
|
-
});
|
|
1373
|
-
note.volumeNode.connect(note.reverbEffectsSend);
|
|
1374
|
-
}
|
|
1375
|
-
note.reverbEffectsSend.connect(reverbEffect.input);
|
|
1700
|
+
this.setReverbEffectsSend(note, 0);
|
|
1376
1701
|
}
|
|
1377
1702
|
});
|
|
1378
|
-
|
|
1703
|
+
state.reverbSendLevel = reverbSendLevel / 127;
|
|
1379
1704
|
reverbEffect.input.gain.cancelScheduledValues(now);
|
|
1380
|
-
reverbEffect.input.gain.setValueAtTime(
|
|
1705
|
+
reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
|
|
1381
1706
|
}
|
|
1382
1707
|
}
|
|
1383
1708
|
}
|
|
1384
1709
|
setChorusSendLevel(channelNumber, chorusSendLevel) {
|
|
1385
1710
|
const channel = this.channels[channelNumber];
|
|
1711
|
+
const state = channel.state;
|
|
1386
1712
|
const chorusEffect = this.chorusEffect;
|
|
1387
|
-
if (0 <
|
|
1713
|
+
if (0 < state.chorusSendLevel) {
|
|
1388
1714
|
if (0 < chorusSendLevel) {
|
|
1389
1715
|
const now = this.audioContext.currentTime;
|
|
1390
|
-
|
|
1716
|
+
state.chorusSendLevel = chorusSendLevel / 127;
|
|
1391
1717
|
chorusEffect.input.gain.cancelScheduledValues(now);
|
|
1392
|
-
chorusEffect.input.gain.setValueAtTime(
|
|
1718
|
+
chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
|
|
1393
1719
|
}
|
|
1394
1720
|
else {
|
|
1395
1721
|
channel.scheduledNotes.forEach((noteList) => {
|
|
@@ -1397,7 +1723,7 @@ class MidyGM2 {
|
|
|
1397
1723
|
const note = noteList[i];
|
|
1398
1724
|
if (!note)
|
|
1399
1725
|
continue;
|
|
1400
|
-
if (note.
|
|
1726
|
+
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
1401
1727
|
continue;
|
|
1402
1728
|
note.chorusEffectsSend.disconnect();
|
|
1403
1729
|
}
|
|
@@ -1412,28 +1738,19 @@ class MidyGM2 {
|
|
|
1412
1738
|
const note = noteList[i];
|
|
1413
1739
|
if (!note)
|
|
1414
1740
|
continue;
|
|
1415
|
-
|
|
1416
|
-
continue;
|
|
1417
|
-
if (!note.chorusEffectsSend) {
|
|
1418
|
-
note.chorusEffectsSend = new GainNode(this.audioContext, {
|
|
1419
|
-
gain: note.instrumentKey.chorusEffectsSend,
|
|
1420
|
-
});
|
|
1421
|
-
note.volumeNode.connect(note.chorusEffectsSend);
|
|
1422
|
-
}
|
|
1423
|
-
note.chorusEffectsSend.connect(chorusEffect.input);
|
|
1741
|
+
this.setChorusEffectsSend(note, 0);
|
|
1424
1742
|
}
|
|
1425
1743
|
});
|
|
1426
|
-
|
|
1744
|
+
state.chorusSendLevel = chorusSendLevel / 127;
|
|
1427
1745
|
chorusEffect.input.gain.cancelScheduledValues(now);
|
|
1428
|
-
chorusEffect.input.gain.setValueAtTime(
|
|
1746
|
+
chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
|
|
1429
1747
|
}
|
|
1430
1748
|
}
|
|
1431
1749
|
}
|
|
1432
1750
|
setSostenutoPedal(channelNumber, value) {
|
|
1433
|
-
const isOn = value >= 64;
|
|
1434
1751
|
const channel = this.channels[channelNumber];
|
|
1435
|
-
channel.sostenutoPedal =
|
|
1436
|
-
if (
|
|
1752
|
+
channel.state.sostenutoPedal = value / 127;
|
|
1753
|
+
if (64 <= value) {
|
|
1437
1754
|
const now = this.audioContext.currentTime;
|
|
1438
1755
|
const activeNotes = this.getActiveNotes(channel, now);
|
|
1439
1756
|
channel.sostenutoNotes = new Map(activeNotes);
|
|
@@ -1444,7 +1761,7 @@ class MidyGM2 {
|
|
|
1444
1761
|
}
|
|
1445
1762
|
setSoftPedal(channelNumber, softPedal) {
|
|
1446
1763
|
const channel = this.channels[channelNumber];
|
|
1447
|
-
channel.softPedal = softPedal / 127;
|
|
1764
|
+
channel.state.softPedal = softPedal / 127;
|
|
1448
1765
|
}
|
|
1449
1766
|
limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
|
|
1450
1767
|
if (maxLSB < channel.dataLSB) {
|
|
@@ -1502,60 +1819,49 @@ class MidyGM2 {
|
|
|
1502
1819
|
this.channels[channelNumber].dataMSB = value;
|
|
1503
1820
|
this.handleRPN(channelNumber);
|
|
1504
1821
|
}
|
|
1505
|
-
updateDetune(channel, detuneChange) {
|
|
1506
|
-
const now = this.audioContext.currentTime;
|
|
1507
|
-
channel.scheduledNotes.forEach((noteList) => {
|
|
1508
|
-
for (let i = 0; i < noteList.length; i++) {
|
|
1509
|
-
const note = noteList[i];
|
|
1510
|
-
if (!note)
|
|
1511
|
-
continue;
|
|
1512
|
-
const { bufferSource } = note;
|
|
1513
|
-
const detune = bufferSource.detune.value + detuneChange;
|
|
1514
|
-
bufferSource.detune
|
|
1515
|
-
.cancelScheduledValues(now)
|
|
1516
|
-
.setValueAtTime(detune, now);
|
|
1517
|
-
}
|
|
1518
|
-
});
|
|
1519
|
-
}
|
|
1520
1822
|
handlePitchBendRangeRPN(channelNumber) {
|
|
1521
1823
|
const channel = this.channels[channelNumber];
|
|
1522
1824
|
this.limitData(channel, 0, 127, 0, 99);
|
|
1523
1825
|
const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
|
|
1524
1826
|
this.setPitchBendRange(channelNumber, pitchBendRange);
|
|
1525
1827
|
}
|
|
1526
|
-
setPitchBendRange(channelNumber,
|
|
1828
|
+
setPitchBendRange(channelNumber, value) {
|
|
1527
1829
|
const channel = this.channels[channelNumber];
|
|
1528
|
-
const
|
|
1529
|
-
|
|
1530
|
-
const
|
|
1531
|
-
|
|
1532
|
-
|
|
1830
|
+
const state = channel.state;
|
|
1831
|
+
const prev = state.pitchWheelSensitivity;
|
|
1832
|
+
const next = value / 128;
|
|
1833
|
+
state.pitchWheelSensitivity = next;
|
|
1834
|
+
channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
|
|
1835
|
+
this.updateDetune(channel);
|
|
1836
|
+
this.applyVoiceParams(channel, 16);
|
|
1533
1837
|
}
|
|
1534
1838
|
handleFineTuningRPN(channelNumber) {
|
|
1535
1839
|
const channel = this.channels[channelNumber];
|
|
1536
1840
|
this.limitData(channel, 0, 127, 0, 127);
|
|
1537
|
-
const fineTuning =
|
|
1841
|
+
const fineTuning = channel.dataMSB * 128 + channel.dataLSB;
|
|
1538
1842
|
this.setFineTuning(channelNumber, fineTuning);
|
|
1539
1843
|
}
|
|
1540
|
-
setFineTuning(channelNumber,
|
|
1844
|
+
setFineTuning(channelNumber, value) {
|
|
1541
1845
|
const channel = this.channels[channelNumber];
|
|
1542
|
-
const
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1846
|
+
const prev = channel.fineTuning;
|
|
1847
|
+
const next = (value - 8192) / 8.192; // cent
|
|
1848
|
+
channel.fineTuning = next;
|
|
1849
|
+
channel.detune += next - prev;
|
|
1850
|
+
this.updateDetune(channel);
|
|
1546
1851
|
}
|
|
1547
1852
|
handleCoarseTuningRPN(channelNumber) {
|
|
1548
1853
|
const channel = this.channels[channelNumber];
|
|
1549
1854
|
this.limitDataMSB(channel, 0, 127);
|
|
1550
|
-
const coarseTuning = channel.dataMSB
|
|
1551
|
-
this.
|
|
1855
|
+
const coarseTuning = channel.dataMSB;
|
|
1856
|
+
this.setCoarseTuning(channelNumber, coarseTuning);
|
|
1552
1857
|
}
|
|
1553
|
-
setCoarseTuning(channelNumber,
|
|
1858
|
+
setCoarseTuning(channelNumber, value) {
|
|
1554
1859
|
const channel = this.channels[channelNumber];
|
|
1555
|
-
const
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1860
|
+
const prev = channel.coarseTuning;
|
|
1861
|
+
const next = (value - 64) * 100; // cent
|
|
1862
|
+
channel.coarseTuning = next;
|
|
1863
|
+
channel.detune += next - prev;
|
|
1864
|
+
this.updateDetune(channel);
|
|
1559
1865
|
}
|
|
1560
1866
|
handleModulationDepthRangeRPN(channelNumber) {
|
|
1561
1867
|
const channel = this.channels[channelNumber];
|
|
@@ -1573,7 +1879,30 @@ class MidyGM2 {
|
|
|
1573
1879
|
return this.stopChannelNotes(channelNumber, 0, true);
|
|
1574
1880
|
}
|
|
1575
1881
|
resetAllControllers(channelNumber) {
|
|
1576
|
-
|
|
1882
|
+
const stateTypes = [
|
|
1883
|
+
"expression",
|
|
1884
|
+
"modulationDepth",
|
|
1885
|
+
"sustainPedal",
|
|
1886
|
+
"portamento",
|
|
1887
|
+
"sostenutoPedal",
|
|
1888
|
+
"softPedal",
|
|
1889
|
+
"channelPressure",
|
|
1890
|
+
"pitchWheelSensitivity",
|
|
1891
|
+
];
|
|
1892
|
+
const channel = this.channels[channelNumber];
|
|
1893
|
+
const state = channel.state;
|
|
1894
|
+
for (let i = 0; i < stateTypes.length; i++) {
|
|
1895
|
+
const type = stateTypes[i];
|
|
1896
|
+
state[type] = defaultControllerState[type];
|
|
1897
|
+
}
|
|
1898
|
+
const settingTypes = [
|
|
1899
|
+
"rpnMSB",
|
|
1900
|
+
"rpnLSB",
|
|
1901
|
+
];
|
|
1902
|
+
for (let i = 0; i < settingTypes.length; i++) {
|
|
1903
|
+
const type = settingTypes[i];
|
|
1904
|
+
channel[type] = this.constructor.channelSettings[type];
|
|
1905
|
+
}
|
|
1577
1906
|
}
|
|
1578
1907
|
allNotesOff(channelNumber) {
|
|
1579
1908
|
return this.stopChannelNotes(channelNumber, 0, false);
|
|
@@ -1592,6 +1921,15 @@ class MidyGM2 {
|
|
|
1592
1921
|
}
|
|
1593
1922
|
handleUniversalNonRealTimeExclusiveMessage(data) {
|
|
1594
1923
|
switch (data[2]) {
|
|
1924
|
+
case 8:
|
|
1925
|
+
switch (data[3]) {
|
|
1926
|
+
case 8:
|
|
1927
|
+
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
1928
|
+
return this.handleScaleOctaveTuning1ByteFormat(data);
|
|
1929
|
+
default:
|
|
1930
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1931
|
+
}
|
|
1932
|
+
break;
|
|
1595
1933
|
case 9:
|
|
1596
1934
|
switch (data[3]) {
|
|
1597
1935
|
case 1:
|
|
@@ -1646,15 +1984,6 @@ class MidyGM2 {
|
|
|
1646
1984
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1647
1985
|
}
|
|
1648
1986
|
break;
|
|
1649
|
-
case 8:
|
|
1650
|
-
switch (data[3]) {
|
|
1651
|
-
// case 8:
|
|
1652
|
-
// // TODO
|
|
1653
|
-
// return this.handleScaleOctaveTuning1ByteFormat();
|
|
1654
|
-
default:
|
|
1655
|
-
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1656
|
-
}
|
|
1657
|
-
break;
|
|
1658
1987
|
case 9:
|
|
1659
1988
|
switch (data[3]) {
|
|
1660
1989
|
// case 1:
|
|
@@ -1695,27 +2024,59 @@ class MidyGM2 {
|
|
|
1695
2024
|
}
|
|
1696
2025
|
}
|
|
1697
2026
|
handleMasterFineTuningSysEx(data) {
|
|
1698
|
-
const fineTuning =
|
|
2027
|
+
const fineTuning = data[5] * 128 + data[4];
|
|
1699
2028
|
this.setMasterFineTuning(fineTuning);
|
|
1700
2029
|
}
|
|
1701
|
-
setMasterFineTuning(
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
}
|
|
2030
|
+
setMasterFineTuning(value) {
|
|
2031
|
+
const prev = this.masterFineTuning;
|
|
2032
|
+
const next = (value - 8192) / 8.192; // cent
|
|
2033
|
+
this.masterFineTuning = next;
|
|
2034
|
+
channel.detune += next - prev;
|
|
2035
|
+
this.updateDetune(channel);
|
|
1708
2036
|
}
|
|
1709
2037
|
handleMasterCoarseTuningSysEx(data) {
|
|
1710
2038
|
const coarseTuning = data[4];
|
|
1711
2039
|
this.setMasterCoarseTuning(coarseTuning);
|
|
1712
2040
|
}
|
|
1713
|
-
setMasterCoarseTuning(
|
|
1714
|
-
|
|
1715
|
-
|
|
2041
|
+
setMasterCoarseTuning(value) {
|
|
2042
|
+
const prev = this.masterCoarseTuning;
|
|
2043
|
+
const next = (value - 64) * 100; // cent
|
|
2044
|
+
this.masterCoarseTuning = next;
|
|
2045
|
+
channel.detune += next - prev;
|
|
2046
|
+
this.updateDetune(channel);
|
|
2047
|
+
}
|
|
2048
|
+
getChannelBitmap(data) {
|
|
2049
|
+
const bitmap = new Array(16).fill(false);
|
|
2050
|
+
const ff = data[4] & 0b11;
|
|
2051
|
+
const gg = data[5] & 0x7F;
|
|
2052
|
+
const hh = data[6] & 0x7F;
|
|
2053
|
+
for (let bit = 0; bit < 7; bit++) {
|
|
2054
|
+
if (hh & (1 << bit))
|
|
2055
|
+
bitmap[bit] = true;
|
|
2056
|
+
}
|
|
2057
|
+
for (let bit = 0; bit < 7; bit++) {
|
|
2058
|
+
if (gg & (1 << bit))
|
|
2059
|
+
bitmap[bit + 7] = true;
|
|
2060
|
+
}
|
|
2061
|
+
for (let bit = 0; bit < 2; bit++) {
|
|
2062
|
+
if (ff & (1 << bit))
|
|
2063
|
+
bitmap[bit + 14] = true;
|
|
2064
|
+
}
|
|
2065
|
+
return bitmap;
|
|
2066
|
+
}
|
|
2067
|
+
handleScaleOctaveTuning1ByteFormat(data) {
|
|
2068
|
+
if (data.length < 18) {
|
|
2069
|
+
console.error("Data length is too short");
|
|
2070
|
+
return;
|
|
1716
2071
|
}
|
|
1717
|
-
|
|
1718
|
-
|
|
2072
|
+
const channelBitmap = this.getChannelBitmap(data);
|
|
2073
|
+
for (let i = 0; i < channelBitmap.length; i++) {
|
|
2074
|
+
if (!channelBitmap[i])
|
|
2075
|
+
continue;
|
|
2076
|
+
for (let j = 0; j < 12; j++) {
|
|
2077
|
+
const value = data[j + 7] - 64; // cent
|
|
2078
|
+
this.channels[i].scaleOctaveTuningTable[j] = value;
|
|
2079
|
+
}
|
|
1719
2080
|
}
|
|
1720
2081
|
}
|
|
1721
2082
|
handleGlobalParameterControlSysEx(data) {
|
|
@@ -1936,43 +2297,21 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
1936
2297
|
writable: true,
|
|
1937
2298
|
value: {
|
|
1938
2299
|
currentBufferSource: null,
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
reverbSendLevel: 0,
|
|
1943
|
-
chorusSendLevel: 0,
|
|
1944
|
-
vibratoRate: 1,
|
|
1945
|
-
vibratoDepth: 1,
|
|
1946
|
-
vibratoDelay: 1,
|
|
2300
|
+
detune: 0,
|
|
2301
|
+
scaleOctaveTuningTable: new Array(12).fill(0), // cent
|
|
2302
|
+
program: 0,
|
|
1947
2303
|
bank: 121 * 128,
|
|
1948
2304
|
bankMSB: 121,
|
|
1949
2305
|
bankLSB: 0,
|
|
1950
2306
|
dataMSB: 0,
|
|
1951
2307
|
dataLSB: 0,
|
|
1952
|
-
|
|
1953
|
-
|
|
2308
|
+
rpnMSB: 127,
|
|
2309
|
+
rpnLSB: 127,
|
|
1954
2310
|
fineTuning: 0, // cb
|
|
1955
2311
|
coarseTuning: 0, // cb
|
|
1956
2312
|
modulationDepthRange: 50, // cent
|
|
1957
2313
|
}
|
|
1958
2314
|
});
|
|
1959
|
-
Object.defineProperty(MidyGM2, "effectSettings", {
|
|
1960
|
-
enumerable: true,
|
|
1961
|
-
configurable: true,
|
|
1962
|
-
writable: true,
|
|
1963
|
-
value: {
|
|
1964
|
-
expression: 1,
|
|
1965
|
-
modulationDepth: 0,
|
|
1966
|
-
sustainPedal: false,
|
|
1967
|
-
portamento: false,
|
|
1968
|
-
sostenutoPedal: false,
|
|
1969
|
-
softPedal: 0,
|
|
1970
|
-
rpnMSB: 127,
|
|
1971
|
-
rpnLSB: 127,
|
|
1972
|
-
channelPressure: 0,
|
|
1973
|
-
pitchBendRange: 2,
|
|
1974
|
-
}
|
|
1975
|
-
});
|
|
1976
2315
|
Object.defineProperty(MidyGM2, "controllerDestinationSettings", {
|
|
1977
2316
|
enumerable: true,
|
|
1978
2317
|
configurable: true,
|