@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.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 Midy {
|
|
60
166
|
constructor(audioContext, options = this.defaultOptions) {
|
|
61
167
|
Object.defineProperty(this, "ticksPerBeat", {
|
|
@@ -205,6 +311,12 @@ export class Midy {
|
|
|
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 Midy {
|
|
|
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 Midy {
|
|
|
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 Midy {
|
|
|
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(),
|
|
@@ -307,38 +420,43 @@ export class Midy {
|
|
|
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
|
}
|
|
@@ -395,7 +513,7 @@ export class Midy {
|
|
|
395
513
|
this.handleChannelPressure(event.channel, event.amount);
|
|
396
514
|
break;
|
|
397
515
|
case "pitchBend":
|
|
398
|
-
this.setPitchBend(event.channel, event.value);
|
|
516
|
+
this.setPitchBend(event.channel, event.value + 8192);
|
|
399
517
|
break;
|
|
400
518
|
case "sysEx":
|
|
401
519
|
this.handleSysEx(event.data);
|
|
@@ -424,6 +542,7 @@ export class Midy {
|
|
|
424
542
|
if (queueIndex >= this.timeline.length) {
|
|
425
543
|
await Promise.all(this.notePromises);
|
|
426
544
|
this.notePromises = [];
|
|
545
|
+
this.exclusiveClassMap.clear();
|
|
427
546
|
resolve();
|
|
428
547
|
return;
|
|
429
548
|
}
|
|
@@ -439,6 +558,7 @@ export class Midy {
|
|
|
439
558
|
}
|
|
440
559
|
else if (this.isStopping) {
|
|
441
560
|
await this.stopNotes(0, true);
|
|
561
|
+
this.exclusiveClassMap.clear();
|
|
442
562
|
this.notePromises = [];
|
|
443
563
|
resolve();
|
|
444
564
|
this.isStopping = false;
|
|
@@ -447,6 +567,7 @@ export class Midy {
|
|
|
447
567
|
}
|
|
448
568
|
else if (this.isSeeking) {
|
|
449
569
|
this.stopNotes(0, true);
|
|
570
|
+
this.exclusiveClassMap.clear();
|
|
450
571
|
this.startTime = this.audioContext.currentTime;
|
|
451
572
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
452
573
|
offset = this.resumeTime - this.startTime;
|
|
@@ -675,14 +796,14 @@ export class Midy {
|
|
|
675
796
|
return impulse;
|
|
676
797
|
}
|
|
677
798
|
createConvolutionReverb(audioContext, impulse) {
|
|
678
|
-
const
|
|
799
|
+
const input = new GainNode(audioContext);
|
|
679
800
|
const convolverNode = new ConvolverNode(audioContext, {
|
|
680
801
|
buffer: impulse,
|
|
681
802
|
});
|
|
682
|
-
|
|
803
|
+
input.connect(convolverNode);
|
|
683
804
|
return {
|
|
684
|
-
input
|
|
685
|
-
output,
|
|
805
|
+
input,
|
|
806
|
+
output: convolverNode,
|
|
686
807
|
convolverNode,
|
|
687
808
|
};
|
|
688
809
|
}
|
|
@@ -724,7 +845,6 @@ export class Midy {
|
|
|
724
845
|
// M.R.Schroeder, "Natural Sounding Artificial Reverberation", J.Audio Eng. Soc., vol.10, p.219, 1962
|
|
725
846
|
createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays) {
|
|
726
847
|
const input = new GainNode(audioContext);
|
|
727
|
-
const output = new GainNode(audioContext);
|
|
728
848
|
const mergerGain = new GainNode(audioContext);
|
|
729
849
|
for (let i = 0; i < combDelays.length; i++) {
|
|
730
850
|
const comb = this.createCombFilter(audioContext, input, combDelays[i], combFeedbacks[i]);
|
|
@@ -735,7 +855,7 @@ export class Midy {
|
|
|
735
855
|
const allpass = this.createAllpassFilter(audioContext, (i === 0) ? mergerGain : allpasses.at(-1), allpassDelays[i], allpassFeedbacks[i]);
|
|
736
856
|
allpasses.push(allpass);
|
|
737
857
|
}
|
|
738
|
-
allpasses.at(-1)
|
|
858
|
+
const output = allpasses.at(-1);
|
|
739
859
|
return { input, output };
|
|
740
860
|
}
|
|
741
861
|
createChorusEffect(audioContext) {
|
|
@@ -790,53 +910,63 @@ export class Midy {
|
|
|
790
910
|
calcSemitoneOffset(channel) {
|
|
791
911
|
const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
|
|
792
912
|
const channelTuning = channel.coarseTuning + channel.fineTuning;
|
|
793
|
-
const
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
return instrumentKey.playbackRate(noteNumber) *
|
|
798
|
-
Math.pow(2, semitoneOffset / 12);
|
|
913
|
+
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
914
|
+
const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 128;
|
|
915
|
+
const pitch = pitchWheel * pitchWheelSensitivity;
|
|
916
|
+
return masterTuning + channelTuning + pitch;
|
|
799
917
|
}
|
|
800
918
|
setPortamentoStartVolumeEnvelope(channel, note) {
|
|
801
|
-
const
|
|
802
|
-
const
|
|
803
|
-
const
|
|
804
|
-
const
|
|
805
|
-
const
|
|
919
|
+
const now = this.audioContext.currentTime;
|
|
920
|
+
const { voiceParams, startTime } = note;
|
|
921
|
+
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
922
|
+
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
923
|
+
const volDelay = startTime + voiceParams.volDelay;
|
|
924
|
+
const portamentoTime = volDelay + channel.state.portamentoTime;
|
|
806
925
|
note.volumeNode.gain
|
|
807
|
-
.cancelScheduledValues(
|
|
926
|
+
.cancelScheduledValues(now)
|
|
808
927
|
.setValueAtTime(0, volDelay)
|
|
809
928
|
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
810
929
|
}
|
|
811
930
|
setVolumeEnvelope(channel, note) {
|
|
812
|
-
const
|
|
813
|
-
const
|
|
814
|
-
const
|
|
815
|
-
const
|
|
816
|
-
const
|
|
817
|
-
const
|
|
818
|
-
const
|
|
931
|
+
const now = this.audioContext.currentTime;
|
|
932
|
+
const state = channel.state;
|
|
933
|
+
const { voiceParams, startTime } = note;
|
|
934
|
+
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
935
|
+
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
936
|
+
const volDelay = startTime + voiceParams.volDelay;
|
|
937
|
+
const volAttack = volDelay + voiceParams.volAttack * state.attackTime * 2;
|
|
938
|
+
const volHold = volAttack + voiceParams.volHold;
|
|
939
|
+
const volDecay = volHold + voiceParams.volDecay * state.decayTime * 2;
|
|
819
940
|
note.volumeNode.gain
|
|
820
|
-
.cancelScheduledValues(
|
|
941
|
+
.cancelScheduledValues(now)
|
|
821
942
|
.setValueAtTime(0, startTime)
|
|
822
943
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
823
944
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
824
945
|
.setValueAtTime(attackVolume, volHold)
|
|
825
946
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
826
947
|
}
|
|
827
|
-
|
|
828
|
-
const
|
|
829
|
-
|
|
830
|
-
|
|
948
|
+
setPlaybackRate(note) {
|
|
949
|
+
const now = this.audioContext.currentTime;
|
|
950
|
+
note.bufferSource.playbackRate
|
|
951
|
+
.cancelScheduledValues(now)
|
|
952
|
+
.setValueAtTime(note.voiceParams.playbackRate, now);
|
|
953
|
+
}
|
|
954
|
+
setPitch(channel, note) {
|
|
955
|
+
const now = this.audioContext.currentTime;
|
|
956
|
+
const { startTime } = note;
|
|
957
|
+
const basePitch = this.calcSemitoneOffset(channel) * 100;
|
|
958
|
+
note.bufferSource.detune
|
|
959
|
+
.cancelScheduledValues(now)
|
|
960
|
+
.setValueAtTime(basePitch, startTime);
|
|
961
|
+
const modEnvToPitch = note.voiceParams.modEnvToPitch;
|
|
831
962
|
if (modEnvToPitch === 0)
|
|
832
963
|
return;
|
|
833
|
-
const
|
|
834
|
-
const
|
|
835
|
-
const
|
|
836
|
-
const
|
|
837
|
-
const
|
|
838
|
-
|
|
839
|
-
note.bufferSource.playbackRate.value
|
|
964
|
+
const peekPitch = basePitch + modEnvToPitch;
|
|
965
|
+
const modDelay = startTime + voiceParams.modDelay;
|
|
966
|
+
const modAttack = modDelay + voiceParams.modAttack;
|
|
967
|
+
const modHold = modAttack + voiceParams.modHold;
|
|
968
|
+
const modDecay = modHold + voiceParams.modDecay;
|
|
969
|
+
note.bufferSource.detune
|
|
840
970
|
.setValueAtTime(basePitch, modDelay)
|
|
841
971
|
.exponentialRampToValueAtTime(peekPitch, modAttack)
|
|
842
972
|
.setValueAtTime(peekPitch, modHold)
|
|
@@ -848,42 +978,46 @@ export class Midy {
|
|
|
848
978
|
return Math.max(minFrequency, Math.min(frequency, maxFrequency));
|
|
849
979
|
}
|
|
850
980
|
setPortamentoStartFilterEnvelope(channel, note) {
|
|
851
|
-
const
|
|
981
|
+
const now = this.audioContext.currentTime;
|
|
982
|
+
const state = channel.state;
|
|
983
|
+
const { voiceParams, noteNumber, startTime } = note;
|
|
852
984
|
const softPedalFactor = 1 -
|
|
853
|
-
(0.1 + (noteNumber / 127) * 0.2) *
|
|
854
|
-
const baseFreq = this.centToHz(
|
|
855
|
-
softPedalFactor *
|
|
856
|
-
const peekFreq = this.centToHz(
|
|
985
|
+
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
986
|
+
const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
|
|
987
|
+
softPedalFactor * state.brightness * 2;
|
|
988
|
+
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor * state.brightness * 2;
|
|
857
989
|
const sustainFreq = baseFreq +
|
|
858
|
-
(peekFreq - baseFreq) * (1 -
|
|
990
|
+
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
859
991
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
860
992
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
861
|
-
const portamentoTime = startTime + channel.portamentoTime;
|
|
862
|
-
const modDelay = startTime +
|
|
993
|
+
const portamentoTime = startTime + channel.state.portamentoTime;
|
|
994
|
+
const modDelay = startTime + voiceParams.modDelay;
|
|
863
995
|
note.filterNode.frequency
|
|
864
|
-
.cancelScheduledValues(
|
|
996
|
+
.cancelScheduledValues(now)
|
|
865
997
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
866
998
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
867
999
|
.linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
|
|
868
1000
|
}
|
|
869
1001
|
setFilterEnvelope(channel, note) {
|
|
870
|
-
const
|
|
1002
|
+
const now = this.audioContext.currentTime;
|
|
1003
|
+
const state = channel.state;
|
|
1004
|
+
const { voiceParams, noteNumber, startTime } = note;
|
|
871
1005
|
const softPedalFactor = 1 -
|
|
872
|
-
(0.1 + (noteNumber / 127) * 0.2) *
|
|
873
|
-
const baseFreq = this.centToHz(
|
|
874
|
-
softPedalFactor *
|
|
875
|
-
const peekFreq = this.centToHz(
|
|
1006
|
+
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1007
|
+
const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
|
|
1008
|
+
softPedalFactor * state.brightness * 2;
|
|
1009
|
+
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor * state.brightness * 2;
|
|
876
1010
|
const sustainFreq = baseFreq +
|
|
877
|
-
(peekFreq - baseFreq) * (1 -
|
|
1011
|
+
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
878
1012
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
879
1013
|
const adjustedPeekFreq = this.clampCutoffFrequency(peekFreq);
|
|
880
1014
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
881
|
-
const modDelay = startTime +
|
|
882
|
-
const modAttack = modDelay +
|
|
883
|
-
const modHold = modAttack +
|
|
884
|
-
const modDecay = modHold +
|
|
1015
|
+
const modDelay = startTime + voiceParams.modDelay;
|
|
1016
|
+
const modAttack = modDelay + voiceParams.modAttack;
|
|
1017
|
+
const modHold = modAttack + voiceParams.modHold;
|
|
1018
|
+
const modDecay = modHold + voiceParams.modDecay;
|
|
885
1019
|
note.filterNode.frequency
|
|
886
|
-
.cancelScheduledValues(
|
|
1020
|
+
.cancelScheduledValues(now)
|
|
887
1021
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
888
1022
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
889
1023
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
@@ -891,25 +1025,18 @@ export class Midy {
|
|
|
891
1025
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
892
1026
|
}
|
|
893
1027
|
startModulation(channel, note, startTime) {
|
|
894
|
-
const {
|
|
895
|
-
const { modLfoToPitch, modLfoToVolume } = instrumentKey;
|
|
1028
|
+
const { voiceParams } = note;
|
|
896
1029
|
note.modulationLFO = new OscillatorNode(this.audioContext, {
|
|
897
|
-
frequency: this.centToHz(
|
|
1030
|
+
frequency: this.centToHz(voiceParams.freqModLFO),
|
|
898
1031
|
});
|
|
899
1032
|
note.filterDepth = new GainNode(this.audioContext, {
|
|
900
|
-
gain:
|
|
1033
|
+
gain: voiceParams.modLfoToFilterFc,
|
|
901
1034
|
});
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
note.
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
908
|
-
const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
|
|
909
|
-
note.volumeDepth = new GainNode(this.audioContext, {
|
|
910
|
-
gain: volumeDepth * volumeDepthSign,
|
|
911
|
-
});
|
|
912
|
-
note.modulationLFO.start(startTime + instrumentKey.delayModLFO);
|
|
1035
|
+
note.modulationDepth = new GainNode(this.audioContext);
|
|
1036
|
+
this.setModLfoToPitch(channel, note);
|
|
1037
|
+
note.volumeDepth = new GainNode(this.audioContext);
|
|
1038
|
+
this.setModLfoToVolume(note);
|
|
1039
|
+
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
913
1040
|
note.modulationLFO.connect(note.filterDepth);
|
|
914
1041
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
915
1042
|
note.modulationLFO.connect(note.modulationDepth);
|
|
@@ -918,54 +1045,59 @@ export class Midy {
|
|
|
918
1045
|
note.volumeDepth.connect(note.volumeNode.gain);
|
|
919
1046
|
}
|
|
920
1047
|
startVibrato(channel, note, startTime) {
|
|
921
|
-
const {
|
|
922
|
-
const
|
|
1048
|
+
const { voiceParams } = note;
|
|
1049
|
+
const state = channel.state;
|
|
923
1050
|
note.vibratoLFO = new OscillatorNode(this.audioContext, {
|
|
924
|
-
frequency: this.centToHz(
|
|
925
|
-
|
|
926
|
-
});
|
|
927
|
-
const vibratoDepth = Math.abs(vibLfoToPitch) * channel.vibratoDepth;
|
|
928
|
-
const vibratoDepthSign = 0 < vibLfoToPitch;
|
|
929
|
-
note.vibratoDepth = new GainNode(this.audioContext, {
|
|
930
|
-
gain: vibratoDepth * vibratoDepthSign,
|
|
1051
|
+
frequency: this.centToHz(voiceParams.freqVibLFO) *
|
|
1052
|
+
state.vibratoRate,
|
|
931
1053
|
});
|
|
932
|
-
note.vibratoLFO.start(startTime +
|
|
1054
|
+
note.vibratoLFO.start(startTime + voiceParams.delayVibLFO * state.vibratoDelay * 2);
|
|
1055
|
+
note.vibratoDepth = new GainNode(this.audioContext);
|
|
1056
|
+
this.setVibLfoToPitch(channel, note);
|
|
933
1057
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
934
1058
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
935
1059
|
}
|
|
936
|
-
async createNote(channel,
|
|
937
|
-
const
|
|
938
|
-
const
|
|
939
|
-
|
|
1060
|
+
async createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3) {
|
|
1061
|
+
const state = channel.state;
|
|
1062
|
+
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1063
|
+
const voiceParams = voice.getAllParams(controllerState);
|
|
1064
|
+
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1065
|
+
note.bufferSource = await this.createNoteBufferNode(voiceParams, isSF3);
|
|
940
1066
|
note.volumeNode = new GainNode(this.audioContext);
|
|
941
1067
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
942
1068
|
type: "lowpass",
|
|
943
|
-
Q:
|
|
1069
|
+
Q: voiceParams.initialFilterQ / 5 * state.filterResonance, // dB
|
|
944
1070
|
});
|
|
945
1071
|
if (portamento) {
|
|
1072
|
+
note.portamento = true;
|
|
946
1073
|
this.setPortamentoStartVolumeEnvelope(channel, note);
|
|
947
1074
|
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
948
1075
|
}
|
|
949
1076
|
else {
|
|
1077
|
+
note.portamento = false;
|
|
950
1078
|
this.setVolumeEnvelope(channel, note);
|
|
951
1079
|
this.setFilterEnvelope(channel, note);
|
|
952
1080
|
}
|
|
953
|
-
if (0 <
|
|
1081
|
+
if (0 < state.vibratoDepth) {
|
|
954
1082
|
this.startVibrato(channel, note, startTime);
|
|
955
1083
|
}
|
|
956
|
-
|
|
957
|
-
|
|
1084
|
+
this.setPlaybackRate(note);
|
|
1085
|
+
if (0 < state.modulationDepth) {
|
|
1086
|
+
this.setPitch(channel, note);
|
|
958
1087
|
this.startModulation(channel, note, startTime);
|
|
959
1088
|
}
|
|
960
|
-
else {
|
|
961
|
-
note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
|
|
962
|
-
}
|
|
963
1089
|
if (this.mono && channel.currentBufferSource) {
|
|
964
1090
|
channel.currentBufferSource.stop(startTime);
|
|
965
1091
|
channel.currentBufferSource = note.bufferSource;
|
|
966
1092
|
}
|
|
967
1093
|
note.bufferSource.connect(note.filterNode);
|
|
968
1094
|
note.filterNode.connect(note.volumeNode);
|
|
1095
|
+
if (0 < channel.chorusSendLevel) {
|
|
1096
|
+
this.setChorusEffectsSend(channel, note, 0);
|
|
1097
|
+
}
|
|
1098
|
+
if (0 < channel.reverbSendLevel) {
|
|
1099
|
+
this.setReverbEffectsSend(channel, note, 0);
|
|
1100
|
+
}
|
|
969
1101
|
note.bufferSource.start(startTime);
|
|
970
1102
|
return note;
|
|
971
1103
|
}
|
|
@@ -986,15 +1118,28 @@ export class Midy {
|
|
|
986
1118
|
return;
|
|
987
1119
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
988
1120
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
989
|
-
const
|
|
990
|
-
if (!
|
|
1121
|
+
const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
|
|
1122
|
+
if (!voice)
|
|
991
1123
|
return;
|
|
992
|
-
const note = await this.createNote(channel,
|
|
1124
|
+
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
|
|
993
1125
|
note.volumeNode.connect(channel.gainL);
|
|
994
1126
|
note.volumeNode.connect(channel.gainR);
|
|
995
|
-
if (channel.sostenutoPedal) {
|
|
1127
|
+
if (channel.state.sostenutoPedal) {
|
|
996
1128
|
channel.sostenutoNotes.set(noteNumber, note);
|
|
997
1129
|
}
|
|
1130
|
+
const exclusiveClass = note.voiceParams.exclusiveClass;
|
|
1131
|
+
if (exclusiveClass !== 0) {
|
|
1132
|
+
if (this.exclusiveClassMap.has(exclusiveClass)) {
|
|
1133
|
+
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
1134
|
+
const [prevNote, prevChannelNumber] = prevEntry;
|
|
1135
|
+
if (!prevNote.ending) {
|
|
1136
|
+
this.scheduleNoteRelease(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1137
|
+
startTime, undefined, // portamentoNoteNumber
|
|
1138
|
+
true);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
|
|
1142
|
+
}
|
|
998
1143
|
const scheduledNotes = channel.scheduledNotes;
|
|
999
1144
|
if (scheduledNotes.has(noteNumber)) {
|
|
1000
1145
|
scheduledNotes.get(noteNumber).push(note);
|
|
@@ -1007,15 +1152,15 @@ export class Midy {
|
|
|
1007
1152
|
const now = this.audioContext.currentTime;
|
|
1008
1153
|
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, now, portamento);
|
|
1009
1154
|
}
|
|
1010
|
-
stopNote(
|
|
1155
|
+
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
1011
1156
|
const note = scheduledNotes[index];
|
|
1012
1157
|
note.volumeNode.gain
|
|
1013
|
-
.cancelScheduledValues(
|
|
1014
|
-
.linearRampToValueAtTime(0,
|
|
1158
|
+
.cancelScheduledValues(endTime)
|
|
1159
|
+
.linearRampToValueAtTime(0, stopTime);
|
|
1015
1160
|
note.ending = true;
|
|
1016
1161
|
this.scheduleTask(() => {
|
|
1017
1162
|
note.bufferSource.loop = false;
|
|
1018
|
-
},
|
|
1163
|
+
}, stopTime);
|
|
1019
1164
|
return new Promise((resolve) => {
|
|
1020
1165
|
note.bufferSource.onended = () => {
|
|
1021
1166
|
scheduledNotes[index] = null;
|
|
@@ -1031,15 +1176,22 @@ export class Midy {
|
|
|
1031
1176
|
note.vibratoDepth.disconnect();
|
|
1032
1177
|
note.vibratoLFO.stop();
|
|
1033
1178
|
}
|
|
1179
|
+
if (note.reverbEffectsSend) {
|
|
1180
|
+
note.reverbEffectsSend.disconnect();
|
|
1181
|
+
}
|
|
1182
|
+
if (note.chorusEffectsSend) {
|
|
1183
|
+
note.chorusEffectsSend.disconnect();
|
|
1184
|
+
}
|
|
1034
1185
|
resolve();
|
|
1035
1186
|
};
|
|
1036
|
-
note.bufferSource.stop(
|
|
1187
|
+
note.bufferSource.stop(stopTime);
|
|
1037
1188
|
});
|
|
1038
1189
|
}
|
|
1039
|
-
scheduleNoteRelease(channelNumber, noteNumber, _velocity,
|
|
1190
|
+
scheduleNoteRelease(channelNumber, noteNumber, _velocity, endTime, portamentoNoteNumber, force) {
|
|
1040
1191
|
const channel = this.channels[channelNumber];
|
|
1192
|
+
const state = channel.state;
|
|
1041
1193
|
if (!force) {
|
|
1042
|
-
if (
|
|
1194
|
+
if (0.5 < state.sustainPedal)
|
|
1043
1195
|
return;
|
|
1044
1196
|
if (channel.sostenutoNotes.has(noteNumber))
|
|
1045
1197
|
return;
|
|
@@ -1054,22 +1206,23 @@ export class Midy {
|
|
|
1054
1206
|
if (note.ending)
|
|
1055
1207
|
continue;
|
|
1056
1208
|
if (portamentoNoteNumber === undefined) {
|
|
1057
|
-
const
|
|
1058
|
-
note.
|
|
1059
|
-
const modRelease =
|
|
1209
|
+
const volRelease = endTime +
|
|
1210
|
+
note.voiceParams.volRelease * state.releaseTime * 2;
|
|
1211
|
+
const modRelease = endTime + note.voiceParams.modRelease;
|
|
1060
1212
|
note.filterNode.frequency
|
|
1061
|
-
.cancelScheduledValues(
|
|
1213
|
+
.cancelScheduledValues(endTime)
|
|
1062
1214
|
.linearRampToValueAtTime(0, modRelease);
|
|
1063
|
-
|
|
1215
|
+
const stopTime = Math.min(volRelease, modRelease);
|
|
1216
|
+
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
1064
1217
|
}
|
|
1065
1218
|
else {
|
|
1066
|
-
const portamentoTime =
|
|
1219
|
+
const portamentoTime = endTime + state.portamentoTime;
|
|
1067
1220
|
const detuneChange = (portamentoNoteNumber - noteNumber) * 100;
|
|
1068
1221
|
const detune = note.bufferSource.detune.value + detuneChange;
|
|
1069
1222
|
note.bufferSource.detune
|
|
1070
|
-
.cancelScheduledValues(
|
|
1223
|
+
.cancelScheduledValues(endTime)
|
|
1071
1224
|
.linearRampToValueAtTime(detune, portamentoTime);
|
|
1072
|
-
return this.stopNote(
|
|
1225
|
+
return this.stopNote(endTime, portamentoTime, scheduledNotes, i);
|
|
1073
1226
|
}
|
|
1074
1227
|
}
|
|
1075
1228
|
}
|
|
@@ -1081,7 +1234,7 @@ export class Midy {
|
|
|
1081
1234
|
const velocity = halfVelocity * 2;
|
|
1082
1235
|
const channel = this.channels[channelNumber];
|
|
1083
1236
|
const promises = [];
|
|
1084
|
-
channel.sustainPedal =
|
|
1237
|
+
channel.state.sustainPedal = halfVelocity;
|
|
1085
1238
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1086
1239
|
for (let i = 0; i < noteList.length; i++) {
|
|
1087
1240
|
const note = noteList[i];
|
|
@@ -1098,7 +1251,7 @@ export class Midy {
|
|
|
1098
1251
|
const velocity = halfVelocity * 2;
|
|
1099
1252
|
const channel = this.channels[channelNumber];
|
|
1100
1253
|
const promises = [];
|
|
1101
|
-
channel.sostenutoPedal =
|
|
1254
|
+
channel.state.sostenutoPedal = 0;
|
|
1102
1255
|
channel.sostenutoNotes.forEach((activeNote) => {
|
|
1103
1256
|
const { noteNumber } = activeNote;
|
|
1104
1257
|
const promise = this.releaseNote(channelNumber, noteNumber, velocity);
|
|
@@ -1116,7 +1269,7 @@ export class Midy {
|
|
|
1116
1269
|
case 0x90:
|
|
1117
1270
|
return this.noteOn(channelNumber, data1, data2);
|
|
1118
1271
|
case 0xA0:
|
|
1119
|
-
return
|
|
1272
|
+
return this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
|
|
1120
1273
|
case 0xB0:
|
|
1121
1274
|
return this.handleControlChange(channelNumber, data1, data2);
|
|
1122
1275
|
case 0xC0:
|
|
@@ -1143,6 +1296,7 @@ export class Midy {
|
|
|
1143
1296
|
.setValueAtTime(gain * pressure, now);
|
|
1144
1297
|
}
|
|
1145
1298
|
}
|
|
1299
|
+
// this.applyVoiceParams(channel, 10);
|
|
1146
1300
|
}
|
|
1147
1301
|
handleProgramChange(channelNumber, program) {
|
|
1148
1302
|
const channel = this.channels[channelNumber];
|
|
@@ -1163,18 +1317,232 @@ export class Midy {
|
|
|
1163
1317
|
.setValueAtTime(gain * pressure, now);
|
|
1164
1318
|
});
|
|
1165
1319
|
}
|
|
1320
|
+
// this.applyVoiceParams(channel, 13);
|
|
1166
1321
|
}
|
|
1167
1322
|
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
1168
|
-
const pitchBend = msb * 128 + lsb
|
|
1323
|
+
const pitchBend = msb * 128 + lsb;
|
|
1169
1324
|
this.setPitchBend(channelNumber, pitchBend);
|
|
1170
1325
|
}
|
|
1171
|
-
setPitchBend(channelNumber,
|
|
1326
|
+
setPitchBend(channelNumber, value) {
|
|
1172
1327
|
const channel = this.channels[channelNumber];
|
|
1173
|
-
const
|
|
1174
|
-
|
|
1175
|
-
const
|
|
1176
|
-
|
|
1328
|
+
const state = channel.state;
|
|
1329
|
+
state.pitchWheel = value / 16383;
|
|
1330
|
+
const pitchWheel = (value - 8192) / 8192;
|
|
1331
|
+
const detuneChange = pitchWheel * state.pitchWheelSensitivity * 12800;
|
|
1177
1332
|
this.updateDetune(channel, detuneChange);
|
|
1333
|
+
this.applyVoiceParams(channel, 14);
|
|
1334
|
+
}
|
|
1335
|
+
setModLfoToPitch(channel, note) {
|
|
1336
|
+
const now = this.audioContext.currentTime;
|
|
1337
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch;
|
|
1338
|
+
const modulationDepth = Math.abs(modLfoToPitch) +
|
|
1339
|
+
channel.state.modulationDepth;
|
|
1340
|
+
const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
|
|
1341
|
+
note.modulationDepth.gain
|
|
1342
|
+
.cancelScheduledValues(now)
|
|
1343
|
+
.setValueAtTime(modulationDepth * modulationDepthSign, now);
|
|
1344
|
+
}
|
|
1345
|
+
setModLfoToVolume(note) {
|
|
1346
|
+
const now = this.audioContext.currentTime;
|
|
1347
|
+
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1348
|
+
const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1349
|
+
const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
|
|
1350
|
+
note.volumeDepth.gain
|
|
1351
|
+
.cancelScheduledValues(now)
|
|
1352
|
+
.setValueAtTime(volumeDepth * volumeDepthSign, now);
|
|
1353
|
+
}
|
|
1354
|
+
setChorusEffectsSend(note, prevValue) {
|
|
1355
|
+
if (0 < prevValue) {
|
|
1356
|
+
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1357
|
+
const now = this.audioContext.currentTime;
|
|
1358
|
+
const value = note.voiceParams.chorusEffectsSend;
|
|
1359
|
+
note.chorusEffectsSend.gain
|
|
1360
|
+
.cancelScheduledValues(now)
|
|
1361
|
+
.setValueAtTime(value, now);
|
|
1362
|
+
}
|
|
1363
|
+
else {
|
|
1364
|
+
note.chorusEffectsSend.disconnect();
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
else {
|
|
1368
|
+
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1369
|
+
if (!note.chorusEffectsSend) {
|
|
1370
|
+
note.chorusEffectsSend = new GainNode(this.audioContext, {
|
|
1371
|
+
gain: note.voiceParams.chorusEffectsSend,
|
|
1372
|
+
});
|
|
1373
|
+
note.volumeNode.connect(note.chorusEffectsSend);
|
|
1374
|
+
}
|
|
1375
|
+
note.chorusEffectsSend.connect(this.chorusEffect.input);
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
setReverbEffectsSend(note, prevValue) {
|
|
1380
|
+
if (0 < prevValue) {
|
|
1381
|
+
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1382
|
+
const now = this.audioContext.currentTime;
|
|
1383
|
+
const value = note.voiceParams.reverbEffectsSend;
|
|
1384
|
+
note.reverbEffectsSend.gain
|
|
1385
|
+
.cancelScheduledValues(now)
|
|
1386
|
+
.setValueAtTime(value, now);
|
|
1387
|
+
}
|
|
1388
|
+
else {
|
|
1389
|
+
note.reverbEffectsSend.disconnect();
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
else {
|
|
1393
|
+
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1394
|
+
if (!note.reverbEffectsSend) {
|
|
1395
|
+
note.reverbEffectsSend = new GainNode(this.audioContext, {
|
|
1396
|
+
gain: note.voiceParams.reverbEffectsSend,
|
|
1397
|
+
});
|
|
1398
|
+
note.volumeNode.connect(note.reverbEffectsSend);
|
|
1399
|
+
}
|
|
1400
|
+
note.reverbEffectsSend.connect(this.reverbEffect.input);
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
setVibLfoToPitch(channel, note) {
|
|
1405
|
+
const now = this.audioContext.currentTime;
|
|
1406
|
+
const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
|
|
1407
|
+
const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
|
|
1408
|
+
2;
|
|
1409
|
+
const vibratoDepthSign = 0 < vibLfoToPitch;
|
|
1410
|
+
note.vibratoDepth.gain
|
|
1411
|
+
.cancelScheduledValues(now)
|
|
1412
|
+
.setValueAtTime(vibratoDepth * vibratoDepthSign, now);
|
|
1413
|
+
}
|
|
1414
|
+
setModLfoToFilterFc(note) {
|
|
1415
|
+
const now = this.audioContext.currentTime;
|
|
1416
|
+
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
|
|
1417
|
+
note.filterDepth.gain
|
|
1418
|
+
.cancelScheduledValues(now)
|
|
1419
|
+
.setValueAtTime(modLfoToFilterFc, now);
|
|
1420
|
+
}
|
|
1421
|
+
setDelayModLFO(note) {
|
|
1422
|
+
const now = this.audioContext.currentTime;
|
|
1423
|
+
const startTime = note.startTime;
|
|
1424
|
+
if (startTime < now)
|
|
1425
|
+
return;
|
|
1426
|
+
note.modulationLFO.stop(now);
|
|
1427
|
+
note.modulationLFO.start(startTime + note.voiceParams.delayModLFO);
|
|
1428
|
+
note.modulationLFO.connect(note.filterDepth);
|
|
1429
|
+
}
|
|
1430
|
+
setFreqModLFO(note) {
|
|
1431
|
+
const now = this.audioContext.currentTime;
|
|
1432
|
+
const freqModLFO = note.voiceParams.freqModLFO;
|
|
1433
|
+
note.modulationLFO.frequency
|
|
1434
|
+
.cancelScheduledValues(now)
|
|
1435
|
+
.setValueAtTime(freqModLFO, now);
|
|
1436
|
+
}
|
|
1437
|
+
createVoiceParamsHandlers() {
|
|
1438
|
+
return {
|
|
1439
|
+
modLfoToPitch: (channel, note, _prevValue) => {
|
|
1440
|
+
if (0 < channel.state.modulationDepth) {
|
|
1441
|
+
this.setModLfoToPitch(channel, note);
|
|
1442
|
+
}
|
|
1443
|
+
},
|
|
1444
|
+
vibLfoToPitch: (channel, note, _prevValue) => {
|
|
1445
|
+
if (0 < channel.state.vibratoDepth) {
|
|
1446
|
+
this.setVibLfoToPitch(channel, note);
|
|
1447
|
+
}
|
|
1448
|
+
},
|
|
1449
|
+
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1450
|
+
if (0 < channel.state.modulationDepth)
|
|
1451
|
+
this.setModLfoToFilterFc(note);
|
|
1452
|
+
},
|
|
1453
|
+
modLfoToVolume: (channel, note) => {
|
|
1454
|
+
if (0 < channel.state.modulationDepth)
|
|
1455
|
+
this.setModLfoToVolume(note);
|
|
1456
|
+
},
|
|
1457
|
+
chorusEffectsSend: (_channel, note, prevValue) => {
|
|
1458
|
+
this.setChorusEffectsSend(note, prevValue);
|
|
1459
|
+
},
|
|
1460
|
+
reverbEffectsSend: (_channel, note, prevValue) => {
|
|
1461
|
+
this.setReverbEffectsSend(note, prevValue);
|
|
1462
|
+
},
|
|
1463
|
+
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
1464
|
+
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
1465
|
+
delayVibLFO: (channel, note, prevValue) => {
|
|
1466
|
+
if (0 < channel.state.vibratoDepth) {
|
|
1467
|
+
const now = this.audioContext.currentTime;
|
|
1468
|
+
const prevStartTime = note.startTime +
|
|
1469
|
+
prevValue * channel.state.vibratoDelay * 2;
|
|
1470
|
+
if (now < prevStartTime)
|
|
1471
|
+
return;
|
|
1472
|
+
const startTime = note.startTime +
|
|
1473
|
+
value * channel.state.vibratoDelay * 2;
|
|
1474
|
+
note.vibratoLFO.stop(now);
|
|
1475
|
+
note.vibratoLFO.start(startTime);
|
|
1476
|
+
}
|
|
1477
|
+
},
|
|
1478
|
+
freqVibLFO: (channel, note, _prevValue) => {
|
|
1479
|
+
if (0 < channel.state.vibratoDepth) {
|
|
1480
|
+
const now = this.audioContext.currentTime;
|
|
1481
|
+
note.vibratoLFO.frequency
|
|
1482
|
+
.cancelScheduledValues(now)
|
|
1483
|
+
.setValueAtTime(value * sate.vibratoRate, now);
|
|
1484
|
+
}
|
|
1485
|
+
},
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
getControllerState(channel, noteNumber, velocity) {
|
|
1489
|
+
const state = new Float32Array(channel.state.array.length);
|
|
1490
|
+
state.set(channel.state.array);
|
|
1491
|
+
state[2] = velocity / 127;
|
|
1492
|
+
state[3] = noteNumber / 127;
|
|
1493
|
+
return state;
|
|
1494
|
+
}
|
|
1495
|
+
applyVoiceParams(channel, controllerType) {
|
|
1496
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1497
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1498
|
+
const note = noteList[i];
|
|
1499
|
+
if (!note)
|
|
1500
|
+
continue;
|
|
1501
|
+
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
|
|
1502
|
+
const voiceParams = note.voice.getParams(controllerType, controllerState);
|
|
1503
|
+
let appliedFilterEnvelope = false;
|
|
1504
|
+
let appliedVolumeEnvelope = false;
|
|
1505
|
+
for (const [key, value] of Object.entries(voiceParams)) {
|
|
1506
|
+
const prevValue = note.voiceParams[key];
|
|
1507
|
+
if (value === prevValue)
|
|
1508
|
+
continue;
|
|
1509
|
+
note.voiceParams[key] = value;
|
|
1510
|
+
if (key in this.voiceParamsHandlers) {
|
|
1511
|
+
this.voiceParamsHandlers[key](channel, note, prevValue);
|
|
1512
|
+
}
|
|
1513
|
+
else if (filterEnvelopeKeySet.has(key)) {
|
|
1514
|
+
if (appliedFilterEnvelope)
|
|
1515
|
+
continue;
|
|
1516
|
+
appliedFilterEnvelope = true;
|
|
1517
|
+
const noteVoiceParams = note.voiceParams;
|
|
1518
|
+
for (let i = 0; i < filterEnvelopeKeys.length; i++) {
|
|
1519
|
+
const key = filterEnvelopeKeys[i];
|
|
1520
|
+
if (key in voiceParams)
|
|
1521
|
+
noteVoiceParams[key] = voiceParams[key];
|
|
1522
|
+
}
|
|
1523
|
+
if (note.portamento) {
|
|
1524
|
+
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
1525
|
+
}
|
|
1526
|
+
else {
|
|
1527
|
+
this.setFilterEnvelope(channel, note);
|
|
1528
|
+
}
|
|
1529
|
+
this.setPitch(channel, note);
|
|
1530
|
+
}
|
|
1531
|
+
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1532
|
+
if (appliedVolumeEnvelope)
|
|
1533
|
+
continue;
|
|
1534
|
+
appliedVolumeEnvelope = true;
|
|
1535
|
+
const noteVoiceParams = note.voiceParams;
|
|
1536
|
+
for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
|
|
1537
|
+
const key = volumeEnvelopeKeys[i];
|
|
1538
|
+
if (key in voiceParams)
|
|
1539
|
+
noteVoiceParams[key] = voiceParams[key];
|
|
1540
|
+
}
|
|
1541
|
+
this.setVolumeEnvelope(channel, note);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
});
|
|
1178
1546
|
}
|
|
1179
1547
|
createControlChangeHandlers() {
|
|
1180
1548
|
return {
|
|
@@ -1214,13 +1582,16 @@ export class Midy {
|
|
|
1214
1582
|
127: this.polyOn,
|
|
1215
1583
|
};
|
|
1216
1584
|
}
|
|
1217
|
-
handleControlChange(channelNumber,
|
|
1218
|
-
const handler = this.controlChangeHandlers[
|
|
1585
|
+
handleControlChange(channelNumber, controllerType, value) {
|
|
1586
|
+
const handler = this.controlChangeHandlers[controllerType];
|
|
1219
1587
|
if (handler) {
|
|
1220
1588
|
handler.call(this, channelNumber, value);
|
|
1589
|
+
const channel = this.channels[channelNumber];
|
|
1590
|
+
const controller = 128 + controllerType;
|
|
1591
|
+
this.applyVoiceParams(channel, controller);
|
|
1221
1592
|
}
|
|
1222
1593
|
else {
|
|
1223
|
-
console.warn(`Unsupported Control change:
|
|
1594
|
+
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
1224
1595
|
}
|
|
1225
1596
|
}
|
|
1226
1597
|
setBankMSB(channelNumber, msb) {
|
|
@@ -1234,11 +1605,10 @@ export class Midy {
|
|
|
1234
1605
|
if (!note)
|
|
1235
1606
|
continue;
|
|
1236
1607
|
if (note.modulationDepth) {
|
|
1237
|
-
note.modulationDepth.gain.setValueAtTime(channel.modulationDepth, now);
|
|
1608
|
+
note.modulationDepth.gain.setValueAtTime(channel.state.modulationDepth, now);
|
|
1238
1609
|
}
|
|
1239
1610
|
else {
|
|
1240
|
-
|
|
1241
|
-
this.setPitch(note, semitoneOffset);
|
|
1611
|
+
this.setPitch(channel, note);
|
|
1242
1612
|
this.startModulation(channel, note, now);
|
|
1243
1613
|
}
|
|
1244
1614
|
}
|
|
@@ -1246,21 +1616,22 @@ export class Midy {
|
|
|
1246
1616
|
}
|
|
1247
1617
|
setModulationDepth(channelNumber, modulation) {
|
|
1248
1618
|
const channel = this.channels[channelNumber];
|
|
1249
|
-
channel.modulationDepth = (modulation / 127) *
|
|
1619
|
+
channel.state.modulationDepth = (modulation / 127) *
|
|
1620
|
+
channel.modulationDepthRange;
|
|
1250
1621
|
this.updateModulation(channel);
|
|
1251
1622
|
}
|
|
1252
1623
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
1253
1624
|
const channel = this.channels[channelNumber];
|
|
1254
1625
|
const factor = 5 * Math.log(10) / 127;
|
|
1255
|
-
channel.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1626
|
+
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1256
1627
|
}
|
|
1257
1628
|
setVolume(channelNumber, volume) {
|
|
1258
1629
|
const channel = this.channels[channelNumber];
|
|
1259
|
-
channel.volume = volume / 127;
|
|
1630
|
+
channel.state.volume = volume / 127;
|
|
1260
1631
|
this.updateChannelVolume(channel);
|
|
1261
1632
|
}
|
|
1262
1633
|
panToGain(pan) {
|
|
1263
|
-
const theta = Math.PI / 2 * Math.max(0, pan - 1) / 126;
|
|
1634
|
+
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
1264
1635
|
return {
|
|
1265
1636
|
gainLeft: Math.cos(theta),
|
|
1266
1637
|
gainRight: Math.sin(theta),
|
|
@@ -1268,12 +1639,12 @@ export class Midy {
|
|
|
1268
1639
|
}
|
|
1269
1640
|
setPan(channelNumber, pan) {
|
|
1270
1641
|
const channel = this.channels[channelNumber];
|
|
1271
|
-
channel.pan = pan;
|
|
1642
|
+
channel.state.pan = pan / 127;
|
|
1272
1643
|
this.updateChannelVolume(channel);
|
|
1273
1644
|
}
|
|
1274
1645
|
setExpression(channelNumber, expression) {
|
|
1275
1646
|
const channel = this.channels[channelNumber];
|
|
1276
|
-
channel.expression = expression / 127;
|
|
1647
|
+
channel.state.expression = expression / 127;
|
|
1277
1648
|
this.updateChannelVolume(channel);
|
|
1278
1649
|
}
|
|
1279
1650
|
setBankLSB(channelNumber, lsb) {
|
|
@@ -1285,8 +1656,9 @@ export class Midy {
|
|
|
1285
1656
|
}
|
|
1286
1657
|
updateChannelVolume(channel) {
|
|
1287
1658
|
const now = this.audioContext.currentTime;
|
|
1288
|
-
const
|
|
1289
|
-
const
|
|
1659
|
+
const state = channel.state;
|
|
1660
|
+
const volume = state.volume * state.expression;
|
|
1661
|
+
const { gainLeft, gainRight } = this.panToGain(state.pan);
|
|
1290
1662
|
channel.gainL.gain
|
|
1291
1663
|
.cancelScheduledValues(now)
|
|
1292
1664
|
.setValueAtTime(volume * gainLeft, now);
|
|
@@ -1295,68 +1667,100 @@ export class Midy {
|
|
|
1295
1667
|
.setValueAtTime(volume * gainRight, now);
|
|
1296
1668
|
}
|
|
1297
1669
|
setSustainPedal(channelNumber, value) {
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
if (!isOn) {
|
|
1670
|
+
this.channels[channelNumber].state.sustainPedal = value / 127;
|
|
1671
|
+
if (value < 64) {
|
|
1301
1672
|
this.releaseSustainPedal(channelNumber, value);
|
|
1302
1673
|
}
|
|
1303
1674
|
}
|
|
1304
1675
|
setPortamento(channelNumber, value) {
|
|
1305
|
-
this.channels[channelNumber].portamento = value
|
|
1676
|
+
this.channels[channelNumber].state.portamento = value / 127;
|
|
1306
1677
|
}
|
|
1307
1678
|
setReverbSendLevel(channelNumber, reverbSendLevel) {
|
|
1308
1679
|
const channel = this.channels[channelNumber];
|
|
1680
|
+
const state = channel.state;
|
|
1309
1681
|
const reverbEffect = this.reverbEffect;
|
|
1310
|
-
if (0 <
|
|
1682
|
+
if (0 < state.reverbSendLevel) {
|
|
1311
1683
|
if (0 < reverbSendLevel) {
|
|
1312
1684
|
const now = this.audioContext.currentTime;
|
|
1313
|
-
|
|
1314
|
-
reverbEffect.
|
|
1315
|
-
reverbEffect.
|
|
1685
|
+
state.reverbSendLevel = reverbSendLevel / 127;
|
|
1686
|
+
reverbEffect.input.gain.cancelScheduledValues(now);
|
|
1687
|
+
reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
|
|
1316
1688
|
}
|
|
1317
1689
|
else {
|
|
1318
|
-
channel.
|
|
1690
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1691
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1692
|
+
const note = noteList[i];
|
|
1693
|
+
if (!note)
|
|
1694
|
+
continue;
|
|
1695
|
+
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
1696
|
+
continue;
|
|
1697
|
+
note.reverbEffectsSend.disconnect();
|
|
1698
|
+
}
|
|
1699
|
+
});
|
|
1319
1700
|
}
|
|
1320
1701
|
}
|
|
1321
1702
|
else {
|
|
1322
1703
|
if (0 < reverbSendLevel) {
|
|
1323
|
-
channel.merger.connect(reverbEffect.input);
|
|
1324
1704
|
const now = this.audioContext.currentTime;
|
|
1325
|
-
channel.
|
|
1326
|
-
|
|
1327
|
-
|
|
1705
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1706
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1707
|
+
const note = noteList[i];
|
|
1708
|
+
if (!note)
|
|
1709
|
+
continue;
|
|
1710
|
+
this.setReverbEffectsSend(note, 0);
|
|
1711
|
+
}
|
|
1712
|
+
});
|
|
1713
|
+
state.reverbSendLevel = reverbSendLevel / 127;
|
|
1714
|
+
reverbEffect.input.gain.cancelScheduledValues(now);
|
|
1715
|
+
reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
|
|
1328
1716
|
}
|
|
1329
1717
|
}
|
|
1330
1718
|
}
|
|
1331
1719
|
setChorusSendLevel(channelNumber, chorusSendLevel) {
|
|
1332
1720
|
const channel = this.channels[channelNumber];
|
|
1721
|
+
const state = channel.state;
|
|
1333
1722
|
const chorusEffect = this.chorusEffect;
|
|
1334
|
-
if (0 <
|
|
1723
|
+
if (0 < state.chorusSendLevel) {
|
|
1335
1724
|
if (0 < chorusSendLevel) {
|
|
1336
1725
|
const now = this.audioContext.currentTime;
|
|
1337
|
-
|
|
1338
|
-
chorusEffect.
|
|
1339
|
-
chorusEffect.
|
|
1726
|
+
state.chorusSendLevel = chorusSendLevel / 127;
|
|
1727
|
+
chorusEffect.input.gain.cancelScheduledValues(now);
|
|
1728
|
+
chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
|
|
1340
1729
|
}
|
|
1341
1730
|
else {
|
|
1342
|
-
channel.
|
|
1731
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1732
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1733
|
+
const note = noteList[i];
|
|
1734
|
+
if (!note)
|
|
1735
|
+
continue;
|
|
1736
|
+
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
1737
|
+
continue;
|
|
1738
|
+
note.chorusEffectsSend.disconnect();
|
|
1739
|
+
}
|
|
1740
|
+
});
|
|
1343
1741
|
}
|
|
1344
1742
|
}
|
|
1345
1743
|
else {
|
|
1346
1744
|
if (0 < chorusSendLevel) {
|
|
1347
|
-
channel.merger.connect(chorusEffect.input);
|
|
1348
1745
|
const now = this.audioContext.currentTime;
|
|
1349
|
-
channel.
|
|
1350
|
-
|
|
1351
|
-
|
|
1746
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1747
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1748
|
+
const note = noteList[i];
|
|
1749
|
+
if (!note)
|
|
1750
|
+
continue;
|
|
1751
|
+
this.setChorusEffectsSend(note, 0);
|
|
1752
|
+
}
|
|
1753
|
+
});
|
|
1754
|
+
state.chorusSendLevel = chorusSendLevel / 127;
|
|
1755
|
+
chorusEffect.input.gain.cancelScheduledValues(now);
|
|
1756
|
+
chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
|
|
1352
1757
|
}
|
|
1353
1758
|
}
|
|
1354
1759
|
}
|
|
1355
1760
|
setSostenutoPedal(channelNumber, value) {
|
|
1356
|
-
const isOn = value >= 64;
|
|
1357
1761
|
const channel = this.channels[channelNumber];
|
|
1358
|
-
channel.sostenutoPedal =
|
|
1359
|
-
if (
|
|
1762
|
+
channel.state.sostenutoPedal = value / 127;
|
|
1763
|
+
if (64 <= value) {
|
|
1360
1764
|
const now = this.audioContext.currentTime;
|
|
1361
1765
|
const activeNotes = this.getActiveNotes(channel, now);
|
|
1362
1766
|
channel.sostenutoNotes = new Map(activeNotes);
|
|
@@ -1367,31 +1771,31 @@ export class Midy {
|
|
|
1367
1771
|
}
|
|
1368
1772
|
setSoftPedal(channelNumber, softPedal) {
|
|
1369
1773
|
const channel = this.channels[channelNumber];
|
|
1370
|
-
channel.softPedal = softPedal / 127;
|
|
1774
|
+
channel.state.softPedal = softPedal / 127;
|
|
1371
1775
|
}
|
|
1372
1776
|
setFilterResonance(channelNumber, filterResonance) {
|
|
1373
1777
|
const now = this.audioContext.currentTime;
|
|
1374
1778
|
const channel = this.channels[channelNumber];
|
|
1375
|
-
|
|
1779
|
+
const state = channel.state;
|
|
1780
|
+
state.filterResonance = filterResonance / 64;
|
|
1376
1781
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1377
1782
|
for (let i = 0; i < noteList.length; i++) {
|
|
1378
1783
|
const note = noteList[i];
|
|
1379
1784
|
if (!note)
|
|
1380
1785
|
continue;
|
|
1381
|
-
const Q = note.
|
|
1382
|
-
channel.filterResonance;
|
|
1786
|
+
const Q = note.voiceParams.initialFilterQ / 5 * state.filterResonance;
|
|
1383
1787
|
note.filterNode.Q.setValueAtTime(Q, now);
|
|
1384
1788
|
}
|
|
1385
1789
|
});
|
|
1386
1790
|
}
|
|
1387
1791
|
setReleaseTime(channelNumber, releaseTime) {
|
|
1388
1792
|
const channel = this.channels[channelNumber];
|
|
1389
|
-
channel.releaseTime = releaseTime / 64;
|
|
1793
|
+
channel.state.releaseTime = releaseTime / 64;
|
|
1390
1794
|
}
|
|
1391
1795
|
setAttackTime(channelNumber, attackTime) {
|
|
1392
1796
|
const now = this.audioContext.currentTime;
|
|
1393
1797
|
const channel = this.channels[channelNumber];
|
|
1394
|
-
channel.attackTime = attackTime / 64;
|
|
1798
|
+
channel.state.attackTime = attackTime / 64;
|
|
1395
1799
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1396
1800
|
for (let i = 0; i < noteList.length; i++) {
|
|
1397
1801
|
const note = noteList[i];
|
|
@@ -1405,7 +1809,7 @@ export class Midy {
|
|
|
1405
1809
|
}
|
|
1406
1810
|
setBrightness(channelNumber, brightness) {
|
|
1407
1811
|
const channel = this.channels[channelNumber];
|
|
1408
|
-
channel.brightness = brightness / 64;
|
|
1812
|
+
channel.state.brightness = brightness / 64;
|
|
1409
1813
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1410
1814
|
for (let i = 0; i < noteList.length; i++) {
|
|
1411
1815
|
const note = noteList[i];
|
|
@@ -1417,7 +1821,7 @@ export class Midy {
|
|
|
1417
1821
|
}
|
|
1418
1822
|
setDecayTime(channelNumber, dacayTime) {
|
|
1419
1823
|
const channel = this.channels[channelNumber];
|
|
1420
|
-
channel.decayTime = dacayTime / 64;
|
|
1824
|
+
channel.state.decayTime = dacayTime / 64;
|
|
1421
1825
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1422
1826
|
for (let i = 0; i < noteList.length; i++) {
|
|
1423
1827
|
const note = noteList[i];
|
|
@@ -1429,7 +1833,7 @@ export class Midy {
|
|
|
1429
1833
|
}
|
|
1430
1834
|
setVibratoRate(channelNumber, vibratoRate) {
|
|
1431
1835
|
const channel = this.channels[channelNumber];
|
|
1432
|
-
channel.vibratoRate = vibratoRate / 64;
|
|
1836
|
+
channel.state.vibratoRate = vibratoRate / 64;
|
|
1433
1837
|
if (channel.vibratoDepth <= 0)
|
|
1434
1838
|
return;
|
|
1435
1839
|
const now = this.audioContext.currentTime;
|
|
@@ -1437,16 +1841,16 @@ export class Midy {
|
|
|
1437
1841
|
activeNotes.forEach((activeNote) => {
|
|
1438
1842
|
activeNote.vibratoLFO.frequency
|
|
1439
1843
|
.cancelScheduledValues(now)
|
|
1440
|
-
.setValueAtTime(channel.vibratoRate, now);
|
|
1844
|
+
.setValueAtTime(channel.state.vibratoRate, now);
|
|
1441
1845
|
});
|
|
1442
1846
|
}
|
|
1443
1847
|
setVibratoDepth(channelNumber, vibratoDepth) {
|
|
1444
1848
|
const channel = this.channels[channelNumber];
|
|
1445
|
-
channel.vibratoDepth = vibratoDepth / 64;
|
|
1849
|
+
channel.state.vibratoDepth = vibratoDepth / 64;
|
|
1446
1850
|
}
|
|
1447
1851
|
setVibratoDelay(channelNumber, vibratoDelay) {
|
|
1448
1852
|
const channel = this.channels[channelNumber];
|
|
1449
|
-
channel.vibratoDelay = vibratoDelay / 64;
|
|
1853
|
+
channel.state.vibratoDelay = vibratoDelay / 64;
|
|
1450
1854
|
}
|
|
1451
1855
|
limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
|
|
1452
1856
|
if (maxLSB < channel.dataLSB) {
|
|
@@ -1516,7 +1920,7 @@ export class Midy {
|
|
|
1516
1920
|
this.channels[channelNumber].dataMSB = value;
|
|
1517
1921
|
this.handleRPN(channelNumber, 0);
|
|
1518
1922
|
}
|
|
1519
|
-
updateDetune(channel,
|
|
1923
|
+
updateDetune(channel, detune) {
|
|
1520
1924
|
const now = this.audioContext.currentTime;
|
|
1521
1925
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1522
1926
|
for (let i = 0; i < noteList.length; i++) {
|
|
@@ -1524,7 +1928,6 @@ export class Midy {
|
|
|
1524
1928
|
if (!note)
|
|
1525
1929
|
continue;
|
|
1526
1930
|
const { bufferSource } = note;
|
|
1527
|
-
const detune = bufferSource.detune.value + detuneChange;
|
|
1528
1931
|
bufferSource.detune
|
|
1529
1932
|
.cancelScheduledValues(now)
|
|
1530
1933
|
.setValueAtTime(detune, now);
|
|
@@ -1537,13 +1940,13 @@ export class Midy {
|
|
|
1537
1940
|
const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
|
|
1538
1941
|
this.setPitchBendRange(channelNumber, pitchBendRange);
|
|
1539
1942
|
}
|
|
1540
|
-
setPitchBendRange(channelNumber,
|
|
1943
|
+
setPitchBendRange(channelNumber, pitchWheelSensitivity) {
|
|
1541
1944
|
const channel = this.channels[channelNumber];
|
|
1542
|
-
const
|
|
1543
|
-
|
|
1544
|
-
const
|
|
1545
|
-
|
|
1546
|
-
this.
|
|
1945
|
+
const state = channel.state;
|
|
1946
|
+
state.pitchWheelSensitivity = pitchWheelSensitivity / 128;
|
|
1947
|
+
const detune = (state.pitchWheel * 2 - 1) * pitchWheelSensitivity * 100;
|
|
1948
|
+
this.updateDetune(channel, detune);
|
|
1949
|
+
this.applyVoiceParams(channel, 16);
|
|
1547
1950
|
}
|
|
1548
1951
|
handleFineTuningRPN(channelNumber) {
|
|
1549
1952
|
const channel = this.channels[channelNumber];
|
|
@@ -1587,7 +1990,30 @@ export class Midy {
|
|
|
1587
1990
|
return this.stopChannelNotes(channelNumber, 0, true);
|
|
1588
1991
|
}
|
|
1589
1992
|
resetAllControllers(channelNumber) {
|
|
1590
|
-
|
|
1993
|
+
const stateTypes = [
|
|
1994
|
+
"expression",
|
|
1995
|
+
"modulationDepth",
|
|
1996
|
+
"sustainPedal",
|
|
1997
|
+
"portamento",
|
|
1998
|
+
"sostenutoPedal",
|
|
1999
|
+
"softPedal",
|
|
2000
|
+
"channelPressure",
|
|
2001
|
+
"pitchWheelSensitivity",
|
|
2002
|
+
];
|
|
2003
|
+
const channel = this.channels[channelNumber];
|
|
2004
|
+
const state = channel.state;
|
|
2005
|
+
for (let i = 0; i < stateTypes.length; i++) {
|
|
2006
|
+
const type = stateTypes[i];
|
|
2007
|
+
state[type] = defaultControllerState[type];
|
|
2008
|
+
}
|
|
2009
|
+
const settingTypes = [
|
|
2010
|
+
"rpnMSB",
|
|
2011
|
+
"rpnLSB",
|
|
2012
|
+
];
|
|
2013
|
+
for (let i = 0; i < settingTypes.length; i++) {
|
|
2014
|
+
const type = settingTypes[i];
|
|
2015
|
+
channel[type] = this.constructor.channelSettings[type];
|
|
2016
|
+
}
|
|
1591
2017
|
}
|
|
1592
2018
|
allNotesOff(channelNumber) {
|
|
1593
2019
|
return this.stopChannelNotes(channelNumber, 0, false);
|
|
@@ -1781,10 +2207,8 @@ export class Midy {
|
|
|
1781
2207
|
}
|
|
1782
2208
|
setReverbTime(value) {
|
|
1783
2209
|
this.reverb.time = this.getReverbTime(value);
|
|
1784
|
-
const { audioContext,
|
|
1785
|
-
|
|
1786
|
-
channels[i].reverbEffect = options.reverbAlgorithm(audioContext);
|
|
1787
|
-
}
|
|
2210
|
+
const { audioContext, options } = this;
|
|
2211
|
+
this.reverbEffect = options.reverbAlgorithm(audioContext);
|
|
1788
2212
|
}
|
|
1789
2213
|
getReverbTime(value) {
|
|
1790
2214
|
return Math.pow(Math.E, (value - 40) * 0.025);
|
|
@@ -1861,10 +2285,7 @@ export class Midy {
|
|
|
1861
2285
|
const now = this.audioContext.currentTime;
|
|
1862
2286
|
const modRate = this.getChorusModRate(value);
|
|
1863
2287
|
this.chorus.modRate = modRate;
|
|
1864
|
-
|
|
1865
|
-
const lfo = this.channels[i].chorusEffect.lfo;
|
|
1866
|
-
lfo.frequency.setValueAtTime(modRate, now);
|
|
1867
|
-
}
|
|
2288
|
+
this.chorusEffect.lfo.frequency.setValueAtTime(modRate, now);
|
|
1868
2289
|
}
|
|
1869
2290
|
getChorusModRate(value) {
|
|
1870
2291
|
return value * 0.122; // Hz
|
|
@@ -1873,12 +2294,9 @@ export class Midy {
|
|
|
1873
2294
|
const now = this.audioContext.currentTime;
|
|
1874
2295
|
const modDepth = this.getChorusModDepth(value);
|
|
1875
2296
|
this.chorus.modDepth = modDepth;
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
.cancelScheduledValues(now)
|
|
1880
|
-
.setValueAtTime(modDepth / 2, now);
|
|
1881
|
-
}
|
|
2297
|
+
this.chorusEffect.lfoGain.gain
|
|
2298
|
+
.cancelScheduledValues(now)
|
|
2299
|
+
.setValueAtTime(modDepth / 2, now);
|
|
1882
2300
|
}
|
|
1883
2301
|
getChorusModDepth(value) {
|
|
1884
2302
|
return (value + 1) / 3200; // second
|
|
@@ -1887,14 +2305,11 @@ export class Midy {
|
|
|
1887
2305
|
const now = this.audioContext.currentTime;
|
|
1888
2306
|
const feedback = this.getChorusFeedback(value);
|
|
1889
2307
|
this.chorus.feedback = feedback;
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
.cancelScheduledValues(now)
|
|
1896
|
-
.setValueAtTime(feedback, now);
|
|
1897
|
-
}
|
|
2308
|
+
const chorusEffect = this.chorusEffect;
|
|
2309
|
+
for (let i = 0; i < chorusEffect.feedbackGains.length; i++) {
|
|
2310
|
+
chorusEffect.feedbackGains[i].gain
|
|
2311
|
+
.cancelScheduledValues(now)
|
|
2312
|
+
.setValueAtTime(feedback, now);
|
|
1898
2313
|
}
|
|
1899
2314
|
}
|
|
1900
2315
|
getChorusFeedback(value) {
|
|
@@ -1902,15 +2317,28 @@ export class Midy {
|
|
|
1902
2317
|
}
|
|
1903
2318
|
setChorusSendToReverb(value) {
|
|
1904
2319
|
const sendToReverb = this.getChorusSendToReverb(value);
|
|
1905
|
-
|
|
1906
|
-
|
|
2320
|
+
const sendGain = this.chorusEffect.sendGain;
|
|
2321
|
+
if (0 < this.chorus.sendToReverb) {
|
|
1907
2322
|
this.chorus.sendToReverb = sendToReverb;
|
|
1908
|
-
|
|
1909
|
-
.
|
|
1910
|
-
.
|
|
2323
|
+
if (0 < sendToReverb) {
|
|
2324
|
+
const now = this.audioContext.currentTime;
|
|
2325
|
+
sendGain.gain
|
|
2326
|
+
.cancelScheduledValues(now)
|
|
2327
|
+
.setValueAtTime(sendToReverb, now);
|
|
2328
|
+
}
|
|
2329
|
+
else {
|
|
2330
|
+
sendGain.disconnect();
|
|
2331
|
+
}
|
|
1911
2332
|
}
|
|
1912
|
-
else
|
|
1913
|
-
this.
|
|
2333
|
+
else {
|
|
2334
|
+
this.chorus.sendToReverb = sendToReverb;
|
|
2335
|
+
if (0 < sendToReverb) {
|
|
2336
|
+
const now = this.audioContext.currentTime;
|
|
2337
|
+
sendGain.connect(this.reverbEffect.input);
|
|
2338
|
+
sendGain.gain
|
|
2339
|
+
.cancelScheduledValues(now)
|
|
2340
|
+
.setValueAtTime(sendToReverb, now);
|
|
2341
|
+
}
|
|
1914
2342
|
}
|
|
1915
2343
|
}
|
|
1916
2344
|
getChorusSendToReverb(value) {
|
|
@@ -1947,48 +2375,19 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
1947
2375
|
writable: true,
|
|
1948
2376
|
value: {
|
|
1949
2377
|
currentBufferSource: null,
|
|
1950
|
-
|
|
1951
|
-
pan: 64,
|
|
1952
|
-
portamentoTime: 1, // sec
|
|
1953
|
-
filterResonance: 1,
|
|
1954
|
-
releaseTime: 1,
|
|
1955
|
-
attackTime: 1,
|
|
1956
|
-
brightness: 1,
|
|
1957
|
-
decayTime: 1,
|
|
1958
|
-
reverbSendLevel: 0,
|
|
1959
|
-
chorusSendLevel: 0,
|
|
1960
|
-
vibratoRate: 1,
|
|
1961
|
-
vibratoDepth: 1,
|
|
1962
|
-
vibratoDelay: 1,
|
|
2378
|
+
program: 0,
|
|
1963
2379
|
bank: 121 * 128,
|
|
1964
2380
|
bankMSB: 121,
|
|
1965
2381
|
bankLSB: 0,
|
|
1966
2382
|
dataMSB: 0,
|
|
1967
2383
|
dataLSB: 0,
|
|
1968
|
-
|
|
1969
|
-
|
|
2384
|
+
rpnMSB: 127,
|
|
2385
|
+
rpnLSB: 127,
|
|
1970
2386
|
fineTuning: 0, // cb
|
|
1971
2387
|
coarseTuning: 0, // cb
|
|
1972
2388
|
modulationDepthRange: 50, // cent
|
|
1973
2389
|
}
|
|
1974
2390
|
});
|
|
1975
|
-
Object.defineProperty(Midy, "effectSettings", {
|
|
1976
|
-
enumerable: true,
|
|
1977
|
-
configurable: true,
|
|
1978
|
-
writable: true,
|
|
1979
|
-
value: {
|
|
1980
|
-
expression: 1,
|
|
1981
|
-
modulationDepth: 0,
|
|
1982
|
-
sustainPedal: false,
|
|
1983
|
-
portamento: false,
|
|
1984
|
-
sostenutoPedal: false,
|
|
1985
|
-
softPedal: 0,
|
|
1986
|
-
rpnMSB: 127,
|
|
1987
|
-
rpnLSB: 127,
|
|
1988
|
-
channelPressure: 0,
|
|
1989
|
-
pitchBendRange: 2,
|
|
1990
|
-
}
|
|
1991
|
-
});
|
|
1992
2391
|
Object.defineProperty(Midy, "controllerDestinationSettings", {
|
|
1993
2392
|
enumerable: true,
|
|
1994
2393
|
configurable: true,
|