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