@marmooo/midy 0.1.6 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/midy-GM1.d.ts +53 -27
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +398 -146
- package/esm/midy-GM2.d.ts +55 -35
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +646 -244
- package/esm/midy-GMLite.d.ts +51 -26
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +379 -148
- package/esm/midy.d.ts +55 -40
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +662 -263
- package/package.json +5 -1
- package/script/midy-GM1.d.ts +53 -27
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +401 -149
- package/script/midy-GM2.d.ts +55 -35
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +649 -247
- package/script/midy-GMLite.d.ts +51 -26
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +382 -151
- package/script/midy.d.ts +55 -40
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +665 -266
- package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.d.ts +0 -149
- package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.d.ts.map +0 -1
- package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.js +0 -180
- package/esm/deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.d.ts +0 -84
- package/esm/deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.d.ts.map +0 -1
- package/esm/deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.js +0 -216
- package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.d.ts +0 -149
- package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.d.ts.map +0 -1
- package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.js +0 -190
- package/script/deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.d.ts +0 -84
- package/script/deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.d.ts.map +0 -1
- package/script/deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.js +0 -221
package/script/midy.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Midy = void 0;
|
|
4
|
-
const
|
|
5
|
-
const
|
|
4
|
+
const midi_file_1 = require("midi-file");
|
|
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,
|
|
@@ -53,12 +53,118 @@ class Note {
|
|
|
53
53
|
writable: true,
|
|
54
54
|
value: void 0
|
|
55
55
|
});
|
|
56
|
+
Object.defineProperty(this, "reverbEffectsSend", {
|
|
57
|
+
enumerable: true,
|
|
58
|
+
configurable: true,
|
|
59
|
+
writable: true,
|
|
60
|
+
value: void 0
|
|
61
|
+
});
|
|
62
|
+
Object.defineProperty(this, "chorusEffectsSend", {
|
|
63
|
+
enumerable: true,
|
|
64
|
+
configurable: true,
|
|
65
|
+
writable: true,
|
|
66
|
+
value: void 0
|
|
67
|
+
});
|
|
68
|
+
Object.defineProperty(this, "portamento", {
|
|
69
|
+
enumerable: true,
|
|
70
|
+
configurable: true,
|
|
71
|
+
writable: true,
|
|
72
|
+
value: void 0
|
|
73
|
+
});
|
|
56
74
|
this.noteNumber = noteNumber;
|
|
57
75
|
this.velocity = velocity;
|
|
58
76
|
this.startTime = startTime;
|
|
59
|
-
this.
|
|
77
|
+
this.voice = voice;
|
|
78
|
+
this.voiceParams = voiceParams;
|
|
79
|
+
}
|
|
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
|
+
}
|
|
60
143
|
}
|
|
61
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);
|
|
62
168
|
class Midy {
|
|
63
169
|
constructor(audioContext, options = this.defaultOptions) {
|
|
64
170
|
Object.defineProperty(this, "ticksPerBeat", {
|
|
@@ -208,6 +314,12 @@ class Midy {
|
|
|
208
314
|
writable: true,
|
|
209
315
|
value: []
|
|
210
316
|
});
|
|
317
|
+
Object.defineProperty(this, "exclusiveClassMap", {
|
|
318
|
+
enumerable: true,
|
|
319
|
+
configurable: true,
|
|
320
|
+
writable: true,
|
|
321
|
+
value: new Map()
|
|
322
|
+
});
|
|
211
323
|
Object.defineProperty(this, "defaultOptions", {
|
|
212
324
|
enumerable: true,
|
|
213
325
|
configurable: true,
|
|
@@ -233,6 +345,7 @@ class Midy {
|
|
|
233
345
|
this.audioContext = audioContext;
|
|
234
346
|
this.options = { ...this.defaultOptions, ...options };
|
|
235
347
|
this.masterGain = new GainNode(audioContext);
|
|
348
|
+
this.voiceParamsHandlers = this.createVoiceParamsHandlers();
|
|
236
349
|
this.controlChangeHandlers = this.createControlChangeHandlers();
|
|
237
350
|
this.channels = this.createChannels(audioContext);
|
|
238
351
|
this.reverbEffect = this.options.reverbAlgorithm(audioContext);
|
|
@@ -264,14 +377,14 @@ class Midy {
|
|
|
264
377
|
async loadSoundFont(soundFontUrl) {
|
|
265
378
|
const response = await fetch(soundFontUrl);
|
|
266
379
|
const arrayBuffer = await response.arrayBuffer();
|
|
267
|
-
const parsed = (0,
|
|
268
|
-
const soundFont = new
|
|
380
|
+
const parsed = (0, soundfont_parser_1.parse)(new Uint8Array(arrayBuffer));
|
|
381
|
+
const soundFont = new soundfont_parser_1.SoundFont(parsed);
|
|
269
382
|
this.addSoundFont(soundFont);
|
|
270
383
|
}
|
|
271
384
|
async loadMIDI(midiUrl) {
|
|
272
385
|
const response = await fetch(midiUrl);
|
|
273
386
|
const arrayBuffer = await response.arrayBuffer();
|
|
274
|
-
const midi = (0,
|
|
387
|
+
const midi = (0, midi_file_1.parseMidi)(new Uint8Array(arrayBuffer));
|
|
275
388
|
this.ticksPerBeat = midi.header.ticksPerBeat;
|
|
276
389
|
const midiData = this.extractMidiData(midi);
|
|
277
390
|
this.instruments = midiData.instruments;
|
|
@@ -279,7 +392,7 @@ class Midy {
|
|
|
279
392
|
this.totalTime = this.calcTotalTime();
|
|
280
393
|
}
|
|
281
394
|
setChannelAudioNodes(audioContext) {
|
|
282
|
-
const { gainLeft, gainRight } = this.panToGain(
|
|
395
|
+
const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
|
|
283
396
|
const gainL = new GainNode(audioContext, { gain: gainLeft });
|
|
284
397
|
const gainR = new GainNode(audioContext, { gain: gainRight });
|
|
285
398
|
const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
|
|
@@ -296,7 +409,7 @@ class Midy {
|
|
|
296
409
|
const channels = Array.from({ length: 16 }, () => {
|
|
297
410
|
return {
|
|
298
411
|
...this.constructor.channelSettings,
|
|
299
|
-
|
|
412
|
+
state: new ControllerState(),
|
|
300
413
|
...this.setChannelAudioNodes(audioContext),
|
|
301
414
|
scheduledNotes: new Map(),
|
|
302
415
|
sostenutoNotes: new Map(),
|
|
@@ -310,38 +423,43 @@ class Midy {
|
|
|
310
423
|
});
|
|
311
424
|
return channels;
|
|
312
425
|
}
|
|
313
|
-
async createNoteBuffer(
|
|
314
|
-
const sampleStart =
|
|
315
|
-
const sampleEnd =
|
|
426
|
+
async createNoteBuffer(voiceParams, isSF3) {
|
|
427
|
+
const sampleStart = voiceParams.start;
|
|
428
|
+
const sampleEnd = voiceParams.sample.length + voiceParams.end;
|
|
316
429
|
if (isSF3) {
|
|
317
|
-
const sample =
|
|
318
|
-
const
|
|
430
|
+
const sample = voiceParams.sample;
|
|
431
|
+
const start = sample.byteOffset + sampleStart;
|
|
432
|
+
const end = sample.byteOffset + sampleEnd;
|
|
433
|
+
const buffer = sample.buffer.slice(start, end);
|
|
434
|
+
const audioBuffer = await this.audioContext.decodeAudioData(buffer);
|
|
319
435
|
return audioBuffer;
|
|
320
436
|
}
|
|
321
437
|
else {
|
|
322
|
-
const sample =
|
|
438
|
+
const sample = voiceParams.sample;
|
|
439
|
+
const start = sample.byteOffset + sampleStart;
|
|
440
|
+
const end = sample.byteOffset + sampleEnd;
|
|
441
|
+
const buffer = sample.buffer.slice(start, end);
|
|
323
442
|
const audioBuffer = new AudioBuffer({
|
|
324
443
|
numberOfChannels: 1,
|
|
325
444
|
length: sample.length,
|
|
326
|
-
sampleRate:
|
|
445
|
+
sampleRate: voiceParams.sampleRate,
|
|
327
446
|
});
|
|
328
447
|
const channelData = audioBuffer.getChannelData(0);
|
|
329
|
-
const int16Array = new Int16Array(
|
|
448
|
+
const int16Array = new Int16Array(buffer);
|
|
330
449
|
for (let i = 0; i < int16Array.length; i++) {
|
|
331
450
|
channelData[i] = int16Array[i] / 32768;
|
|
332
451
|
}
|
|
333
452
|
return audioBuffer;
|
|
334
453
|
}
|
|
335
454
|
}
|
|
336
|
-
async createNoteBufferNode(
|
|
455
|
+
async createNoteBufferNode(voiceParams, isSF3) {
|
|
337
456
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
338
|
-
const audioBuffer = await this.createNoteBuffer(
|
|
457
|
+
const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
|
|
339
458
|
bufferSource.buffer = audioBuffer;
|
|
340
|
-
bufferSource.loop =
|
|
459
|
+
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
341
460
|
if (bufferSource.loop) {
|
|
342
|
-
bufferSource.loopStart =
|
|
343
|
-
|
|
344
|
-
bufferSource.loopEnd = instrumentKey.loopEnd / instrumentKey.sampleRate;
|
|
461
|
+
bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
|
|
462
|
+
bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
|
|
345
463
|
}
|
|
346
464
|
return bufferSource;
|
|
347
465
|
}
|
|
@@ -398,7 +516,7 @@ class Midy {
|
|
|
398
516
|
this.handleChannelPressure(event.channel, event.amount);
|
|
399
517
|
break;
|
|
400
518
|
case "pitchBend":
|
|
401
|
-
this.setPitchBend(event.channel, event.value);
|
|
519
|
+
this.setPitchBend(event.channel, event.value + 8192);
|
|
402
520
|
break;
|
|
403
521
|
case "sysEx":
|
|
404
522
|
this.handleSysEx(event.data);
|
|
@@ -427,6 +545,7 @@ class Midy {
|
|
|
427
545
|
if (queueIndex >= this.timeline.length) {
|
|
428
546
|
await Promise.all(this.notePromises);
|
|
429
547
|
this.notePromises = [];
|
|
548
|
+
this.exclusiveClassMap.clear();
|
|
430
549
|
resolve();
|
|
431
550
|
return;
|
|
432
551
|
}
|
|
@@ -442,6 +561,7 @@ class Midy {
|
|
|
442
561
|
}
|
|
443
562
|
else if (this.isStopping) {
|
|
444
563
|
await this.stopNotes(0, true);
|
|
564
|
+
this.exclusiveClassMap.clear();
|
|
445
565
|
this.notePromises = [];
|
|
446
566
|
resolve();
|
|
447
567
|
this.isStopping = false;
|
|
@@ -450,6 +570,7 @@ class Midy {
|
|
|
450
570
|
}
|
|
451
571
|
else if (this.isSeeking) {
|
|
452
572
|
this.stopNotes(0, true);
|
|
573
|
+
this.exclusiveClassMap.clear();
|
|
453
574
|
this.startTime = this.audioContext.currentTime;
|
|
454
575
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
455
576
|
offset = this.resumeTime - this.startTime;
|
|
@@ -678,14 +799,14 @@ class Midy {
|
|
|
678
799
|
return impulse;
|
|
679
800
|
}
|
|
680
801
|
createConvolutionReverb(audioContext, impulse) {
|
|
681
|
-
const
|
|
802
|
+
const input = new GainNode(audioContext);
|
|
682
803
|
const convolverNode = new ConvolverNode(audioContext, {
|
|
683
804
|
buffer: impulse,
|
|
684
805
|
});
|
|
685
|
-
|
|
806
|
+
input.connect(convolverNode);
|
|
686
807
|
return {
|
|
687
|
-
input
|
|
688
|
-
output,
|
|
808
|
+
input,
|
|
809
|
+
output: convolverNode,
|
|
689
810
|
convolverNode,
|
|
690
811
|
};
|
|
691
812
|
}
|
|
@@ -727,7 +848,6 @@ class Midy {
|
|
|
727
848
|
// M.R.Schroeder, "Natural Sounding Artificial Reverberation", J.Audio Eng. Soc., vol.10, p.219, 1962
|
|
728
849
|
createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays) {
|
|
729
850
|
const input = new GainNode(audioContext);
|
|
730
|
-
const output = new GainNode(audioContext);
|
|
731
851
|
const mergerGain = new GainNode(audioContext);
|
|
732
852
|
for (let i = 0; i < combDelays.length; i++) {
|
|
733
853
|
const comb = this.createCombFilter(audioContext, input, combDelays[i], combFeedbacks[i]);
|
|
@@ -738,7 +858,7 @@ class Midy {
|
|
|
738
858
|
const allpass = this.createAllpassFilter(audioContext, (i === 0) ? mergerGain : allpasses.at(-1), allpassDelays[i], allpassFeedbacks[i]);
|
|
739
859
|
allpasses.push(allpass);
|
|
740
860
|
}
|
|
741
|
-
allpasses.at(-1)
|
|
861
|
+
const output = allpasses.at(-1);
|
|
742
862
|
return { input, output };
|
|
743
863
|
}
|
|
744
864
|
createChorusEffect(audioContext) {
|
|
@@ -793,53 +913,63 @@ class Midy {
|
|
|
793
913
|
calcSemitoneOffset(channel) {
|
|
794
914
|
const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
|
|
795
915
|
const channelTuning = channel.coarseTuning + channel.fineTuning;
|
|
796
|
-
const
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
return instrumentKey.playbackRate(noteNumber) *
|
|
801
|
-
Math.pow(2, semitoneOffset / 12);
|
|
916
|
+
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
917
|
+
const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 128;
|
|
918
|
+
const pitch = pitchWheel * pitchWheelSensitivity;
|
|
919
|
+
return masterTuning + channelTuning + pitch;
|
|
802
920
|
}
|
|
803
921
|
setPortamentoStartVolumeEnvelope(channel, note) {
|
|
804
|
-
const
|
|
805
|
-
const
|
|
806
|
-
const
|
|
807
|
-
const
|
|
808
|
-
const
|
|
922
|
+
const now = this.audioContext.currentTime;
|
|
923
|
+
const { voiceParams, startTime } = note;
|
|
924
|
+
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
925
|
+
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
926
|
+
const volDelay = startTime + voiceParams.volDelay;
|
|
927
|
+
const portamentoTime = volDelay + channel.state.portamentoTime;
|
|
809
928
|
note.volumeNode.gain
|
|
810
|
-
.cancelScheduledValues(
|
|
929
|
+
.cancelScheduledValues(now)
|
|
811
930
|
.setValueAtTime(0, volDelay)
|
|
812
931
|
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
813
932
|
}
|
|
814
933
|
setVolumeEnvelope(channel, note) {
|
|
815
|
-
const
|
|
816
|
-
const
|
|
817
|
-
const
|
|
818
|
-
const
|
|
819
|
-
const
|
|
820
|
-
const
|
|
821
|
-
const
|
|
934
|
+
const now = this.audioContext.currentTime;
|
|
935
|
+
const state = channel.state;
|
|
936
|
+
const { voiceParams, startTime } = note;
|
|
937
|
+
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
938
|
+
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
939
|
+
const volDelay = startTime + voiceParams.volDelay;
|
|
940
|
+
const volAttack = volDelay + voiceParams.volAttack * state.attackTime * 2;
|
|
941
|
+
const volHold = volAttack + voiceParams.volHold;
|
|
942
|
+
const volDecay = volHold + voiceParams.volDecay * state.decayTime * 2;
|
|
822
943
|
note.volumeNode.gain
|
|
823
|
-
.cancelScheduledValues(
|
|
944
|
+
.cancelScheduledValues(now)
|
|
824
945
|
.setValueAtTime(0, startTime)
|
|
825
946
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
826
947
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
827
948
|
.setValueAtTime(attackVolume, volHold)
|
|
828
949
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
829
950
|
}
|
|
830
|
-
|
|
831
|
-
const
|
|
832
|
-
|
|
833
|
-
|
|
951
|
+
setPlaybackRate(note) {
|
|
952
|
+
const now = this.audioContext.currentTime;
|
|
953
|
+
note.bufferSource.playbackRate
|
|
954
|
+
.cancelScheduledValues(now)
|
|
955
|
+
.setValueAtTime(note.voiceParams.playbackRate, now);
|
|
956
|
+
}
|
|
957
|
+
setPitch(channel, note) {
|
|
958
|
+
const now = this.audioContext.currentTime;
|
|
959
|
+
const { startTime } = note;
|
|
960
|
+
const basePitch = this.calcSemitoneOffset(channel) * 100;
|
|
961
|
+
note.bufferSource.detune
|
|
962
|
+
.cancelScheduledValues(now)
|
|
963
|
+
.setValueAtTime(basePitch, startTime);
|
|
964
|
+
const modEnvToPitch = note.voiceParams.modEnvToPitch;
|
|
834
965
|
if (modEnvToPitch === 0)
|
|
835
966
|
return;
|
|
836
|
-
const
|
|
837
|
-
const
|
|
838
|
-
const
|
|
839
|
-
const
|
|
840
|
-
const
|
|
841
|
-
|
|
842
|
-
note.bufferSource.playbackRate.value
|
|
967
|
+
const peekPitch = basePitch + modEnvToPitch;
|
|
968
|
+
const modDelay = startTime + voiceParams.modDelay;
|
|
969
|
+
const modAttack = modDelay + voiceParams.modAttack;
|
|
970
|
+
const modHold = modAttack + voiceParams.modHold;
|
|
971
|
+
const modDecay = modHold + voiceParams.modDecay;
|
|
972
|
+
note.bufferSource.detune
|
|
843
973
|
.setValueAtTime(basePitch, modDelay)
|
|
844
974
|
.exponentialRampToValueAtTime(peekPitch, modAttack)
|
|
845
975
|
.setValueAtTime(peekPitch, modHold)
|
|
@@ -851,42 +981,46 @@ class Midy {
|
|
|
851
981
|
return Math.max(minFrequency, Math.min(frequency, maxFrequency));
|
|
852
982
|
}
|
|
853
983
|
setPortamentoStartFilterEnvelope(channel, note) {
|
|
854
|
-
const
|
|
984
|
+
const now = this.audioContext.currentTime;
|
|
985
|
+
const state = channel.state;
|
|
986
|
+
const { voiceParams, noteNumber, startTime } = note;
|
|
855
987
|
const softPedalFactor = 1 -
|
|
856
|
-
(0.1 + (noteNumber / 127) * 0.2) *
|
|
857
|
-
const baseFreq = this.centToHz(
|
|
858
|
-
softPedalFactor *
|
|
859
|
-
const peekFreq = this.centToHz(
|
|
988
|
+
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
989
|
+
const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
|
|
990
|
+
softPedalFactor * state.brightness * 2;
|
|
991
|
+
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor * state.brightness * 2;
|
|
860
992
|
const sustainFreq = baseFreq +
|
|
861
|
-
(peekFreq - baseFreq) * (1 -
|
|
993
|
+
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
862
994
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
863
995
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
864
|
-
const portamentoTime = startTime + channel.portamentoTime;
|
|
865
|
-
const modDelay = startTime +
|
|
996
|
+
const portamentoTime = startTime + channel.state.portamentoTime;
|
|
997
|
+
const modDelay = startTime + voiceParams.modDelay;
|
|
866
998
|
note.filterNode.frequency
|
|
867
|
-
.cancelScheduledValues(
|
|
999
|
+
.cancelScheduledValues(now)
|
|
868
1000
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
869
1001
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
870
1002
|
.linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
|
|
871
1003
|
}
|
|
872
1004
|
setFilterEnvelope(channel, note) {
|
|
873
|
-
const
|
|
1005
|
+
const now = this.audioContext.currentTime;
|
|
1006
|
+
const state = channel.state;
|
|
1007
|
+
const { voiceParams, noteNumber, startTime } = note;
|
|
874
1008
|
const softPedalFactor = 1 -
|
|
875
|
-
(0.1 + (noteNumber / 127) * 0.2) *
|
|
876
|
-
const baseFreq = this.centToHz(
|
|
877
|
-
softPedalFactor *
|
|
878
|
-
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;
|
|
879
1013
|
const sustainFreq = baseFreq +
|
|
880
|
-
(peekFreq - baseFreq) * (1 -
|
|
1014
|
+
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
881
1015
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
882
1016
|
const adjustedPeekFreq = this.clampCutoffFrequency(peekFreq);
|
|
883
1017
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
884
|
-
const modDelay = startTime +
|
|
885
|
-
const modAttack = modDelay +
|
|
886
|
-
const modHold = modAttack +
|
|
887
|
-
const modDecay = modHold +
|
|
1018
|
+
const modDelay = startTime + voiceParams.modDelay;
|
|
1019
|
+
const modAttack = modDelay + voiceParams.modAttack;
|
|
1020
|
+
const modHold = modAttack + voiceParams.modHold;
|
|
1021
|
+
const modDecay = modHold + voiceParams.modDecay;
|
|
888
1022
|
note.filterNode.frequency
|
|
889
|
-
.cancelScheduledValues(
|
|
1023
|
+
.cancelScheduledValues(now)
|
|
890
1024
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
891
1025
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
892
1026
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
@@ -894,25 +1028,18 @@ class Midy {
|
|
|
894
1028
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
895
1029
|
}
|
|
896
1030
|
startModulation(channel, note, startTime) {
|
|
897
|
-
const {
|
|
898
|
-
const { modLfoToPitch, modLfoToVolume } = instrumentKey;
|
|
1031
|
+
const { voiceParams } = note;
|
|
899
1032
|
note.modulationLFO = new OscillatorNode(this.audioContext, {
|
|
900
|
-
frequency: this.centToHz(
|
|
1033
|
+
frequency: this.centToHz(voiceParams.freqModLFO),
|
|
901
1034
|
});
|
|
902
1035
|
note.filterDepth = new GainNode(this.audioContext, {
|
|
903
|
-
gain:
|
|
1036
|
+
gain: voiceParams.modLfoToFilterFc,
|
|
904
1037
|
});
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
note.
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
911
|
-
const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
|
|
912
|
-
note.volumeDepth = new GainNode(this.audioContext, {
|
|
913
|
-
gain: volumeDepth * volumeDepthSign,
|
|
914
|
-
});
|
|
915
|
-
note.modulationLFO.start(startTime + instrumentKey.delayModLFO);
|
|
1038
|
+
note.modulationDepth = new GainNode(this.audioContext);
|
|
1039
|
+
this.setModLfoToPitch(channel, note);
|
|
1040
|
+
note.volumeDepth = new GainNode(this.audioContext);
|
|
1041
|
+
this.setModLfoToVolume(note);
|
|
1042
|
+
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
916
1043
|
note.modulationLFO.connect(note.filterDepth);
|
|
917
1044
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
918
1045
|
note.modulationLFO.connect(note.modulationDepth);
|
|
@@ -921,54 +1048,59 @@ class Midy {
|
|
|
921
1048
|
note.volumeDepth.connect(note.volumeNode.gain);
|
|
922
1049
|
}
|
|
923
1050
|
startVibrato(channel, note, startTime) {
|
|
924
|
-
const {
|
|
925
|
-
const
|
|
1051
|
+
const { voiceParams } = note;
|
|
1052
|
+
const state = channel.state;
|
|
926
1053
|
note.vibratoLFO = new OscillatorNode(this.audioContext, {
|
|
927
|
-
frequency: this.centToHz(
|
|
928
|
-
|
|
929
|
-
});
|
|
930
|
-
const vibratoDepth = Math.abs(vibLfoToPitch) * channel.vibratoDepth;
|
|
931
|
-
const vibratoDepthSign = 0 < vibLfoToPitch;
|
|
932
|
-
note.vibratoDepth = new GainNode(this.audioContext, {
|
|
933
|
-
gain: vibratoDepth * vibratoDepthSign,
|
|
1054
|
+
frequency: this.centToHz(voiceParams.freqVibLFO) *
|
|
1055
|
+
state.vibratoRate,
|
|
934
1056
|
});
|
|
935
|
-
note.vibratoLFO.start(startTime +
|
|
1057
|
+
note.vibratoLFO.start(startTime + voiceParams.delayVibLFO * state.vibratoDelay * 2);
|
|
1058
|
+
note.vibratoDepth = new GainNode(this.audioContext);
|
|
1059
|
+
this.setVibLfoToPitch(channel, note);
|
|
936
1060
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
937
1061
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
938
1062
|
}
|
|
939
|
-
async createNote(channel,
|
|
940
|
-
const
|
|
941
|
-
const
|
|
942
|
-
|
|
1063
|
+
async createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3) {
|
|
1064
|
+
const state = channel.state;
|
|
1065
|
+
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1066
|
+
const voiceParams = voice.getAllParams(controllerState);
|
|
1067
|
+
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1068
|
+
note.bufferSource = await this.createNoteBufferNode(voiceParams, isSF3);
|
|
943
1069
|
note.volumeNode = new GainNode(this.audioContext);
|
|
944
1070
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
945
1071
|
type: "lowpass",
|
|
946
|
-
Q:
|
|
1072
|
+
Q: voiceParams.initialFilterQ / 5 * state.filterResonance, // dB
|
|
947
1073
|
});
|
|
948
1074
|
if (portamento) {
|
|
1075
|
+
note.portamento = true;
|
|
949
1076
|
this.setPortamentoStartVolumeEnvelope(channel, note);
|
|
950
1077
|
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
951
1078
|
}
|
|
952
1079
|
else {
|
|
1080
|
+
note.portamento = false;
|
|
953
1081
|
this.setVolumeEnvelope(channel, note);
|
|
954
1082
|
this.setFilterEnvelope(channel, note);
|
|
955
1083
|
}
|
|
956
|
-
if (0 <
|
|
1084
|
+
if (0 < state.vibratoDepth) {
|
|
957
1085
|
this.startVibrato(channel, note, startTime);
|
|
958
1086
|
}
|
|
959
|
-
|
|
960
|
-
|
|
1087
|
+
this.setPlaybackRate(note);
|
|
1088
|
+
if (0 < state.modulationDepth) {
|
|
1089
|
+
this.setPitch(channel, note);
|
|
961
1090
|
this.startModulation(channel, note, startTime);
|
|
962
1091
|
}
|
|
963
|
-
else {
|
|
964
|
-
note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
|
|
965
|
-
}
|
|
966
1092
|
if (this.mono && channel.currentBufferSource) {
|
|
967
1093
|
channel.currentBufferSource.stop(startTime);
|
|
968
1094
|
channel.currentBufferSource = note.bufferSource;
|
|
969
1095
|
}
|
|
970
1096
|
note.bufferSource.connect(note.filterNode);
|
|
971
1097
|
note.filterNode.connect(note.volumeNode);
|
|
1098
|
+
if (0 < channel.chorusSendLevel) {
|
|
1099
|
+
this.setChorusEffectsSend(channel, note, 0);
|
|
1100
|
+
}
|
|
1101
|
+
if (0 < channel.reverbSendLevel) {
|
|
1102
|
+
this.setReverbEffectsSend(channel, note, 0);
|
|
1103
|
+
}
|
|
972
1104
|
note.bufferSource.start(startTime);
|
|
973
1105
|
return note;
|
|
974
1106
|
}
|
|
@@ -989,15 +1121,28 @@ class Midy {
|
|
|
989
1121
|
return;
|
|
990
1122
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
991
1123
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
992
|
-
const
|
|
993
|
-
if (!
|
|
1124
|
+
const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
|
|
1125
|
+
if (!voice)
|
|
994
1126
|
return;
|
|
995
|
-
const note = await this.createNote(channel,
|
|
1127
|
+
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
|
|
996
1128
|
note.volumeNode.connect(channel.gainL);
|
|
997
1129
|
note.volumeNode.connect(channel.gainR);
|
|
998
|
-
if (channel.sostenutoPedal) {
|
|
1130
|
+
if (channel.state.sostenutoPedal) {
|
|
999
1131
|
channel.sostenutoNotes.set(noteNumber, note);
|
|
1000
1132
|
}
|
|
1133
|
+
const exclusiveClass = note.voiceParams.exclusiveClass;
|
|
1134
|
+
if (exclusiveClass !== 0) {
|
|
1135
|
+
if (this.exclusiveClassMap.has(exclusiveClass)) {
|
|
1136
|
+
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
1137
|
+
const [prevNote, prevChannelNumber] = prevEntry;
|
|
1138
|
+
if (!prevNote.ending) {
|
|
1139
|
+
this.scheduleNoteRelease(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1140
|
+
startTime, undefined, // portamentoNoteNumber
|
|
1141
|
+
true);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
|
|
1145
|
+
}
|
|
1001
1146
|
const scheduledNotes = channel.scheduledNotes;
|
|
1002
1147
|
if (scheduledNotes.has(noteNumber)) {
|
|
1003
1148
|
scheduledNotes.get(noteNumber).push(note);
|
|
@@ -1010,15 +1155,15 @@ class Midy {
|
|
|
1010
1155
|
const now = this.audioContext.currentTime;
|
|
1011
1156
|
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, now, portamento);
|
|
1012
1157
|
}
|
|
1013
|
-
stopNote(
|
|
1158
|
+
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
1014
1159
|
const note = scheduledNotes[index];
|
|
1015
1160
|
note.volumeNode.gain
|
|
1016
|
-
.cancelScheduledValues(
|
|
1017
|
-
.linearRampToValueAtTime(0,
|
|
1161
|
+
.cancelScheduledValues(endTime)
|
|
1162
|
+
.linearRampToValueAtTime(0, stopTime);
|
|
1018
1163
|
note.ending = true;
|
|
1019
1164
|
this.scheduleTask(() => {
|
|
1020
1165
|
note.bufferSource.loop = false;
|
|
1021
|
-
},
|
|
1166
|
+
}, stopTime);
|
|
1022
1167
|
return new Promise((resolve) => {
|
|
1023
1168
|
note.bufferSource.onended = () => {
|
|
1024
1169
|
scheduledNotes[index] = null;
|
|
@@ -1034,15 +1179,22 @@ class Midy {
|
|
|
1034
1179
|
note.vibratoDepth.disconnect();
|
|
1035
1180
|
note.vibratoLFO.stop();
|
|
1036
1181
|
}
|
|
1182
|
+
if (note.reverbEffectsSend) {
|
|
1183
|
+
note.reverbEffectsSend.disconnect();
|
|
1184
|
+
}
|
|
1185
|
+
if (note.chorusEffectsSend) {
|
|
1186
|
+
note.chorusEffectsSend.disconnect();
|
|
1187
|
+
}
|
|
1037
1188
|
resolve();
|
|
1038
1189
|
};
|
|
1039
|
-
note.bufferSource.stop(
|
|
1190
|
+
note.bufferSource.stop(stopTime);
|
|
1040
1191
|
});
|
|
1041
1192
|
}
|
|
1042
|
-
scheduleNoteRelease(channelNumber, noteNumber, _velocity,
|
|
1193
|
+
scheduleNoteRelease(channelNumber, noteNumber, _velocity, endTime, portamentoNoteNumber, force) {
|
|
1043
1194
|
const channel = this.channels[channelNumber];
|
|
1195
|
+
const state = channel.state;
|
|
1044
1196
|
if (!force) {
|
|
1045
|
-
if (
|
|
1197
|
+
if (0.5 < state.sustainPedal)
|
|
1046
1198
|
return;
|
|
1047
1199
|
if (channel.sostenutoNotes.has(noteNumber))
|
|
1048
1200
|
return;
|
|
@@ -1057,22 +1209,23 @@ class Midy {
|
|
|
1057
1209
|
if (note.ending)
|
|
1058
1210
|
continue;
|
|
1059
1211
|
if (portamentoNoteNumber === undefined) {
|
|
1060
|
-
const
|
|
1061
|
-
note.
|
|
1062
|
-
const modRelease =
|
|
1212
|
+
const volRelease = endTime +
|
|
1213
|
+
note.voiceParams.volRelease * state.releaseTime * 2;
|
|
1214
|
+
const modRelease = endTime + note.voiceParams.modRelease;
|
|
1063
1215
|
note.filterNode.frequency
|
|
1064
|
-
.cancelScheduledValues(
|
|
1216
|
+
.cancelScheduledValues(endTime)
|
|
1065
1217
|
.linearRampToValueAtTime(0, modRelease);
|
|
1066
|
-
|
|
1218
|
+
const stopTime = Math.min(volRelease, modRelease);
|
|
1219
|
+
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
1067
1220
|
}
|
|
1068
1221
|
else {
|
|
1069
|
-
const portamentoTime =
|
|
1222
|
+
const portamentoTime = endTime + state.portamentoTime;
|
|
1070
1223
|
const detuneChange = (portamentoNoteNumber - noteNumber) * 100;
|
|
1071
1224
|
const detune = note.bufferSource.detune.value + detuneChange;
|
|
1072
1225
|
note.bufferSource.detune
|
|
1073
|
-
.cancelScheduledValues(
|
|
1226
|
+
.cancelScheduledValues(endTime)
|
|
1074
1227
|
.linearRampToValueAtTime(detune, portamentoTime);
|
|
1075
|
-
return this.stopNote(
|
|
1228
|
+
return this.stopNote(endTime, portamentoTime, scheduledNotes, i);
|
|
1076
1229
|
}
|
|
1077
1230
|
}
|
|
1078
1231
|
}
|
|
@@ -1084,7 +1237,7 @@ class Midy {
|
|
|
1084
1237
|
const velocity = halfVelocity * 2;
|
|
1085
1238
|
const channel = this.channels[channelNumber];
|
|
1086
1239
|
const promises = [];
|
|
1087
|
-
channel.sustainPedal =
|
|
1240
|
+
channel.state.sustainPedal = halfVelocity;
|
|
1088
1241
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1089
1242
|
for (let i = 0; i < noteList.length; i++) {
|
|
1090
1243
|
const note = noteList[i];
|
|
@@ -1101,7 +1254,7 @@ class Midy {
|
|
|
1101
1254
|
const velocity = halfVelocity * 2;
|
|
1102
1255
|
const channel = this.channels[channelNumber];
|
|
1103
1256
|
const promises = [];
|
|
1104
|
-
channel.sostenutoPedal =
|
|
1257
|
+
channel.state.sostenutoPedal = 0;
|
|
1105
1258
|
channel.sostenutoNotes.forEach((activeNote) => {
|
|
1106
1259
|
const { noteNumber } = activeNote;
|
|
1107
1260
|
const promise = this.releaseNote(channelNumber, noteNumber, velocity);
|
|
@@ -1119,7 +1272,7 @@ class Midy {
|
|
|
1119
1272
|
case 0x90:
|
|
1120
1273
|
return this.noteOn(channelNumber, data1, data2);
|
|
1121
1274
|
case 0xA0:
|
|
1122
|
-
return
|
|
1275
|
+
return this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
|
|
1123
1276
|
case 0xB0:
|
|
1124
1277
|
return this.handleControlChange(channelNumber, data1, data2);
|
|
1125
1278
|
case 0xC0:
|
|
@@ -1146,6 +1299,7 @@ class Midy {
|
|
|
1146
1299
|
.setValueAtTime(gain * pressure, now);
|
|
1147
1300
|
}
|
|
1148
1301
|
}
|
|
1302
|
+
// this.applyVoiceParams(channel, 10);
|
|
1149
1303
|
}
|
|
1150
1304
|
handleProgramChange(channelNumber, program) {
|
|
1151
1305
|
const channel = this.channels[channelNumber];
|
|
@@ -1166,18 +1320,232 @@ class Midy {
|
|
|
1166
1320
|
.setValueAtTime(gain * pressure, now);
|
|
1167
1321
|
});
|
|
1168
1322
|
}
|
|
1323
|
+
// this.applyVoiceParams(channel, 13);
|
|
1169
1324
|
}
|
|
1170
1325
|
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
1171
|
-
const pitchBend = msb * 128 + lsb
|
|
1326
|
+
const pitchBend = msb * 128 + lsb;
|
|
1172
1327
|
this.setPitchBend(channelNumber, pitchBend);
|
|
1173
1328
|
}
|
|
1174
|
-
setPitchBend(channelNumber,
|
|
1329
|
+
setPitchBend(channelNumber, value) {
|
|
1175
1330
|
const channel = this.channels[channelNumber];
|
|
1176
|
-
const
|
|
1177
|
-
|
|
1178
|
-
const
|
|
1179
|
-
|
|
1331
|
+
const state = channel.state;
|
|
1332
|
+
state.pitchWheel = value / 16383;
|
|
1333
|
+
const pitchWheel = (value - 8192) / 8192;
|
|
1334
|
+
const detuneChange = pitchWheel * state.pitchWheelSensitivity * 12800;
|
|
1180
1335
|
this.updateDetune(channel, detuneChange);
|
|
1336
|
+
this.applyVoiceParams(channel, 14);
|
|
1337
|
+
}
|
|
1338
|
+
setModLfoToPitch(channel, note) {
|
|
1339
|
+
const now = this.audioContext.currentTime;
|
|
1340
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch;
|
|
1341
|
+
const modulationDepth = Math.abs(modLfoToPitch) +
|
|
1342
|
+
channel.state.modulationDepth;
|
|
1343
|
+
const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
|
|
1344
|
+
note.modulationDepth.gain
|
|
1345
|
+
.cancelScheduledValues(now)
|
|
1346
|
+
.setValueAtTime(modulationDepth * modulationDepthSign, now);
|
|
1347
|
+
}
|
|
1348
|
+
setModLfoToVolume(note) {
|
|
1349
|
+
const now = this.audioContext.currentTime;
|
|
1350
|
+
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1351
|
+
const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1352
|
+
const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
|
|
1353
|
+
note.volumeDepth.gain
|
|
1354
|
+
.cancelScheduledValues(now)
|
|
1355
|
+
.setValueAtTime(volumeDepth * volumeDepthSign, now);
|
|
1356
|
+
}
|
|
1357
|
+
setChorusEffectsSend(note, prevValue) {
|
|
1358
|
+
if (0 < prevValue) {
|
|
1359
|
+
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1360
|
+
const now = this.audioContext.currentTime;
|
|
1361
|
+
const value = note.voiceParams.chorusEffectsSend;
|
|
1362
|
+
note.chorusEffectsSend.gain
|
|
1363
|
+
.cancelScheduledValues(now)
|
|
1364
|
+
.setValueAtTime(value, now);
|
|
1365
|
+
}
|
|
1366
|
+
else {
|
|
1367
|
+
note.chorusEffectsSend.disconnect();
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
else {
|
|
1371
|
+
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1372
|
+
if (!note.chorusEffectsSend) {
|
|
1373
|
+
note.chorusEffectsSend = new GainNode(this.audioContext, {
|
|
1374
|
+
gain: note.voiceParams.chorusEffectsSend,
|
|
1375
|
+
});
|
|
1376
|
+
note.volumeNode.connect(note.chorusEffectsSend);
|
|
1377
|
+
}
|
|
1378
|
+
note.chorusEffectsSend.connect(this.chorusEffect.input);
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
setReverbEffectsSend(note, prevValue) {
|
|
1383
|
+
if (0 < prevValue) {
|
|
1384
|
+
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1385
|
+
const now = this.audioContext.currentTime;
|
|
1386
|
+
const value = note.voiceParams.reverbEffectsSend;
|
|
1387
|
+
note.reverbEffectsSend.gain
|
|
1388
|
+
.cancelScheduledValues(now)
|
|
1389
|
+
.setValueAtTime(value, now);
|
|
1390
|
+
}
|
|
1391
|
+
else {
|
|
1392
|
+
note.reverbEffectsSend.disconnect();
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
else {
|
|
1396
|
+
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1397
|
+
if (!note.reverbEffectsSend) {
|
|
1398
|
+
note.reverbEffectsSend = new GainNode(this.audioContext, {
|
|
1399
|
+
gain: note.voiceParams.reverbEffectsSend,
|
|
1400
|
+
});
|
|
1401
|
+
note.volumeNode.connect(note.reverbEffectsSend);
|
|
1402
|
+
}
|
|
1403
|
+
note.reverbEffectsSend.connect(this.reverbEffect.input);
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
setVibLfoToPitch(channel, note) {
|
|
1408
|
+
const now = this.audioContext.currentTime;
|
|
1409
|
+
const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
|
|
1410
|
+
const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
|
|
1411
|
+
2;
|
|
1412
|
+
const vibratoDepthSign = 0 < vibLfoToPitch;
|
|
1413
|
+
note.vibratoDepth.gain
|
|
1414
|
+
.cancelScheduledValues(now)
|
|
1415
|
+
.setValueAtTime(vibratoDepth * vibratoDepthSign, now);
|
|
1416
|
+
}
|
|
1417
|
+
setModLfoToFilterFc(note) {
|
|
1418
|
+
const now = this.audioContext.currentTime;
|
|
1419
|
+
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
|
|
1420
|
+
note.filterDepth.gain
|
|
1421
|
+
.cancelScheduledValues(now)
|
|
1422
|
+
.setValueAtTime(modLfoToFilterFc, now);
|
|
1423
|
+
}
|
|
1424
|
+
setDelayModLFO(note) {
|
|
1425
|
+
const now = this.audioContext.currentTime;
|
|
1426
|
+
const startTime = note.startTime;
|
|
1427
|
+
if (startTime < now)
|
|
1428
|
+
return;
|
|
1429
|
+
note.modulationLFO.stop(now);
|
|
1430
|
+
note.modulationLFO.start(startTime + note.voiceParams.delayModLFO);
|
|
1431
|
+
note.modulationLFO.connect(note.filterDepth);
|
|
1432
|
+
}
|
|
1433
|
+
setFreqModLFO(note) {
|
|
1434
|
+
const now = this.audioContext.currentTime;
|
|
1435
|
+
const freqModLFO = note.voiceParams.freqModLFO;
|
|
1436
|
+
note.modulationLFO.frequency
|
|
1437
|
+
.cancelScheduledValues(now)
|
|
1438
|
+
.setValueAtTime(freqModLFO, now);
|
|
1439
|
+
}
|
|
1440
|
+
createVoiceParamsHandlers() {
|
|
1441
|
+
return {
|
|
1442
|
+
modLfoToPitch: (channel, note, _prevValue) => {
|
|
1443
|
+
if (0 < channel.state.modulationDepth) {
|
|
1444
|
+
this.setModLfoToPitch(channel, note);
|
|
1445
|
+
}
|
|
1446
|
+
},
|
|
1447
|
+
vibLfoToPitch: (channel, note, _prevValue) => {
|
|
1448
|
+
if (0 < channel.state.vibratoDepth) {
|
|
1449
|
+
this.setVibLfoToPitch(channel, note);
|
|
1450
|
+
}
|
|
1451
|
+
},
|
|
1452
|
+
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1453
|
+
if (0 < channel.state.modulationDepth)
|
|
1454
|
+
this.setModLfoToFilterFc(note);
|
|
1455
|
+
},
|
|
1456
|
+
modLfoToVolume: (channel, note) => {
|
|
1457
|
+
if (0 < channel.state.modulationDepth)
|
|
1458
|
+
this.setModLfoToVolume(note);
|
|
1459
|
+
},
|
|
1460
|
+
chorusEffectsSend: (_channel, note, prevValue) => {
|
|
1461
|
+
this.setChorusEffectsSend(note, prevValue);
|
|
1462
|
+
},
|
|
1463
|
+
reverbEffectsSend: (_channel, note, prevValue) => {
|
|
1464
|
+
this.setReverbEffectsSend(note, prevValue);
|
|
1465
|
+
},
|
|
1466
|
+
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
1467
|
+
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
1468
|
+
delayVibLFO: (channel, note, prevValue) => {
|
|
1469
|
+
if (0 < channel.state.vibratoDepth) {
|
|
1470
|
+
const now = this.audioContext.currentTime;
|
|
1471
|
+
const prevStartTime = note.startTime +
|
|
1472
|
+
prevValue * channel.state.vibratoDelay * 2;
|
|
1473
|
+
if (now < prevStartTime)
|
|
1474
|
+
return;
|
|
1475
|
+
const startTime = note.startTime +
|
|
1476
|
+
value * channel.state.vibratoDelay * 2;
|
|
1477
|
+
note.vibratoLFO.stop(now);
|
|
1478
|
+
note.vibratoLFO.start(startTime);
|
|
1479
|
+
}
|
|
1480
|
+
},
|
|
1481
|
+
freqVibLFO: (channel, note, _prevValue) => {
|
|
1482
|
+
if (0 < channel.state.vibratoDepth) {
|
|
1483
|
+
const now = this.audioContext.currentTime;
|
|
1484
|
+
note.vibratoLFO.frequency
|
|
1485
|
+
.cancelScheduledValues(now)
|
|
1486
|
+
.setValueAtTime(value * sate.vibratoRate, now);
|
|
1487
|
+
}
|
|
1488
|
+
},
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
getControllerState(channel, noteNumber, velocity) {
|
|
1492
|
+
const state = new Float32Array(channel.state.array.length);
|
|
1493
|
+
state.set(channel.state.array);
|
|
1494
|
+
state[2] = velocity / 127;
|
|
1495
|
+
state[3] = noteNumber / 127;
|
|
1496
|
+
return state;
|
|
1497
|
+
}
|
|
1498
|
+
applyVoiceParams(channel, controllerType) {
|
|
1499
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1500
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1501
|
+
const note = noteList[i];
|
|
1502
|
+
if (!note)
|
|
1503
|
+
continue;
|
|
1504
|
+
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
|
|
1505
|
+
const voiceParams = note.voice.getParams(controllerType, controllerState);
|
|
1506
|
+
let appliedFilterEnvelope = false;
|
|
1507
|
+
let appliedVolumeEnvelope = false;
|
|
1508
|
+
for (const [key, value] of Object.entries(voiceParams)) {
|
|
1509
|
+
const prevValue = note.voiceParams[key];
|
|
1510
|
+
if (value === prevValue)
|
|
1511
|
+
continue;
|
|
1512
|
+
note.voiceParams[key] = value;
|
|
1513
|
+
if (key in this.voiceParamsHandlers) {
|
|
1514
|
+
this.voiceParamsHandlers[key](channel, note, prevValue);
|
|
1515
|
+
}
|
|
1516
|
+
else if (filterEnvelopeKeySet.has(key)) {
|
|
1517
|
+
if (appliedFilterEnvelope)
|
|
1518
|
+
continue;
|
|
1519
|
+
appliedFilterEnvelope = true;
|
|
1520
|
+
const noteVoiceParams = note.voiceParams;
|
|
1521
|
+
for (let i = 0; i < filterEnvelopeKeys.length; i++) {
|
|
1522
|
+
const key = filterEnvelopeKeys[i];
|
|
1523
|
+
if (key in voiceParams)
|
|
1524
|
+
noteVoiceParams[key] = voiceParams[key];
|
|
1525
|
+
}
|
|
1526
|
+
if (note.portamento) {
|
|
1527
|
+
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
1528
|
+
}
|
|
1529
|
+
else {
|
|
1530
|
+
this.setFilterEnvelope(channel, note);
|
|
1531
|
+
}
|
|
1532
|
+
this.setPitch(channel, note);
|
|
1533
|
+
}
|
|
1534
|
+
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1535
|
+
if (appliedVolumeEnvelope)
|
|
1536
|
+
continue;
|
|
1537
|
+
appliedVolumeEnvelope = true;
|
|
1538
|
+
const noteVoiceParams = note.voiceParams;
|
|
1539
|
+
for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
|
|
1540
|
+
const key = volumeEnvelopeKeys[i];
|
|
1541
|
+
if (key in voiceParams)
|
|
1542
|
+
noteVoiceParams[key] = voiceParams[key];
|
|
1543
|
+
}
|
|
1544
|
+
this.setVolumeEnvelope(channel, note);
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
});
|
|
1181
1549
|
}
|
|
1182
1550
|
createControlChangeHandlers() {
|
|
1183
1551
|
return {
|
|
@@ -1217,13 +1585,16 @@ class Midy {
|
|
|
1217
1585
|
127: this.polyOn,
|
|
1218
1586
|
};
|
|
1219
1587
|
}
|
|
1220
|
-
handleControlChange(channelNumber,
|
|
1221
|
-
const handler = this.controlChangeHandlers[
|
|
1588
|
+
handleControlChange(channelNumber, controllerType, value) {
|
|
1589
|
+
const handler = this.controlChangeHandlers[controllerType];
|
|
1222
1590
|
if (handler) {
|
|
1223
1591
|
handler.call(this, channelNumber, value);
|
|
1592
|
+
const channel = this.channels[channelNumber];
|
|
1593
|
+
const controller = 128 + controllerType;
|
|
1594
|
+
this.applyVoiceParams(channel, controller);
|
|
1224
1595
|
}
|
|
1225
1596
|
else {
|
|
1226
|
-
console.warn(`Unsupported Control change:
|
|
1597
|
+
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
1227
1598
|
}
|
|
1228
1599
|
}
|
|
1229
1600
|
setBankMSB(channelNumber, msb) {
|
|
@@ -1237,11 +1608,10 @@ class Midy {
|
|
|
1237
1608
|
if (!note)
|
|
1238
1609
|
continue;
|
|
1239
1610
|
if (note.modulationDepth) {
|
|
1240
|
-
note.modulationDepth.gain.setValueAtTime(channel.modulationDepth, now);
|
|
1611
|
+
note.modulationDepth.gain.setValueAtTime(channel.state.modulationDepth, now);
|
|
1241
1612
|
}
|
|
1242
1613
|
else {
|
|
1243
|
-
|
|
1244
|
-
this.setPitch(note, semitoneOffset);
|
|
1614
|
+
this.setPitch(channel, note);
|
|
1245
1615
|
this.startModulation(channel, note, now);
|
|
1246
1616
|
}
|
|
1247
1617
|
}
|
|
@@ -1249,21 +1619,22 @@ class Midy {
|
|
|
1249
1619
|
}
|
|
1250
1620
|
setModulationDepth(channelNumber, modulation) {
|
|
1251
1621
|
const channel = this.channels[channelNumber];
|
|
1252
|
-
channel.modulationDepth = (modulation / 127) *
|
|
1622
|
+
channel.state.modulationDepth = (modulation / 127) *
|
|
1623
|
+
channel.modulationDepthRange;
|
|
1253
1624
|
this.updateModulation(channel);
|
|
1254
1625
|
}
|
|
1255
1626
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
1256
1627
|
const channel = this.channels[channelNumber];
|
|
1257
1628
|
const factor = 5 * Math.log(10) / 127;
|
|
1258
|
-
channel.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1629
|
+
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1259
1630
|
}
|
|
1260
1631
|
setVolume(channelNumber, volume) {
|
|
1261
1632
|
const channel = this.channels[channelNumber];
|
|
1262
|
-
channel.volume = volume / 127;
|
|
1633
|
+
channel.state.volume = volume / 127;
|
|
1263
1634
|
this.updateChannelVolume(channel);
|
|
1264
1635
|
}
|
|
1265
1636
|
panToGain(pan) {
|
|
1266
|
-
const theta = Math.PI / 2 * Math.max(0, pan - 1) / 126;
|
|
1637
|
+
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
1267
1638
|
return {
|
|
1268
1639
|
gainLeft: Math.cos(theta),
|
|
1269
1640
|
gainRight: Math.sin(theta),
|
|
@@ -1271,12 +1642,12 @@ class Midy {
|
|
|
1271
1642
|
}
|
|
1272
1643
|
setPan(channelNumber, pan) {
|
|
1273
1644
|
const channel = this.channels[channelNumber];
|
|
1274
|
-
channel.pan = pan;
|
|
1645
|
+
channel.state.pan = pan / 127;
|
|
1275
1646
|
this.updateChannelVolume(channel);
|
|
1276
1647
|
}
|
|
1277
1648
|
setExpression(channelNumber, expression) {
|
|
1278
1649
|
const channel = this.channels[channelNumber];
|
|
1279
|
-
channel.expression = expression / 127;
|
|
1650
|
+
channel.state.expression = expression / 127;
|
|
1280
1651
|
this.updateChannelVolume(channel);
|
|
1281
1652
|
}
|
|
1282
1653
|
setBankLSB(channelNumber, lsb) {
|
|
@@ -1288,8 +1659,9 @@ class Midy {
|
|
|
1288
1659
|
}
|
|
1289
1660
|
updateChannelVolume(channel) {
|
|
1290
1661
|
const now = this.audioContext.currentTime;
|
|
1291
|
-
const
|
|
1292
|
-
const
|
|
1662
|
+
const state = channel.state;
|
|
1663
|
+
const volume = state.volume * state.expression;
|
|
1664
|
+
const { gainLeft, gainRight } = this.panToGain(state.pan);
|
|
1293
1665
|
channel.gainL.gain
|
|
1294
1666
|
.cancelScheduledValues(now)
|
|
1295
1667
|
.setValueAtTime(volume * gainLeft, now);
|
|
@@ -1298,68 +1670,100 @@ class Midy {
|
|
|
1298
1670
|
.setValueAtTime(volume * gainRight, now);
|
|
1299
1671
|
}
|
|
1300
1672
|
setSustainPedal(channelNumber, value) {
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
if (!isOn) {
|
|
1673
|
+
this.channels[channelNumber].state.sustainPedal = value / 127;
|
|
1674
|
+
if (value < 64) {
|
|
1304
1675
|
this.releaseSustainPedal(channelNumber, value);
|
|
1305
1676
|
}
|
|
1306
1677
|
}
|
|
1307
1678
|
setPortamento(channelNumber, value) {
|
|
1308
|
-
this.channels[channelNumber].portamento = value
|
|
1679
|
+
this.channels[channelNumber].state.portamento = value / 127;
|
|
1309
1680
|
}
|
|
1310
1681
|
setReverbSendLevel(channelNumber, reverbSendLevel) {
|
|
1311
1682
|
const channel = this.channels[channelNumber];
|
|
1683
|
+
const state = channel.state;
|
|
1312
1684
|
const reverbEffect = this.reverbEffect;
|
|
1313
|
-
if (0 <
|
|
1685
|
+
if (0 < state.reverbSendLevel) {
|
|
1314
1686
|
if (0 < reverbSendLevel) {
|
|
1315
1687
|
const now = this.audioContext.currentTime;
|
|
1316
|
-
|
|
1317
|
-
reverbEffect.
|
|
1318
|
-
reverbEffect.
|
|
1688
|
+
state.reverbSendLevel = reverbSendLevel / 127;
|
|
1689
|
+
reverbEffect.input.gain.cancelScheduledValues(now);
|
|
1690
|
+
reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
|
|
1319
1691
|
}
|
|
1320
1692
|
else {
|
|
1321
|
-
channel.
|
|
1693
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1694
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1695
|
+
const note = noteList[i];
|
|
1696
|
+
if (!note)
|
|
1697
|
+
continue;
|
|
1698
|
+
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
1699
|
+
continue;
|
|
1700
|
+
note.reverbEffectsSend.disconnect();
|
|
1701
|
+
}
|
|
1702
|
+
});
|
|
1322
1703
|
}
|
|
1323
1704
|
}
|
|
1324
1705
|
else {
|
|
1325
1706
|
if (0 < reverbSendLevel) {
|
|
1326
|
-
channel.merger.connect(reverbEffect.input);
|
|
1327
1707
|
const now = this.audioContext.currentTime;
|
|
1328
|
-
channel.
|
|
1329
|
-
|
|
1330
|
-
|
|
1708
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1709
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1710
|
+
const note = noteList[i];
|
|
1711
|
+
if (!note)
|
|
1712
|
+
continue;
|
|
1713
|
+
this.setReverbEffectsSend(note, 0);
|
|
1714
|
+
}
|
|
1715
|
+
});
|
|
1716
|
+
state.reverbSendLevel = reverbSendLevel / 127;
|
|
1717
|
+
reverbEffect.input.gain.cancelScheduledValues(now);
|
|
1718
|
+
reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
|
|
1331
1719
|
}
|
|
1332
1720
|
}
|
|
1333
1721
|
}
|
|
1334
1722
|
setChorusSendLevel(channelNumber, chorusSendLevel) {
|
|
1335
1723
|
const channel = this.channels[channelNumber];
|
|
1724
|
+
const state = channel.state;
|
|
1336
1725
|
const chorusEffect = this.chorusEffect;
|
|
1337
|
-
if (0 <
|
|
1726
|
+
if (0 < state.chorusSendLevel) {
|
|
1338
1727
|
if (0 < chorusSendLevel) {
|
|
1339
1728
|
const now = this.audioContext.currentTime;
|
|
1340
|
-
|
|
1341
|
-
chorusEffect.
|
|
1342
|
-
chorusEffect.
|
|
1729
|
+
state.chorusSendLevel = chorusSendLevel / 127;
|
|
1730
|
+
chorusEffect.input.gain.cancelScheduledValues(now);
|
|
1731
|
+
chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
|
|
1343
1732
|
}
|
|
1344
1733
|
else {
|
|
1345
|
-
channel.
|
|
1734
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1735
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1736
|
+
const note = noteList[i];
|
|
1737
|
+
if (!note)
|
|
1738
|
+
continue;
|
|
1739
|
+
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
1740
|
+
continue;
|
|
1741
|
+
note.chorusEffectsSend.disconnect();
|
|
1742
|
+
}
|
|
1743
|
+
});
|
|
1346
1744
|
}
|
|
1347
1745
|
}
|
|
1348
1746
|
else {
|
|
1349
1747
|
if (0 < chorusSendLevel) {
|
|
1350
|
-
channel.merger.connect(chorusEffect.input);
|
|
1351
1748
|
const now = this.audioContext.currentTime;
|
|
1352
|
-
channel.
|
|
1353
|
-
|
|
1354
|
-
|
|
1749
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1750
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1751
|
+
const note = noteList[i];
|
|
1752
|
+
if (!note)
|
|
1753
|
+
continue;
|
|
1754
|
+
this.setChorusEffectsSend(note, 0);
|
|
1755
|
+
}
|
|
1756
|
+
});
|
|
1757
|
+
state.chorusSendLevel = chorusSendLevel / 127;
|
|
1758
|
+
chorusEffect.input.gain.cancelScheduledValues(now);
|
|
1759
|
+
chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
|
|
1355
1760
|
}
|
|
1356
1761
|
}
|
|
1357
1762
|
}
|
|
1358
1763
|
setSostenutoPedal(channelNumber, value) {
|
|
1359
|
-
const isOn = value >= 64;
|
|
1360
1764
|
const channel = this.channels[channelNumber];
|
|
1361
|
-
channel.sostenutoPedal =
|
|
1362
|
-
if (
|
|
1765
|
+
channel.state.sostenutoPedal = value / 127;
|
|
1766
|
+
if (64 <= value) {
|
|
1363
1767
|
const now = this.audioContext.currentTime;
|
|
1364
1768
|
const activeNotes = this.getActiveNotes(channel, now);
|
|
1365
1769
|
channel.sostenutoNotes = new Map(activeNotes);
|
|
@@ -1370,31 +1774,31 @@ class Midy {
|
|
|
1370
1774
|
}
|
|
1371
1775
|
setSoftPedal(channelNumber, softPedal) {
|
|
1372
1776
|
const channel = this.channels[channelNumber];
|
|
1373
|
-
channel.softPedal = softPedal / 127;
|
|
1777
|
+
channel.state.softPedal = softPedal / 127;
|
|
1374
1778
|
}
|
|
1375
1779
|
setFilterResonance(channelNumber, filterResonance) {
|
|
1376
1780
|
const now = this.audioContext.currentTime;
|
|
1377
1781
|
const channel = this.channels[channelNumber];
|
|
1378
|
-
|
|
1782
|
+
const state = channel.state;
|
|
1783
|
+
state.filterResonance = filterResonance / 64;
|
|
1379
1784
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1380
1785
|
for (let i = 0; i < noteList.length; i++) {
|
|
1381
1786
|
const note = noteList[i];
|
|
1382
1787
|
if (!note)
|
|
1383
1788
|
continue;
|
|
1384
|
-
const Q = note.
|
|
1385
|
-
channel.filterResonance;
|
|
1789
|
+
const Q = note.voiceParams.initialFilterQ / 5 * state.filterResonance;
|
|
1386
1790
|
note.filterNode.Q.setValueAtTime(Q, now);
|
|
1387
1791
|
}
|
|
1388
1792
|
});
|
|
1389
1793
|
}
|
|
1390
1794
|
setReleaseTime(channelNumber, releaseTime) {
|
|
1391
1795
|
const channel = this.channels[channelNumber];
|
|
1392
|
-
channel.releaseTime = releaseTime / 64;
|
|
1796
|
+
channel.state.releaseTime = releaseTime / 64;
|
|
1393
1797
|
}
|
|
1394
1798
|
setAttackTime(channelNumber, attackTime) {
|
|
1395
1799
|
const now = this.audioContext.currentTime;
|
|
1396
1800
|
const channel = this.channels[channelNumber];
|
|
1397
|
-
channel.attackTime = attackTime / 64;
|
|
1801
|
+
channel.state.attackTime = attackTime / 64;
|
|
1398
1802
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1399
1803
|
for (let i = 0; i < noteList.length; i++) {
|
|
1400
1804
|
const note = noteList[i];
|
|
@@ -1408,7 +1812,7 @@ class Midy {
|
|
|
1408
1812
|
}
|
|
1409
1813
|
setBrightness(channelNumber, brightness) {
|
|
1410
1814
|
const channel = this.channels[channelNumber];
|
|
1411
|
-
channel.brightness = brightness / 64;
|
|
1815
|
+
channel.state.brightness = brightness / 64;
|
|
1412
1816
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1413
1817
|
for (let i = 0; i < noteList.length; i++) {
|
|
1414
1818
|
const note = noteList[i];
|
|
@@ -1420,7 +1824,7 @@ class Midy {
|
|
|
1420
1824
|
}
|
|
1421
1825
|
setDecayTime(channelNumber, dacayTime) {
|
|
1422
1826
|
const channel = this.channels[channelNumber];
|
|
1423
|
-
channel.decayTime = dacayTime / 64;
|
|
1827
|
+
channel.state.decayTime = dacayTime / 64;
|
|
1424
1828
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1425
1829
|
for (let i = 0; i < noteList.length; i++) {
|
|
1426
1830
|
const note = noteList[i];
|
|
@@ -1432,7 +1836,7 @@ class Midy {
|
|
|
1432
1836
|
}
|
|
1433
1837
|
setVibratoRate(channelNumber, vibratoRate) {
|
|
1434
1838
|
const channel = this.channels[channelNumber];
|
|
1435
|
-
channel.vibratoRate = vibratoRate / 64;
|
|
1839
|
+
channel.state.vibratoRate = vibratoRate / 64;
|
|
1436
1840
|
if (channel.vibratoDepth <= 0)
|
|
1437
1841
|
return;
|
|
1438
1842
|
const now = this.audioContext.currentTime;
|
|
@@ -1440,16 +1844,16 @@ class Midy {
|
|
|
1440
1844
|
activeNotes.forEach((activeNote) => {
|
|
1441
1845
|
activeNote.vibratoLFO.frequency
|
|
1442
1846
|
.cancelScheduledValues(now)
|
|
1443
|
-
.setValueAtTime(channel.vibratoRate, now);
|
|
1847
|
+
.setValueAtTime(channel.state.vibratoRate, now);
|
|
1444
1848
|
});
|
|
1445
1849
|
}
|
|
1446
1850
|
setVibratoDepth(channelNumber, vibratoDepth) {
|
|
1447
1851
|
const channel = this.channels[channelNumber];
|
|
1448
|
-
channel.vibratoDepth = vibratoDepth / 64;
|
|
1852
|
+
channel.state.vibratoDepth = vibratoDepth / 64;
|
|
1449
1853
|
}
|
|
1450
1854
|
setVibratoDelay(channelNumber, vibratoDelay) {
|
|
1451
1855
|
const channel = this.channels[channelNumber];
|
|
1452
|
-
channel.vibratoDelay = vibratoDelay / 64;
|
|
1856
|
+
channel.state.vibratoDelay = vibratoDelay / 64;
|
|
1453
1857
|
}
|
|
1454
1858
|
limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
|
|
1455
1859
|
if (maxLSB < channel.dataLSB) {
|
|
@@ -1519,7 +1923,7 @@ class Midy {
|
|
|
1519
1923
|
this.channels[channelNumber].dataMSB = value;
|
|
1520
1924
|
this.handleRPN(channelNumber, 0);
|
|
1521
1925
|
}
|
|
1522
|
-
updateDetune(channel,
|
|
1926
|
+
updateDetune(channel, detune) {
|
|
1523
1927
|
const now = this.audioContext.currentTime;
|
|
1524
1928
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1525
1929
|
for (let i = 0; i < noteList.length; i++) {
|
|
@@ -1527,7 +1931,6 @@ class Midy {
|
|
|
1527
1931
|
if (!note)
|
|
1528
1932
|
continue;
|
|
1529
1933
|
const { bufferSource } = note;
|
|
1530
|
-
const detune = bufferSource.detune.value + detuneChange;
|
|
1531
1934
|
bufferSource.detune
|
|
1532
1935
|
.cancelScheduledValues(now)
|
|
1533
1936
|
.setValueAtTime(detune, now);
|
|
@@ -1540,13 +1943,13 @@ class Midy {
|
|
|
1540
1943
|
const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
|
|
1541
1944
|
this.setPitchBendRange(channelNumber, pitchBendRange);
|
|
1542
1945
|
}
|
|
1543
|
-
setPitchBendRange(channelNumber,
|
|
1946
|
+
setPitchBendRange(channelNumber, pitchWheelSensitivity) {
|
|
1544
1947
|
const channel = this.channels[channelNumber];
|
|
1545
|
-
const
|
|
1546
|
-
|
|
1547
|
-
const
|
|
1548
|
-
|
|
1549
|
-
this.
|
|
1948
|
+
const state = channel.state;
|
|
1949
|
+
state.pitchWheelSensitivity = pitchWheelSensitivity / 128;
|
|
1950
|
+
const detune = (state.pitchWheel * 2 - 1) * pitchWheelSensitivity * 100;
|
|
1951
|
+
this.updateDetune(channel, detune);
|
|
1952
|
+
this.applyVoiceParams(channel, 16);
|
|
1550
1953
|
}
|
|
1551
1954
|
handleFineTuningRPN(channelNumber) {
|
|
1552
1955
|
const channel = this.channels[channelNumber];
|
|
@@ -1590,7 +1993,30 @@ class Midy {
|
|
|
1590
1993
|
return this.stopChannelNotes(channelNumber, 0, true);
|
|
1591
1994
|
}
|
|
1592
1995
|
resetAllControllers(channelNumber) {
|
|
1593
|
-
|
|
1996
|
+
const stateTypes = [
|
|
1997
|
+
"expression",
|
|
1998
|
+
"modulationDepth",
|
|
1999
|
+
"sustainPedal",
|
|
2000
|
+
"portamento",
|
|
2001
|
+
"sostenutoPedal",
|
|
2002
|
+
"softPedal",
|
|
2003
|
+
"channelPressure",
|
|
2004
|
+
"pitchWheelSensitivity",
|
|
2005
|
+
];
|
|
2006
|
+
const channel = this.channels[channelNumber];
|
|
2007
|
+
const state = channel.state;
|
|
2008
|
+
for (let i = 0; i < stateTypes.length; i++) {
|
|
2009
|
+
const type = stateTypes[i];
|
|
2010
|
+
state[type] = defaultControllerState[type];
|
|
2011
|
+
}
|
|
2012
|
+
const settingTypes = [
|
|
2013
|
+
"rpnMSB",
|
|
2014
|
+
"rpnLSB",
|
|
2015
|
+
];
|
|
2016
|
+
for (let i = 0; i < settingTypes.length; i++) {
|
|
2017
|
+
const type = settingTypes[i];
|
|
2018
|
+
channel[type] = this.constructor.channelSettings[type];
|
|
2019
|
+
}
|
|
1594
2020
|
}
|
|
1595
2021
|
allNotesOff(channelNumber) {
|
|
1596
2022
|
return this.stopChannelNotes(channelNumber, 0, false);
|
|
@@ -1784,10 +2210,8 @@ class Midy {
|
|
|
1784
2210
|
}
|
|
1785
2211
|
setReverbTime(value) {
|
|
1786
2212
|
this.reverb.time = this.getReverbTime(value);
|
|
1787
|
-
const { audioContext,
|
|
1788
|
-
|
|
1789
|
-
channels[i].reverbEffect = options.reverbAlgorithm(audioContext);
|
|
1790
|
-
}
|
|
2213
|
+
const { audioContext, options } = this;
|
|
2214
|
+
this.reverbEffect = options.reverbAlgorithm(audioContext);
|
|
1791
2215
|
}
|
|
1792
2216
|
getReverbTime(value) {
|
|
1793
2217
|
return Math.pow(Math.E, (value - 40) * 0.025);
|
|
@@ -1864,10 +2288,7 @@ class Midy {
|
|
|
1864
2288
|
const now = this.audioContext.currentTime;
|
|
1865
2289
|
const modRate = this.getChorusModRate(value);
|
|
1866
2290
|
this.chorus.modRate = modRate;
|
|
1867
|
-
|
|
1868
|
-
const lfo = this.channels[i].chorusEffect.lfo;
|
|
1869
|
-
lfo.frequency.setValueAtTime(modRate, now);
|
|
1870
|
-
}
|
|
2291
|
+
this.chorusEffect.lfo.frequency.setValueAtTime(modRate, now);
|
|
1871
2292
|
}
|
|
1872
2293
|
getChorusModRate(value) {
|
|
1873
2294
|
return value * 0.122; // Hz
|
|
@@ -1876,12 +2297,9 @@ class Midy {
|
|
|
1876
2297
|
const now = this.audioContext.currentTime;
|
|
1877
2298
|
const modDepth = this.getChorusModDepth(value);
|
|
1878
2299
|
this.chorus.modDepth = modDepth;
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
.cancelScheduledValues(now)
|
|
1883
|
-
.setValueAtTime(modDepth / 2, now);
|
|
1884
|
-
}
|
|
2300
|
+
this.chorusEffect.lfoGain.gain
|
|
2301
|
+
.cancelScheduledValues(now)
|
|
2302
|
+
.setValueAtTime(modDepth / 2, now);
|
|
1885
2303
|
}
|
|
1886
2304
|
getChorusModDepth(value) {
|
|
1887
2305
|
return (value + 1) / 3200; // second
|
|
@@ -1890,14 +2308,11 @@ class Midy {
|
|
|
1890
2308
|
const now = this.audioContext.currentTime;
|
|
1891
2309
|
const feedback = this.getChorusFeedback(value);
|
|
1892
2310
|
this.chorus.feedback = feedback;
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
.cancelScheduledValues(now)
|
|
1899
|
-
.setValueAtTime(feedback, now);
|
|
1900
|
-
}
|
|
2311
|
+
const chorusEffect = this.chorusEffect;
|
|
2312
|
+
for (let i = 0; i < chorusEffect.feedbackGains.length; i++) {
|
|
2313
|
+
chorusEffect.feedbackGains[i].gain
|
|
2314
|
+
.cancelScheduledValues(now)
|
|
2315
|
+
.setValueAtTime(feedback, now);
|
|
1901
2316
|
}
|
|
1902
2317
|
}
|
|
1903
2318
|
getChorusFeedback(value) {
|
|
@@ -1905,15 +2320,28 @@ class Midy {
|
|
|
1905
2320
|
}
|
|
1906
2321
|
setChorusSendToReverb(value) {
|
|
1907
2322
|
const sendToReverb = this.getChorusSendToReverb(value);
|
|
1908
|
-
|
|
1909
|
-
|
|
2323
|
+
const sendGain = this.chorusEffect.sendGain;
|
|
2324
|
+
if (0 < this.chorus.sendToReverb) {
|
|
1910
2325
|
this.chorus.sendToReverb = sendToReverb;
|
|
1911
|
-
|
|
1912
|
-
.
|
|
1913
|
-
.
|
|
2326
|
+
if (0 < sendToReverb) {
|
|
2327
|
+
const now = this.audioContext.currentTime;
|
|
2328
|
+
sendGain.gain
|
|
2329
|
+
.cancelScheduledValues(now)
|
|
2330
|
+
.setValueAtTime(sendToReverb, now);
|
|
2331
|
+
}
|
|
2332
|
+
else {
|
|
2333
|
+
sendGain.disconnect();
|
|
2334
|
+
}
|
|
1914
2335
|
}
|
|
1915
|
-
else
|
|
1916
|
-
this.
|
|
2336
|
+
else {
|
|
2337
|
+
this.chorus.sendToReverb = sendToReverb;
|
|
2338
|
+
if (0 < sendToReverb) {
|
|
2339
|
+
const now = this.audioContext.currentTime;
|
|
2340
|
+
sendGain.connect(this.reverbEffect.input);
|
|
2341
|
+
sendGain.gain
|
|
2342
|
+
.cancelScheduledValues(now)
|
|
2343
|
+
.setValueAtTime(sendToReverb, now);
|
|
2344
|
+
}
|
|
1917
2345
|
}
|
|
1918
2346
|
}
|
|
1919
2347
|
getChorusSendToReverb(value) {
|
|
@@ -1951,48 +2379,19 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
1951
2379
|
writable: true,
|
|
1952
2380
|
value: {
|
|
1953
2381
|
currentBufferSource: null,
|
|
1954
|
-
|
|
1955
|
-
pan: 64,
|
|
1956
|
-
portamentoTime: 1, // sec
|
|
1957
|
-
filterResonance: 1,
|
|
1958
|
-
releaseTime: 1,
|
|
1959
|
-
attackTime: 1,
|
|
1960
|
-
brightness: 1,
|
|
1961
|
-
decayTime: 1,
|
|
1962
|
-
reverbSendLevel: 0,
|
|
1963
|
-
chorusSendLevel: 0,
|
|
1964
|
-
vibratoRate: 1,
|
|
1965
|
-
vibratoDepth: 1,
|
|
1966
|
-
vibratoDelay: 1,
|
|
2382
|
+
program: 0,
|
|
1967
2383
|
bank: 121 * 128,
|
|
1968
2384
|
bankMSB: 121,
|
|
1969
2385
|
bankLSB: 0,
|
|
1970
2386
|
dataMSB: 0,
|
|
1971
2387
|
dataLSB: 0,
|
|
1972
|
-
|
|
1973
|
-
|
|
2388
|
+
rpnMSB: 127,
|
|
2389
|
+
rpnLSB: 127,
|
|
1974
2390
|
fineTuning: 0, // cb
|
|
1975
2391
|
coarseTuning: 0, // cb
|
|
1976
2392
|
modulationDepthRange: 50, // cent
|
|
1977
2393
|
}
|
|
1978
2394
|
});
|
|
1979
|
-
Object.defineProperty(Midy, "effectSettings", {
|
|
1980
|
-
enumerable: true,
|
|
1981
|
-
configurable: true,
|
|
1982
|
-
writable: true,
|
|
1983
|
-
value: {
|
|
1984
|
-
expression: 1,
|
|
1985
|
-
modulationDepth: 0,
|
|
1986
|
-
sustainPedal: false,
|
|
1987
|
-
portamento: false,
|
|
1988
|
-
sostenutoPedal: false,
|
|
1989
|
-
softPedal: 0,
|
|
1990
|
-
rpnMSB: 127,
|
|
1991
|
-
rpnLSB: 127,
|
|
1992
|
-
channelPressure: 0,
|
|
1993
|
-
pitchBendRange: 2,
|
|
1994
|
-
}
|
|
1995
|
-
});
|
|
1996
2395
|
Object.defineProperty(Midy, "controllerDestinationSettings", {
|
|
1997
2396
|
enumerable: true,
|
|
1998
2397
|
configurable: true,
|