@marmooo/midy 0.1.7 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +118 -0
- package/esm/midy-GM1.d.ts +53 -26
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +397 -167
- package/esm/midy-GM2.d.ts +61 -38
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +617 -278
- package/esm/midy-GMLite.d.ts +49 -23
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +361 -156
- package/esm/midy.d.ts +63 -45
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +699 -354
- package/package.json +2 -2
- package/script/midy-GM1.d.ts +53 -26
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +397 -167
- package/script/midy-GM2.d.ts +61 -38
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +617 -278
- package/script/midy-GMLite.d.ts +49 -23
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +361 -156
- package/script/midy.d.ts +63 -45
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +699 -354
package/script/midy.js
CHANGED
|
@@ -4,7 +4,7 @@ exports.Midy = 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 Midy {
|
|
75
169
|
constructor(audioContext, options = this.defaultOptions) {
|
|
76
170
|
Object.defineProperty(this, "ticksPerBeat", {
|
|
@@ -251,6 +345,7 @@ class Midy {
|
|
|
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 Midy {
|
|
|
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 Midy {
|
|
|
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(),
|
|
@@ -328,11 +423,11 @@ class Midy {
|
|
|
328
423
|
});
|
|
329
424
|
return channels;
|
|
330
425
|
}
|
|
331
|
-
async createNoteBuffer(
|
|
332
|
-
const sampleStart =
|
|
333
|
-
const sampleEnd =
|
|
426
|
+
async createNoteBuffer(voiceParams, isSF3) {
|
|
427
|
+
const sampleStart = voiceParams.start;
|
|
428
|
+
const sampleEnd = voiceParams.sample.length + voiceParams.end;
|
|
334
429
|
if (isSF3) {
|
|
335
|
-
const sample =
|
|
430
|
+
const sample = voiceParams.sample;
|
|
336
431
|
const start = sample.byteOffset + sampleStart;
|
|
337
432
|
const end = sample.byteOffset + sampleEnd;
|
|
338
433
|
const buffer = sample.buffer.slice(start, end);
|
|
@@ -340,14 +435,14 @@ class Midy {
|
|
|
340
435
|
return audioBuffer;
|
|
341
436
|
}
|
|
342
437
|
else {
|
|
343
|
-
const sample =
|
|
438
|
+
const sample = voiceParams.sample;
|
|
344
439
|
const start = sample.byteOffset + sampleStart;
|
|
345
440
|
const end = sample.byteOffset + sampleEnd;
|
|
346
441
|
const buffer = sample.buffer.slice(start, end);
|
|
347
442
|
const audioBuffer = new AudioBuffer({
|
|
348
443
|
numberOfChannels: 1,
|
|
349
444
|
length: sample.length,
|
|
350
|
-
sampleRate:
|
|
445
|
+
sampleRate: voiceParams.sampleRate,
|
|
351
446
|
});
|
|
352
447
|
const channelData = audioBuffer.getChannelData(0);
|
|
353
448
|
const int16Array = new Int16Array(buffer);
|
|
@@ -357,15 +452,14 @@ class Midy {
|
|
|
357
452
|
return audioBuffer;
|
|
358
453
|
}
|
|
359
454
|
}
|
|
360
|
-
async createNoteBufferNode(
|
|
455
|
+
async createNoteBufferNode(voiceParams, isSF3) {
|
|
361
456
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
362
|
-
const audioBuffer = await this.createNoteBuffer(
|
|
457
|
+
const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
|
|
363
458
|
bufferSource.buffer = audioBuffer;
|
|
364
|
-
bufferSource.loop =
|
|
459
|
+
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
365
460
|
if (bufferSource.loop) {
|
|
366
|
-
bufferSource.loopStart =
|
|
367
|
-
|
|
368
|
-
bufferSource.loopEnd = instrumentKey.loopEnd / instrumentKey.sampleRate;
|
|
461
|
+
bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
|
|
462
|
+
bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
|
|
369
463
|
}
|
|
370
464
|
return bufferSource;
|
|
371
465
|
}
|
|
@@ -422,7 +516,7 @@ class Midy {
|
|
|
422
516
|
this.handleChannelPressure(event.channel, event.amount);
|
|
423
517
|
break;
|
|
424
518
|
case "pitchBend":
|
|
425
|
-
this.setPitchBend(event.channel, event.value);
|
|
519
|
+
this.setPitchBend(event.channel, event.value + 8192);
|
|
426
520
|
break;
|
|
427
521
|
case "sysEx":
|
|
428
522
|
this.handleSysEx(event.data);
|
|
@@ -813,63 +907,94 @@ class Midy {
|
|
|
813
907
|
cbToRatio(cb) {
|
|
814
908
|
return Math.pow(10, cb / 200);
|
|
815
909
|
}
|
|
910
|
+
rateToCent(rate) {
|
|
911
|
+
return 1200 * Math.log2(rate);
|
|
912
|
+
}
|
|
913
|
+
centToRate(cent) {
|
|
914
|
+
return Math.pow(2, cent / 1200);
|
|
915
|
+
}
|
|
816
916
|
centToHz(cent) {
|
|
817
|
-
return 8.176 *
|
|
917
|
+
return 8.176 * this.centToRate(cent);
|
|
818
918
|
}
|
|
819
|
-
|
|
919
|
+
calcChannelDetune(channel) {
|
|
820
920
|
const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
|
|
821
921
|
const channelTuning = channel.coarseTuning + channel.fineTuning;
|
|
822
922
|
const tuning = masterTuning + channelTuning;
|
|
823
|
-
|
|
923
|
+
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
924
|
+
const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
|
|
925
|
+
const pitch = pitchWheel * pitchWheelSensitivity;
|
|
926
|
+
return tuning + pitch;
|
|
824
927
|
}
|
|
825
|
-
|
|
826
|
-
return
|
|
827
|
-
|
|
928
|
+
calcNoteDetune(channel, note) {
|
|
929
|
+
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
930
|
+
}
|
|
931
|
+
updateDetune(channel) {
|
|
932
|
+
const now = this.audioContext.currentTime;
|
|
933
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
934
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
935
|
+
const note = noteList[i];
|
|
936
|
+
if (!note)
|
|
937
|
+
continue;
|
|
938
|
+
const noteDetune = this.calcNoteDetune(channel, note);
|
|
939
|
+
const detune = channel.detune + noteDetune;
|
|
940
|
+
note.bufferSource.detune
|
|
941
|
+
.cancelScheduledValues(now)
|
|
942
|
+
.setValueAtTime(detune, now);
|
|
943
|
+
}
|
|
944
|
+
});
|
|
828
945
|
}
|
|
829
946
|
setPortamentoStartVolumeEnvelope(channel, note) {
|
|
830
|
-
const
|
|
831
|
-
const
|
|
832
|
-
const
|
|
833
|
-
const
|
|
834
|
-
const
|
|
947
|
+
const now = this.audioContext.currentTime;
|
|
948
|
+
const { voiceParams, startTime } = note;
|
|
949
|
+
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
950
|
+
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
951
|
+
const volDelay = startTime + voiceParams.volDelay;
|
|
952
|
+
const portamentoTime = volDelay + channel.state.portamentoTime;
|
|
835
953
|
note.volumeNode.gain
|
|
836
|
-
.cancelScheduledValues(
|
|
954
|
+
.cancelScheduledValues(now)
|
|
837
955
|
.setValueAtTime(0, volDelay)
|
|
838
956
|
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
839
957
|
}
|
|
840
958
|
setVolumeEnvelope(channel, note) {
|
|
841
|
-
const
|
|
842
|
-
const
|
|
843
|
-
const
|
|
844
|
-
const
|
|
845
|
-
const
|
|
846
|
-
const
|
|
847
|
-
const
|
|
959
|
+
const now = this.audioContext.currentTime;
|
|
960
|
+
const state = channel.state;
|
|
961
|
+
const { voiceParams, startTime } = note;
|
|
962
|
+
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
963
|
+
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
964
|
+
const volDelay = startTime + voiceParams.volDelay;
|
|
965
|
+
const volAttack = volDelay + voiceParams.volAttack * state.attackTime * 2;
|
|
966
|
+
const volHold = volAttack + voiceParams.volHold;
|
|
967
|
+
const volDecay = volHold + voiceParams.volDecay * state.decayTime * 2;
|
|
848
968
|
note.volumeNode.gain
|
|
849
|
-
.cancelScheduledValues(
|
|
969
|
+
.cancelScheduledValues(now)
|
|
850
970
|
.setValueAtTime(0, startTime)
|
|
851
971
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
852
972
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
853
973
|
.setValueAtTime(attackVolume, volHold)
|
|
854
974
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
855
975
|
}
|
|
856
|
-
|
|
857
|
-
const
|
|
858
|
-
const
|
|
859
|
-
|
|
976
|
+
setPitchEnvelope(note) {
|
|
977
|
+
const now = this.audioContext.currentTime;
|
|
978
|
+
const { voiceParams } = note;
|
|
979
|
+
const baseRate = voiceParams.playbackRate;
|
|
980
|
+
note.bufferSource.playbackRate
|
|
981
|
+
.cancelScheduledValues(now)
|
|
982
|
+
.setValueAtTime(baseRate, now);
|
|
983
|
+
const modEnvToPitch = voiceParams.modEnvToPitch;
|
|
860
984
|
if (modEnvToPitch === 0)
|
|
861
985
|
return;
|
|
862
|
-
const basePitch =
|
|
863
|
-
const peekPitch =
|
|
864
|
-
const
|
|
865
|
-
const
|
|
866
|
-
const
|
|
867
|
-
const
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
.
|
|
871
|
-
.
|
|
872
|
-
.
|
|
986
|
+
const basePitch = this.rateToCent(baseRate);
|
|
987
|
+
const peekPitch = basePitch + modEnvToPitch;
|
|
988
|
+
const peekRate = this.centToRate(peekPitch);
|
|
989
|
+
const modDelay = startTime + voiceParams.modDelay;
|
|
990
|
+
const modAttack = modDelay + voiceParams.modAttack;
|
|
991
|
+
const modHold = modAttack + voiceParams.modHold;
|
|
992
|
+
const modDecay = modHold + voiceParams.modDecay;
|
|
993
|
+
note.bufferSource.playbackRate
|
|
994
|
+
.setValueAtTime(baseRate, modDelay)
|
|
995
|
+
.exponentialRampToValueAtTime(peekRate, modAttack)
|
|
996
|
+
.setValueAtTime(peekRate, modHold)
|
|
997
|
+
.linearRampToValueAtTime(baseRate, modDecay);
|
|
873
998
|
}
|
|
874
999
|
clampCutoffFrequency(frequency) {
|
|
875
1000
|
const minFrequency = 20; // min Hz of initialFilterFc
|
|
@@ -877,42 +1002,46 @@ class Midy {
|
|
|
877
1002
|
return Math.max(minFrequency, Math.min(frequency, maxFrequency));
|
|
878
1003
|
}
|
|
879
1004
|
setPortamentoStartFilterEnvelope(channel, note) {
|
|
880
|
-
const
|
|
1005
|
+
const now = this.audioContext.currentTime;
|
|
1006
|
+
const state = channel.state;
|
|
1007
|
+
const { voiceParams, noteNumber, startTime } = note;
|
|
881
1008
|
const softPedalFactor = 1 -
|
|
882
|
-
(0.1 + (noteNumber / 127) * 0.2) *
|
|
883
|
-
const baseFreq = this.centToHz(
|
|
884
|
-
softPedalFactor *
|
|
885
|
-
const peekFreq = this.centToHz(
|
|
1009
|
+
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1010
|
+
const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
|
|
1011
|
+
softPedalFactor * state.brightness * 2;
|
|
1012
|
+
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor * state.brightness * 2;
|
|
886
1013
|
const sustainFreq = baseFreq +
|
|
887
|
-
(peekFreq - baseFreq) * (1 -
|
|
1014
|
+
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
888
1015
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
889
1016
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
890
|
-
const portamentoTime = startTime + channel.portamentoTime;
|
|
891
|
-
const modDelay = startTime +
|
|
1017
|
+
const portamentoTime = startTime + channel.state.portamentoTime;
|
|
1018
|
+
const modDelay = startTime + voiceParams.modDelay;
|
|
892
1019
|
note.filterNode.frequency
|
|
893
|
-
.cancelScheduledValues(
|
|
1020
|
+
.cancelScheduledValues(now)
|
|
894
1021
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
895
1022
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
896
1023
|
.linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
|
|
897
1024
|
}
|
|
898
1025
|
setFilterEnvelope(channel, note) {
|
|
899
|
-
const
|
|
1026
|
+
const now = this.audioContext.currentTime;
|
|
1027
|
+
const state = channel.state;
|
|
1028
|
+
const { voiceParams, noteNumber, startTime } = note;
|
|
900
1029
|
const softPedalFactor = 1 -
|
|
901
|
-
(0.1 + (noteNumber / 127) * 0.2) *
|
|
902
|
-
const baseFreq = this.centToHz(
|
|
903
|
-
softPedalFactor *
|
|
904
|
-
const peekFreq = this.centToHz(
|
|
1030
|
+
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1031
|
+
const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
|
|
1032
|
+
softPedalFactor * state.brightness * 2;
|
|
1033
|
+
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor * state.brightness * 2;
|
|
905
1034
|
const sustainFreq = baseFreq +
|
|
906
|
-
(peekFreq - baseFreq) * (1 -
|
|
1035
|
+
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
907
1036
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
908
1037
|
const adjustedPeekFreq = this.clampCutoffFrequency(peekFreq);
|
|
909
1038
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
910
|
-
const modDelay = startTime +
|
|
911
|
-
const modAttack = modDelay +
|
|
912
|
-
const modHold = modAttack +
|
|
913
|
-
const modDecay = modHold +
|
|
1039
|
+
const modDelay = startTime + voiceParams.modDelay;
|
|
1040
|
+
const modAttack = modDelay + voiceParams.modAttack;
|
|
1041
|
+
const modHold = modAttack + voiceParams.modHold;
|
|
1042
|
+
const modDecay = modHold + voiceParams.modDecay;
|
|
914
1043
|
note.filterNode.frequency
|
|
915
|
-
.cancelScheduledValues(
|
|
1044
|
+
.cancelScheduledValues(now)
|
|
916
1045
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
917
1046
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
918
1047
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
@@ -920,25 +1049,18 @@ class Midy {
|
|
|
920
1049
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
921
1050
|
}
|
|
922
1051
|
startModulation(channel, note, startTime) {
|
|
923
|
-
const {
|
|
924
|
-
const { modLfoToPitch, modLfoToVolume } = instrumentKey;
|
|
1052
|
+
const { voiceParams } = note;
|
|
925
1053
|
note.modulationLFO = new OscillatorNode(this.audioContext, {
|
|
926
|
-
frequency: this.centToHz(
|
|
1054
|
+
frequency: this.centToHz(voiceParams.freqModLFO),
|
|
927
1055
|
});
|
|
928
1056
|
note.filterDepth = new GainNode(this.audioContext, {
|
|
929
|
-
gain:
|
|
930
|
-
});
|
|
931
|
-
const modulationDepth = Math.abs(modLfoToPitch) + channel.modulationDepth;
|
|
932
|
-
const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
|
|
933
|
-
note.modulationDepth = new GainNode(this.audioContext, {
|
|
934
|
-
gain: modulationDepth * modulationDepthSign,
|
|
1057
|
+
gain: voiceParams.modLfoToFilterFc,
|
|
935
1058
|
});
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
note.volumeDepth = new GainNode(this.audioContext
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
note.modulationLFO.start(startTime + instrumentKey.delayModLFO);
|
|
1059
|
+
note.modulationDepth = new GainNode(this.audioContext);
|
|
1060
|
+
this.setModLfoToPitch(channel, note);
|
|
1061
|
+
note.volumeDepth = new GainNode(this.audioContext);
|
|
1062
|
+
this.setModLfoToVolume(note);
|
|
1063
|
+
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
942
1064
|
note.modulationLFO.connect(note.filterDepth);
|
|
943
1065
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
944
1066
|
note.modulationLFO.connect(note.modulationDepth);
|
|
@@ -947,67 +1069,57 @@ class Midy {
|
|
|
947
1069
|
note.volumeDepth.connect(note.volumeNode.gain);
|
|
948
1070
|
}
|
|
949
1071
|
startVibrato(channel, note, startTime) {
|
|
950
|
-
const {
|
|
951
|
-
const
|
|
1072
|
+
const { voiceParams } = note;
|
|
1073
|
+
const state = channel.state;
|
|
952
1074
|
note.vibratoLFO = new OscillatorNode(this.audioContext, {
|
|
953
|
-
frequency: this.centToHz(
|
|
954
|
-
|
|
955
|
-
});
|
|
956
|
-
const vibratoDepth = Math.abs(vibLfoToPitch) * channel.vibratoDepth;
|
|
957
|
-
const vibratoDepthSign = 0 < vibLfoToPitch;
|
|
958
|
-
note.vibratoDepth = new GainNode(this.audioContext, {
|
|
959
|
-
gain: vibratoDepth * vibratoDepthSign,
|
|
1075
|
+
frequency: this.centToHz(voiceParams.freqVibLFO) *
|
|
1076
|
+
state.vibratoRate,
|
|
960
1077
|
});
|
|
961
|
-
note.vibratoLFO.start(startTime +
|
|
1078
|
+
note.vibratoLFO.start(startTime + voiceParams.delayVibLFO * state.vibratoDelay * 2);
|
|
1079
|
+
note.vibratoDepth = new GainNode(this.audioContext);
|
|
1080
|
+
this.setVibLfoToPitch(channel, note);
|
|
962
1081
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
963
1082
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
964
1083
|
}
|
|
965
|
-
async createNote(channel,
|
|
966
|
-
const
|
|
967
|
-
const
|
|
968
|
-
|
|
1084
|
+
async createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3) {
|
|
1085
|
+
const state = channel.state;
|
|
1086
|
+
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1087
|
+
const voiceParams = voice.getAllParams(controllerState);
|
|
1088
|
+
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1089
|
+
note.bufferSource = await this.createNoteBufferNode(voiceParams, isSF3);
|
|
969
1090
|
note.volumeNode = new GainNode(this.audioContext);
|
|
970
1091
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
971
1092
|
type: "lowpass",
|
|
972
|
-
Q:
|
|
1093
|
+
Q: voiceParams.initialFilterQ / 5 * state.filterResonance, // dB
|
|
973
1094
|
});
|
|
974
1095
|
if (portamento) {
|
|
1096
|
+
note.portamento = true;
|
|
975
1097
|
this.setPortamentoStartVolumeEnvelope(channel, note);
|
|
976
1098
|
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
977
1099
|
}
|
|
978
1100
|
else {
|
|
1101
|
+
note.portamento = false;
|
|
979
1102
|
this.setVolumeEnvelope(channel, note);
|
|
980
1103
|
this.setFilterEnvelope(channel, note);
|
|
981
1104
|
}
|
|
982
|
-
if (0 <
|
|
1105
|
+
if (0 < state.vibratoDepth) {
|
|
983
1106
|
this.startVibrato(channel, note, startTime);
|
|
984
1107
|
}
|
|
985
|
-
|
|
986
|
-
|
|
1108
|
+
this.setPitchEnvelope(note);
|
|
1109
|
+
if (0 < state.modulationDepth) {
|
|
987
1110
|
this.startModulation(channel, note, startTime);
|
|
988
1111
|
}
|
|
989
|
-
else {
|
|
990
|
-
note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
|
|
991
|
-
}
|
|
992
1112
|
if (this.mono && channel.currentBufferSource) {
|
|
993
1113
|
channel.currentBufferSource.stop(startTime);
|
|
994
1114
|
channel.currentBufferSource = note.bufferSource;
|
|
995
1115
|
}
|
|
996
1116
|
note.bufferSource.connect(note.filterNode);
|
|
997
1117
|
note.filterNode.connect(note.volumeNode);
|
|
998
|
-
if (0 < channel.
|
|
999
|
-
|
|
1000
|
-
gain: instrumentKey.reverbEffectsSend,
|
|
1001
|
-
});
|
|
1002
|
-
note.volumeNode.connect(note.reverbEffectsSend);
|
|
1003
|
-
note.reverbEffectsSend.connect(this.reverbEffect.input);
|
|
1118
|
+
if (0 < channel.chorusSendLevel) {
|
|
1119
|
+
this.setChorusEffectsSend(channel, note, 0);
|
|
1004
1120
|
}
|
|
1005
|
-
if (0 < channel.
|
|
1006
|
-
|
|
1007
|
-
gain: instrumentKey.chorusEffectsSend,
|
|
1008
|
-
});
|
|
1009
|
-
note.volumeNode.connect(note.chorusEffectsSend);
|
|
1010
|
-
note.chorusEffectsSend.connect(this.chorusEffect.input);
|
|
1121
|
+
if (0 < channel.reverbSendLevel) {
|
|
1122
|
+
this.setReverbEffectsSend(channel, note, 0);
|
|
1011
1123
|
}
|
|
1012
1124
|
note.bufferSource.start(startTime);
|
|
1013
1125
|
return note;
|
|
@@ -1029,16 +1141,16 @@ class Midy {
|
|
|
1029
1141
|
return;
|
|
1030
1142
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
1031
1143
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
1032
|
-
const
|
|
1033
|
-
if (!
|
|
1144
|
+
const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
|
|
1145
|
+
if (!voice)
|
|
1034
1146
|
return;
|
|
1035
|
-
const note = await this.createNote(channel,
|
|
1147
|
+
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
|
|
1036
1148
|
note.volumeNode.connect(channel.gainL);
|
|
1037
1149
|
note.volumeNode.connect(channel.gainR);
|
|
1038
|
-
if (channel.sostenutoPedal) {
|
|
1150
|
+
if (channel.state.sostenutoPedal) {
|
|
1039
1151
|
channel.sostenutoNotes.set(noteNumber, note);
|
|
1040
1152
|
}
|
|
1041
|
-
const exclusiveClass =
|
|
1153
|
+
const exclusiveClass = note.voiceParams.exclusiveClass;
|
|
1042
1154
|
if (exclusiveClass !== 0) {
|
|
1043
1155
|
if (this.exclusiveClassMap.has(exclusiveClass)) {
|
|
1044
1156
|
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
@@ -1100,8 +1212,9 @@ class Midy {
|
|
|
1100
1212
|
}
|
|
1101
1213
|
scheduleNoteRelease(channelNumber, noteNumber, _velocity, endTime, portamentoNoteNumber, force) {
|
|
1102
1214
|
const channel = this.channels[channelNumber];
|
|
1215
|
+
const state = channel.state;
|
|
1103
1216
|
if (!force) {
|
|
1104
|
-
if (
|
|
1217
|
+
if (0.5 < state.sustainPedal)
|
|
1105
1218
|
return;
|
|
1106
1219
|
if (channel.sostenutoNotes.has(noteNumber))
|
|
1107
1220
|
return;
|
|
@@ -1117,8 +1230,8 @@ class Midy {
|
|
|
1117
1230
|
continue;
|
|
1118
1231
|
if (portamentoNoteNumber === undefined) {
|
|
1119
1232
|
const volRelease = endTime +
|
|
1120
|
-
note.
|
|
1121
|
-
const modRelease = endTime + note.
|
|
1233
|
+
note.voiceParams.volRelease * state.releaseTime * 2;
|
|
1234
|
+
const modRelease = endTime + note.voiceParams.modRelease;
|
|
1122
1235
|
note.filterNode.frequency
|
|
1123
1236
|
.cancelScheduledValues(endTime)
|
|
1124
1237
|
.linearRampToValueAtTime(0, modRelease);
|
|
@@ -1126,12 +1239,13 @@ class Midy {
|
|
|
1126
1239
|
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
1127
1240
|
}
|
|
1128
1241
|
else {
|
|
1129
|
-
const portamentoTime = endTime +
|
|
1130
|
-
const
|
|
1131
|
-
const
|
|
1132
|
-
|
|
1242
|
+
const portamentoTime = endTime + state.portamentoTime;
|
|
1243
|
+
const deltaNote = portamentoNoteNumber - noteNumber;
|
|
1244
|
+
const baseRate = note.voiceParams.playbackRate;
|
|
1245
|
+
const targetRate = baseRate * Math.pow(2, deltaNote / 12);
|
|
1246
|
+
note.bufferSource.playbackRate
|
|
1133
1247
|
.cancelScheduledValues(endTime)
|
|
1134
|
-
.linearRampToValueAtTime(
|
|
1248
|
+
.linearRampToValueAtTime(targetRate, portamentoTime);
|
|
1135
1249
|
return this.stopNote(endTime, portamentoTime, scheduledNotes, i);
|
|
1136
1250
|
}
|
|
1137
1251
|
}
|
|
@@ -1144,7 +1258,7 @@ class Midy {
|
|
|
1144
1258
|
const velocity = halfVelocity * 2;
|
|
1145
1259
|
const channel = this.channels[channelNumber];
|
|
1146
1260
|
const promises = [];
|
|
1147
|
-
channel.sustainPedal =
|
|
1261
|
+
channel.state.sustainPedal = halfVelocity;
|
|
1148
1262
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1149
1263
|
for (let i = 0; i < noteList.length; i++) {
|
|
1150
1264
|
const note = noteList[i];
|
|
@@ -1161,7 +1275,7 @@ class Midy {
|
|
|
1161
1275
|
const velocity = halfVelocity * 2;
|
|
1162
1276
|
const channel = this.channels[channelNumber];
|
|
1163
1277
|
const promises = [];
|
|
1164
|
-
channel.sostenutoPedal =
|
|
1278
|
+
channel.state.sostenutoPedal = 0;
|
|
1165
1279
|
channel.sostenutoNotes.forEach((activeNote) => {
|
|
1166
1280
|
const { noteNumber } = activeNote;
|
|
1167
1281
|
const promise = this.releaseNote(channelNumber, noteNumber, velocity);
|
|
@@ -1206,6 +1320,7 @@ class Midy {
|
|
|
1206
1320
|
.setValueAtTime(gain * pressure, now);
|
|
1207
1321
|
}
|
|
1208
1322
|
}
|
|
1323
|
+
// this.applyVoiceParams(channel, 10);
|
|
1209
1324
|
}
|
|
1210
1325
|
handleProgramChange(channelNumber, program) {
|
|
1211
1326
|
const channel = this.channels[channelNumber];
|
|
@@ -1226,18 +1341,233 @@ class Midy {
|
|
|
1226
1341
|
.setValueAtTime(gain * pressure, now);
|
|
1227
1342
|
});
|
|
1228
1343
|
}
|
|
1344
|
+
// this.applyVoiceParams(channel, 13);
|
|
1229
1345
|
}
|
|
1230
1346
|
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
1231
|
-
const pitchBend = msb * 128 + lsb
|
|
1347
|
+
const pitchBend = msb * 128 + lsb;
|
|
1232
1348
|
this.setPitchBend(channelNumber, pitchBend);
|
|
1233
1349
|
}
|
|
1234
|
-
setPitchBend(channelNumber,
|
|
1350
|
+
setPitchBend(channelNumber, value) {
|
|
1235
1351
|
const channel = this.channels[channelNumber];
|
|
1236
|
-
const
|
|
1237
|
-
|
|
1238
|
-
const
|
|
1239
|
-
|
|
1240
|
-
|
|
1352
|
+
const state = channel.state;
|
|
1353
|
+
const prev = state.pitchWheel * 2 - 1;
|
|
1354
|
+
const next = (value - 8192) / 8192;
|
|
1355
|
+
state.pitchWheel = value / 16383;
|
|
1356
|
+
channel.detune += (next - prev) * state.pitchWheelSensitivity * 12800;
|
|
1357
|
+
this.updateDetune(channel);
|
|
1358
|
+
this.applyVoiceParams(channel, 14);
|
|
1359
|
+
}
|
|
1360
|
+
setModLfoToPitch(channel, note) {
|
|
1361
|
+
const now = this.audioContext.currentTime;
|
|
1362
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch;
|
|
1363
|
+
const modulationDepth = Math.abs(modLfoToPitch) +
|
|
1364
|
+
channel.state.modulationDepth;
|
|
1365
|
+
const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
|
|
1366
|
+
note.modulationDepth.gain
|
|
1367
|
+
.cancelScheduledValues(now)
|
|
1368
|
+
.setValueAtTime(modulationDepth * modulationDepthSign, now);
|
|
1369
|
+
}
|
|
1370
|
+
setVibLfoToPitch(channel, note) {
|
|
1371
|
+
const now = this.audioContext.currentTime;
|
|
1372
|
+
const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
|
|
1373
|
+
const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
|
|
1374
|
+
2;
|
|
1375
|
+
const vibratoDepthSign = 0 < vibLfoToPitch;
|
|
1376
|
+
note.vibratoDepth.gain
|
|
1377
|
+
.cancelScheduledValues(now)
|
|
1378
|
+
.setValueAtTime(vibratoDepth * vibratoDepthSign, now);
|
|
1379
|
+
}
|
|
1380
|
+
setModLfoToFilterFc(note) {
|
|
1381
|
+
const now = this.audioContext.currentTime;
|
|
1382
|
+
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
|
|
1383
|
+
note.filterDepth.gain
|
|
1384
|
+
.cancelScheduledValues(now)
|
|
1385
|
+
.setValueAtTime(modLfoToFilterFc, now);
|
|
1386
|
+
}
|
|
1387
|
+
setModLfoToVolume(note) {
|
|
1388
|
+
const now = this.audioContext.currentTime;
|
|
1389
|
+
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1390
|
+
const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1391
|
+
const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
|
|
1392
|
+
note.volumeDepth.gain
|
|
1393
|
+
.cancelScheduledValues(now)
|
|
1394
|
+
.setValueAtTime(volumeDepth * volumeDepthSign, now);
|
|
1395
|
+
}
|
|
1396
|
+
setChorusEffectsSend(note, prevValue) {
|
|
1397
|
+
if (0 < prevValue) {
|
|
1398
|
+
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1399
|
+
const now = this.audioContext.currentTime;
|
|
1400
|
+
const value = note.voiceParams.chorusEffectsSend;
|
|
1401
|
+
note.chorusEffectsSend.gain
|
|
1402
|
+
.cancelScheduledValues(now)
|
|
1403
|
+
.setValueAtTime(value, now);
|
|
1404
|
+
}
|
|
1405
|
+
else {
|
|
1406
|
+
note.chorusEffectsSend.disconnect();
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
else {
|
|
1410
|
+
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1411
|
+
if (!note.chorusEffectsSend) {
|
|
1412
|
+
note.chorusEffectsSend = new GainNode(this.audioContext, {
|
|
1413
|
+
gain: note.voiceParams.chorusEffectsSend,
|
|
1414
|
+
});
|
|
1415
|
+
note.volumeNode.connect(note.chorusEffectsSend);
|
|
1416
|
+
}
|
|
1417
|
+
note.chorusEffectsSend.connect(this.chorusEffect.input);
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
setReverbEffectsSend(note, prevValue) {
|
|
1422
|
+
if (0 < prevValue) {
|
|
1423
|
+
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1424
|
+
const now = this.audioContext.currentTime;
|
|
1425
|
+
const value = note.voiceParams.reverbEffectsSend;
|
|
1426
|
+
note.reverbEffectsSend.gain
|
|
1427
|
+
.cancelScheduledValues(now)
|
|
1428
|
+
.setValueAtTime(value, now);
|
|
1429
|
+
}
|
|
1430
|
+
else {
|
|
1431
|
+
note.reverbEffectsSend.disconnect();
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
else {
|
|
1435
|
+
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1436
|
+
if (!note.reverbEffectsSend) {
|
|
1437
|
+
note.reverbEffectsSend = new GainNode(this.audioContext, {
|
|
1438
|
+
gain: note.voiceParams.reverbEffectsSend,
|
|
1439
|
+
});
|
|
1440
|
+
note.volumeNode.connect(note.reverbEffectsSend);
|
|
1441
|
+
}
|
|
1442
|
+
note.reverbEffectsSend.connect(this.reverbEffect.input);
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
setDelayModLFO(note) {
|
|
1447
|
+
const now = this.audioContext.currentTime;
|
|
1448
|
+
const startTime = note.startTime;
|
|
1449
|
+
if (startTime < now)
|
|
1450
|
+
return;
|
|
1451
|
+
note.modulationLFO.stop(now);
|
|
1452
|
+
note.modulationLFO.start(startTime + note.voiceParams.delayModLFO);
|
|
1453
|
+
note.modulationLFO.connect(note.filterDepth);
|
|
1454
|
+
}
|
|
1455
|
+
setFreqModLFO(note) {
|
|
1456
|
+
const now = this.audioContext.currentTime;
|
|
1457
|
+
const freqModLFO = note.voiceParams.freqModLFO;
|
|
1458
|
+
note.modulationLFO.frequency
|
|
1459
|
+
.cancelScheduledValues(now)
|
|
1460
|
+
.setValueAtTime(freqModLFO, now);
|
|
1461
|
+
}
|
|
1462
|
+
createVoiceParamsHandlers() {
|
|
1463
|
+
return {
|
|
1464
|
+
modLfoToPitch: (channel, note, _prevValue) => {
|
|
1465
|
+
if (0 < channel.state.modulationDepth) {
|
|
1466
|
+
this.setModLfoToPitch(channel, note);
|
|
1467
|
+
}
|
|
1468
|
+
},
|
|
1469
|
+
vibLfoToPitch: (channel, note, _prevValue) => {
|
|
1470
|
+
if (0 < channel.state.vibratoDepth) {
|
|
1471
|
+
this.setVibLfoToPitch(channel, note);
|
|
1472
|
+
}
|
|
1473
|
+
},
|
|
1474
|
+
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1475
|
+
if (0 < channel.state.modulationDepth)
|
|
1476
|
+
this.setModLfoToFilterFc(note);
|
|
1477
|
+
},
|
|
1478
|
+
modLfoToVolume: (channel, note) => {
|
|
1479
|
+
if (0 < channel.state.modulationDepth)
|
|
1480
|
+
this.setModLfoToVolume(note);
|
|
1481
|
+
},
|
|
1482
|
+
chorusEffectsSend: (_channel, note, prevValue) => {
|
|
1483
|
+
this.setChorusEffectsSend(note, prevValue);
|
|
1484
|
+
},
|
|
1485
|
+
reverbEffectsSend: (_channel, note, prevValue) => {
|
|
1486
|
+
this.setReverbEffectsSend(note, prevValue);
|
|
1487
|
+
},
|
|
1488
|
+
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
1489
|
+
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
1490
|
+
delayVibLFO: (channel, note, prevValue) => {
|
|
1491
|
+
if (0 < channel.state.vibratoDepth) {
|
|
1492
|
+
const now = this.audioContext.currentTime;
|
|
1493
|
+
const prevStartTime = note.startTime +
|
|
1494
|
+
prevValue * channel.state.vibratoDelay * 2;
|
|
1495
|
+
if (now < prevStartTime)
|
|
1496
|
+
return;
|
|
1497
|
+
const startTime = note.startTime +
|
|
1498
|
+
value * channel.state.vibratoDelay * 2;
|
|
1499
|
+
note.vibratoLFO.stop(now);
|
|
1500
|
+
note.vibratoLFO.start(startTime);
|
|
1501
|
+
}
|
|
1502
|
+
},
|
|
1503
|
+
freqVibLFO: (channel, note, _prevValue) => {
|
|
1504
|
+
if (0 < channel.state.vibratoDepth) {
|
|
1505
|
+
const now = this.audioContext.currentTime;
|
|
1506
|
+
note.vibratoLFO.frequency
|
|
1507
|
+
.cancelScheduledValues(now)
|
|
1508
|
+
.setValueAtTime(value * sate.vibratoRate, now);
|
|
1509
|
+
}
|
|
1510
|
+
},
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
getControllerState(channel, noteNumber, velocity) {
|
|
1514
|
+
const state = new Float32Array(channel.state.array.length);
|
|
1515
|
+
state.set(channel.state.array);
|
|
1516
|
+
state[2] = velocity / 127;
|
|
1517
|
+
state[3] = noteNumber / 127;
|
|
1518
|
+
return state;
|
|
1519
|
+
}
|
|
1520
|
+
applyVoiceParams(channel, controllerType) {
|
|
1521
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1522
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1523
|
+
const note = noteList[i];
|
|
1524
|
+
if (!note)
|
|
1525
|
+
continue;
|
|
1526
|
+
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
|
|
1527
|
+
const voiceParams = note.voice.getParams(controllerType, controllerState);
|
|
1528
|
+
let appliedFilterEnvelope = false;
|
|
1529
|
+
let appliedVolumeEnvelope = false;
|
|
1530
|
+
for (const [key, value] of Object.entries(voiceParams)) {
|
|
1531
|
+
const prevValue = note.voiceParams[key];
|
|
1532
|
+
if (value === prevValue)
|
|
1533
|
+
continue;
|
|
1534
|
+
note.voiceParams[key] = value;
|
|
1535
|
+
if (key in this.voiceParamsHandlers) {
|
|
1536
|
+
this.voiceParamsHandlers[key](channel, note, prevValue);
|
|
1537
|
+
}
|
|
1538
|
+
else if (filterEnvelopeKeySet.has(key)) {
|
|
1539
|
+
if (appliedFilterEnvelope)
|
|
1540
|
+
continue;
|
|
1541
|
+
appliedFilterEnvelope = true;
|
|
1542
|
+
const noteVoiceParams = note.voiceParams;
|
|
1543
|
+
for (let i = 0; i < filterEnvelopeKeys.length; i++) {
|
|
1544
|
+
const key = filterEnvelopeKeys[i];
|
|
1545
|
+
if (key in voiceParams)
|
|
1546
|
+
noteVoiceParams[key] = voiceParams[key];
|
|
1547
|
+
}
|
|
1548
|
+
if (note.portamento) {
|
|
1549
|
+
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
1550
|
+
}
|
|
1551
|
+
else {
|
|
1552
|
+
this.setFilterEnvelope(channel, note);
|
|
1553
|
+
}
|
|
1554
|
+
this.setPitchEnvelope(note);
|
|
1555
|
+
}
|
|
1556
|
+
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1557
|
+
if (appliedVolumeEnvelope)
|
|
1558
|
+
continue;
|
|
1559
|
+
appliedVolumeEnvelope = true;
|
|
1560
|
+
const noteVoiceParams = note.voiceParams;
|
|
1561
|
+
for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
|
|
1562
|
+
const key = volumeEnvelopeKeys[i];
|
|
1563
|
+
if (key in voiceParams)
|
|
1564
|
+
noteVoiceParams[key] = voiceParams[key];
|
|
1565
|
+
}
|
|
1566
|
+
this.setVolumeEnvelope(channel, note);
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
});
|
|
1241
1571
|
}
|
|
1242
1572
|
createControlChangeHandlers() {
|
|
1243
1573
|
return {
|
|
@@ -1277,13 +1607,16 @@ class Midy {
|
|
|
1277
1607
|
127: this.polyOn,
|
|
1278
1608
|
};
|
|
1279
1609
|
}
|
|
1280
|
-
handleControlChange(channelNumber,
|
|
1281
|
-
const handler = this.controlChangeHandlers[
|
|
1610
|
+
handleControlChange(channelNumber, controllerType, value) {
|
|
1611
|
+
const handler = this.controlChangeHandlers[controllerType];
|
|
1282
1612
|
if (handler) {
|
|
1283
1613
|
handler.call(this, channelNumber, value);
|
|
1614
|
+
const channel = this.channels[channelNumber];
|
|
1615
|
+
const controller = 128 + controllerType;
|
|
1616
|
+
this.applyVoiceParams(channel, controller);
|
|
1284
1617
|
}
|
|
1285
1618
|
else {
|
|
1286
|
-
console.warn(`Unsupported Control change:
|
|
1619
|
+
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
1287
1620
|
}
|
|
1288
1621
|
}
|
|
1289
1622
|
setBankMSB(channelNumber, msb) {
|
|
@@ -1297,11 +1630,10 @@ class Midy {
|
|
|
1297
1630
|
if (!note)
|
|
1298
1631
|
continue;
|
|
1299
1632
|
if (note.modulationDepth) {
|
|
1300
|
-
note.modulationDepth.gain.setValueAtTime(channel.modulationDepth, now);
|
|
1633
|
+
note.modulationDepth.gain.setValueAtTime(channel.state.modulationDepth, now);
|
|
1301
1634
|
}
|
|
1302
1635
|
else {
|
|
1303
|
-
|
|
1304
|
-
this.setPitch(note, semitoneOffset);
|
|
1636
|
+
this.setPitchEnvelope(note);
|
|
1305
1637
|
this.startModulation(channel, note, now);
|
|
1306
1638
|
}
|
|
1307
1639
|
}
|
|
@@ -1309,21 +1641,22 @@ class Midy {
|
|
|
1309
1641
|
}
|
|
1310
1642
|
setModulationDepth(channelNumber, modulation) {
|
|
1311
1643
|
const channel = this.channels[channelNumber];
|
|
1312
|
-
channel.modulationDepth = (modulation / 127) *
|
|
1644
|
+
channel.state.modulationDepth = (modulation / 127) *
|
|
1645
|
+
channel.modulationDepthRange;
|
|
1313
1646
|
this.updateModulation(channel);
|
|
1314
1647
|
}
|
|
1315
1648
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
1316
1649
|
const channel = this.channels[channelNumber];
|
|
1317
1650
|
const factor = 5 * Math.log(10) / 127;
|
|
1318
|
-
channel.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1651
|
+
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1319
1652
|
}
|
|
1320
1653
|
setVolume(channelNumber, volume) {
|
|
1321
1654
|
const channel = this.channels[channelNumber];
|
|
1322
|
-
channel.volume = volume / 127;
|
|
1655
|
+
channel.state.volume = volume / 127;
|
|
1323
1656
|
this.updateChannelVolume(channel);
|
|
1324
1657
|
}
|
|
1325
1658
|
panToGain(pan) {
|
|
1326
|
-
const theta = Math.PI / 2 * Math.max(0, pan - 1) / 126;
|
|
1659
|
+
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
1327
1660
|
return {
|
|
1328
1661
|
gainLeft: Math.cos(theta),
|
|
1329
1662
|
gainRight: Math.sin(theta),
|
|
@@ -1331,12 +1664,12 @@ class Midy {
|
|
|
1331
1664
|
}
|
|
1332
1665
|
setPan(channelNumber, pan) {
|
|
1333
1666
|
const channel = this.channels[channelNumber];
|
|
1334
|
-
channel.pan = pan;
|
|
1667
|
+
channel.state.pan = pan / 127;
|
|
1335
1668
|
this.updateChannelVolume(channel);
|
|
1336
1669
|
}
|
|
1337
1670
|
setExpression(channelNumber, expression) {
|
|
1338
1671
|
const channel = this.channels[channelNumber];
|
|
1339
|
-
channel.expression = expression / 127;
|
|
1672
|
+
channel.state.expression = expression / 127;
|
|
1340
1673
|
this.updateChannelVolume(channel);
|
|
1341
1674
|
}
|
|
1342
1675
|
setBankLSB(channelNumber, lsb) {
|
|
@@ -1348,8 +1681,9 @@ class Midy {
|
|
|
1348
1681
|
}
|
|
1349
1682
|
updateChannelVolume(channel) {
|
|
1350
1683
|
const now = this.audioContext.currentTime;
|
|
1351
|
-
const
|
|
1352
|
-
const
|
|
1684
|
+
const state = channel.state;
|
|
1685
|
+
const volume = state.volume * state.expression;
|
|
1686
|
+
const { gainLeft, gainRight } = this.panToGain(state.pan);
|
|
1353
1687
|
channel.gainL.gain
|
|
1354
1688
|
.cancelScheduledValues(now)
|
|
1355
1689
|
.setValueAtTime(volume * gainLeft, now);
|
|
@@ -1358,116 +1692,18 @@ class Midy {
|
|
|
1358
1692
|
.setValueAtTime(volume * gainRight, now);
|
|
1359
1693
|
}
|
|
1360
1694
|
setSustainPedal(channelNumber, value) {
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
if (!isOn) {
|
|
1695
|
+
this.channels[channelNumber].state.sustainPedal = value / 127;
|
|
1696
|
+
if (value < 64) {
|
|
1364
1697
|
this.releaseSustainPedal(channelNumber, value);
|
|
1365
1698
|
}
|
|
1366
1699
|
}
|
|
1367
1700
|
setPortamento(channelNumber, value) {
|
|
1368
|
-
this.channels[channelNumber].portamento = value
|
|
1369
|
-
}
|
|
1370
|
-
setReverbSendLevel(channelNumber, reverbSendLevel) {
|
|
1371
|
-
const channel = this.channels[channelNumber];
|
|
1372
|
-
const reverbEffect = this.reverbEffect;
|
|
1373
|
-
if (0 < channel.reverbSendLevel) {
|
|
1374
|
-
if (0 < reverbSendLevel) {
|
|
1375
|
-
const now = this.audioContext.currentTime;
|
|
1376
|
-
channel.reverbSendLevel = reverbSendLevel / 127;
|
|
1377
|
-
reverbEffect.input.gain.cancelScheduledValues(now);
|
|
1378
|
-
reverbEffect.input.gain.setValueAtTime(channel.reverbSendLevel, now);
|
|
1379
|
-
}
|
|
1380
|
-
else {
|
|
1381
|
-
channel.scheduledNotes.forEach((noteList) => {
|
|
1382
|
-
for (let i = 0; i < noteList.length; i++) {
|
|
1383
|
-
const note = noteList[i];
|
|
1384
|
-
if (!note)
|
|
1385
|
-
continue;
|
|
1386
|
-
if (note.instrumentKey.reverbEffectsSend <= 0)
|
|
1387
|
-
continue;
|
|
1388
|
-
note.reverbEffectsSend.disconnect();
|
|
1389
|
-
}
|
|
1390
|
-
});
|
|
1391
|
-
}
|
|
1392
|
-
}
|
|
1393
|
-
else {
|
|
1394
|
-
if (0 < reverbSendLevel) {
|
|
1395
|
-
const now = this.audioContext.currentTime;
|
|
1396
|
-
channel.scheduledNotes.forEach((noteList) => {
|
|
1397
|
-
for (let i = 0; i < noteList.length; i++) {
|
|
1398
|
-
const note = noteList[i];
|
|
1399
|
-
if (!note)
|
|
1400
|
-
continue;
|
|
1401
|
-
if (note.instrumentKey.reverbEffectsSend <= 0)
|
|
1402
|
-
continue;
|
|
1403
|
-
if (!note.reverbEffectsSend) {
|
|
1404
|
-
note.reverbEffectsSend = new GainNode(this.audioContext, {
|
|
1405
|
-
gain: note.instrumentKey.reverbEffectsSend,
|
|
1406
|
-
});
|
|
1407
|
-
note.volumeNode.connect(note.reverbEffectsSend);
|
|
1408
|
-
}
|
|
1409
|
-
note.reverbEffectsSend.connect(reverbEffect.input);
|
|
1410
|
-
}
|
|
1411
|
-
});
|
|
1412
|
-
channel.reverbSendLevel = reverbSendLevel / 127;
|
|
1413
|
-
reverbEffect.input.gain.cancelScheduledValues(now);
|
|
1414
|
-
reverbEffect.input.gain.setValueAtTime(channel.reverbSendLevel, now);
|
|
1415
|
-
}
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
setChorusSendLevel(channelNumber, chorusSendLevel) {
|
|
1419
|
-
const channel = this.channels[channelNumber];
|
|
1420
|
-
const chorusEffect = this.chorusEffect;
|
|
1421
|
-
if (0 < channel.chorusSendLevel) {
|
|
1422
|
-
if (0 < chorusSendLevel) {
|
|
1423
|
-
const now = this.audioContext.currentTime;
|
|
1424
|
-
channel.chorusSendLevel = chorusSendLevel / 127;
|
|
1425
|
-
chorusEffect.input.gain.cancelScheduledValues(now);
|
|
1426
|
-
chorusEffect.input.gain.setValueAtTime(channel.chorusSendLevel, now);
|
|
1427
|
-
}
|
|
1428
|
-
else {
|
|
1429
|
-
channel.scheduledNotes.forEach((noteList) => {
|
|
1430
|
-
for (let i = 0; i < noteList.length; i++) {
|
|
1431
|
-
const note = noteList[i];
|
|
1432
|
-
if (!note)
|
|
1433
|
-
continue;
|
|
1434
|
-
if (note.instrumentKey.chorusEffectsSend <= 0)
|
|
1435
|
-
continue;
|
|
1436
|
-
note.chorusEffectsSend.disconnect();
|
|
1437
|
-
}
|
|
1438
|
-
});
|
|
1439
|
-
}
|
|
1440
|
-
}
|
|
1441
|
-
else {
|
|
1442
|
-
if (0 < chorusSendLevel) {
|
|
1443
|
-
const now = this.audioContext.currentTime;
|
|
1444
|
-
channel.scheduledNotes.forEach((noteList) => {
|
|
1445
|
-
for (let i = 0; i < noteList.length; i++) {
|
|
1446
|
-
const note = noteList[i];
|
|
1447
|
-
if (!note)
|
|
1448
|
-
continue;
|
|
1449
|
-
if (note.instrumentKey.chorusEffectsSend <= 0)
|
|
1450
|
-
continue;
|
|
1451
|
-
if (!note.chorusEffectsSend) {
|
|
1452
|
-
note.chorusEffectsSend = new GainNode(this.audioContext, {
|
|
1453
|
-
gain: note.instrumentKey.chorusEffectsSend,
|
|
1454
|
-
});
|
|
1455
|
-
note.volumeNode.connect(note.chorusEffectsSend);
|
|
1456
|
-
}
|
|
1457
|
-
note.chorusEffectsSend.connect(chorusEffect.input);
|
|
1458
|
-
}
|
|
1459
|
-
});
|
|
1460
|
-
channel.chorusSendLevel = chorusSendLevel / 127;
|
|
1461
|
-
chorusEffect.input.gain.cancelScheduledValues(now);
|
|
1462
|
-
chorusEffect.input.gain.setValueAtTime(channel.chorusSendLevel, now);
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1701
|
+
this.channels[channelNumber].state.portamento = value / 127;
|
|
1465
1702
|
}
|
|
1466
1703
|
setSostenutoPedal(channelNumber, value) {
|
|
1467
|
-
const isOn = value >= 64;
|
|
1468
1704
|
const channel = this.channels[channelNumber];
|
|
1469
|
-
channel.sostenutoPedal =
|
|
1470
|
-
if (
|
|
1705
|
+
channel.state.sostenutoPedal = value / 127;
|
|
1706
|
+
if (64 <= value) {
|
|
1471
1707
|
const now = this.audioContext.currentTime;
|
|
1472
1708
|
const activeNotes = this.getActiveNotes(channel, now);
|
|
1473
1709
|
channel.sostenutoNotes = new Map(activeNotes);
|
|
@@ -1478,31 +1714,31 @@ class Midy {
|
|
|
1478
1714
|
}
|
|
1479
1715
|
setSoftPedal(channelNumber, softPedal) {
|
|
1480
1716
|
const channel = this.channels[channelNumber];
|
|
1481
|
-
channel.softPedal = softPedal / 127;
|
|
1717
|
+
channel.state.softPedal = softPedal / 127;
|
|
1482
1718
|
}
|
|
1483
1719
|
setFilterResonance(channelNumber, filterResonance) {
|
|
1484
1720
|
const now = this.audioContext.currentTime;
|
|
1485
1721
|
const channel = this.channels[channelNumber];
|
|
1486
|
-
|
|
1722
|
+
const state = channel.state;
|
|
1723
|
+
state.filterResonance = filterResonance / 64;
|
|
1487
1724
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1488
1725
|
for (let i = 0; i < noteList.length; i++) {
|
|
1489
1726
|
const note = noteList[i];
|
|
1490
1727
|
if (!note)
|
|
1491
1728
|
continue;
|
|
1492
|
-
const Q = note.
|
|
1493
|
-
channel.filterResonance;
|
|
1729
|
+
const Q = note.voiceParams.initialFilterQ / 5 * state.filterResonance;
|
|
1494
1730
|
note.filterNode.Q.setValueAtTime(Q, now);
|
|
1495
1731
|
}
|
|
1496
1732
|
});
|
|
1497
1733
|
}
|
|
1498
1734
|
setReleaseTime(channelNumber, releaseTime) {
|
|
1499
1735
|
const channel = this.channels[channelNumber];
|
|
1500
|
-
channel.releaseTime = releaseTime / 64;
|
|
1736
|
+
channel.state.releaseTime = releaseTime / 64;
|
|
1501
1737
|
}
|
|
1502
1738
|
setAttackTime(channelNumber, attackTime) {
|
|
1503
1739
|
const now = this.audioContext.currentTime;
|
|
1504
1740
|
const channel = this.channels[channelNumber];
|
|
1505
|
-
channel.attackTime = attackTime / 64;
|
|
1741
|
+
channel.state.attackTime = attackTime / 64;
|
|
1506
1742
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1507
1743
|
for (let i = 0; i < noteList.length; i++) {
|
|
1508
1744
|
const note = noteList[i];
|
|
@@ -1516,7 +1752,7 @@ class Midy {
|
|
|
1516
1752
|
}
|
|
1517
1753
|
setBrightness(channelNumber, brightness) {
|
|
1518
1754
|
const channel = this.channels[channelNumber];
|
|
1519
|
-
channel.brightness = brightness / 64;
|
|
1755
|
+
channel.state.brightness = brightness / 64;
|
|
1520
1756
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1521
1757
|
for (let i = 0; i < noteList.length; i++) {
|
|
1522
1758
|
const note = noteList[i];
|
|
@@ -1528,7 +1764,7 @@ class Midy {
|
|
|
1528
1764
|
}
|
|
1529
1765
|
setDecayTime(channelNumber, dacayTime) {
|
|
1530
1766
|
const channel = this.channels[channelNumber];
|
|
1531
|
-
channel.decayTime = dacayTime / 64;
|
|
1767
|
+
channel.state.decayTime = dacayTime / 64;
|
|
1532
1768
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1533
1769
|
for (let i = 0; i < noteList.length; i++) {
|
|
1534
1770
|
const note = noteList[i];
|
|
@@ -1540,7 +1776,7 @@ class Midy {
|
|
|
1540
1776
|
}
|
|
1541
1777
|
setVibratoRate(channelNumber, vibratoRate) {
|
|
1542
1778
|
const channel = this.channels[channelNumber];
|
|
1543
|
-
channel.vibratoRate = vibratoRate / 64;
|
|
1779
|
+
channel.state.vibratoRate = vibratoRate / 64;
|
|
1544
1780
|
if (channel.vibratoDepth <= 0)
|
|
1545
1781
|
return;
|
|
1546
1782
|
const now = this.audioContext.currentTime;
|
|
@@ -1548,16 +1784,98 @@ class Midy {
|
|
|
1548
1784
|
activeNotes.forEach((activeNote) => {
|
|
1549
1785
|
activeNote.vibratoLFO.frequency
|
|
1550
1786
|
.cancelScheduledValues(now)
|
|
1551
|
-
.setValueAtTime(channel.vibratoRate, now);
|
|
1787
|
+
.setValueAtTime(channel.state.vibratoRate, now);
|
|
1552
1788
|
});
|
|
1553
1789
|
}
|
|
1554
1790
|
setVibratoDepth(channelNumber, vibratoDepth) {
|
|
1555
1791
|
const channel = this.channels[channelNumber];
|
|
1556
|
-
channel.vibratoDepth = vibratoDepth / 64;
|
|
1792
|
+
channel.state.vibratoDepth = vibratoDepth / 64;
|
|
1557
1793
|
}
|
|
1558
1794
|
setVibratoDelay(channelNumber, vibratoDelay) {
|
|
1559
1795
|
const channel = this.channels[channelNumber];
|
|
1560
|
-
channel.vibratoDelay = vibratoDelay / 64;
|
|
1796
|
+
channel.state.vibratoDelay = vibratoDelay / 64;
|
|
1797
|
+
}
|
|
1798
|
+
setReverbSendLevel(channelNumber, reverbSendLevel) {
|
|
1799
|
+
const channel = this.channels[channelNumber];
|
|
1800
|
+
const state = channel.state;
|
|
1801
|
+
const reverbEffect = this.reverbEffect;
|
|
1802
|
+
if (0 < state.reverbSendLevel) {
|
|
1803
|
+
if (0 < reverbSendLevel) {
|
|
1804
|
+
const now = this.audioContext.currentTime;
|
|
1805
|
+
state.reverbSendLevel = reverbSendLevel / 127;
|
|
1806
|
+
reverbEffect.input.gain.cancelScheduledValues(now);
|
|
1807
|
+
reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
|
|
1808
|
+
}
|
|
1809
|
+
else {
|
|
1810
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1811
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1812
|
+
const note = noteList[i];
|
|
1813
|
+
if (!note)
|
|
1814
|
+
continue;
|
|
1815
|
+
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
1816
|
+
continue;
|
|
1817
|
+
note.reverbEffectsSend.disconnect();
|
|
1818
|
+
}
|
|
1819
|
+
});
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
else {
|
|
1823
|
+
if (0 < reverbSendLevel) {
|
|
1824
|
+
const now = this.audioContext.currentTime;
|
|
1825
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1826
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1827
|
+
const note = noteList[i];
|
|
1828
|
+
if (!note)
|
|
1829
|
+
continue;
|
|
1830
|
+
this.setReverbEffectsSend(note, 0);
|
|
1831
|
+
}
|
|
1832
|
+
});
|
|
1833
|
+
state.reverbSendLevel = reverbSendLevel / 127;
|
|
1834
|
+
reverbEffect.input.gain.cancelScheduledValues(now);
|
|
1835
|
+
reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
setChorusSendLevel(channelNumber, chorusSendLevel) {
|
|
1840
|
+
const channel = this.channels[channelNumber];
|
|
1841
|
+
const state = channel.state;
|
|
1842
|
+
const chorusEffect = this.chorusEffect;
|
|
1843
|
+
if (0 < state.chorusSendLevel) {
|
|
1844
|
+
if (0 < chorusSendLevel) {
|
|
1845
|
+
const now = this.audioContext.currentTime;
|
|
1846
|
+
state.chorusSendLevel = chorusSendLevel / 127;
|
|
1847
|
+
chorusEffect.input.gain.cancelScheduledValues(now);
|
|
1848
|
+
chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
|
|
1849
|
+
}
|
|
1850
|
+
else {
|
|
1851
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1852
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1853
|
+
const note = noteList[i];
|
|
1854
|
+
if (!note)
|
|
1855
|
+
continue;
|
|
1856
|
+
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
1857
|
+
continue;
|
|
1858
|
+
note.chorusEffectsSend.disconnect();
|
|
1859
|
+
}
|
|
1860
|
+
});
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
else {
|
|
1864
|
+
if (0 < chorusSendLevel) {
|
|
1865
|
+
const now = this.audioContext.currentTime;
|
|
1866
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1867
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1868
|
+
const note = noteList[i];
|
|
1869
|
+
if (!note)
|
|
1870
|
+
continue;
|
|
1871
|
+
this.setChorusEffectsSend(note, 0);
|
|
1872
|
+
}
|
|
1873
|
+
});
|
|
1874
|
+
state.chorusSendLevel = chorusSendLevel / 127;
|
|
1875
|
+
chorusEffect.input.gain.cancelScheduledValues(now);
|
|
1876
|
+
chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1561
1879
|
}
|
|
1562
1880
|
limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
|
|
1563
1881
|
if (maxLSB < channel.dataLSB) {
|
|
@@ -1627,60 +1945,49 @@ class Midy {
|
|
|
1627
1945
|
this.channels[channelNumber].dataMSB = value;
|
|
1628
1946
|
this.handleRPN(channelNumber, 0);
|
|
1629
1947
|
}
|
|
1630
|
-
updateDetune(channel, detuneChange) {
|
|
1631
|
-
const now = this.audioContext.currentTime;
|
|
1632
|
-
channel.scheduledNotes.forEach((noteList) => {
|
|
1633
|
-
for (let i = 0; i < noteList.length; i++) {
|
|
1634
|
-
const note = noteList[i];
|
|
1635
|
-
if (!note)
|
|
1636
|
-
continue;
|
|
1637
|
-
const { bufferSource } = note;
|
|
1638
|
-
const detune = bufferSource.detune.value + detuneChange;
|
|
1639
|
-
bufferSource.detune
|
|
1640
|
-
.cancelScheduledValues(now)
|
|
1641
|
-
.setValueAtTime(detune, now);
|
|
1642
|
-
}
|
|
1643
|
-
});
|
|
1644
|
-
}
|
|
1645
1948
|
handlePitchBendRangeRPN(channelNumber) {
|
|
1646
1949
|
const channel = this.channels[channelNumber];
|
|
1647
1950
|
this.limitData(channel, 0, 127, 0, 99);
|
|
1648
1951
|
const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
|
|
1649
1952
|
this.setPitchBendRange(channelNumber, pitchBendRange);
|
|
1650
1953
|
}
|
|
1651
|
-
setPitchBendRange(channelNumber,
|
|
1954
|
+
setPitchBendRange(channelNumber, value) {
|
|
1652
1955
|
const channel = this.channels[channelNumber];
|
|
1653
|
-
const
|
|
1654
|
-
|
|
1655
|
-
const
|
|
1656
|
-
|
|
1657
|
-
|
|
1956
|
+
const state = channel.state;
|
|
1957
|
+
const prev = state.pitchWheelSensitivity;
|
|
1958
|
+
const next = value / 128;
|
|
1959
|
+
state.pitchWheelSensitivity = next;
|
|
1960
|
+
channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
|
|
1961
|
+
this.updateDetune(channel);
|
|
1962
|
+
this.applyVoiceParams(channel, 16);
|
|
1658
1963
|
}
|
|
1659
1964
|
handleFineTuningRPN(channelNumber) {
|
|
1660
1965
|
const channel = this.channels[channelNumber];
|
|
1661
1966
|
this.limitData(channel, 0, 127, 0, 127);
|
|
1662
|
-
const fineTuning =
|
|
1967
|
+
const fineTuning = channel.dataMSB * 128 + channel.dataLSB;
|
|
1663
1968
|
this.setFineTuning(channelNumber, fineTuning);
|
|
1664
1969
|
}
|
|
1665
|
-
setFineTuning(channelNumber,
|
|
1970
|
+
setFineTuning(channelNumber, value) {
|
|
1666
1971
|
const channel = this.channels[channelNumber];
|
|
1667
|
-
const
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1972
|
+
const prev = channel.fineTuning;
|
|
1973
|
+
const next = (value - 8192) / 8.192; // cent
|
|
1974
|
+
channel.fineTuning = next;
|
|
1975
|
+
channel.detune += next - prev;
|
|
1976
|
+
this.updateDetune(channel);
|
|
1671
1977
|
}
|
|
1672
1978
|
handleCoarseTuningRPN(channelNumber) {
|
|
1673
1979
|
const channel = this.channels[channelNumber];
|
|
1674
1980
|
this.limitDataMSB(channel, 0, 127);
|
|
1675
|
-
const coarseTuning = channel.dataMSB
|
|
1676
|
-
this.
|
|
1981
|
+
const coarseTuning = channel.dataMSB;
|
|
1982
|
+
this.setCoarseTuning(channelNumber, coarseTuning);
|
|
1677
1983
|
}
|
|
1678
|
-
setCoarseTuning(channelNumber,
|
|
1984
|
+
setCoarseTuning(channelNumber, value) {
|
|
1679
1985
|
const channel = this.channels[channelNumber];
|
|
1680
|
-
const
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1986
|
+
const prev = channel.coarseTuning;
|
|
1987
|
+
const next = (value - 64) * 100; // cent
|
|
1988
|
+
channel.coarseTuning = next;
|
|
1989
|
+
channel.detune += next - prev;
|
|
1990
|
+
this.updateDetune(channel);
|
|
1684
1991
|
}
|
|
1685
1992
|
handleModulationDepthRangeRPN(channelNumber) {
|
|
1686
1993
|
const channel = this.channels[channelNumber];
|
|
@@ -1698,7 +2005,30 @@ class Midy {
|
|
|
1698
2005
|
return this.stopChannelNotes(channelNumber, 0, true);
|
|
1699
2006
|
}
|
|
1700
2007
|
resetAllControllers(channelNumber) {
|
|
1701
|
-
|
|
2008
|
+
const stateTypes = [
|
|
2009
|
+
"expression",
|
|
2010
|
+
"modulationDepth",
|
|
2011
|
+
"sustainPedal",
|
|
2012
|
+
"portamento",
|
|
2013
|
+
"sostenutoPedal",
|
|
2014
|
+
"softPedal",
|
|
2015
|
+
"channelPressure",
|
|
2016
|
+
"pitchWheelSensitivity",
|
|
2017
|
+
];
|
|
2018
|
+
const channel = this.channels[channelNumber];
|
|
2019
|
+
const state = channel.state;
|
|
2020
|
+
for (let i = 0; i < stateTypes.length; i++) {
|
|
2021
|
+
const type = stateTypes[i];
|
|
2022
|
+
state[type] = defaultControllerState[type];
|
|
2023
|
+
}
|
|
2024
|
+
const settingTypes = [
|
|
2025
|
+
"rpnMSB",
|
|
2026
|
+
"rpnLSB",
|
|
2027
|
+
];
|
|
2028
|
+
for (let i = 0; i < settingTypes.length; i++) {
|
|
2029
|
+
const type = settingTypes[i];
|
|
2030
|
+
channel[type] = this.constructor.channelSettings[type];
|
|
2031
|
+
}
|
|
1702
2032
|
}
|
|
1703
2033
|
allNotesOff(channelNumber) {
|
|
1704
2034
|
return this.stopChannelNotes(channelNumber, 0, false);
|
|
@@ -1717,6 +2047,15 @@ class Midy {
|
|
|
1717
2047
|
}
|
|
1718
2048
|
handleUniversalNonRealTimeExclusiveMessage(data) {
|
|
1719
2049
|
switch (data[2]) {
|
|
2050
|
+
case 8:
|
|
2051
|
+
switch (data[3]) {
|
|
2052
|
+
case 8:
|
|
2053
|
+
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2054
|
+
return this.handleScaleOctaveTuning1ByteFormat(data);
|
|
2055
|
+
default:
|
|
2056
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2057
|
+
}
|
|
2058
|
+
break;
|
|
1720
2059
|
case 9:
|
|
1721
2060
|
switch (data[3]) {
|
|
1722
2061
|
case 1:
|
|
@@ -1773,9 +2112,10 @@ class Midy {
|
|
|
1773
2112
|
break;
|
|
1774
2113
|
case 8:
|
|
1775
2114
|
switch (data[3]) {
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
2115
|
+
case 8:
|
|
2116
|
+
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2117
|
+
// TODO: realtime
|
|
2118
|
+
return this.handleScaleOctaveTuning1ByteFormat(data);
|
|
1779
2119
|
default:
|
|
1780
2120
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1781
2121
|
}
|
|
@@ -1820,27 +2160,59 @@ class Midy {
|
|
|
1820
2160
|
}
|
|
1821
2161
|
}
|
|
1822
2162
|
handleMasterFineTuningSysEx(data) {
|
|
1823
|
-
const fineTuning =
|
|
2163
|
+
const fineTuning = data[5] * 128 + data[4];
|
|
1824
2164
|
this.setMasterFineTuning(fineTuning);
|
|
1825
2165
|
}
|
|
1826
|
-
setMasterFineTuning(
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
}
|
|
2166
|
+
setMasterFineTuning(value) {
|
|
2167
|
+
const prev = this.masterFineTuning;
|
|
2168
|
+
const next = (value - 8192) / 8.192; // cent
|
|
2169
|
+
this.masterFineTuning = next;
|
|
2170
|
+
channel.detune += next - prev;
|
|
2171
|
+
this.updateDetune(channel);
|
|
1833
2172
|
}
|
|
1834
2173
|
handleMasterCoarseTuningSysEx(data) {
|
|
1835
2174
|
const coarseTuning = data[4];
|
|
1836
2175
|
this.setMasterCoarseTuning(coarseTuning);
|
|
1837
2176
|
}
|
|
1838
|
-
setMasterCoarseTuning(
|
|
1839
|
-
|
|
1840
|
-
|
|
2177
|
+
setMasterCoarseTuning(value) {
|
|
2178
|
+
const prev = this.masterCoarseTuning;
|
|
2179
|
+
const next = (value - 64) * 100; // cent
|
|
2180
|
+
this.masterCoarseTuning = next;
|
|
2181
|
+
channel.detune += next - prev;
|
|
2182
|
+
this.updateDetune(channel);
|
|
2183
|
+
}
|
|
2184
|
+
getChannelBitmap(data) {
|
|
2185
|
+
const bitmap = new Array(16).fill(false);
|
|
2186
|
+
const ff = data[4] & 0b11;
|
|
2187
|
+
const gg = data[5] & 0x7F;
|
|
2188
|
+
const hh = data[6] & 0x7F;
|
|
2189
|
+
for (let bit = 0; bit < 7; bit++) {
|
|
2190
|
+
if (hh & (1 << bit))
|
|
2191
|
+
bitmap[bit] = true;
|
|
2192
|
+
}
|
|
2193
|
+
for (let bit = 0; bit < 7; bit++) {
|
|
2194
|
+
if (gg & (1 << bit))
|
|
2195
|
+
bitmap[bit + 7] = true;
|
|
2196
|
+
}
|
|
2197
|
+
for (let bit = 0; bit < 2; bit++) {
|
|
2198
|
+
if (ff & (1 << bit))
|
|
2199
|
+
bitmap[bit + 14] = true;
|
|
2200
|
+
}
|
|
2201
|
+
return bitmap;
|
|
2202
|
+
}
|
|
2203
|
+
handleScaleOctaveTuning1ByteFormat(data) {
|
|
2204
|
+
if (data.length < 18) {
|
|
2205
|
+
console.error("Data length is too short");
|
|
2206
|
+
return;
|
|
1841
2207
|
}
|
|
1842
|
-
|
|
1843
|
-
|
|
2208
|
+
const channelBitmap = this.getChannelBitmap(data);
|
|
2209
|
+
for (let i = 0; i < channelBitmap.length; i++) {
|
|
2210
|
+
if (!channelBitmap[i])
|
|
2211
|
+
continue;
|
|
2212
|
+
for (let j = 0; j < 12; j++) {
|
|
2213
|
+
const value = data[j + 7] - 64; // cent
|
|
2214
|
+
this.channels[i].scaleOctaveTuningTable[j] = value;
|
|
2215
|
+
}
|
|
1844
2216
|
}
|
|
1845
2217
|
}
|
|
1846
2218
|
handleGlobalParameterControlSysEx(data) {
|
|
@@ -2061,48 +2433,21 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
2061
2433
|
writable: true,
|
|
2062
2434
|
value: {
|
|
2063
2435
|
currentBufferSource: null,
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
filterResonance: 1,
|
|
2068
|
-
releaseTime: 1,
|
|
2069
|
-
attackTime: 1,
|
|
2070
|
-
brightness: 1,
|
|
2071
|
-
decayTime: 1,
|
|
2072
|
-
reverbSendLevel: 0,
|
|
2073
|
-
chorusSendLevel: 0,
|
|
2074
|
-
vibratoRate: 1,
|
|
2075
|
-
vibratoDepth: 1,
|
|
2076
|
-
vibratoDelay: 1,
|
|
2436
|
+
detune: 0,
|
|
2437
|
+
scaleOctaveTuningTable: new Array(12).fill(0), // cent
|
|
2438
|
+
program: 0,
|
|
2077
2439
|
bank: 121 * 128,
|
|
2078
2440
|
bankMSB: 121,
|
|
2079
2441
|
bankLSB: 0,
|
|
2080
2442
|
dataMSB: 0,
|
|
2081
2443
|
dataLSB: 0,
|
|
2082
|
-
|
|
2083
|
-
|
|
2444
|
+
rpnMSB: 127,
|
|
2445
|
+
rpnLSB: 127,
|
|
2084
2446
|
fineTuning: 0, // cb
|
|
2085
2447
|
coarseTuning: 0, // cb
|
|
2086
2448
|
modulationDepthRange: 50, // cent
|
|
2087
2449
|
}
|
|
2088
2450
|
});
|
|
2089
|
-
Object.defineProperty(Midy, "effectSettings", {
|
|
2090
|
-
enumerable: true,
|
|
2091
|
-
configurable: true,
|
|
2092
|
-
writable: true,
|
|
2093
|
-
value: {
|
|
2094
|
-
expression: 1,
|
|
2095
|
-
modulationDepth: 0,
|
|
2096
|
-
sustainPedal: false,
|
|
2097
|
-
portamento: false,
|
|
2098
|
-
sostenutoPedal: false,
|
|
2099
|
-
softPedal: 0,
|
|
2100
|
-
rpnMSB: 127,
|
|
2101
|
-
rpnLSB: 127,
|
|
2102
|
-
channelPressure: 0,
|
|
2103
|
-
pitchBendRange: 2,
|
|
2104
|
-
}
|
|
2105
|
-
});
|
|
2106
2451
|
Object.defineProperty(Midy, "controllerDestinationSettings", {
|
|
2107
2452
|
enumerable: true,
|
|
2108
2453
|
configurable: true,
|