@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-GM2.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MidyGM2 = 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 MidyGM2 {
|
|
63
169
|
constructor(audioContext, options = this.defaultOptions) {
|
|
64
170
|
Object.defineProperty(this, "ticksPerBeat", {
|
|
@@ -208,6 +314,12 @@ class MidyGM2 {
|
|
|
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 MidyGM2 {
|
|
|
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 MidyGM2 {
|
|
|
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 MidyGM2 {
|
|
|
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 MidyGM2 {
|
|
|
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(),
|
|
@@ -307,38 +420,43 @@ class MidyGM2 {
|
|
|
307
420
|
});
|
|
308
421
|
return channels;
|
|
309
422
|
}
|
|
310
|
-
async createNoteBuffer(
|
|
311
|
-
const sampleStart =
|
|
312
|
-
const sampleEnd =
|
|
423
|
+
async createNoteBuffer(voiceParams, isSF3) {
|
|
424
|
+
const sampleStart = voiceParams.start;
|
|
425
|
+
const sampleEnd = voiceParams.sample.length + voiceParams.end;
|
|
313
426
|
if (isSF3) {
|
|
314
|
-
const sample =
|
|
315
|
-
const
|
|
427
|
+
const sample = voiceParams.sample;
|
|
428
|
+
const start = sample.byteOffset + sampleStart;
|
|
429
|
+
const end = sample.byteOffset + sampleEnd;
|
|
430
|
+
const buffer = sample.buffer.slice(start, end);
|
|
431
|
+
const audioBuffer = await this.audioContext.decodeAudioData(buffer);
|
|
316
432
|
return audioBuffer;
|
|
317
433
|
}
|
|
318
434
|
else {
|
|
319
|
-
const sample =
|
|
435
|
+
const sample = voiceParams.sample;
|
|
436
|
+
const start = sample.byteOffset + sampleStart;
|
|
437
|
+
const end = sample.byteOffset + sampleEnd;
|
|
438
|
+
const buffer = sample.buffer.slice(start, end);
|
|
320
439
|
const audioBuffer = new AudioBuffer({
|
|
321
440
|
numberOfChannels: 1,
|
|
322
441
|
length: sample.length,
|
|
323
|
-
sampleRate:
|
|
442
|
+
sampleRate: voiceParams.sampleRate,
|
|
324
443
|
});
|
|
325
444
|
const channelData = audioBuffer.getChannelData(0);
|
|
326
|
-
const int16Array = new Int16Array(
|
|
445
|
+
const int16Array = new Int16Array(buffer);
|
|
327
446
|
for (let i = 0; i < int16Array.length; i++) {
|
|
328
447
|
channelData[i] = int16Array[i] / 32768;
|
|
329
448
|
}
|
|
330
449
|
return audioBuffer;
|
|
331
450
|
}
|
|
332
451
|
}
|
|
333
|
-
async createNoteBufferNode(
|
|
452
|
+
async createNoteBufferNode(voiceParams, isSF3) {
|
|
334
453
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
335
|
-
const audioBuffer = await this.createNoteBuffer(
|
|
454
|
+
const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
|
|
336
455
|
bufferSource.buffer = audioBuffer;
|
|
337
|
-
bufferSource.loop =
|
|
456
|
+
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
338
457
|
if (bufferSource.loop) {
|
|
339
|
-
bufferSource.loopStart =
|
|
340
|
-
|
|
341
|
-
bufferSource.loopEnd = instrumentKey.loopEnd / instrumentKey.sampleRate;
|
|
458
|
+
bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
|
|
459
|
+
bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
|
|
342
460
|
}
|
|
343
461
|
return bufferSource;
|
|
344
462
|
}
|
|
@@ -392,7 +510,7 @@ class MidyGM2 {
|
|
|
392
510
|
this.handleChannelPressure(event.channel, event.amount);
|
|
393
511
|
break;
|
|
394
512
|
case "pitchBend":
|
|
395
|
-
this.setPitchBend(event.channel, event.value);
|
|
513
|
+
this.setPitchBend(event.channel, event.value + 8192);
|
|
396
514
|
break;
|
|
397
515
|
case "sysEx":
|
|
398
516
|
this.handleSysEx(event.data);
|
|
@@ -421,6 +539,7 @@ class MidyGM2 {
|
|
|
421
539
|
if (queueIndex >= this.timeline.length) {
|
|
422
540
|
await Promise.all(this.notePromises);
|
|
423
541
|
this.notePromises = [];
|
|
542
|
+
this.exclusiveClassMap.clear();
|
|
424
543
|
resolve();
|
|
425
544
|
return;
|
|
426
545
|
}
|
|
@@ -436,6 +555,7 @@ class MidyGM2 {
|
|
|
436
555
|
}
|
|
437
556
|
else if (this.isStopping) {
|
|
438
557
|
await this.stopNotes(0, true);
|
|
558
|
+
this.exclusiveClassMap.clear();
|
|
439
559
|
this.notePromises = [];
|
|
440
560
|
resolve();
|
|
441
561
|
this.isStopping = false;
|
|
@@ -444,6 +564,7 @@ class MidyGM2 {
|
|
|
444
564
|
}
|
|
445
565
|
else if (this.isSeeking) {
|
|
446
566
|
this.stopNotes(0, true);
|
|
567
|
+
this.exclusiveClassMap.clear();
|
|
447
568
|
this.startTime = this.audioContext.currentTime;
|
|
448
569
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
449
570
|
offset = this.resumeTime - this.startTime;
|
|
@@ -672,14 +793,14 @@ class MidyGM2 {
|
|
|
672
793
|
return impulse;
|
|
673
794
|
}
|
|
674
795
|
createConvolutionReverb(audioContext, impulse) {
|
|
675
|
-
const
|
|
796
|
+
const input = new GainNode(audioContext);
|
|
676
797
|
const convolverNode = new ConvolverNode(audioContext, {
|
|
677
798
|
buffer: impulse,
|
|
678
799
|
});
|
|
679
|
-
|
|
800
|
+
input.connect(convolverNode);
|
|
680
801
|
return {
|
|
681
|
-
input
|
|
682
|
-
output,
|
|
802
|
+
input,
|
|
803
|
+
output: convolverNode,
|
|
683
804
|
convolverNode,
|
|
684
805
|
};
|
|
685
806
|
}
|
|
@@ -721,7 +842,6 @@ class MidyGM2 {
|
|
|
721
842
|
// M.R.Schroeder, "Natural Sounding Artificial Reverberation", J.Audio Eng. Soc., vol.10, p.219, 1962
|
|
722
843
|
createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays) {
|
|
723
844
|
const input = new GainNode(audioContext);
|
|
724
|
-
const output = new GainNode(audioContext);
|
|
725
845
|
const mergerGain = new GainNode(audioContext);
|
|
726
846
|
for (let i = 0; i < combDelays.length; i++) {
|
|
727
847
|
const comb = this.createCombFilter(audioContext, input, combDelays[i], combFeedbacks[i]);
|
|
@@ -732,7 +852,7 @@ class MidyGM2 {
|
|
|
732
852
|
const allpass = this.createAllpassFilter(audioContext, (i === 0) ? mergerGain : allpasses.at(-1), allpassDelays[i], allpassFeedbacks[i]);
|
|
733
853
|
allpasses.push(allpass);
|
|
734
854
|
}
|
|
735
|
-
allpasses.at(-1)
|
|
855
|
+
const output = allpasses.at(-1);
|
|
736
856
|
return { input, output };
|
|
737
857
|
}
|
|
738
858
|
createChorusEffect(audioContext) {
|
|
@@ -787,53 +907,62 @@ class MidyGM2 {
|
|
|
787
907
|
calcSemitoneOffset(channel) {
|
|
788
908
|
const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
|
|
789
909
|
const channelTuning = channel.coarseTuning + channel.fineTuning;
|
|
790
|
-
const
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
return instrumentKey.playbackRate(noteNumber) *
|
|
795
|
-
Math.pow(2, semitoneOffset / 12);
|
|
910
|
+
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
911
|
+
const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 128;
|
|
912
|
+
const pitch = pitchWheel * pitchWheelSensitivity;
|
|
913
|
+
return masterTuning + channelTuning + pitch;
|
|
796
914
|
}
|
|
797
915
|
setPortamentoStartVolumeEnvelope(channel, note) {
|
|
798
|
-
const
|
|
799
|
-
const
|
|
800
|
-
const
|
|
801
|
-
const
|
|
802
|
-
const
|
|
916
|
+
const now = this.audioContext.currentTime;
|
|
917
|
+
const { voiceParams, startTime } = note;
|
|
918
|
+
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
919
|
+
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
920
|
+
const volDelay = startTime + voiceParams.volDelay;
|
|
921
|
+
const portamentoTime = volDelay + channel.state.portamentoTime;
|
|
803
922
|
note.volumeNode.gain
|
|
804
|
-
.cancelScheduledValues(
|
|
923
|
+
.cancelScheduledValues(now)
|
|
805
924
|
.setValueAtTime(0, volDelay)
|
|
806
925
|
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
807
926
|
}
|
|
808
927
|
setVolumeEnvelope(note) {
|
|
809
|
-
const
|
|
810
|
-
const
|
|
811
|
-
const
|
|
812
|
-
const
|
|
813
|
-
const
|
|
814
|
-
const
|
|
815
|
-
const
|
|
928
|
+
const now = this.audioContext.currentTime;
|
|
929
|
+
const { voiceParams, startTime } = note;
|
|
930
|
+
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
931
|
+
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
932
|
+
const volDelay = startTime + voiceParams.volDelay;
|
|
933
|
+
const volAttack = volDelay + voiceParams.volAttack;
|
|
934
|
+
const volHold = volAttack + voiceParams.volHold;
|
|
935
|
+
const volDecay = volHold + voiceParams.volDecay;
|
|
816
936
|
note.volumeNode.gain
|
|
817
|
-
.cancelScheduledValues(
|
|
937
|
+
.cancelScheduledValues(now)
|
|
818
938
|
.setValueAtTime(0, startTime)
|
|
819
939
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
820
940
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
821
941
|
.setValueAtTime(attackVolume, volHold)
|
|
822
942
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
823
943
|
}
|
|
824
|
-
|
|
825
|
-
const
|
|
826
|
-
|
|
827
|
-
|
|
944
|
+
setPlaybackRate(note) {
|
|
945
|
+
const now = this.audioContext.currentTime;
|
|
946
|
+
note.bufferSource.playbackRate
|
|
947
|
+
.cancelScheduledValues(now)
|
|
948
|
+
.setValueAtTime(note.voiceParams.playbackRate, now);
|
|
949
|
+
}
|
|
950
|
+
setPitch(channel, note) {
|
|
951
|
+
const now = this.audioContext.currentTime;
|
|
952
|
+
const { startTime } = note;
|
|
953
|
+
const basePitch = this.calcSemitoneOffset(channel) * 100;
|
|
954
|
+
note.bufferSource.detune
|
|
955
|
+
.cancelScheduledValues(now)
|
|
956
|
+
.setValueAtTime(basePitch, startTime);
|
|
957
|
+
const modEnvToPitch = note.voiceParams.modEnvToPitch;
|
|
828
958
|
if (modEnvToPitch === 0)
|
|
829
959
|
return;
|
|
830
|
-
const
|
|
831
|
-
const
|
|
832
|
-
const
|
|
833
|
-
const
|
|
834
|
-
const
|
|
835
|
-
|
|
836
|
-
note.bufferSource.playbackRate.value
|
|
960
|
+
const peekPitch = basePitch + modEnvToPitch;
|
|
961
|
+
const modDelay = startTime + voiceParams.modDelay;
|
|
962
|
+
const modAttack = modDelay + voiceParams.modAttack;
|
|
963
|
+
const modHold = modAttack + voiceParams.modHold;
|
|
964
|
+
const modDecay = modHold + voiceParams.modDecay;
|
|
965
|
+
note.bufferSource.detune
|
|
837
966
|
.setValueAtTime(basePitch, modDelay)
|
|
838
967
|
.exponentialRampToValueAtTime(peekPitch, modAttack)
|
|
839
968
|
.setValueAtTime(peekPitch, modHold)
|
|
@@ -845,42 +974,46 @@ class MidyGM2 {
|
|
|
845
974
|
return Math.max(minFrequency, Math.min(frequency, maxFrequency));
|
|
846
975
|
}
|
|
847
976
|
setPortamentoStartFilterEnvelope(channel, note) {
|
|
848
|
-
const
|
|
977
|
+
const now = this.audioContext.currentTime;
|
|
978
|
+
const state = channel.state;
|
|
979
|
+
const { voiceParams, noteNumber, startTime } = note;
|
|
849
980
|
const softPedalFactor = 1 -
|
|
850
|
-
(0.1 + (noteNumber / 127) * 0.2) *
|
|
851
|
-
const baseFreq = this.centToHz(
|
|
981
|
+
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
982
|
+
const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
|
|
852
983
|
softPedalFactor;
|
|
853
|
-
const peekFreq = this.centToHz(
|
|
984
|
+
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor;
|
|
854
985
|
const sustainFreq = baseFreq +
|
|
855
|
-
(peekFreq - baseFreq) * (1 -
|
|
986
|
+
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
856
987
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
857
988
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
858
|
-
const portamentoTime = startTime + channel.portamentoTime;
|
|
859
|
-
const modDelay = startTime +
|
|
989
|
+
const portamentoTime = startTime + channel.state.portamentoTime;
|
|
990
|
+
const modDelay = startTime + voiceParams.modDelay;
|
|
860
991
|
note.filterNode.frequency
|
|
861
|
-
.cancelScheduledValues(
|
|
992
|
+
.cancelScheduledValues(now)
|
|
862
993
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
863
994
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
864
995
|
.linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
|
|
865
996
|
}
|
|
866
997
|
setFilterEnvelope(channel, note) {
|
|
867
|
-
const
|
|
998
|
+
const now = this.audioContext.currentTime;
|
|
999
|
+
const state = channel.state;
|
|
1000
|
+
const { voiceParams, noteNumber, startTime } = note;
|
|
868
1001
|
const softPedalFactor = 1 -
|
|
869
|
-
(0.1 + (noteNumber / 127) * 0.2) *
|
|
870
|
-
const baseFreq = this.centToHz(
|
|
1002
|
+
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1003
|
+
const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
|
|
871
1004
|
softPedalFactor;
|
|
872
|
-
const peekFreq = this.centToHz(
|
|
1005
|
+
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor;
|
|
873
1006
|
const sustainFreq = baseFreq +
|
|
874
|
-
(peekFreq - baseFreq) * (1 -
|
|
1007
|
+
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
875
1008
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
876
1009
|
const adjustedPeekFreq = this.clampCutoffFrequency(peekFreq);
|
|
877
1010
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
878
|
-
const modDelay = startTime +
|
|
879
|
-
const modAttack = modDelay +
|
|
880
|
-
const modHold = modAttack +
|
|
881
|
-
const modDecay = modHold +
|
|
1011
|
+
const modDelay = startTime + voiceParams.modDelay;
|
|
1012
|
+
const modAttack = modDelay + voiceParams.modAttack;
|
|
1013
|
+
const modHold = modAttack + voiceParams.modHold;
|
|
1014
|
+
const modDecay = modHold + voiceParams.modDecay;
|
|
882
1015
|
note.filterNode.frequency
|
|
883
|
-
.cancelScheduledValues(
|
|
1016
|
+
.cancelScheduledValues(now)
|
|
884
1017
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
885
1018
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
886
1019
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
@@ -888,25 +1021,18 @@ class MidyGM2 {
|
|
|
888
1021
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
889
1022
|
}
|
|
890
1023
|
startModulation(channel, note, startTime) {
|
|
891
|
-
const {
|
|
892
|
-
const { modLfoToPitch, modLfoToVolume } = instrumentKey;
|
|
1024
|
+
const { voiceParams } = note;
|
|
893
1025
|
note.modulationLFO = new OscillatorNode(this.audioContext, {
|
|
894
|
-
frequency: this.centToHz(
|
|
1026
|
+
frequency: this.centToHz(voiceParams.freqModLFO),
|
|
895
1027
|
});
|
|
896
1028
|
note.filterDepth = new GainNode(this.audioContext, {
|
|
897
|
-
gain:
|
|
1029
|
+
gain: voiceParams.modLfoToFilterFc,
|
|
898
1030
|
});
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
note.
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
905
|
-
const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
|
|
906
|
-
note.volumeDepth = new GainNode(this.audioContext, {
|
|
907
|
-
gain: volumeDepth * volumeDepthSign,
|
|
908
|
-
});
|
|
909
|
-
note.modulationLFO.start(startTime + instrumentKey.delayModLFO);
|
|
1031
|
+
note.modulationDepth = new GainNode(this.audioContext);
|
|
1032
|
+
this.setModLfoToPitch(channel, note);
|
|
1033
|
+
note.volumeDepth = new GainNode(this.audioContext);
|
|
1034
|
+
this.setModLfoToVolume(note);
|
|
1035
|
+
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
910
1036
|
note.modulationLFO.connect(note.filterDepth);
|
|
911
1037
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
912
1038
|
note.modulationLFO.connect(note.modulationDepth);
|
|
@@ -915,54 +1041,59 @@ class MidyGM2 {
|
|
|
915
1041
|
note.volumeDepth.connect(note.volumeNode.gain);
|
|
916
1042
|
}
|
|
917
1043
|
startVibrato(channel, note, startTime) {
|
|
918
|
-
const {
|
|
919
|
-
const
|
|
1044
|
+
const { voiceParams } = note;
|
|
1045
|
+
const state = channel.state;
|
|
920
1046
|
note.vibratoLFO = new OscillatorNode(this.audioContext, {
|
|
921
|
-
frequency: this.centToHz(
|
|
922
|
-
|
|
923
|
-
});
|
|
924
|
-
const vibratoDepth = Math.abs(vibLfoToPitch) * channel.vibratoDepth;
|
|
925
|
-
const vibratoDepthSign = 0 < vibLfoToPitch;
|
|
926
|
-
note.vibratoDepth = new GainNode(this.audioContext, {
|
|
927
|
-
gain: vibratoDepth * vibratoDepthSign,
|
|
1047
|
+
frequency: this.centToHz(voiceParams.freqVibLFO) *
|
|
1048
|
+
state.vibratoRate,
|
|
928
1049
|
});
|
|
929
|
-
note.vibratoLFO.start(startTime +
|
|
1050
|
+
note.vibratoLFO.start(startTime + voiceParams.delayVibLFO * state.vibratoDelay * 2);
|
|
1051
|
+
note.vibratoDepth = new GainNode(this.audioContext);
|
|
1052
|
+
this.setVibLfoToPitch(channel, note);
|
|
930
1053
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
931
1054
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
932
1055
|
}
|
|
933
|
-
async createNote(channel,
|
|
934
|
-
const
|
|
935
|
-
const
|
|
936
|
-
|
|
1056
|
+
async createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3) {
|
|
1057
|
+
const state = channel.state;
|
|
1058
|
+
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1059
|
+
const voiceParams = voice.getAllParams(controllerState);
|
|
1060
|
+
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1061
|
+
note.bufferSource = await this.createNoteBufferNode(voiceParams, isSF3);
|
|
937
1062
|
note.volumeNode = new GainNode(this.audioContext);
|
|
938
1063
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
939
1064
|
type: "lowpass",
|
|
940
|
-
Q:
|
|
1065
|
+
Q: voiceParams.initialFilterQ / 10, // dB
|
|
941
1066
|
});
|
|
942
1067
|
if (portamento) {
|
|
1068
|
+
note.portamento = true;
|
|
943
1069
|
this.setPortamentoStartVolumeEnvelope(channel, note);
|
|
944
1070
|
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
945
1071
|
}
|
|
946
1072
|
else {
|
|
947
|
-
|
|
1073
|
+
note.portamento = false;
|
|
1074
|
+
this.setVolumeEnvelope(channel, note);
|
|
948
1075
|
this.setFilterEnvelope(channel, note);
|
|
949
1076
|
}
|
|
950
|
-
if (0 <
|
|
1077
|
+
if (0 < state.vibratoDepth) {
|
|
951
1078
|
this.startVibrato(channel, note, startTime);
|
|
952
1079
|
}
|
|
953
|
-
|
|
954
|
-
|
|
1080
|
+
this.setPlaybackRate(note);
|
|
1081
|
+
if (0 < state.modulationDepth) {
|
|
1082
|
+
this.setPitch(channel, note);
|
|
955
1083
|
this.startModulation(channel, note, startTime);
|
|
956
1084
|
}
|
|
957
|
-
else {
|
|
958
|
-
note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
|
|
959
|
-
}
|
|
960
1085
|
if (this.mono && channel.currentBufferSource) {
|
|
961
1086
|
channel.currentBufferSource.stop(startTime);
|
|
962
1087
|
channel.currentBufferSource = note.bufferSource;
|
|
963
1088
|
}
|
|
964
1089
|
note.bufferSource.connect(note.filterNode);
|
|
965
1090
|
note.filterNode.connect(note.volumeNode);
|
|
1091
|
+
if (0 < channel.chorusSendLevel) {
|
|
1092
|
+
this.setChorusEffectsSend(channel, note, 0);
|
|
1093
|
+
}
|
|
1094
|
+
if (0 < channel.reverbSendLevel) {
|
|
1095
|
+
this.setReverbEffectsSend(channel, note, 0);
|
|
1096
|
+
}
|
|
966
1097
|
note.bufferSource.start(startTime);
|
|
967
1098
|
return note;
|
|
968
1099
|
}
|
|
@@ -983,15 +1114,28 @@ class MidyGM2 {
|
|
|
983
1114
|
return;
|
|
984
1115
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
985
1116
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
986
|
-
const
|
|
987
|
-
if (!
|
|
1117
|
+
const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
|
|
1118
|
+
if (!voice)
|
|
988
1119
|
return;
|
|
989
|
-
const note = await this.createNote(channel,
|
|
1120
|
+
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
|
|
990
1121
|
note.volumeNode.connect(channel.gainL);
|
|
991
1122
|
note.volumeNode.connect(channel.gainR);
|
|
992
|
-
if (channel.sostenutoPedal) {
|
|
1123
|
+
if (channel.state.sostenutoPedal) {
|
|
993
1124
|
channel.sostenutoNotes.set(noteNumber, note);
|
|
994
1125
|
}
|
|
1126
|
+
const exclusiveClass = note.voiceParams.exclusiveClass;
|
|
1127
|
+
if (exclusiveClass !== 0) {
|
|
1128
|
+
if (this.exclusiveClassMap.has(exclusiveClass)) {
|
|
1129
|
+
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
1130
|
+
const [prevNote, prevChannelNumber] = prevEntry;
|
|
1131
|
+
if (!prevNote.ending) {
|
|
1132
|
+
this.scheduleNoteRelease(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1133
|
+
startTime, undefined, // portamentoNoteNumber
|
|
1134
|
+
true);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
|
|
1138
|
+
}
|
|
995
1139
|
const scheduledNotes = channel.scheduledNotes;
|
|
996
1140
|
if (scheduledNotes.has(noteNumber)) {
|
|
997
1141
|
scheduledNotes.get(noteNumber).push(note);
|
|
@@ -1004,15 +1148,15 @@ class MidyGM2 {
|
|
|
1004
1148
|
const now = this.audioContext.currentTime;
|
|
1005
1149
|
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, now, portamento);
|
|
1006
1150
|
}
|
|
1007
|
-
stopNote(
|
|
1151
|
+
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
1008
1152
|
const note = scheduledNotes[index];
|
|
1009
1153
|
note.volumeNode.gain
|
|
1010
|
-
.cancelScheduledValues(
|
|
1011
|
-
.linearRampToValueAtTime(0,
|
|
1154
|
+
.cancelScheduledValues(endTime)
|
|
1155
|
+
.linearRampToValueAtTime(0, stopTime);
|
|
1012
1156
|
note.ending = true;
|
|
1013
1157
|
this.scheduleTask(() => {
|
|
1014
1158
|
note.bufferSource.loop = false;
|
|
1015
|
-
},
|
|
1159
|
+
}, stopTime);
|
|
1016
1160
|
return new Promise((resolve) => {
|
|
1017
1161
|
note.bufferSource.onended = () => {
|
|
1018
1162
|
scheduledNotes[index] = null;
|
|
@@ -1028,15 +1172,22 @@ class MidyGM2 {
|
|
|
1028
1172
|
note.vibratoDepth.disconnect();
|
|
1029
1173
|
note.vibratoLFO.stop();
|
|
1030
1174
|
}
|
|
1175
|
+
if (note.reverbEffectsSend) {
|
|
1176
|
+
note.reverbEffectsSend.disconnect();
|
|
1177
|
+
}
|
|
1178
|
+
if (note.chorusEffectsSend) {
|
|
1179
|
+
note.chorusEffectsSend.disconnect();
|
|
1180
|
+
}
|
|
1031
1181
|
resolve();
|
|
1032
1182
|
};
|
|
1033
|
-
note.bufferSource.stop(
|
|
1183
|
+
note.bufferSource.stop(stopTime);
|
|
1034
1184
|
});
|
|
1035
1185
|
}
|
|
1036
|
-
scheduleNoteRelease(channelNumber, noteNumber, _velocity,
|
|
1186
|
+
scheduleNoteRelease(channelNumber, noteNumber, _velocity, endTime, portamentoNoteNumber, force) {
|
|
1037
1187
|
const channel = this.channels[channelNumber];
|
|
1188
|
+
const state = channel.state;
|
|
1038
1189
|
if (!force) {
|
|
1039
|
-
if (
|
|
1190
|
+
if (0.5 < state.sustainPedal)
|
|
1040
1191
|
return;
|
|
1041
1192
|
if (channel.sostenutoNotes.has(noteNumber))
|
|
1042
1193
|
return;
|
|
@@ -1051,21 +1202,22 @@ class MidyGM2 {
|
|
|
1051
1202
|
if (note.ending)
|
|
1052
1203
|
continue;
|
|
1053
1204
|
if (portamentoNoteNumber === undefined) {
|
|
1054
|
-
const
|
|
1055
|
-
const modRelease =
|
|
1205
|
+
const volRelease = endTime + note.voiceParams.volRelease;
|
|
1206
|
+
const modRelease = endTime + note.voiceParams.modRelease;
|
|
1056
1207
|
note.filterNode.frequency
|
|
1057
|
-
.cancelScheduledValues(
|
|
1208
|
+
.cancelScheduledValues(endTime)
|
|
1058
1209
|
.linearRampToValueAtTime(0, modRelease);
|
|
1059
|
-
|
|
1210
|
+
const stopTime = Math.min(volRelease, modRelease);
|
|
1211
|
+
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
1060
1212
|
}
|
|
1061
1213
|
else {
|
|
1062
|
-
const portamentoTime =
|
|
1214
|
+
const portamentoTime = endTime + state.portamentoTime;
|
|
1063
1215
|
const detuneChange = (portamentoNoteNumber - noteNumber) * 100;
|
|
1064
1216
|
const detune = note.bufferSource.detune.value + detuneChange;
|
|
1065
1217
|
note.bufferSource.detune
|
|
1066
|
-
.cancelScheduledValues(
|
|
1218
|
+
.cancelScheduledValues(endTime)
|
|
1067
1219
|
.linearRampToValueAtTime(detune, portamentoTime);
|
|
1068
|
-
return this.stopNote(
|
|
1220
|
+
return this.stopNote(endTime, portamentoTime, scheduledNotes, i);
|
|
1069
1221
|
}
|
|
1070
1222
|
}
|
|
1071
1223
|
}
|
|
@@ -1077,7 +1229,7 @@ class MidyGM2 {
|
|
|
1077
1229
|
const velocity = halfVelocity * 2;
|
|
1078
1230
|
const channel = this.channels[channelNumber];
|
|
1079
1231
|
const promises = [];
|
|
1080
|
-
channel.sustainPedal =
|
|
1232
|
+
channel.state.sustainPedal = halfVelocity;
|
|
1081
1233
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1082
1234
|
for (let i = 0; i < noteList.length; i++) {
|
|
1083
1235
|
const note = noteList[i];
|
|
@@ -1094,7 +1246,7 @@ class MidyGM2 {
|
|
|
1094
1246
|
const velocity = halfVelocity * 2;
|
|
1095
1247
|
const channel = this.channels[channelNumber];
|
|
1096
1248
|
const promises = [];
|
|
1097
|
-
channel.sostenutoPedal =
|
|
1249
|
+
channel.state.sostenutoPedal = 0;
|
|
1098
1250
|
channel.sostenutoNotes.forEach((activeNote) => {
|
|
1099
1251
|
const { noteNumber } = activeNote;
|
|
1100
1252
|
const promise = this.releaseNote(channelNumber, noteNumber, velocity);
|
|
@@ -1142,18 +1294,232 @@ class MidyGM2 {
|
|
|
1142
1294
|
.setValueAtTime(gain * pressure, now);
|
|
1143
1295
|
});
|
|
1144
1296
|
}
|
|
1297
|
+
// this.applyVoiceParams(channel, 13);
|
|
1145
1298
|
}
|
|
1146
1299
|
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
1147
|
-
const pitchBend = msb * 128 + lsb
|
|
1300
|
+
const pitchBend = msb * 128 + lsb;
|
|
1148
1301
|
this.setPitchBend(channelNumber, pitchBend);
|
|
1149
1302
|
}
|
|
1150
|
-
setPitchBend(channelNumber,
|
|
1303
|
+
setPitchBend(channelNumber, value) {
|
|
1151
1304
|
const channel = this.channels[channelNumber];
|
|
1152
|
-
const
|
|
1153
|
-
|
|
1154
|
-
const
|
|
1155
|
-
|
|
1305
|
+
const state = channel.state;
|
|
1306
|
+
state.pitchWheel = value / 16383;
|
|
1307
|
+
const pitchWheel = (value - 8192) / 8192;
|
|
1308
|
+
const detuneChange = pitchWheel * state.pitchWheelSensitivity * 12800;
|
|
1156
1309
|
this.updateDetune(channel, detuneChange);
|
|
1310
|
+
this.applyVoiceParams(channel, 14);
|
|
1311
|
+
}
|
|
1312
|
+
setModLfoToPitch(channel, note) {
|
|
1313
|
+
const now = this.audioContext.currentTime;
|
|
1314
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch;
|
|
1315
|
+
const modulationDepth = Math.abs(modLfoToPitch) +
|
|
1316
|
+
channel.state.modulationDepth;
|
|
1317
|
+
const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
|
|
1318
|
+
note.modulationDepth.gain
|
|
1319
|
+
.cancelScheduledValues(now)
|
|
1320
|
+
.setValueAtTime(modulationDepth * modulationDepthSign, now);
|
|
1321
|
+
}
|
|
1322
|
+
setModLfoToVolume(note) {
|
|
1323
|
+
const now = this.audioContext.currentTime;
|
|
1324
|
+
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1325
|
+
const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1326
|
+
const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
|
|
1327
|
+
note.volumeDepth.gain
|
|
1328
|
+
.cancelScheduledValues(now)
|
|
1329
|
+
.setValueAtTime(volumeDepth * volumeDepthSign, now);
|
|
1330
|
+
}
|
|
1331
|
+
setChorusEffectsSend(note, prevValue) {
|
|
1332
|
+
if (0 < prevValue) {
|
|
1333
|
+
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1334
|
+
const now = this.audioContext.currentTime;
|
|
1335
|
+
const value = note.voiceParams.chorusEffectsSend;
|
|
1336
|
+
note.chorusEffectsSend.gain
|
|
1337
|
+
.cancelScheduledValues(now)
|
|
1338
|
+
.setValueAtTime(value, now);
|
|
1339
|
+
}
|
|
1340
|
+
else {
|
|
1341
|
+
note.chorusEffectsSend.disconnect();
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
else {
|
|
1345
|
+
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1346
|
+
if (!note.chorusEffectsSend) {
|
|
1347
|
+
note.chorusEffectsSend = new GainNode(this.audioContext, {
|
|
1348
|
+
gain: note.voiceParams.chorusEffectsSend,
|
|
1349
|
+
});
|
|
1350
|
+
note.volumeNode.connect(note.chorusEffectsSend);
|
|
1351
|
+
}
|
|
1352
|
+
note.chorusEffectsSend.connect(this.chorusEffect.input);
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
setReverbEffectsSend(note, prevValue) {
|
|
1357
|
+
if (0 < prevValue) {
|
|
1358
|
+
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1359
|
+
const now = this.audioContext.currentTime;
|
|
1360
|
+
const value = note.voiceParams.reverbEffectsSend;
|
|
1361
|
+
note.reverbEffectsSend.gain
|
|
1362
|
+
.cancelScheduledValues(now)
|
|
1363
|
+
.setValueAtTime(value, now);
|
|
1364
|
+
}
|
|
1365
|
+
else {
|
|
1366
|
+
note.reverbEffectsSend.disconnect();
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
else {
|
|
1370
|
+
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1371
|
+
if (!note.reverbEffectsSend) {
|
|
1372
|
+
note.reverbEffectsSend = new GainNode(this.audioContext, {
|
|
1373
|
+
gain: note.voiceParams.reverbEffectsSend,
|
|
1374
|
+
});
|
|
1375
|
+
note.volumeNode.connect(note.reverbEffectsSend);
|
|
1376
|
+
}
|
|
1377
|
+
note.reverbEffectsSend.connect(this.reverbEffect.input);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
setVibLfoToPitch(channel, note) {
|
|
1382
|
+
const now = this.audioContext.currentTime;
|
|
1383
|
+
const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
|
|
1384
|
+
const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
|
|
1385
|
+
2;
|
|
1386
|
+
const vibratoDepthSign = 0 < vibLfoToPitch;
|
|
1387
|
+
note.vibratoDepth.gain
|
|
1388
|
+
.cancelScheduledValues(now)
|
|
1389
|
+
.setValueAtTime(vibratoDepth * vibratoDepthSign, now);
|
|
1390
|
+
}
|
|
1391
|
+
setModLfoToFilterFc(note) {
|
|
1392
|
+
const now = this.audioContext.currentTime;
|
|
1393
|
+
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
|
|
1394
|
+
note.filterDepth.gain
|
|
1395
|
+
.cancelScheduledValues(now)
|
|
1396
|
+
.setValueAtTime(modLfoToFilterFc, now);
|
|
1397
|
+
}
|
|
1398
|
+
setDelayModLFO(note) {
|
|
1399
|
+
const now = this.audioContext.currentTime;
|
|
1400
|
+
const startTime = note.startTime;
|
|
1401
|
+
if (startTime < now)
|
|
1402
|
+
return;
|
|
1403
|
+
note.modulationLFO.stop(now);
|
|
1404
|
+
note.modulationLFO.start(startTime + note.voiceParams.delayModLFO);
|
|
1405
|
+
note.modulationLFO.connect(note.filterDepth);
|
|
1406
|
+
}
|
|
1407
|
+
setFreqModLFO(note) {
|
|
1408
|
+
const now = this.audioContext.currentTime;
|
|
1409
|
+
const freqModLFO = note.voiceParams.freqModLFO;
|
|
1410
|
+
note.modulationLFO.frequency
|
|
1411
|
+
.cancelScheduledValues(now)
|
|
1412
|
+
.setValueAtTime(freqModLFO, now);
|
|
1413
|
+
}
|
|
1414
|
+
createVoiceParamsHandlers() {
|
|
1415
|
+
return {
|
|
1416
|
+
modLfoToPitch: (channel, note, _prevValue) => {
|
|
1417
|
+
if (0 < channel.state.modulationDepth) {
|
|
1418
|
+
this.setModLfoToPitch(channel, note);
|
|
1419
|
+
}
|
|
1420
|
+
},
|
|
1421
|
+
vibLfoToPitch: (channel, note, _prevValue) => {
|
|
1422
|
+
if (0 < channel.state.vibratoDepth) {
|
|
1423
|
+
this.setVibLfoToPitch(channel, note);
|
|
1424
|
+
}
|
|
1425
|
+
},
|
|
1426
|
+
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1427
|
+
if (0 < channel.state.modulationDepth)
|
|
1428
|
+
this.setModLfoToFilterFc(note);
|
|
1429
|
+
},
|
|
1430
|
+
modLfoToVolume: (channel, note) => {
|
|
1431
|
+
if (0 < channel.state.modulationDepth)
|
|
1432
|
+
this.setModLfoToVolume(note);
|
|
1433
|
+
},
|
|
1434
|
+
chorusEffectsSend: (_channel, note, prevValue) => {
|
|
1435
|
+
this.setChorusEffectsSend(note, prevValue);
|
|
1436
|
+
},
|
|
1437
|
+
reverbEffectsSend: (_channel, note, prevValue) => {
|
|
1438
|
+
this.setReverbEffectsSend(note, prevValue);
|
|
1439
|
+
},
|
|
1440
|
+
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
1441
|
+
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
1442
|
+
delayVibLFO: (channel, note, prevValue) => {
|
|
1443
|
+
if (0 < channel.state.vibratoDepth) {
|
|
1444
|
+
const now = this.audioContext.currentTime;
|
|
1445
|
+
const prevStartTime = note.startTime +
|
|
1446
|
+
prevValue * channel.state.vibratoDelay * 2;
|
|
1447
|
+
if (now < prevStartTime)
|
|
1448
|
+
return;
|
|
1449
|
+
const startTime = note.startTime +
|
|
1450
|
+
value * channel.state.vibratoDelay * 2;
|
|
1451
|
+
note.vibratoLFO.stop(now);
|
|
1452
|
+
note.vibratoLFO.start(startTime);
|
|
1453
|
+
}
|
|
1454
|
+
},
|
|
1455
|
+
freqVibLFO: (channel, note, _prevValue) => {
|
|
1456
|
+
if (0 < channel.state.vibratoDepth) {
|
|
1457
|
+
const now = this.audioContext.currentTime;
|
|
1458
|
+
note.vibratoLFO.frequency
|
|
1459
|
+
.cancelScheduledValues(now)
|
|
1460
|
+
.setValueAtTime(value * sate.vibratoRate, now);
|
|
1461
|
+
}
|
|
1462
|
+
},
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1465
|
+
getControllerState(channel, noteNumber, velocity) {
|
|
1466
|
+
const state = new Float32Array(channel.state.array.length);
|
|
1467
|
+
state.set(channel.state.array);
|
|
1468
|
+
state[2] = velocity / 127;
|
|
1469
|
+
state[3] = noteNumber / 127;
|
|
1470
|
+
return state;
|
|
1471
|
+
}
|
|
1472
|
+
applyVoiceParams(channel, controllerType) {
|
|
1473
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1474
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1475
|
+
const note = noteList[i];
|
|
1476
|
+
if (!note)
|
|
1477
|
+
continue;
|
|
1478
|
+
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
|
|
1479
|
+
const voiceParams = note.voice.getParams(controllerType, controllerState);
|
|
1480
|
+
let appliedFilterEnvelope = false;
|
|
1481
|
+
let appliedVolumeEnvelope = false;
|
|
1482
|
+
for (const [key, value] of Object.entries(voiceParams)) {
|
|
1483
|
+
const prevValue = note.voiceParams[key];
|
|
1484
|
+
if (value === prevValue)
|
|
1485
|
+
continue;
|
|
1486
|
+
note.voiceParams[key] = value;
|
|
1487
|
+
if (key in this.voiceParamsHandlers) {
|
|
1488
|
+
this.voiceParamsHandlers[key](channel, note, prevValue);
|
|
1489
|
+
}
|
|
1490
|
+
else if (filterEnvelopeKeySet.has(key)) {
|
|
1491
|
+
if (appliedFilterEnvelope)
|
|
1492
|
+
continue;
|
|
1493
|
+
appliedFilterEnvelope = true;
|
|
1494
|
+
const noteVoiceParams = note.voiceParams;
|
|
1495
|
+
for (let i = 0; i < filterEnvelopeKeys.length; i++) {
|
|
1496
|
+
const key = filterEnvelopeKeys[i];
|
|
1497
|
+
if (key in voiceParams)
|
|
1498
|
+
noteVoiceParams[key] = voiceParams[key];
|
|
1499
|
+
}
|
|
1500
|
+
if (note.portamento) {
|
|
1501
|
+
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
1502
|
+
}
|
|
1503
|
+
else {
|
|
1504
|
+
this.setFilterEnvelope(channel, note);
|
|
1505
|
+
}
|
|
1506
|
+
this.setPitch(channel, note);
|
|
1507
|
+
}
|
|
1508
|
+
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1509
|
+
if (appliedVolumeEnvelope)
|
|
1510
|
+
continue;
|
|
1511
|
+
appliedVolumeEnvelope = true;
|
|
1512
|
+
const noteVoiceParams = note.voiceParams;
|
|
1513
|
+
for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
|
|
1514
|
+
const key = volumeEnvelopeKeys[i];
|
|
1515
|
+
if (key in voiceParams)
|
|
1516
|
+
noteVoiceParams[key] = voiceParams[key];
|
|
1517
|
+
}
|
|
1518
|
+
this.setVolumeEnvelope(channel, note);
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
});
|
|
1157
1523
|
}
|
|
1158
1524
|
createControlChangeHandlers() {
|
|
1159
1525
|
return {
|
|
@@ -1183,13 +1549,16 @@ class MidyGM2 {
|
|
|
1183
1549
|
127: this.polyOn,
|
|
1184
1550
|
};
|
|
1185
1551
|
}
|
|
1186
|
-
handleControlChange(channelNumber,
|
|
1187
|
-
const handler = this.controlChangeHandlers[
|
|
1552
|
+
handleControlChange(channelNumber, controllerType, value) {
|
|
1553
|
+
const handler = this.controlChangeHandlers[controllerType];
|
|
1188
1554
|
if (handler) {
|
|
1189
1555
|
handler.call(this, channelNumber, value);
|
|
1556
|
+
const channel = this.channels[channelNumber];
|
|
1557
|
+
const controller = 128 + controllerType;
|
|
1558
|
+
this.applyVoiceParams(channel, controller);
|
|
1190
1559
|
}
|
|
1191
1560
|
else {
|
|
1192
|
-
console.warn(`Unsupported Control change:
|
|
1561
|
+
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
1193
1562
|
}
|
|
1194
1563
|
}
|
|
1195
1564
|
setBankMSB(channelNumber, msb) {
|
|
@@ -1203,11 +1572,10 @@ class MidyGM2 {
|
|
|
1203
1572
|
if (!note)
|
|
1204
1573
|
continue;
|
|
1205
1574
|
if (note.modulationDepth) {
|
|
1206
|
-
note.modulationDepth.gain.setValueAtTime(channel.modulationDepth, now);
|
|
1575
|
+
note.modulationDepth.gain.setValueAtTime(channel.state.modulationDepth, now);
|
|
1207
1576
|
}
|
|
1208
1577
|
else {
|
|
1209
|
-
|
|
1210
|
-
this.setPitch(note, semitoneOffset);
|
|
1578
|
+
this.setPitch(channel, note);
|
|
1211
1579
|
this.startModulation(channel, note, now);
|
|
1212
1580
|
}
|
|
1213
1581
|
}
|
|
@@ -1215,21 +1583,22 @@ class MidyGM2 {
|
|
|
1215
1583
|
}
|
|
1216
1584
|
setModulationDepth(channelNumber, modulation) {
|
|
1217
1585
|
const channel = this.channels[channelNumber];
|
|
1218
|
-
channel.modulationDepth = (modulation / 127) *
|
|
1586
|
+
channel.state.modulationDepth = (modulation / 127) *
|
|
1587
|
+
channel.modulationDepthRange;
|
|
1219
1588
|
this.updateModulation(channel);
|
|
1220
1589
|
}
|
|
1221
1590
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
1222
1591
|
const channel = this.channels[channelNumber];
|
|
1223
1592
|
const factor = 5 * Math.log(10) / 127;
|
|
1224
|
-
channel.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1593
|
+
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1225
1594
|
}
|
|
1226
1595
|
setVolume(channelNumber, volume) {
|
|
1227
1596
|
const channel = this.channels[channelNumber];
|
|
1228
|
-
channel.volume = volume / 127;
|
|
1597
|
+
channel.state.volume = volume / 127;
|
|
1229
1598
|
this.updateChannelVolume(channel);
|
|
1230
1599
|
}
|
|
1231
1600
|
panToGain(pan) {
|
|
1232
|
-
const theta = Math.PI / 2 * Math.max(0, pan - 1) / 126;
|
|
1601
|
+
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
1233
1602
|
return {
|
|
1234
1603
|
gainLeft: Math.cos(theta),
|
|
1235
1604
|
gainRight: Math.sin(theta),
|
|
@@ -1237,12 +1606,12 @@ class MidyGM2 {
|
|
|
1237
1606
|
}
|
|
1238
1607
|
setPan(channelNumber, pan) {
|
|
1239
1608
|
const channel = this.channels[channelNumber];
|
|
1240
|
-
channel.pan = pan;
|
|
1609
|
+
channel.state.pan = pan / 127;
|
|
1241
1610
|
this.updateChannelVolume(channel);
|
|
1242
1611
|
}
|
|
1243
1612
|
setExpression(channelNumber, expression) {
|
|
1244
1613
|
const channel = this.channels[channelNumber];
|
|
1245
|
-
channel.expression = expression / 127;
|
|
1614
|
+
channel.state.expression = expression / 127;
|
|
1246
1615
|
this.updateChannelVolume(channel);
|
|
1247
1616
|
}
|
|
1248
1617
|
setBankLSB(channelNumber, lsb) {
|
|
@@ -1254,8 +1623,9 @@ class MidyGM2 {
|
|
|
1254
1623
|
}
|
|
1255
1624
|
updateChannelVolume(channel) {
|
|
1256
1625
|
const now = this.audioContext.currentTime;
|
|
1257
|
-
const
|
|
1258
|
-
const
|
|
1626
|
+
const state = channel.state;
|
|
1627
|
+
const volume = state.volume * state.expression;
|
|
1628
|
+
const { gainLeft, gainRight } = this.panToGain(state.pan);
|
|
1259
1629
|
channel.gainL.gain
|
|
1260
1630
|
.cancelScheduledValues(now)
|
|
1261
1631
|
.setValueAtTime(volume * gainLeft, now);
|
|
@@ -1264,68 +1634,100 @@ class MidyGM2 {
|
|
|
1264
1634
|
.setValueAtTime(volume * gainRight, now);
|
|
1265
1635
|
}
|
|
1266
1636
|
setSustainPedal(channelNumber, value) {
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
if (!isOn) {
|
|
1637
|
+
this.channels[channelNumber].state.sustainPedal = value / 127;
|
|
1638
|
+
if (value < 64) {
|
|
1270
1639
|
this.releaseSustainPedal(channelNumber, value);
|
|
1271
1640
|
}
|
|
1272
1641
|
}
|
|
1273
1642
|
setPortamento(channelNumber, value) {
|
|
1274
|
-
this.channels[channelNumber].portamento = value
|
|
1643
|
+
this.channels[channelNumber].state.portamento = value / 127;
|
|
1275
1644
|
}
|
|
1276
1645
|
setReverbSendLevel(channelNumber, reverbSendLevel) {
|
|
1277
1646
|
const channel = this.channels[channelNumber];
|
|
1647
|
+
const state = channel.state;
|
|
1278
1648
|
const reverbEffect = this.reverbEffect;
|
|
1279
|
-
if (0 <
|
|
1649
|
+
if (0 < state.reverbSendLevel) {
|
|
1280
1650
|
if (0 < reverbSendLevel) {
|
|
1281
1651
|
const now = this.audioContext.currentTime;
|
|
1282
|
-
|
|
1283
|
-
reverbEffect.
|
|
1284
|
-
reverbEffect.
|
|
1652
|
+
state.reverbSendLevel = reverbSendLevel / 127;
|
|
1653
|
+
reverbEffect.input.gain.cancelScheduledValues(now);
|
|
1654
|
+
reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
|
|
1285
1655
|
}
|
|
1286
1656
|
else {
|
|
1287
|
-
channel.
|
|
1657
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1658
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1659
|
+
const note = noteList[i];
|
|
1660
|
+
if (!note)
|
|
1661
|
+
continue;
|
|
1662
|
+
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
1663
|
+
continue;
|
|
1664
|
+
note.reverbEffectsSend.disconnect();
|
|
1665
|
+
}
|
|
1666
|
+
});
|
|
1288
1667
|
}
|
|
1289
1668
|
}
|
|
1290
1669
|
else {
|
|
1291
1670
|
if (0 < reverbSendLevel) {
|
|
1292
|
-
channel.merger.connect(reverbEffect.input);
|
|
1293
1671
|
const now = this.audioContext.currentTime;
|
|
1294
|
-
channel.
|
|
1295
|
-
|
|
1296
|
-
|
|
1672
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1673
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1674
|
+
const note = noteList[i];
|
|
1675
|
+
if (!note)
|
|
1676
|
+
continue;
|
|
1677
|
+
this.setReverbEffectsSend(note, 0);
|
|
1678
|
+
}
|
|
1679
|
+
});
|
|
1680
|
+
state.reverbSendLevel = reverbSendLevel / 127;
|
|
1681
|
+
reverbEffect.input.gain.cancelScheduledValues(now);
|
|
1682
|
+
reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
|
|
1297
1683
|
}
|
|
1298
1684
|
}
|
|
1299
1685
|
}
|
|
1300
1686
|
setChorusSendLevel(channelNumber, chorusSendLevel) {
|
|
1301
1687
|
const channel = this.channels[channelNumber];
|
|
1688
|
+
const state = channel.state;
|
|
1302
1689
|
const chorusEffect = this.chorusEffect;
|
|
1303
|
-
if (0 <
|
|
1690
|
+
if (0 < state.chorusSendLevel) {
|
|
1304
1691
|
if (0 < chorusSendLevel) {
|
|
1305
1692
|
const now = this.audioContext.currentTime;
|
|
1306
|
-
|
|
1307
|
-
chorusEffect.
|
|
1308
|
-
chorusEffect.
|
|
1693
|
+
state.chorusSendLevel = chorusSendLevel / 127;
|
|
1694
|
+
chorusEffect.input.gain.cancelScheduledValues(now);
|
|
1695
|
+
chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
|
|
1309
1696
|
}
|
|
1310
1697
|
else {
|
|
1311
|
-
channel.
|
|
1698
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1699
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1700
|
+
const note = noteList[i];
|
|
1701
|
+
if (!note)
|
|
1702
|
+
continue;
|
|
1703
|
+
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
1704
|
+
continue;
|
|
1705
|
+
note.chorusEffectsSend.disconnect();
|
|
1706
|
+
}
|
|
1707
|
+
});
|
|
1312
1708
|
}
|
|
1313
1709
|
}
|
|
1314
1710
|
else {
|
|
1315
1711
|
if (0 < chorusSendLevel) {
|
|
1316
|
-
channel.merger.connect(chorusEffect.input);
|
|
1317
1712
|
const now = this.audioContext.currentTime;
|
|
1318
|
-
channel.
|
|
1319
|
-
|
|
1320
|
-
|
|
1713
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1714
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1715
|
+
const note = noteList[i];
|
|
1716
|
+
if (!note)
|
|
1717
|
+
continue;
|
|
1718
|
+
this.setChorusEffectsSend(note, 0);
|
|
1719
|
+
}
|
|
1720
|
+
});
|
|
1721
|
+
state.chorusSendLevel = chorusSendLevel / 127;
|
|
1722
|
+
chorusEffect.input.gain.cancelScheduledValues(now);
|
|
1723
|
+
chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
|
|
1321
1724
|
}
|
|
1322
1725
|
}
|
|
1323
1726
|
}
|
|
1324
1727
|
setSostenutoPedal(channelNumber, value) {
|
|
1325
|
-
const isOn = value >= 64;
|
|
1326
1728
|
const channel = this.channels[channelNumber];
|
|
1327
|
-
channel.sostenutoPedal =
|
|
1328
|
-
if (
|
|
1729
|
+
channel.state.sostenutoPedal = value / 127;
|
|
1730
|
+
if (64 <= value) {
|
|
1329
1731
|
const now = this.audioContext.currentTime;
|
|
1330
1732
|
const activeNotes = this.getActiveNotes(channel, now);
|
|
1331
1733
|
channel.sostenutoNotes = new Map(activeNotes);
|
|
@@ -1336,7 +1738,7 @@ class MidyGM2 {
|
|
|
1336
1738
|
}
|
|
1337
1739
|
setSoftPedal(channelNumber, softPedal) {
|
|
1338
1740
|
const channel = this.channels[channelNumber];
|
|
1339
|
-
channel.softPedal = softPedal / 127;
|
|
1741
|
+
channel.state.softPedal = softPedal / 127;
|
|
1340
1742
|
}
|
|
1341
1743
|
limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
|
|
1342
1744
|
if (maxLSB < channel.dataLSB) {
|
|
@@ -1394,7 +1796,7 @@ class MidyGM2 {
|
|
|
1394
1796
|
this.channels[channelNumber].dataMSB = value;
|
|
1395
1797
|
this.handleRPN(channelNumber);
|
|
1396
1798
|
}
|
|
1397
|
-
updateDetune(channel,
|
|
1799
|
+
updateDetune(channel, detune) {
|
|
1398
1800
|
const now = this.audioContext.currentTime;
|
|
1399
1801
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1400
1802
|
for (let i = 0; i < noteList.length; i++) {
|
|
@@ -1402,7 +1804,6 @@ class MidyGM2 {
|
|
|
1402
1804
|
if (!note)
|
|
1403
1805
|
continue;
|
|
1404
1806
|
const { bufferSource } = note;
|
|
1405
|
-
const detune = bufferSource.detune.value + detuneChange;
|
|
1406
1807
|
bufferSource.detune
|
|
1407
1808
|
.cancelScheduledValues(now)
|
|
1408
1809
|
.setValueAtTime(detune, now);
|
|
@@ -1415,13 +1816,13 @@ class MidyGM2 {
|
|
|
1415
1816
|
const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
|
|
1416
1817
|
this.setPitchBendRange(channelNumber, pitchBendRange);
|
|
1417
1818
|
}
|
|
1418
|
-
setPitchBendRange(channelNumber,
|
|
1819
|
+
setPitchBendRange(channelNumber, pitchWheelSensitivity) {
|
|
1419
1820
|
const channel = this.channels[channelNumber];
|
|
1420
|
-
const
|
|
1421
|
-
|
|
1422
|
-
const
|
|
1423
|
-
|
|
1424
|
-
this.
|
|
1821
|
+
const state = channel.state;
|
|
1822
|
+
state.pitchWheelSensitivity = pitchWheelSensitivity / 128;
|
|
1823
|
+
const detune = (state.pitchWheel * 2 - 1) * pitchWheelSensitivity * 100;
|
|
1824
|
+
this.updateDetune(channel, detune);
|
|
1825
|
+
this.applyVoiceParams(channel, 16);
|
|
1425
1826
|
}
|
|
1426
1827
|
handleFineTuningRPN(channelNumber) {
|
|
1427
1828
|
const channel = this.channels[channelNumber];
|
|
@@ -1465,7 +1866,30 @@ class MidyGM2 {
|
|
|
1465
1866
|
return this.stopChannelNotes(channelNumber, 0, true);
|
|
1466
1867
|
}
|
|
1467
1868
|
resetAllControllers(channelNumber) {
|
|
1468
|
-
|
|
1869
|
+
const stateTypes = [
|
|
1870
|
+
"expression",
|
|
1871
|
+
"modulationDepth",
|
|
1872
|
+
"sustainPedal",
|
|
1873
|
+
"portamento",
|
|
1874
|
+
"sostenutoPedal",
|
|
1875
|
+
"softPedal",
|
|
1876
|
+
"channelPressure",
|
|
1877
|
+
"pitchWheelSensitivity",
|
|
1878
|
+
];
|
|
1879
|
+
const channel = this.channels[channelNumber];
|
|
1880
|
+
const state = channel.state;
|
|
1881
|
+
for (let i = 0; i < stateTypes.length; i++) {
|
|
1882
|
+
const type = stateTypes[i];
|
|
1883
|
+
state[type] = defaultControllerState[type];
|
|
1884
|
+
}
|
|
1885
|
+
const settingTypes = [
|
|
1886
|
+
"rpnMSB",
|
|
1887
|
+
"rpnLSB",
|
|
1888
|
+
];
|
|
1889
|
+
for (let i = 0; i < settingTypes.length; i++) {
|
|
1890
|
+
const type = settingTypes[i];
|
|
1891
|
+
channel[type] = this.constructor.channelSettings[type];
|
|
1892
|
+
}
|
|
1469
1893
|
}
|
|
1470
1894
|
allNotesOff(channelNumber) {
|
|
1471
1895
|
return this.stopChannelNotes(channelNumber, 0, false);
|
|
@@ -1659,10 +2083,8 @@ class MidyGM2 {
|
|
|
1659
2083
|
}
|
|
1660
2084
|
setReverbTime(value) {
|
|
1661
2085
|
this.reverb.time = this.getReverbTime(value);
|
|
1662
|
-
const { audioContext,
|
|
1663
|
-
|
|
1664
|
-
channels[i].reverbEffect = options.reverbAlgorithm(audioContext);
|
|
1665
|
-
}
|
|
2086
|
+
const { audioContext, options } = this;
|
|
2087
|
+
this.reverbEffect = options.reverbAlgorithm(audioContext);
|
|
1666
2088
|
}
|
|
1667
2089
|
getReverbTime(value) {
|
|
1668
2090
|
return Math.pow(Math.E, (value - 40) * 0.025);
|
|
@@ -1739,10 +2161,7 @@ class MidyGM2 {
|
|
|
1739
2161
|
const now = this.audioContext.currentTime;
|
|
1740
2162
|
const modRate = this.getChorusModRate(value);
|
|
1741
2163
|
this.chorus.modRate = modRate;
|
|
1742
|
-
|
|
1743
|
-
const lfo = this.channels[i].chorusEffect.lfo;
|
|
1744
|
-
lfo.frequency.setValueAtTime(modRate, now);
|
|
1745
|
-
}
|
|
2164
|
+
this.chorusEffect.lfo.frequency.setValueAtTime(modRate, now);
|
|
1746
2165
|
}
|
|
1747
2166
|
getChorusModRate(value) {
|
|
1748
2167
|
return value * 0.122; // Hz
|
|
@@ -1751,12 +2170,9 @@ class MidyGM2 {
|
|
|
1751
2170
|
const now = this.audioContext.currentTime;
|
|
1752
2171
|
const modDepth = this.getChorusModDepth(value);
|
|
1753
2172
|
this.chorus.modDepth = modDepth;
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
.cancelScheduledValues(now)
|
|
1758
|
-
.setValueAtTime(modDepth / 2, now);
|
|
1759
|
-
}
|
|
2173
|
+
this.chorusEffect.lfoGain.gain
|
|
2174
|
+
.cancelScheduledValues(now)
|
|
2175
|
+
.setValueAtTime(modDepth / 2, now);
|
|
1760
2176
|
}
|
|
1761
2177
|
getChorusModDepth(value) {
|
|
1762
2178
|
return (value + 1) / 3200; // second
|
|
@@ -1765,14 +2181,11 @@ class MidyGM2 {
|
|
|
1765
2181
|
const now = this.audioContext.currentTime;
|
|
1766
2182
|
const feedback = this.getChorusFeedback(value);
|
|
1767
2183
|
this.chorus.feedback = feedback;
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
.cancelScheduledValues(now)
|
|
1774
|
-
.setValueAtTime(feedback, now);
|
|
1775
|
-
}
|
|
2184
|
+
const chorusEffect = this.chorusEffect;
|
|
2185
|
+
for (let i = 0; i < chorusEffect.feedbackGains.length; i++) {
|
|
2186
|
+
chorusEffect.feedbackGains[i].gain
|
|
2187
|
+
.cancelScheduledValues(now)
|
|
2188
|
+
.setValueAtTime(feedback, now);
|
|
1776
2189
|
}
|
|
1777
2190
|
}
|
|
1778
2191
|
getChorusFeedback(value) {
|
|
@@ -1780,15 +2193,28 @@ class MidyGM2 {
|
|
|
1780
2193
|
}
|
|
1781
2194
|
setChorusSendToReverb(value) {
|
|
1782
2195
|
const sendToReverb = this.getChorusSendToReverb(value);
|
|
1783
|
-
|
|
1784
|
-
|
|
2196
|
+
const sendGain = this.chorusEffect.sendGain;
|
|
2197
|
+
if (0 < this.chorus.sendToReverb) {
|
|
1785
2198
|
this.chorus.sendToReverb = sendToReverb;
|
|
1786
|
-
|
|
1787
|
-
.
|
|
1788
|
-
.
|
|
2199
|
+
if (0 < sendToReverb) {
|
|
2200
|
+
const now = this.audioContext.currentTime;
|
|
2201
|
+
sendGain.gain
|
|
2202
|
+
.cancelScheduledValues(now)
|
|
2203
|
+
.setValueAtTime(sendToReverb, now);
|
|
2204
|
+
}
|
|
2205
|
+
else {
|
|
2206
|
+
sendGain.disconnect();
|
|
2207
|
+
}
|
|
1789
2208
|
}
|
|
1790
|
-
else
|
|
1791
|
-
this.
|
|
2209
|
+
else {
|
|
2210
|
+
this.chorus.sendToReverb = sendToReverb;
|
|
2211
|
+
if (0 < sendToReverb) {
|
|
2212
|
+
const now = this.audioContext.currentTime;
|
|
2213
|
+
sendGain.connect(this.reverbEffect.input);
|
|
2214
|
+
sendGain.gain
|
|
2215
|
+
.cancelScheduledValues(now)
|
|
2216
|
+
.setValueAtTime(sendToReverb, now);
|
|
2217
|
+
}
|
|
1792
2218
|
}
|
|
1793
2219
|
}
|
|
1794
2220
|
getChorusSendToReverb(value) {
|
|
@@ -1826,43 +2252,19 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
1826
2252
|
writable: true,
|
|
1827
2253
|
value: {
|
|
1828
2254
|
currentBufferSource: null,
|
|
1829
|
-
|
|
1830
|
-
pan: 64,
|
|
1831
|
-
portamentoTime: 1, // sec
|
|
1832
|
-
reverbSendLevel: 0,
|
|
1833
|
-
chorusSendLevel: 0,
|
|
1834
|
-
vibratoRate: 1,
|
|
1835
|
-
vibratoDepth: 1,
|
|
1836
|
-
vibratoDelay: 1,
|
|
2255
|
+
program: 0,
|
|
1837
2256
|
bank: 121 * 128,
|
|
1838
2257
|
bankMSB: 121,
|
|
1839
2258
|
bankLSB: 0,
|
|
1840
2259
|
dataMSB: 0,
|
|
1841
2260
|
dataLSB: 0,
|
|
1842
|
-
|
|
1843
|
-
|
|
2261
|
+
rpnMSB: 127,
|
|
2262
|
+
rpnLSB: 127,
|
|
1844
2263
|
fineTuning: 0, // cb
|
|
1845
2264
|
coarseTuning: 0, // cb
|
|
1846
2265
|
modulationDepthRange: 50, // cent
|
|
1847
2266
|
}
|
|
1848
2267
|
});
|
|
1849
|
-
Object.defineProperty(MidyGM2, "effectSettings", {
|
|
1850
|
-
enumerable: true,
|
|
1851
|
-
configurable: true,
|
|
1852
|
-
writable: true,
|
|
1853
|
-
value: {
|
|
1854
|
-
expression: 1,
|
|
1855
|
-
modulationDepth: 0,
|
|
1856
|
-
sustainPedal: false,
|
|
1857
|
-
portamento: false,
|
|
1858
|
-
sostenutoPedal: false,
|
|
1859
|
-
softPedal: 0,
|
|
1860
|
-
rpnMSB: 127,
|
|
1861
|
-
rpnLSB: 127,
|
|
1862
|
-
channelPressure: 0,
|
|
1863
|
-
pitchBendRange: 2,
|
|
1864
|
-
}
|
|
1865
|
-
});
|
|
1866
2268
|
Object.defineProperty(MidyGM2, "controllerDestinationSettings", {
|
|
1867
2269
|
enumerable: true,
|
|
1868
2270
|
configurable: true,
|