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