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