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