@marmooo/midy 0.1.7 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/midy-GM1.d.ts +48 -23
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +358 -135
- package/esm/midy-GM2.d.ts +50 -33
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +512 -220
- package/esm/midy-GMLite.d.ts +46 -22
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +339 -137
- package/esm/midy.d.ts +50 -38
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +526 -237
- package/package.json +2 -2
- package/script/midy-GM1.d.ts +48 -23
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +358 -135
- package/script/midy-GM2.d.ts +50 -33
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +512 -220
- package/script/midy-GMLite.d.ts +46 -22
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +339 -137
- package/script/midy.d.ts +50 -38
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +526 -237
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;
|
|
72
79
|
}
|
|
73
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
|
+
}
|
|
143
|
+
}
|
|
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);
|
|
@@ -813,53 +907,62 @@ class MidyGM2 {
|
|
|
813
907
|
calcSemitoneOffset(channel) {
|
|
814
908
|
const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
|
|
815
909
|
const channelTuning = channel.coarseTuning + channel.fineTuning;
|
|
816
|
-
const
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
return instrumentKey.playbackRate(noteNumber) *
|
|
821
|
-
Math.pow(2, semitoneOffset / 12);
|
|
910
|
+
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
911
|
+
const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 128;
|
|
912
|
+
const pitch = pitchWheel * pitchWheelSensitivity;
|
|
913
|
+
return masterTuning + channelTuning + pitch;
|
|
822
914
|
}
|
|
823
915
|
setPortamentoStartVolumeEnvelope(channel, note) {
|
|
824
|
-
const
|
|
825
|
-
const
|
|
826
|
-
const
|
|
827
|
-
const
|
|
828
|
-
const
|
|
916
|
+
const now = this.audioContext.currentTime;
|
|
917
|
+
const { voiceParams, startTime } = note;
|
|
918
|
+
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
919
|
+
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
920
|
+
const volDelay = startTime + voiceParams.volDelay;
|
|
921
|
+
const portamentoTime = volDelay + channel.state.portamentoTime;
|
|
829
922
|
note.volumeNode.gain
|
|
830
|
-
.cancelScheduledValues(
|
|
923
|
+
.cancelScheduledValues(now)
|
|
831
924
|
.setValueAtTime(0, volDelay)
|
|
832
925
|
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
833
926
|
}
|
|
834
927
|
setVolumeEnvelope(note) {
|
|
835
|
-
const
|
|
836
|
-
const
|
|
837
|
-
const
|
|
838
|
-
const
|
|
839
|
-
const
|
|
840
|
-
const
|
|
841
|
-
const
|
|
928
|
+
const now = this.audioContext.currentTime;
|
|
929
|
+
const { voiceParams, startTime } = note;
|
|
930
|
+
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
931
|
+
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
932
|
+
const volDelay = startTime + voiceParams.volDelay;
|
|
933
|
+
const volAttack = volDelay + voiceParams.volAttack;
|
|
934
|
+
const volHold = volAttack + voiceParams.volHold;
|
|
935
|
+
const volDecay = volHold + voiceParams.volDecay;
|
|
842
936
|
note.volumeNode.gain
|
|
843
|
-
.cancelScheduledValues(
|
|
937
|
+
.cancelScheduledValues(now)
|
|
844
938
|
.setValueAtTime(0, startTime)
|
|
845
939
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
846
940
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
847
941
|
.setValueAtTime(attackVolume, volHold)
|
|
848
942
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
849
943
|
}
|
|
850
|
-
|
|
851
|
-
const
|
|
852
|
-
|
|
853
|
-
|
|
944
|
+
setPlaybackRate(note) {
|
|
945
|
+
const now = this.audioContext.currentTime;
|
|
946
|
+
note.bufferSource.playbackRate
|
|
947
|
+
.cancelScheduledValues(now)
|
|
948
|
+
.setValueAtTime(note.voiceParams.playbackRate, now);
|
|
949
|
+
}
|
|
950
|
+
setPitch(channel, note) {
|
|
951
|
+
const now = this.audioContext.currentTime;
|
|
952
|
+
const { startTime } = note;
|
|
953
|
+
const basePitch = this.calcSemitoneOffset(channel) * 100;
|
|
954
|
+
note.bufferSource.detune
|
|
955
|
+
.cancelScheduledValues(now)
|
|
956
|
+
.setValueAtTime(basePitch, startTime);
|
|
957
|
+
const modEnvToPitch = note.voiceParams.modEnvToPitch;
|
|
854
958
|
if (modEnvToPitch === 0)
|
|
855
959
|
return;
|
|
856
|
-
const
|
|
857
|
-
const
|
|
858
|
-
const
|
|
859
|
-
const
|
|
860
|
-
const
|
|
861
|
-
|
|
862
|
-
note.bufferSource.playbackRate.value
|
|
960
|
+
const peekPitch = basePitch + modEnvToPitch;
|
|
961
|
+
const modDelay = startTime + voiceParams.modDelay;
|
|
962
|
+
const modAttack = modDelay + voiceParams.modAttack;
|
|
963
|
+
const modHold = modAttack + voiceParams.modHold;
|
|
964
|
+
const modDecay = modHold + voiceParams.modDecay;
|
|
965
|
+
note.bufferSource.detune
|
|
863
966
|
.setValueAtTime(basePitch, modDelay)
|
|
864
967
|
.exponentialRampToValueAtTime(peekPitch, modAttack)
|
|
865
968
|
.setValueAtTime(peekPitch, modHold)
|
|
@@ -871,42 +974,46 @@ class MidyGM2 {
|
|
|
871
974
|
return Math.max(minFrequency, Math.min(frequency, maxFrequency));
|
|
872
975
|
}
|
|
873
976
|
setPortamentoStartFilterEnvelope(channel, note) {
|
|
874
|
-
const
|
|
977
|
+
const now = this.audioContext.currentTime;
|
|
978
|
+
const state = channel.state;
|
|
979
|
+
const { voiceParams, noteNumber, startTime } = note;
|
|
875
980
|
const softPedalFactor = 1 -
|
|
876
|
-
(0.1 + (noteNumber / 127) * 0.2) *
|
|
877
|
-
const baseFreq = this.centToHz(
|
|
981
|
+
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
982
|
+
const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
|
|
878
983
|
softPedalFactor;
|
|
879
|
-
const peekFreq = this.centToHz(
|
|
984
|
+
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor;
|
|
880
985
|
const sustainFreq = baseFreq +
|
|
881
|
-
(peekFreq - baseFreq) * (1 -
|
|
986
|
+
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
882
987
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
883
988
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
884
|
-
const portamentoTime = startTime + channel.portamentoTime;
|
|
885
|
-
const modDelay = startTime +
|
|
989
|
+
const portamentoTime = startTime + channel.state.portamentoTime;
|
|
990
|
+
const modDelay = startTime + voiceParams.modDelay;
|
|
886
991
|
note.filterNode.frequency
|
|
887
|
-
.cancelScheduledValues(
|
|
992
|
+
.cancelScheduledValues(now)
|
|
888
993
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
889
994
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
890
995
|
.linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
|
|
891
996
|
}
|
|
892
997
|
setFilterEnvelope(channel, note) {
|
|
893
|
-
const
|
|
998
|
+
const now = this.audioContext.currentTime;
|
|
999
|
+
const state = channel.state;
|
|
1000
|
+
const { voiceParams, noteNumber, startTime } = note;
|
|
894
1001
|
const softPedalFactor = 1 -
|
|
895
|
-
(0.1 + (noteNumber / 127) * 0.2) *
|
|
896
|
-
const baseFreq = this.centToHz(
|
|
1002
|
+
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1003
|
+
const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
|
|
897
1004
|
softPedalFactor;
|
|
898
|
-
const peekFreq = this.centToHz(
|
|
1005
|
+
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor;
|
|
899
1006
|
const sustainFreq = baseFreq +
|
|
900
|
-
(peekFreq - baseFreq) * (1 -
|
|
1007
|
+
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
901
1008
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
902
1009
|
const adjustedPeekFreq = this.clampCutoffFrequency(peekFreq);
|
|
903
1010
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
904
|
-
const modDelay = startTime +
|
|
905
|
-
const modAttack = modDelay +
|
|
906
|
-
const modHold = modAttack +
|
|
907
|
-
const modDecay = modHold +
|
|
1011
|
+
const modDelay = startTime + voiceParams.modDelay;
|
|
1012
|
+
const modAttack = modDelay + voiceParams.modAttack;
|
|
1013
|
+
const modHold = modAttack + voiceParams.modHold;
|
|
1014
|
+
const modDecay = modHold + voiceParams.modDecay;
|
|
908
1015
|
note.filterNode.frequency
|
|
909
|
-
.cancelScheduledValues(
|
|
1016
|
+
.cancelScheduledValues(now)
|
|
910
1017
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
911
1018
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
912
1019
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
@@ -914,25 +1021,18 @@ class MidyGM2 {
|
|
|
914
1021
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
915
1022
|
}
|
|
916
1023
|
startModulation(channel, note, startTime) {
|
|
917
|
-
const {
|
|
918
|
-
const { modLfoToPitch, modLfoToVolume } = instrumentKey;
|
|
1024
|
+
const { voiceParams } = note;
|
|
919
1025
|
note.modulationLFO = new OscillatorNode(this.audioContext, {
|
|
920
|
-
frequency: this.centToHz(
|
|
1026
|
+
frequency: this.centToHz(voiceParams.freqModLFO),
|
|
921
1027
|
});
|
|
922
1028
|
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,
|
|
929
|
-
});
|
|
930
|
-
const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
931
|
-
const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
|
|
932
|
-
note.volumeDepth = new GainNode(this.audioContext, {
|
|
933
|
-
gain: volumeDepth * volumeDepthSign,
|
|
1029
|
+
gain: voiceParams.modLfoToFilterFc,
|
|
934
1030
|
});
|
|
935
|
-
note.
|
|
1031
|
+
note.modulationDepth = new GainNode(this.audioContext);
|
|
1032
|
+
this.setModLfoToPitch(channel, note);
|
|
1033
|
+
note.volumeDepth = new GainNode(this.audioContext);
|
|
1034
|
+
this.setModLfoToVolume(note);
|
|
1035
|
+
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
936
1036
|
note.modulationLFO.connect(note.filterDepth);
|
|
937
1037
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
938
1038
|
note.modulationLFO.connect(note.modulationDepth);
|
|
@@ -941,67 +1041,58 @@ class MidyGM2 {
|
|
|
941
1041
|
note.volumeDepth.connect(note.volumeNode.gain);
|
|
942
1042
|
}
|
|
943
1043
|
startVibrato(channel, note, startTime) {
|
|
944
|
-
const {
|
|
945
|
-
const
|
|
1044
|
+
const { voiceParams } = note;
|
|
1045
|
+
const state = channel.state;
|
|
946
1046
|
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,
|
|
1047
|
+
frequency: this.centToHz(voiceParams.freqVibLFO) *
|
|
1048
|
+
state.vibratoRate,
|
|
954
1049
|
});
|
|
955
|
-
note.vibratoLFO.start(startTime +
|
|
1050
|
+
note.vibratoLFO.start(startTime + voiceParams.delayVibLFO * state.vibratoDelay * 2);
|
|
1051
|
+
note.vibratoDepth = new GainNode(this.audioContext);
|
|
1052
|
+
this.setVibLfoToPitch(channel, note);
|
|
956
1053
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
957
1054
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
958
1055
|
}
|
|
959
|
-
async createNote(channel,
|
|
960
|
-
const
|
|
961
|
-
const
|
|
962
|
-
|
|
1056
|
+
async createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3) {
|
|
1057
|
+
const state = channel.state;
|
|
1058
|
+
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1059
|
+
const voiceParams = voice.getAllParams(controllerState);
|
|
1060
|
+
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1061
|
+
note.bufferSource = await this.createNoteBufferNode(voiceParams, isSF3);
|
|
963
1062
|
note.volumeNode = new GainNode(this.audioContext);
|
|
964
1063
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
965
1064
|
type: "lowpass",
|
|
966
|
-
Q:
|
|
1065
|
+
Q: voiceParams.initialFilterQ / 10, // dB
|
|
967
1066
|
});
|
|
968
1067
|
if (portamento) {
|
|
1068
|
+
note.portamento = true;
|
|
969
1069
|
this.setPortamentoStartVolumeEnvelope(channel, note);
|
|
970
1070
|
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
971
1071
|
}
|
|
972
1072
|
else {
|
|
973
|
-
|
|
1073
|
+
note.portamento = false;
|
|
1074
|
+
this.setVolumeEnvelope(channel, note);
|
|
974
1075
|
this.setFilterEnvelope(channel, note);
|
|
975
1076
|
}
|
|
976
|
-
if (0 <
|
|
1077
|
+
if (0 < state.vibratoDepth) {
|
|
977
1078
|
this.startVibrato(channel, note, startTime);
|
|
978
1079
|
}
|
|
979
|
-
|
|
980
|
-
|
|
1080
|
+
this.setPlaybackRate(note);
|
|
1081
|
+
if (0 < state.modulationDepth) {
|
|
1082
|
+
this.setPitch(channel, note);
|
|
981
1083
|
this.startModulation(channel, note, startTime);
|
|
982
1084
|
}
|
|
983
|
-
else {
|
|
984
|
-
note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
|
|
985
|
-
}
|
|
986
1085
|
if (this.mono && channel.currentBufferSource) {
|
|
987
1086
|
channel.currentBufferSource.stop(startTime);
|
|
988
1087
|
channel.currentBufferSource = note.bufferSource;
|
|
989
1088
|
}
|
|
990
1089
|
note.bufferSource.connect(note.filterNode);
|
|
991
1090
|
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);
|
|
1091
|
+
if (0 < channel.chorusSendLevel) {
|
|
1092
|
+
this.setChorusEffectsSend(channel, note, 0);
|
|
998
1093
|
}
|
|
999
|
-
if (0 < channel.
|
|
1000
|
-
|
|
1001
|
-
gain: instrumentKey.chorusEffectsSend,
|
|
1002
|
-
});
|
|
1003
|
-
note.volumeNode.connect(note.chorusEffectsSend);
|
|
1004
|
-
note.chorusEffectsSend.connect(this.chorusEffect.input);
|
|
1094
|
+
if (0 < channel.reverbSendLevel) {
|
|
1095
|
+
this.setReverbEffectsSend(channel, note, 0);
|
|
1005
1096
|
}
|
|
1006
1097
|
note.bufferSource.start(startTime);
|
|
1007
1098
|
return note;
|
|
@@ -1023,16 +1114,16 @@ class MidyGM2 {
|
|
|
1023
1114
|
return;
|
|
1024
1115
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
1025
1116
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
1026
|
-
const
|
|
1027
|
-
if (!
|
|
1117
|
+
const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
|
|
1118
|
+
if (!voice)
|
|
1028
1119
|
return;
|
|
1029
|
-
const note = await this.createNote(channel,
|
|
1120
|
+
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
|
|
1030
1121
|
note.volumeNode.connect(channel.gainL);
|
|
1031
1122
|
note.volumeNode.connect(channel.gainR);
|
|
1032
|
-
if (channel.sostenutoPedal) {
|
|
1123
|
+
if (channel.state.sostenutoPedal) {
|
|
1033
1124
|
channel.sostenutoNotes.set(noteNumber, note);
|
|
1034
1125
|
}
|
|
1035
|
-
const exclusiveClass =
|
|
1126
|
+
const exclusiveClass = note.voiceParams.exclusiveClass;
|
|
1036
1127
|
if (exclusiveClass !== 0) {
|
|
1037
1128
|
if (this.exclusiveClassMap.has(exclusiveClass)) {
|
|
1038
1129
|
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
@@ -1094,8 +1185,9 @@ class MidyGM2 {
|
|
|
1094
1185
|
}
|
|
1095
1186
|
scheduleNoteRelease(channelNumber, noteNumber, _velocity, endTime, portamentoNoteNumber, force) {
|
|
1096
1187
|
const channel = this.channels[channelNumber];
|
|
1188
|
+
const state = channel.state;
|
|
1097
1189
|
if (!force) {
|
|
1098
|
-
if (
|
|
1190
|
+
if (0.5 < state.sustainPedal)
|
|
1099
1191
|
return;
|
|
1100
1192
|
if (channel.sostenutoNotes.has(noteNumber))
|
|
1101
1193
|
return;
|
|
@@ -1110,8 +1202,8 @@ class MidyGM2 {
|
|
|
1110
1202
|
if (note.ending)
|
|
1111
1203
|
continue;
|
|
1112
1204
|
if (portamentoNoteNumber === undefined) {
|
|
1113
|
-
const volRelease = endTime + note.
|
|
1114
|
-
const modRelease = endTime + note.
|
|
1205
|
+
const volRelease = endTime + note.voiceParams.volRelease;
|
|
1206
|
+
const modRelease = endTime + note.voiceParams.modRelease;
|
|
1115
1207
|
note.filterNode.frequency
|
|
1116
1208
|
.cancelScheduledValues(endTime)
|
|
1117
1209
|
.linearRampToValueAtTime(0, modRelease);
|
|
@@ -1119,7 +1211,7 @@ class MidyGM2 {
|
|
|
1119
1211
|
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
1120
1212
|
}
|
|
1121
1213
|
else {
|
|
1122
|
-
const portamentoTime = endTime +
|
|
1214
|
+
const portamentoTime = endTime + state.portamentoTime;
|
|
1123
1215
|
const detuneChange = (portamentoNoteNumber - noteNumber) * 100;
|
|
1124
1216
|
const detune = note.bufferSource.detune.value + detuneChange;
|
|
1125
1217
|
note.bufferSource.detune
|
|
@@ -1137,7 +1229,7 @@ class MidyGM2 {
|
|
|
1137
1229
|
const velocity = halfVelocity * 2;
|
|
1138
1230
|
const channel = this.channels[channelNumber];
|
|
1139
1231
|
const promises = [];
|
|
1140
|
-
channel.sustainPedal =
|
|
1232
|
+
channel.state.sustainPedal = halfVelocity;
|
|
1141
1233
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1142
1234
|
for (let i = 0; i < noteList.length; i++) {
|
|
1143
1235
|
const note = noteList[i];
|
|
@@ -1154,7 +1246,7 @@ class MidyGM2 {
|
|
|
1154
1246
|
const velocity = halfVelocity * 2;
|
|
1155
1247
|
const channel = this.channels[channelNumber];
|
|
1156
1248
|
const promises = [];
|
|
1157
|
-
channel.sostenutoPedal =
|
|
1249
|
+
channel.state.sostenutoPedal = 0;
|
|
1158
1250
|
channel.sostenutoNotes.forEach((activeNote) => {
|
|
1159
1251
|
const { noteNumber } = activeNote;
|
|
1160
1252
|
const promise = this.releaseNote(channelNumber, noteNumber, velocity);
|
|
@@ -1202,18 +1294,232 @@ class MidyGM2 {
|
|
|
1202
1294
|
.setValueAtTime(gain * pressure, now);
|
|
1203
1295
|
});
|
|
1204
1296
|
}
|
|
1297
|
+
// this.applyVoiceParams(channel, 13);
|
|
1205
1298
|
}
|
|
1206
1299
|
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
1207
|
-
const pitchBend = msb * 128 + lsb
|
|
1300
|
+
const pitchBend = msb * 128 + lsb;
|
|
1208
1301
|
this.setPitchBend(channelNumber, pitchBend);
|
|
1209
1302
|
}
|
|
1210
|
-
setPitchBend(channelNumber,
|
|
1303
|
+
setPitchBend(channelNumber, value) {
|
|
1211
1304
|
const channel = this.channels[channelNumber];
|
|
1212
|
-
const
|
|
1213
|
-
|
|
1214
|
-
const
|
|
1215
|
-
|
|
1305
|
+
const state = channel.state;
|
|
1306
|
+
state.pitchWheel = value / 16383;
|
|
1307
|
+
const pitchWheel = (value - 8192) / 8192;
|
|
1308
|
+
const detuneChange = pitchWheel * state.pitchWheelSensitivity * 12800;
|
|
1216
1309
|
this.updateDetune(channel, detuneChange);
|
|
1310
|
+
this.applyVoiceParams(channel, 14);
|
|
1311
|
+
}
|
|
1312
|
+
setModLfoToPitch(channel, note) {
|
|
1313
|
+
const now = this.audioContext.currentTime;
|
|
1314
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch;
|
|
1315
|
+
const modulationDepth = Math.abs(modLfoToPitch) +
|
|
1316
|
+
channel.state.modulationDepth;
|
|
1317
|
+
const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
|
|
1318
|
+
note.modulationDepth.gain
|
|
1319
|
+
.cancelScheduledValues(now)
|
|
1320
|
+
.setValueAtTime(modulationDepth * modulationDepthSign, now);
|
|
1321
|
+
}
|
|
1322
|
+
setModLfoToVolume(note) {
|
|
1323
|
+
const now = this.audioContext.currentTime;
|
|
1324
|
+
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1325
|
+
const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1326
|
+
const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
|
|
1327
|
+
note.volumeDepth.gain
|
|
1328
|
+
.cancelScheduledValues(now)
|
|
1329
|
+
.setValueAtTime(volumeDepth * volumeDepthSign, now);
|
|
1330
|
+
}
|
|
1331
|
+
setChorusEffectsSend(note, prevValue) {
|
|
1332
|
+
if (0 < prevValue) {
|
|
1333
|
+
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1334
|
+
const now = this.audioContext.currentTime;
|
|
1335
|
+
const value = note.voiceParams.chorusEffectsSend;
|
|
1336
|
+
note.chorusEffectsSend.gain
|
|
1337
|
+
.cancelScheduledValues(now)
|
|
1338
|
+
.setValueAtTime(value, now);
|
|
1339
|
+
}
|
|
1340
|
+
else {
|
|
1341
|
+
note.chorusEffectsSend.disconnect();
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
else {
|
|
1345
|
+
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1346
|
+
if (!note.chorusEffectsSend) {
|
|
1347
|
+
note.chorusEffectsSend = new GainNode(this.audioContext, {
|
|
1348
|
+
gain: note.voiceParams.chorusEffectsSend,
|
|
1349
|
+
});
|
|
1350
|
+
note.volumeNode.connect(note.chorusEffectsSend);
|
|
1351
|
+
}
|
|
1352
|
+
note.chorusEffectsSend.connect(this.chorusEffect.input);
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
setReverbEffectsSend(note, prevValue) {
|
|
1357
|
+
if (0 < prevValue) {
|
|
1358
|
+
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1359
|
+
const now = this.audioContext.currentTime;
|
|
1360
|
+
const value = note.voiceParams.reverbEffectsSend;
|
|
1361
|
+
note.reverbEffectsSend.gain
|
|
1362
|
+
.cancelScheduledValues(now)
|
|
1363
|
+
.setValueAtTime(value, now);
|
|
1364
|
+
}
|
|
1365
|
+
else {
|
|
1366
|
+
note.reverbEffectsSend.disconnect();
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
else {
|
|
1370
|
+
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1371
|
+
if (!note.reverbEffectsSend) {
|
|
1372
|
+
note.reverbEffectsSend = new GainNode(this.audioContext, {
|
|
1373
|
+
gain: note.voiceParams.reverbEffectsSend,
|
|
1374
|
+
});
|
|
1375
|
+
note.volumeNode.connect(note.reverbEffectsSend);
|
|
1376
|
+
}
|
|
1377
|
+
note.reverbEffectsSend.connect(this.reverbEffect.input);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
setVibLfoToPitch(channel, note) {
|
|
1382
|
+
const now = this.audioContext.currentTime;
|
|
1383
|
+
const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
|
|
1384
|
+
const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
|
|
1385
|
+
2;
|
|
1386
|
+
const vibratoDepthSign = 0 < vibLfoToPitch;
|
|
1387
|
+
note.vibratoDepth.gain
|
|
1388
|
+
.cancelScheduledValues(now)
|
|
1389
|
+
.setValueAtTime(vibratoDepth * vibratoDepthSign, now);
|
|
1390
|
+
}
|
|
1391
|
+
setModLfoToFilterFc(note) {
|
|
1392
|
+
const now = this.audioContext.currentTime;
|
|
1393
|
+
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
|
|
1394
|
+
note.filterDepth.gain
|
|
1395
|
+
.cancelScheduledValues(now)
|
|
1396
|
+
.setValueAtTime(modLfoToFilterFc, now);
|
|
1397
|
+
}
|
|
1398
|
+
setDelayModLFO(note) {
|
|
1399
|
+
const now = this.audioContext.currentTime;
|
|
1400
|
+
const startTime = note.startTime;
|
|
1401
|
+
if (startTime < now)
|
|
1402
|
+
return;
|
|
1403
|
+
note.modulationLFO.stop(now);
|
|
1404
|
+
note.modulationLFO.start(startTime + note.voiceParams.delayModLFO);
|
|
1405
|
+
note.modulationLFO.connect(note.filterDepth);
|
|
1406
|
+
}
|
|
1407
|
+
setFreqModLFO(note) {
|
|
1408
|
+
const now = this.audioContext.currentTime;
|
|
1409
|
+
const freqModLFO = note.voiceParams.freqModLFO;
|
|
1410
|
+
note.modulationLFO.frequency
|
|
1411
|
+
.cancelScheduledValues(now)
|
|
1412
|
+
.setValueAtTime(freqModLFO, now);
|
|
1413
|
+
}
|
|
1414
|
+
createVoiceParamsHandlers() {
|
|
1415
|
+
return {
|
|
1416
|
+
modLfoToPitch: (channel, note, _prevValue) => {
|
|
1417
|
+
if (0 < channel.state.modulationDepth) {
|
|
1418
|
+
this.setModLfoToPitch(channel, note);
|
|
1419
|
+
}
|
|
1420
|
+
},
|
|
1421
|
+
vibLfoToPitch: (channel, note, _prevValue) => {
|
|
1422
|
+
if (0 < channel.state.vibratoDepth) {
|
|
1423
|
+
this.setVibLfoToPitch(channel, note);
|
|
1424
|
+
}
|
|
1425
|
+
},
|
|
1426
|
+
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1427
|
+
if (0 < channel.state.modulationDepth)
|
|
1428
|
+
this.setModLfoToFilterFc(note);
|
|
1429
|
+
},
|
|
1430
|
+
modLfoToVolume: (channel, note) => {
|
|
1431
|
+
if (0 < channel.state.modulationDepth)
|
|
1432
|
+
this.setModLfoToVolume(note);
|
|
1433
|
+
},
|
|
1434
|
+
chorusEffectsSend: (_channel, note, prevValue) => {
|
|
1435
|
+
this.setChorusEffectsSend(note, prevValue);
|
|
1436
|
+
},
|
|
1437
|
+
reverbEffectsSend: (_channel, note, prevValue) => {
|
|
1438
|
+
this.setReverbEffectsSend(note, prevValue);
|
|
1439
|
+
},
|
|
1440
|
+
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
1441
|
+
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
1442
|
+
delayVibLFO: (channel, note, prevValue) => {
|
|
1443
|
+
if (0 < channel.state.vibratoDepth) {
|
|
1444
|
+
const now = this.audioContext.currentTime;
|
|
1445
|
+
const prevStartTime = note.startTime +
|
|
1446
|
+
prevValue * channel.state.vibratoDelay * 2;
|
|
1447
|
+
if (now < prevStartTime)
|
|
1448
|
+
return;
|
|
1449
|
+
const startTime = note.startTime +
|
|
1450
|
+
value * channel.state.vibratoDelay * 2;
|
|
1451
|
+
note.vibratoLFO.stop(now);
|
|
1452
|
+
note.vibratoLFO.start(startTime);
|
|
1453
|
+
}
|
|
1454
|
+
},
|
|
1455
|
+
freqVibLFO: (channel, note, _prevValue) => {
|
|
1456
|
+
if (0 < channel.state.vibratoDepth) {
|
|
1457
|
+
const now = this.audioContext.currentTime;
|
|
1458
|
+
note.vibratoLFO.frequency
|
|
1459
|
+
.cancelScheduledValues(now)
|
|
1460
|
+
.setValueAtTime(value * sate.vibratoRate, now);
|
|
1461
|
+
}
|
|
1462
|
+
},
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1465
|
+
getControllerState(channel, noteNumber, velocity) {
|
|
1466
|
+
const state = new Float32Array(channel.state.array.length);
|
|
1467
|
+
state.set(channel.state.array);
|
|
1468
|
+
state[2] = velocity / 127;
|
|
1469
|
+
state[3] = noteNumber / 127;
|
|
1470
|
+
return state;
|
|
1471
|
+
}
|
|
1472
|
+
applyVoiceParams(channel, controllerType) {
|
|
1473
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1474
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1475
|
+
const note = noteList[i];
|
|
1476
|
+
if (!note)
|
|
1477
|
+
continue;
|
|
1478
|
+
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
|
|
1479
|
+
const voiceParams = note.voice.getParams(controllerType, controllerState);
|
|
1480
|
+
let appliedFilterEnvelope = false;
|
|
1481
|
+
let appliedVolumeEnvelope = false;
|
|
1482
|
+
for (const [key, value] of Object.entries(voiceParams)) {
|
|
1483
|
+
const prevValue = note.voiceParams[key];
|
|
1484
|
+
if (value === prevValue)
|
|
1485
|
+
continue;
|
|
1486
|
+
note.voiceParams[key] = value;
|
|
1487
|
+
if (key in this.voiceParamsHandlers) {
|
|
1488
|
+
this.voiceParamsHandlers[key](channel, note, prevValue);
|
|
1489
|
+
}
|
|
1490
|
+
else if (filterEnvelopeKeySet.has(key)) {
|
|
1491
|
+
if (appliedFilterEnvelope)
|
|
1492
|
+
continue;
|
|
1493
|
+
appliedFilterEnvelope = true;
|
|
1494
|
+
const noteVoiceParams = note.voiceParams;
|
|
1495
|
+
for (let i = 0; i < filterEnvelopeKeys.length; i++) {
|
|
1496
|
+
const key = filterEnvelopeKeys[i];
|
|
1497
|
+
if (key in voiceParams)
|
|
1498
|
+
noteVoiceParams[key] = voiceParams[key];
|
|
1499
|
+
}
|
|
1500
|
+
if (note.portamento) {
|
|
1501
|
+
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
1502
|
+
}
|
|
1503
|
+
else {
|
|
1504
|
+
this.setFilterEnvelope(channel, note);
|
|
1505
|
+
}
|
|
1506
|
+
this.setPitch(channel, note);
|
|
1507
|
+
}
|
|
1508
|
+
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1509
|
+
if (appliedVolumeEnvelope)
|
|
1510
|
+
continue;
|
|
1511
|
+
appliedVolumeEnvelope = true;
|
|
1512
|
+
const noteVoiceParams = note.voiceParams;
|
|
1513
|
+
for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
|
|
1514
|
+
const key = volumeEnvelopeKeys[i];
|
|
1515
|
+
if (key in voiceParams)
|
|
1516
|
+
noteVoiceParams[key] = voiceParams[key];
|
|
1517
|
+
}
|
|
1518
|
+
this.setVolumeEnvelope(channel, note);
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
});
|
|
1217
1523
|
}
|
|
1218
1524
|
createControlChangeHandlers() {
|
|
1219
1525
|
return {
|
|
@@ -1243,13 +1549,16 @@ class MidyGM2 {
|
|
|
1243
1549
|
127: this.polyOn,
|
|
1244
1550
|
};
|
|
1245
1551
|
}
|
|
1246
|
-
handleControlChange(channelNumber,
|
|
1247
|
-
const handler = this.controlChangeHandlers[
|
|
1552
|
+
handleControlChange(channelNumber, controllerType, value) {
|
|
1553
|
+
const handler = this.controlChangeHandlers[controllerType];
|
|
1248
1554
|
if (handler) {
|
|
1249
1555
|
handler.call(this, channelNumber, value);
|
|
1556
|
+
const channel = this.channels[channelNumber];
|
|
1557
|
+
const controller = 128 + controllerType;
|
|
1558
|
+
this.applyVoiceParams(channel, controller);
|
|
1250
1559
|
}
|
|
1251
1560
|
else {
|
|
1252
|
-
console.warn(`Unsupported Control change:
|
|
1561
|
+
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
1253
1562
|
}
|
|
1254
1563
|
}
|
|
1255
1564
|
setBankMSB(channelNumber, msb) {
|
|
@@ -1263,11 +1572,10 @@ class MidyGM2 {
|
|
|
1263
1572
|
if (!note)
|
|
1264
1573
|
continue;
|
|
1265
1574
|
if (note.modulationDepth) {
|
|
1266
|
-
note.modulationDepth.gain.setValueAtTime(channel.modulationDepth, now);
|
|
1575
|
+
note.modulationDepth.gain.setValueAtTime(channel.state.modulationDepth, now);
|
|
1267
1576
|
}
|
|
1268
1577
|
else {
|
|
1269
|
-
|
|
1270
|
-
this.setPitch(note, semitoneOffset);
|
|
1578
|
+
this.setPitch(channel, note);
|
|
1271
1579
|
this.startModulation(channel, note, now);
|
|
1272
1580
|
}
|
|
1273
1581
|
}
|
|
@@ -1275,21 +1583,22 @@ class MidyGM2 {
|
|
|
1275
1583
|
}
|
|
1276
1584
|
setModulationDepth(channelNumber, modulation) {
|
|
1277
1585
|
const channel = this.channels[channelNumber];
|
|
1278
|
-
channel.modulationDepth = (modulation / 127) *
|
|
1586
|
+
channel.state.modulationDepth = (modulation / 127) *
|
|
1587
|
+
channel.modulationDepthRange;
|
|
1279
1588
|
this.updateModulation(channel);
|
|
1280
1589
|
}
|
|
1281
1590
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
1282
1591
|
const channel = this.channels[channelNumber];
|
|
1283
1592
|
const factor = 5 * Math.log(10) / 127;
|
|
1284
|
-
channel.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1593
|
+
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1285
1594
|
}
|
|
1286
1595
|
setVolume(channelNumber, volume) {
|
|
1287
1596
|
const channel = this.channels[channelNumber];
|
|
1288
|
-
channel.volume = volume / 127;
|
|
1597
|
+
channel.state.volume = volume / 127;
|
|
1289
1598
|
this.updateChannelVolume(channel);
|
|
1290
1599
|
}
|
|
1291
1600
|
panToGain(pan) {
|
|
1292
|
-
const theta = Math.PI / 2 * Math.max(0, pan - 1) / 126;
|
|
1601
|
+
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
1293
1602
|
return {
|
|
1294
1603
|
gainLeft: Math.cos(theta),
|
|
1295
1604
|
gainRight: Math.sin(theta),
|
|
@@ -1297,12 +1606,12 @@ class MidyGM2 {
|
|
|
1297
1606
|
}
|
|
1298
1607
|
setPan(channelNumber, pan) {
|
|
1299
1608
|
const channel = this.channels[channelNumber];
|
|
1300
|
-
channel.pan = pan;
|
|
1609
|
+
channel.state.pan = pan / 127;
|
|
1301
1610
|
this.updateChannelVolume(channel);
|
|
1302
1611
|
}
|
|
1303
1612
|
setExpression(channelNumber, expression) {
|
|
1304
1613
|
const channel = this.channels[channelNumber];
|
|
1305
|
-
channel.expression = expression / 127;
|
|
1614
|
+
channel.state.expression = expression / 127;
|
|
1306
1615
|
this.updateChannelVolume(channel);
|
|
1307
1616
|
}
|
|
1308
1617
|
setBankLSB(channelNumber, lsb) {
|
|
@@ -1314,8 +1623,9 @@ class MidyGM2 {
|
|
|
1314
1623
|
}
|
|
1315
1624
|
updateChannelVolume(channel) {
|
|
1316
1625
|
const now = this.audioContext.currentTime;
|
|
1317
|
-
const
|
|
1318
|
-
const
|
|
1626
|
+
const state = channel.state;
|
|
1627
|
+
const volume = state.volume * state.expression;
|
|
1628
|
+
const { gainLeft, gainRight } = this.panToGain(state.pan);
|
|
1319
1629
|
channel.gainL.gain
|
|
1320
1630
|
.cancelScheduledValues(now)
|
|
1321
1631
|
.setValueAtTime(volume * gainLeft, now);
|
|
@@ -1324,24 +1634,24 @@ class MidyGM2 {
|
|
|
1324
1634
|
.setValueAtTime(volume * gainRight, now);
|
|
1325
1635
|
}
|
|
1326
1636
|
setSustainPedal(channelNumber, value) {
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
if (!isOn) {
|
|
1637
|
+
this.channels[channelNumber].state.sustainPedal = value / 127;
|
|
1638
|
+
if (value < 64) {
|
|
1330
1639
|
this.releaseSustainPedal(channelNumber, value);
|
|
1331
1640
|
}
|
|
1332
1641
|
}
|
|
1333
1642
|
setPortamento(channelNumber, value) {
|
|
1334
|
-
this.channels[channelNumber].portamento = value
|
|
1643
|
+
this.channels[channelNumber].state.portamento = value / 127;
|
|
1335
1644
|
}
|
|
1336
1645
|
setReverbSendLevel(channelNumber, reverbSendLevel) {
|
|
1337
1646
|
const channel = this.channels[channelNumber];
|
|
1647
|
+
const state = channel.state;
|
|
1338
1648
|
const reverbEffect = this.reverbEffect;
|
|
1339
|
-
if (0 <
|
|
1649
|
+
if (0 < state.reverbSendLevel) {
|
|
1340
1650
|
if (0 < reverbSendLevel) {
|
|
1341
1651
|
const now = this.audioContext.currentTime;
|
|
1342
|
-
|
|
1652
|
+
state.reverbSendLevel = reverbSendLevel / 127;
|
|
1343
1653
|
reverbEffect.input.gain.cancelScheduledValues(now);
|
|
1344
|
-
reverbEffect.input.gain.setValueAtTime(
|
|
1654
|
+
reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
|
|
1345
1655
|
}
|
|
1346
1656
|
else {
|
|
1347
1657
|
channel.scheduledNotes.forEach((noteList) => {
|
|
@@ -1349,7 +1659,7 @@ class MidyGM2 {
|
|
|
1349
1659
|
const note = noteList[i];
|
|
1350
1660
|
if (!note)
|
|
1351
1661
|
continue;
|
|
1352
|
-
if (note.
|
|
1662
|
+
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
1353
1663
|
continue;
|
|
1354
1664
|
note.reverbEffectsSend.disconnect();
|
|
1355
1665
|
}
|
|
@@ -1364,32 +1674,25 @@ class MidyGM2 {
|
|
|
1364
1674
|
const note = noteList[i];
|
|
1365
1675
|
if (!note)
|
|
1366
1676
|
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);
|
|
1677
|
+
this.setReverbEffectsSend(note, 0);
|
|
1376
1678
|
}
|
|
1377
1679
|
});
|
|
1378
|
-
|
|
1680
|
+
state.reverbSendLevel = reverbSendLevel / 127;
|
|
1379
1681
|
reverbEffect.input.gain.cancelScheduledValues(now);
|
|
1380
|
-
reverbEffect.input.gain.setValueAtTime(
|
|
1682
|
+
reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
|
|
1381
1683
|
}
|
|
1382
1684
|
}
|
|
1383
1685
|
}
|
|
1384
1686
|
setChorusSendLevel(channelNumber, chorusSendLevel) {
|
|
1385
1687
|
const channel = this.channels[channelNumber];
|
|
1688
|
+
const state = channel.state;
|
|
1386
1689
|
const chorusEffect = this.chorusEffect;
|
|
1387
|
-
if (0 <
|
|
1690
|
+
if (0 < state.chorusSendLevel) {
|
|
1388
1691
|
if (0 < chorusSendLevel) {
|
|
1389
1692
|
const now = this.audioContext.currentTime;
|
|
1390
|
-
|
|
1693
|
+
state.chorusSendLevel = chorusSendLevel / 127;
|
|
1391
1694
|
chorusEffect.input.gain.cancelScheduledValues(now);
|
|
1392
|
-
chorusEffect.input.gain.setValueAtTime(
|
|
1695
|
+
chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
|
|
1393
1696
|
}
|
|
1394
1697
|
else {
|
|
1395
1698
|
channel.scheduledNotes.forEach((noteList) => {
|
|
@@ -1397,7 +1700,7 @@ class MidyGM2 {
|
|
|
1397
1700
|
const note = noteList[i];
|
|
1398
1701
|
if (!note)
|
|
1399
1702
|
continue;
|
|
1400
|
-
if (note.
|
|
1703
|
+
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
1401
1704
|
continue;
|
|
1402
1705
|
note.chorusEffectsSend.disconnect();
|
|
1403
1706
|
}
|
|
@@ -1412,28 +1715,19 @@ class MidyGM2 {
|
|
|
1412
1715
|
const note = noteList[i];
|
|
1413
1716
|
if (!note)
|
|
1414
1717
|
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);
|
|
1718
|
+
this.setChorusEffectsSend(note, 0);
|
|
1424
1719
|
}
|
|
1425
1720
|
});
|
|
1426
|
-
|
|
1721
|
+
state.chorusSendLevel = chorusSendLevel / 127;
|
|
1427
1722
|
chorusEffect.input.gain.cancelScheduledValues(now);
|
|
1428
|
-
chorusEffect.input.gain.setValueAtTime(
|
|
1723
|
+
chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
|
|
1429
1724
|
}
|
|
1430
1725
|
}
|
|
1431
1726
|
}
|
|
1432
1727
|
setSostenutoPedal(channelNumber, value) {
|
|
1433
|
-
const isOn = value >= 64;
|
|
1434
1728
|
const channel = this.channels[channelNumber];
|
|
1435
|
-
channel.sostenutoPedal =
|
|
1436
|
-
if (
|
|
1729
|
+
channel.state.sostenutoPedal = value / 127;
|
|
1730
|
+
if (64 <= value) {
|
|
1437
1731
|
const now = this.audioContext.currentTime;
|
|
1438
1732
|
const activeNotes = this.getActiveNotes(channel, now);
|
|
1439
1733
|
channel.sostenutoNotes = new Map(activeNotes);
|
|
@@ -1444,7 +1738,7 @@ class MidyGM2 {
|
|
|
1444
1738
|
}
|
|
1445
1739
|
setSoftPedal(channelNumber, softPedal) {
|
|
1446
1740
|
const channel = this.channels[channelNumber];
|
|
1447
|
-
channel.softPedal = softPedal / 127;
|
|
1741
|
+
channel.state.softPedal = softPedal / 127;
|
|
1448
1742
|
}
|
|
1449
1743
|
limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
|
|
1450
1744
|
if (maxLSB < channel.dataLSB) {
|
|
@@ -1502,7 +1796,7 @@ class MidyGM2 {
|
|
|
1502
1796
|
this.channels[channelNumber].dataMSB = value;
|
|
1503
1797
|
this.handleRPN(channelNumber);
|
|
1504
1798
|
}
|
|
1505
|
-
updateDetune(channel,
|
|
1799
|
+
updateDetune(channel, detune) {
|
|
1506
1800
|
const now = this.audioContext.currentTime;
|
|
1507
1801
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1508
1802
|
for (let i = 0; i < noteList.length; i++) {
|
|
@@ -1510,7 +1804,6 @@ class MidyGM2 {
|
|
|
1510
1804
|
if (!note)
|
|
1511
1805
|
continue;
|
|
1512
1806
|
const { bufferSource } = note;
|
|
1513
|
-
const detune = bufferSource.detune.value + detuneChange;
|
|
1514
1807
|
bufferSource.detune
|
|
1515
1808
|
.cancelScheduledValues(now)
|
|
1516
1809
|
.setValueAtTime(detune, now);
|
|
@@ -1523,13 +1816,13 @@ class MidyGM2 {
|
|
|
1523
1816
|
const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
|
|
1524
1817
|
this.setPitchBendRange(channelNumber, pitchBendRange);
|
|
1525
1818
|
}
|
|
1526
|
-
setPitchBendRange(channelNumber,
|
|
1819
|
+
setPitchBendRange(channelNumber, pitchWheelSensitivity) {
|
|
1527
1820
|
const channel = this.channels[channelNumber];
|
|
1528
|
-
const
|
|
1529
|
-
|
|
1530
|
-
const
|
|
1531
|
-
|
|
1532
|
-
this.
|
|
1821
|
+
const state = channel.state;
|
|
1822
|
+
state.pitchWheelSensitivity = pitchWheelSensitivity / 128;
|
|
1823
|
+
const detune = (state.pitchWheel * 2 - 1) * pitchWheelSensitivity * 100;
|
|
1824
|
+
this.updateDetune(channel, detune);
|
|
1825
|
+
this.applyVoiceParams(channel, 16);
|
|
1533
1826
|
}
|
|
1534
1827
|
handleFineTuningRPN(channelNumber) {
|
|
1535
1828
|
const channel = this.channels[channelNumber];
|
|
@@ -1573,7 +1866,30 @@ class MidyGM2 {
|
|
|
1573
1866
|
return this.stopChannelNotes(channelNumber, 0, true);
|
|
1574
1867
|
}
|
|
1575
1868
|
resetAllControllers(channelNumber) {
|
|
1576
|
-
|
|
1869
|
+
const stateTypes = [
|
|
1870
|
+
"expression",
|
|
1871
|
+
"modulationDepth",
|
|
1872
|
+
"sustainPedal",
|
|
1873
|
+
"portamento",
|
|
1874
|
+
"sostenutoPedal",
|
|
1875
|
+
"softPedal",
|
|
1876
|
+
"channelPressure",
|
|
1877
|
+
"pitchWheelSensitivity",
|
|
1878
|
+
];
|
|
1879
|
+
const channel = this.channels[channelNumber];
|
|
1880
|
+
const state = channel.state;
|
|
1881
|
+
for (let i = 0; i < stateTypes.length; i++) {
|
|
1882
|
+
const type = stateTypes[i];
|
|
1883
|
+
state[type] = defaultControllerState[type];
|
|
1884
|
+
}
|
|
1885
|
+
const settingTypes = [
|
|
1886
|
+
"rpnMSB",
|
|
1887
|
+
"rpnLSB",
|
|
1888
|
+
];
|
|
1889
|
+
for (let i = 0; i < settingTypes.length; i++) {
|
|
1890
|
+
const type = settingTypes[i];
|
|
1891
|
+
channel[type] = this.constructor.channelSettings[type];
|
|
1892
|
+
}
|
|
1577
1893
|
}
|
|
1578
1894
|
allNotesOff(channelNumber) {
|
|
1579
1895
|
return this.stopChannelNotes(channelNumber, 0, false);
|
|
@@ -1936,43 +2252,19 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
1936
2252
|
writable: true,
|
|
1937
2253
|
value: {
|
|
1938
2254
|
currentBufferSource: null,
|
|
1939
|
-
|
|
1940
|
-
pan: 64,
|
|
1941
|
-
portamentoTime: 1, // sec
|
|
1942
|
-
reverbSendLevel: 0,
|
|
1943
|
-
chorusSendLevel: 0,
|
|
1944
|
-
vibratoRate: 1,
|
|
1945
|
-
vibratoDepth: 1,
|
|
1946
|
-
vibratoDelay: 1,
|
|
2255
|
+
program: 0,
|
|
1947
2256
|
bank: 121 * 128,
|
|
1948
2257
|
bankMSB: 121,
|
|
1949
2258
|
bankLSB: 0,
|
|
1950
2259
|
dataMSB: 0,
|
|
1951
2260
|
dataLSB: 0,
|
|
1952
|
-
|
|
1953
|
-
|
|
2261
|
+
rpnMSB: 127,
|
|
2262
|
+
rpnLSB: 127,
|
|
1954
2263
|
fineTuning: 0, // cb
|
|
1955
2264
|
coarseTuning: 0, // cb
|
|
1956
2265
|
modulationDepthRange: 50, // cent
|
|
1957
2266
|
}
|
|
1958
2267
|
});
|
|
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
2268
|
Object.defineProperty(MidyGM2, "controllerDestinationSettings", {
|
|
1977
2269
|
enumerable: true,
|
|
1978
2270
|
configurable: true,
|