@marmooo/midy 0.3.2 → 0.3.4
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 +11 -10
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +114 -123
- package/esm/midy-GM2.d.ts +26 -42
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +295 -328
- package/esm/midy-GMLite.d.ts +12 -11
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +124 -141
- package/esm/midy.d.ts +27 -43
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +335 -352
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +11 -10
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +114 -123
- package/script/midy-GM2.d.ts +26 -42
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +295 -328
- package/script/midy-GMLite.d.ts +12 -11
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +124 -141
- package/script/midy.d.ts +27 -43
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +335 -352
package/script/midy.js
CHANGED
|
@@ -11,11 +11,11 @@ class Note {
|
|
|
11
11
|
writable: true,
|
|
12
12
|
value: -1
|
|
13
13
|
});
|
|
14
|
-
Object.defineProperty(this, "
|
|
14
|
+
Object.defineProperty(this, "ending", {
|
|
15
15
|
enumerable: true,
|
|
16
16
|
configurable: true,
|
|
17
17
|
writable: true,
|
|
18
|
-
value:
|
|
18
|
+
value: false
|
|
19
19
|
});
|
|
20
20
|
Object.defineProperty(this, "bufferSource", {
|
|
21
21
|
enumerable: true,
|
|
@@ -47,24 +47,6 @@ class Note {
|
|
|
47
47
|
writable: true,
|
|
48
48
|
value: void 0
|
|
49
49
|
});
|
|
50
|
-
Object.defineProperty(this, "volumeNode", {
|
|
51
|
-
enumerable: true,
|
|
52
|
-
configurable: true,
|
|
53
|
-
writable: true,
|
|
54
|
-
value: void 0
|
|
55
|
-
});
|
|
56
|
-
Object.defineProperty(this, "gainL", {
|
|
57
|
-
enumerable: true,
|
|
58
|
-
configurable: true,
|
|
59
|
-
writable: true,
|
|
60
|
-
value: void 0
|
|
61
|
-
});
|
|
62
|
-
Object.defineProperty(this, "gainR", {
|
|
63
|
-
enumerable: true,
|
|
64
|
-
configurable: true,
|
|
65
|
-
writable: true,
|
|
66
|
-
value: void 0
|
|
67
|
-
});
|
|
68
50
|
Object.defineProperty(this, "modulationLFO", {
|
|
69
51
|
enumerable: true,
|
|
70
52
|
configurable: true,
|
|
@@ -217,6 +199,16 @@ class ControllerState {
|
|
|
217
199
|
}
|
|
218
200
|
}
|
|
219
201
|
}
|
|
202
|
+
const volumeEnvelopeKeys = [
|
|
203
|
+
"volDelay",
|
|
204
|
+
"volAttack",
|
|
205
|
+
"volHold",
|
|
206
|
+
"volDecay",
|
|
207
|
+
"volSustain",
|
|
208
|
+
"volRelease",
|
|
209
|
+
"initialAttenuation",
|
|
210
|
+
];
|
|
211
|
+
const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
|
|
220
212
|
const filterEnvelopeKeys = [
|
|
221
213
|
"modEnvToPitch",
|
|
222
214
|
"initialFilterFc",
|
|
@@ -226,22 +218,20 @@ const filterEnvelopeKeys = [
|
|
|
226
218
|
"modHold",
|
|
227
219
|
"modDecay",
|
|
228
220
|
"modSustain",
|
|
229
|
-
"modRelease",
|
|
230
|
-
"playbackRate",
|
|
231
221
|
];
|
|
232
222
|
const filterEnvelopeKeySet = new Set(filterEnvelopeKeys);
|
|
233
|
-
const
|
|
234
|
-
"
|
|
235
|
-
"
|
|
236
|
-
"
|
|
237
|
-
"
|
|
238
|
-
"
|
|
239
|
-
"
|
|
240
|
-
"
|
|
223
|
+
const pitchEnvelopeKeys = [
|
|
224
|
+
"modEnvToPitch",
|
|
225
|
+
"modDelay",
|
|
226
|
+
"modAttack",
|
|
227
|
+
"modHold",
|
|
228
|
+
"modDecay",
|
|
229
|
+
"modSustain",
|
|
230
|
+
"playbackRate",
|
|
241
231
|
];
|
|
242
|
-
const
|
|
232
|
+
const pitchEnvelopeKeySet = new Set(pitchEnvelopeKeys);
|
|
243
233
|
class Midy {
|
|
244
|
-
constructor(audioContext
|
|
234
|
+
constructor(audioContext) {
|
|
245
235
|
Object.defineProperty(this, "mode", {
|
|
246
236
|
enumerable: true,
|
|
247
237
|
configurable: true,
|
|
@@ -265,6 +255,7 @@ class Midy {
|
|
|
265
255
|
configurable: true,
|
|
266
256
|
writable: true,
|
|
267
257
|
value: {
|
|
258
|
+
algorithm: "SchroederReverb",
|
|
268
259
|
time: this.getReverbTime(64),
|
|
269
260
|
feedback: 0.8,
|
|
270
261
|
}
|
|
@@ -413,30 +404,7 @@ class Midy {
|
|
|
413
404
|
writable: true,
|
|
414
405
|
value: new Array(this.numChannels * drumExclusiveClassCount)
|
|
415
406
|
});
|
|
416
|
-
Object.defineProperty(this, "defaultOptions", {
|
|
417
|
-
enumerable: true,
|
|
418
|
-
configurable: true,
|
|
419
|
-
writable: true,
|
|
420
|
-
value: {
|
|
421
|
-
reverbAlgorithm: (audioContext) => {
|
|
422
|
-
const { time: rt60, feedback } = this.reverb;
|
|
423
|
-
// const delay = this.calcDelay(rt60, feedback);
|
|
424
|
-
// const impulse = this.createConvolutionReverbImpulse(
|
|
425
|
-
// audioContext,
|
|
426
|
-
// rt60,
|
|
427
|
-
// delay,
|
|
428
|
-
// );
|
|
429
|
-
// return this.createConvolutionReverb(audioContext, impulse);
|
|
430
|
-
const combFeedbacks = this.generateDistributedArray(feedback, 4);
|
|
431
|
-
const combDelays = combFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
|
|
432
|
-
const allpassFeedbacks = this.generateDistributedArray(feedback, 4);
|
|
433
|
-
const allpassDelays = allpassFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
|
|
434
|
-
return this.createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays);
|
|
435
|
-
},
|
|
436
|
-
}
|
|
437
|
-
});
|
|
438
407
|
this.audioContext = audioContext;
|
|
439
|
-
this.options = { ...this.defaultOptions, ...options };
|
|
440
408
|
this.masterVolume = new GainNode(audioContext);
|
|
441
409
|
this.scheduler = new GainNode(audioContext, { gain: 0 });
|
|
442
410
|
this.schedulerBuffer = new AudioBuffer({
|
|
@@ -446,7 +414,7 @@ class Midy {
|
|
|
446
414
|
this.voiceParamsHandlers = this.createVoiceParamsHandlers();
|
|
447
415
|
this.controlChangeHandlers = this.createControlChangeHandlers();
|
|
448
416
|
this.channels = this.createChannels(audioContext);
|
|
449
|
-
this.reverbEffect = this.
|
|
417
|
+
this.reverbEffect = this.createReverbEffect(audioContext);
|
|
450
418
|
this.chorusEffect = this.createChorusEffect(audioContext);
|
|
451
419
|
this.chorusEffect.output.connect(this.masterVolume);
|
|
452
420
|
this.reverbEffect.output.connect(this.masterVolume);
|
|
@@ -473,24 +441,44 @@ class Midy {
|
|
|
473
441
|
}
|
|
474
442
|
}
|
|
475
443
|
}
|
|
476
|
-
async loadSoundFont(
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
444
|
+
async loadSoundFont(input) {
|
|
445
|
+
let uint8Array;
|
|
446
|
+
if (typeof input === "string") {
|
|
447
|
+
const response = await fetch(input);
|
|
448
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
449
|
+
uint8Array = new Uint8Array(arrayBuffer);
|
|
450
|
+
}
|
|
451
|
+
else if (input instanceof Uint8Array) {
|
|
452
|
+
uint8Array = input;
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
throw new TypeError("input must be a URL string or Uint8Array");
|
|
456
|
+
}
|
|
457
|
+
const parsed = (0, soundfont_parser_1.parse)(uint8Array);
|
|
480
458
|
const soundFont = new soundfont_parser_1.SoundFont(parsed);
|
|
481
459
|
this.addSoundFont(soundFont);
|
|
482
460
|
}
|
|
483
|
-
async loadMIDI(
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
461
|
+
async loadMIDI(input) {
|
|
462
|
+
let uint8Array;
|
|
463
|
+
if (typeof input === "string") {
|
|
464
|
+
const response = await fetch(input);
|
|
465
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
466
|
+
uint8Array = new Uint8Array(arrayBuffer);
|
|
467
|
+
}
|
|
468
|
+
else if (input instanceof Uint8Array) {
|
|
469
|
+
uint8Array = input;
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
throw new TypeError("input must be a URL string or Uint8Array");
|
|
473
|
+
}
|
|
474
|
+
const midi = (0, midi_file_1.parseMidi)(uint8Array);
|
|
487
475
|
this.ticksPerBeat = midi.header.ticksPerBeat;
|
|
488
476
|
const midiData = this.extractMidiData(midi);
|
|
489
477
|
this.instruments = midiData.instruments;
|
|
490
478
|
this.timeline = midiData.timeline;
|
|
491
479
|
this.totalTime = this.calcTotalTime();
|
|
492
480
|
}
|
|
493
|
-
|
|
481
|
+
createChannelAudioNodes(audioContext) {
|
|
494
482
|
const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
|
|
495
483
|
const gainL = new GainNode(audioContext, { gain: gainLeft });
|
|
496
484
|
const gainR = new GainNode(audioContext, { gain: gainRight });
|
|
@@ -505,11 +493,11 @@ class Midy {
|
|
|
505
493
|
};
|
|
506
494
|
}
|
|
507
495
|
resetChannelTable(channel) {
|
|
508
|
-
|
|
496
|
+
channel.controlTable.fill(-1);
|
|
509
497
|
channel.scaleOctaveTuningTable.fill(0); // [-100, 100] cent
|
|
510
|
-
channel.channelPressureTable.
|
|
511
|
-
channel.polyphonicKeyPressureTable.
|
|
512
|
-
channel.keyBasedInstrumentControlTable.fill(
|
|
498
|
+
channel.channelPressureTable.fill(-1);
|
|
499
|
+
channel.polyphonicKeyPressureTable.fill(-1);
|
|
500
|
+
channel.keyBasedInstrumentControlTable.fill(-1);
|
|
513
501
|
}
|
|
514
502
|
createChannels(audioContext) {
|
|
515
503
|
const channels = Array.from({ length: this.numChannels }, () => {
|
|
@@ -518,15 +506,17 @@ class Midy {
|
|
|
518
506
|
isDrum: false,
|
|
519
507
|
state: new ControllerState(),
|
|
520
508
|
...this.constructor.channelSettings,
|
|
521
|
-
...this.
|
|
509
|
+
...this.createChannelAudioNodes(audioContext),
|
|
522
510
|
scheduledNotes: [],
|
|
523
511
|
sustainNotes: [],
|
|
524
512
|
sostenutoNotes: [],
|
|
525
513
|
controlTable: this.initControlTable(),
|
|
526
514
|
scaleOctaveTuningTable: new Float32Array(12), // [-100, 100] cent
|
|
527
|
-
channelPressureTable: new
|
|
528
|
-
polyphonicKeyPressureTable: new
|
|
529
|
-
keyBasedInstrumentControlTable: new Int8Array(128 * 128)
|
|
515
|
+
channelPressureTable: new Int8Array(6).fill(-1),
|
|
516
|
+
polyphonicKeyPressureTable: new Int8Array(6).fill(-1),
|
|
517
|
+
keyBasedInstrumentControlTable: new Int8Array(128 * 128).fill(-1),
|
|
518
|
+
keyBasedGainLs: new Array(128),
|
|
519
|
+
keyBasedGainRs: new Array(128),
|
|
530
520
|
};
|
|
531
521
|
});
|
|
532
522
|
return channels;
|
|
@@ -560,10 +550,17 @@ class Midy {
|
|
|
560
550
|
return audioBuffer;
|
|
561
551
|
}
|
|
562
552
|
}
|
|
563
|
-
|
|
553
|
+
isLoopDrum(channel, noteNumber) {
|
|
554
|
+
const programNumber = channel.programNumber;
|
|
555
|
+
return ((programNumber === 48 && noteNumber === 88) ||
|
|
556
|
+
(programNumber === 56 && 47 <= noteNumber && noteNumber <= 84));
|
|
557
|
+
}
|
|
558
|
+
createBufferSource(channel, noteNumber, voiceParams, audioBuffer) {
|
|
564
559
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
565
560
|
bufferSource.buffer = audioBuffer;
|
|
566
|
-
bufferSource.loop =
|
|
561
|
+
bufferSource.loop = channel.isDrum
|
|
562
|
+
? this.isLoopDrum(channel, noteNumber)
|
|
563
|
+
: (voiceParams.sampleModes % 2 !== 0);
|
|
567
564
|
if (bufferSource.loop) {
|
|
568
565
|
bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
|
|
569
566
|
bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
|
|
@@ -578,12 +575,13 @@ class Midy {
|
|
|
578
575
|
const delay = this.startDelay - resumeTime;
|
|
579
576
|
const startTime = event.startTime + delay;
|
|
580
577
|
switch (event.type) {
|
|
581
|
-
case "noteOn":
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
578
|
+
case "noteOn":
|
|
579
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
|
|
580
|
+
break;
|
|
581
|
+
case "noteOff": {
|
|
582
|
+
const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
|
|
583
|
+
if (notePromise)
|
|
584
|
+
this.notePromises.push(notePromise);
|
|
587
585
|
break;
|
|
588
586
|
}
|
|
589
587
|
case "noteAftertouch":
|
|
@@ -691,6 +689,7 @@ class Midy {
|
|
|
691
689
|
return `${programNumber}:${noteNumber}:${velocity}`;
|
|
692
690
|
}
|
|
693
691
|
extractMidiData(midi) {
|
|
692
|
+
this.audioBufferCounter.clear();
|
|
694
693
|
const instruments = new Set();
|
|
695
694
|
const timeline = [];
|
|
696
695
|
const tmpChannels = new Array(this.channels.length);
|
|
@@ -790,38 +789,13 @@ class Midy {
|
|
|
790
789
|
prevTempoTicks = event.ticks;
|
|
791
790
|
}
|
|
792
791
|
}
|
|
793
|
-
const activeNotes = new Array(this.channels.length * 128);
|
|
794
|
-
for (let i = 0; i < activeNotes.length; i++) {
|
|
795
|
-
activeNotes[i] = [];
|
|
796
|
-
}
|
|
797
|
-
for (let i = 0; i < timeline.length; i++) {
|
|
798
|
-
const event = timeline[i];
|
|
799
|
-
switch (event.type) {
|
|
800
|
-
case "noteOn": {
|
|
801
|
-
const index = event.channel * 128 + event.noteNumber;
|
|
802
|
-
activeNotes[index].push(event);
|
|
803
|
-
break;
|
|
804
|
-
}
|
|
805
|
-
case "noteOff": {
|
|
806
|
-
const index = event.channel * 128 + event.noteNumber;
|
|
807
|
-
const noteOn = activeNotes[index].pop();
|
|
808
|
-
if (noteOn) {
|
|
809
|
-
noteOn.noteOffEvent = event;
|
|
810
|
-
}
|
|
811
|
-
else {
|
|
812
|
-
const eventString = JSON.stringify(event, null, 2);
|
|
813
|
-
console.warn(`noteOff without matching noteOn: ${eventString}`);
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
792
|
return { instruments, timeline };
|
|
819
793
|
}
|
|
820
794
|
stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
|
|
821
795
|
const channel = this.channels[channelNumber];
|
|
822
796
|
const promises = [];
|
|
823
797
|
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
824
|
-
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force
|
|
798
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
825
799
|
this.notePromises.push(promise);
|
|
826
800
|
promises.push(promise);
|
|
827
801
|
});
|
|
@@ -830,8 +804,8 @@ class Midy {
|
|
|
830
804
|
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
831
805
|
const channel = this.channels[channelNumber];
|
|
832
806
|
const promises = [];
|
|
833
|
-
this.processScheduledNotes(channel, (note) => {
|
|
834
|
-
const promise = this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, force);
|
|
807
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
808
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
835
809
|
this.notePromises.push(promise);
|
|
836
810
|
promises.push(promise);
|
|
837
811
|
});
|
|
@@ -889,7 +863,7 @@ class Midy {
|
|
|
889
863
|
const now = this.audioContext.currentTime;
|
|
890
864
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
891
865
|
}
|
|
892
|
-
processScheduledNotes(channel, callback) {
|
|
866
|
+
processScheduledNotes(channel, scheduleTime, callback) {
|
|
893
867
|
const scheduledNotes = channel.scheduledNotes;
|
|
894
868
|
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
895
869
|
const note = scheduledNotes[i];
|
|
@@ -897,6 +871,8 @@ class Midy {
|
|
|
897
871
|
continue;
|
|
898
872
|
if (note.ending)
|
|
899
873
|
continue;
|
|
874
|
+
if (note.startTime < scheduleTime)
|
|
875
|
+
continue;
|
|
900
876
|
callback(note);
|
|
901
877
|
}
|
|
902
878
|
}
|
|
@@ -908,11 +884,8 @@ class Midy {
|
|
|
908
884
|
continue;
|
|
909
885
|
if (note.ending)
|
|
910
886
|
continue;
|
|
911
|
-
const noteOffEvent = note.noteOffEvent;
|
|
912
|
-
if (noteOffEvent && noteOffEvent.startTime < scheduleTime)
|
|
913
|
-
continue;
|
|
914
887
|
if (scheduleTime < note.startTime)
|
|
915
|
-
|
|
888
|
+
break;
|
|
916
889
|
callback(note);
|
|
917
890
|
}
|
|
918
891
|
}
|
|
@@ -1001,6 +974,22 @@ class Midy {
|
|
|
1001
974
|
const output = allpasses.at(-1);
|
|
1002
975
|
return { input, output };
|
|
1003
976
|
}
|
|
977
|
+
createReverbEffect(audioContext) {
|
|
978
|
+
const { algorithm, time: rt60, feedback } = this.reverb;
|
|
979
|
+
switch (algorithm) {
|
|
980
|
+
case "ConvolutionReverb": {
|
|
981
|
+
const impulse = this.createConvolutionReverbImpulse(audioContext, rt60, this.calcDelay(rt60, feedback));
|
|
982
|
+
return this.createConvolutionReverb(audioContext, impulse);
|
|
983
|
+
}
|
|
984
|
+
case "SchroederReverb": {
|
|
985
|
+
const combFeedbacks = this.generateDistributedArray(feedback, 4);
|
|
986
|
+
const combDelays = combFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
|
|
987
|
+
const allpassFeedbacks = this.generateDistributedArray(feedback, 4);
|
|
988
|
+
const allpassDelays = allpassFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
|
|
989
|
+
return this.createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
}
|
|
1004
993
|
createChorusEffect(audioContext) {
|
|
1005
994
|
const input = new GainNode(audioContext);
|
|
1006
995
|
const output = new GainNode(audioContext);
|
|
@@ -1065,15 +1054,22 @@ class Midy {
|
|
|
1065
1054
|
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
1066
1055
|
const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
|
|
1067
1056
|
const pitch = pitchWheel * pitchWheelSensitivity;
|
|
1068
|
-
const
|
|
1069
|
-
|
|
1070
|
-
|
|
1057
|
+
const channelPressureRaw = channel.channelPressureTable[0];
|
|
1058
|
+
if (0 <= channelPressureRaw) {
|
|
1059
|
+
const channelPressureDepth = (channelPressureRaw - 64) / 37.5; // 2400 / 64;
|
|
1060
|
+
const channelPressure = channelPressureDepth *
|
|
1061
|
+
channel.state.channelPressure;
|
|
1062
|
+
return tuning + pitch + channelPressure;
|
|
1063
|
+
}
|
|
1064
|
+
else {
|
|
1065
|
+
return tuning + pitch;
|
|
1066
|
+
}
|
|
1071
1067
|
}
|
|
1072
1068
|
calcNoteDetune(channel, note) {
|
|
1073
1069
|
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
1074
1070
|
}
|
|
1075
1071
|
updateChannelDetune(channel, scheduleTime) {
|
|
1076
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1072
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1077
1073
|
this.updateDetune(channel, note, scheduleTime);
|
|
1078
1074
|
});
|
|
1079
1075
|
}
|
|
@@ -1223,9 +1219,8 @@ class Midy {
|
|
|
1223
1219
|
}
|
|
1224
1220
|
setPortamentoFilterEnvelope(channel, note, scheduleTime) {
|
|
1225
1221
|
const state = channel.state;
|
|
1226
|
-
const { voiceParams,
|
|
1227
|
-
const softPedalFactor =
|
|
1228
|
-
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1222
|
+
const { voiceParams, startTime } = note;
|
|
1223
|
+
const softPedalFactor = this.getSoftPedalFactor(channel, note);
|
|
1229
1224
|
const baseCent = voiceParams.initialFilterFc +
|
|
1230
1225
|
this.getFilterCutoffControl(channel, note);
|
|
1231
1226
|
const baseFreq = this.centToHz(baseCent) * softPedalFactor *
|
|
@@ -1245,9 +1240,8 @@ class Midy {
|
|
|
1245
1240
|
}
|
|
1246
1241
|
setFilterEnvelope(channel, note, scheduleTime) {
|
|
1247
1242
|
const state = channel.state;
|
|
1248
|
-
const { voiceParams,
|
|
1249
|
-
const softPedalFactor =
|
|
1250
|
-
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1243
|
+
const { voiceParams, startTime } = note;
|
|
1244
|
+
const softPedalFactor = this.getSoftPedalFactor(channel, note);
|
|
1251
1245
|
const baseCent = voiceParams.initialFilterFc +
|
|
1252
1246
|
this.getFilterCutoffControl(channel, note);
|
|
1253
1247
|
const baseFreq = this.centToHz(baseCent) * softPedalFactor *
|
|
@@ -1324,14 +1318,11 @@ class Midy {
|
|
|
1324
1318
|
async createNote(channel, voice, noteNumber, velocity, startTime, isSF3) {
|
|
1325
1319
|
const now = this.audioContext.currentTime;
|
|
1326
1320
|
const state = channel.state;
|
|
1327
|
-
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1321
|
+
const controllerState = this.getControllerState(channel, noteNumber, velocity, 0);
|
|
1328
1322
|
const voiceParams = voice.getAllParams(controllerState);
|
|
1329
1323
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1330
1324
|
const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
|
|
1331
|
-
note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
|
|
1332
|
-
note.volumeNode = new GainNode(this.audioContext);
|
|
1333
|
-
note.gainL = new GainNode(this.audioContext);
|
|
1334
|
-
note.gainR = new GainNode(this.audioContext);
|
|
1325
|
+
note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
|
|
1335
1326
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
1336
1327
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
1337
1328
|
type: "lowpass",
|
|
@@ -1364,13 +1355,10 @@ class Midy {
|
|
|
1364
1355
|
}
|
|
1365
1356
|
note.bufferSource.connect(note.filterNode);
|
|
1366
1357
|
note.filterNode.connect(note.volumeEnvelopeNode);
|
|
1367
|
-
|
|
1368
|
-
note.volumeNode.connect(note.gainL);
|
|
1369
|
-
note.volumeNode.connect(note.gainR);
|
|
1370
|
-
if (0 < channel.chorusSendLevel) {
|
|
1358
|
+
if (0 < state.chorusSendLevel) {
|
|
1371
1359
|
this.setChorusEffectsSend(channel, note, 0, now);
|
|
1372
1360
|
}
|
|
1373
|
-
if (0 <
|
|
1361
|
+
if (0 < state.reverbSendLevel) {
|
|
1374
1362
|
this.setReverbEffectsSend(channel, note, 0, now);
|
|
1375
1363
|
}
|
|
1376
1364
|
note.bufferSource.start(startTime);
|
|
@@ -1400,7 +1388,7 @@ class Midy {
|
|
|
1400
1388
|
if (prev) {
|
|
1401
1389
|
const [prevNote, prevChannelNumber] = prev;
|
|
1402
1390
|
if (prevNote && !prevNote.ending) {
|
|
1403
|
-
this.scheduleNoteOff(prevChannelNumber, prevNote, 0, // velocity,
|
|
1391
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1404
1392
|
startTime, true);
|
|
1405
1393
|
}
|
|
1406
1394
|
}
|
|
@@ -1420,19 +1408,12 @@ class Midy {
|
|
|
1420
1408
|
channelNumber;
|
|
1421
1409
|
const prevNote = this.drumExclusiveClassNotes[index];
|
|
1422
1410
|
if (prevNote && !prevNote.ending) {
|
|
1423
|
-
this.scheduleNoteOff(channelNumber, prevNote, 0, // velocity,
|
|
1411
|
+
this.scheduleNoteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1424
1412
|
startTime, true);
|
|
1425
1413
|
}
|
|
1426
1414
|
this.drumExclusiveClassNotes[index] = note;
|
|
1427
1415
|
}
|
|
1428
|
-
|
|
1429
|
-
if (!channel.isDrum)
|
|
1430
|
-
return false;
|
|
1431
|
-
const programNumber = channel.programNumber;
|
|
1432
|
-
return !((programNumber === 48 && noteNumber === 88) ||
|
|
1433
|
-
(programNumber === 56 && 47 <= noteNumber && noteNumber <= 84));
|
|
1434
|
-
}
|
|
1435
|
-
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, noteOffEvent) {
|
|
1416
|
+
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
1436
1417
|
const channel = this.channels[channelNumber];
|
|
1437
1418
|
const bankNumber = this.calcBank(channel, channelNumber);
|
|
1438
1419
|
const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
|
|
@@ -1444,9 +1425,18 @@ class Midy {
|
|
|
1444
1425
|
return;
|
|
1445
1426
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
1446
1427
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1428
|
+
if (channel.isDrum) {
|
|
1429
|
+
const audioContext = this.audioContext;
|
|
1430
|
+
const { gainL, gainR } = this.createChannelAudioNodes(audioContext);
|
|
1431
|
+
channel.keyBasedGainLs[noteNumber] = gainL;
|
|
1432
|
+
channel.keyBasedGainRs[noteNumber] = gainR;
|
|
1433
|
+
note.volumeEnvelopeNode.connect(gainL);
|
|
1434
|
+
note.volumeEnvelopeNode.connect(gainR);
|
|
1435
|
+
}
|
|
1436
|
+
else {
|
|
1437
|
+
note.volumeEnvelopeNode.connect(channel.gainL);
|
|
1438
|
+
note.volumeEnvelopeNode.connect(channel.gainR);
|
|
1439
|
+
}
|
|
1450
1440
|
if (0.5 <= channel.state.sustainPedal) {
|
|
1451
1441
|
channel.sustainNotes.push(note);
|
|
1452
1442
|
}
|
|
@@ -1455,31 +1445,6 @@ class Midy {
|
|
|
1455
1445
|
const scheduledNotes = channel.scheduledNotes;
|
|
1456
1446
|
note.index = scheduledNotes.length;
|
|
1457
1447
|
scheduledNotes.push(note);
|
|
1458
|
-
if (this.isDrumNoteOffException(channel, noteNumber)) {
|
|
1459
|
-
const stopTime = startTime + note.bufferSource.buffer.duration;
|
|
1460
|
-
const promise = new Promise((resolve) => {
|
|
1461
|
-
note.bufferSource.onended = () => {
|
|
1462
|
-
scheduledNotes[note.index] = undefined;
|
|
1463
|
-
this.disconnectNote(note);
|
|
1464
|
-
resolve();
|
|
1465
|
-
};
|
|
1466
|
-
note.bufferSource.stop(stopTime);
|
|
1467
|
-
});
|
|
1468
|
-
this.notePromises.push(promise);
|
|
1469
|
-
}
|
|
1470
|
-
else if (noteOffEvent) {
|
|
1471
|
-
if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
1472
|
-
const portamentoTime = this.getPortamentoTime(channel, note);
|
|
1473
|
-
const portamentoEndTime = startTime + portamentoTime;
|
|
1474
|
-
const notePromise = this.scheduleNoteOff(channelNumber, note, 0, // velocity
|
|
1475
|
-
Math.max(noteOffEvent.startTime, portamentoEndTime), false);
|
|
1476
|
-
this.notePromises.push(notePromise);
|
|
1477
|
-
}
|
|
1478
|
-
else {
|
|
1479
|
-
const notePromise = this.scheduleNoteOff(channelNumber, note, noteOffEvent.velocity, noteOffEvent.startTime, false);
|
|
1480
|
-
this.notePromises.push(notePromise);
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
1448
|
}
|
|
1484
1449
|
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1485
1450
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -1489,9 +1454,6 @@ class Midy {
|
|
|
1489
1454
|
note.bufferSource.disconnect();
|
|
1490
1455
|
note.filterNode.disconnect();
|
|
1491
1456
|
note.volumeEnvelopeNode.disconnect();
|
|
1492
|
-
note.volumeNode.disconnect();
|
|
1493
|
-
note.gainL.disconnect();
|
|
1494
|
-
note.gainR.disconnect();
|
|
1495
1457
|
if (note.modulationDepth) {
|
|
1496
1458
|
note.volumeDepth.disconnect();
|
|
1497
1459
|
note.modulationDepth.disconnect();
|
|
@@ -1508,42 +1470,48 @@ class Midy {
|
|
|
1508
1470
|
note.chorusEffectsSend.disconnect();
|
|
1509
1471
|
}
|
|
1510
1472
|
}
|
|
1511
|
-
|
|
1473
|
+
releaseNote(channel, note, endTime) {
|
|
1474
|
+
const volRelease = endTime +
|
|
1475
|
+
note.voiceParams.volRelease * channel.state.releaseTime * 2;
|
|
1476
|
+
const modRelease = endTime + note.voiceParams.modRelease;
|
|
1477
|
+
const stopTime = Math.min(volRelease, modRelease);
|
|
1478
|
+
note.filterNode.frequency
|
|
1479
|
+
.cancelScheduledValues(endTime)
|
|
1480
|
+
.linearRampToValueAtTime(0, modRelease);
|
|
1512
1481
|
note.volumeEnvelopeNode.gain
|
|
1513
1482
|
.cancelScheduledValues(endTime)
|
|
1514
|
-
.linearRampToValueAtTime(0,
|
|
1515
|
-
note.ending = true;
|
|
1516
|
-
this.scheduleTask(() => {
|
|
1517
|
-
note.bufferSource.loop = false;
|
|
1518
|
-
}, stopTime);
|
|
1483
|
+
.linearRampToValueAtTime(0, volRelease);
|
|
1519
1484
|
return new Promise((resolve) => {
|
|
1520
|
-
|
|
1521
|
-
|
|
1485
|
+
this.scheduleTask(() => {
|
|
1486
|
+
const bufferSource = note.bufferSource;
|
|
1487
|
+
bufferSource.loop = false;
|
|
1488
|
+
bufferSource.stop(stopTime);
|
|
1522
1489
|
this.disconnectNote(note);
|
|
1490
|
+
channel.scheduledNotes[note.index] = undefined;
|
|
1523
1491
|
resolve();
|
|
1524
|
-
};
|
|
1525
|
-
note.bufferSource.stop(stopTime);
|
|
1492
|
+
}, stopTime);
|
|
1526
1493
|
});
|
|
1527
1494
|
}
|
|
1528
|
-
scheduleNoteOff(channelNumber,
|
|
1495
|
+
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
|
|
1529
1496
|
const channel = this.channels[channelNumber];
|
|
1530
|
-
if (this.isDrumNoteOffException(channel, note.noteNumber))
|
|
1531
|
-
return;
|
|
1532
1497
|
const state = channel.state;
|
|
1533
1498
|
if (!force) {
|
|
1534
|
-
if (
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1499
|
+
if (channel.isDrum) {
|
|
1500
|
+
if (!this.isLoopDrum(channel, noteNumber))
|
|
1501
|
+
return;
|
|
1502
|
+
}
|
|
1503
|
+
else {
|
|
1504
|
+
if (0.5 <= state.sustainPedal)
|
|
1505
|
+
return;
|
|
1506
|
+
if (0.5 <= state.sostenutoPedal)
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1538
1509
|
}
|
|
1539
|
-
const
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
note.
|
|
1543
|
-
|
|
1544
|
-
.linearRampToValueAtTime(0, modRelease);
|
|
1545
|
-
const stopTime = Math.min(volRelease, modRelease);
|
|
1546
|
-
return this.stopNote(channel, note, endTime, stopTime);
|
|
1510
|
+
const note = this.findNoteOffTarget(channel, noteNumber);
|
|
1511
|
+
if (!note)
|
|
1512
|
+
return;
|
|
1513
|
+
note.ending = true;
|
|
1514
|
+
this.releaseNote(channel, note, endTime);
|
|
1547
1515
|
}
|
|
1548
1516
|
findNoteOffTarget(channel, noteNumber) {
|
|
1549
1517
|
const scheduledNotes = channel.scheduledNotes;
|
|
@@ -1560,16 +1528,14 @@ class Midy {
|
|
|
1560
1528
|
}
|
|
1561
1529
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1562
1530
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1563
|
-
|
|
1564
|
-
const note = this.findNoteOffTarget(channel, noteNumber);
|
|
1565
|
-
return this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, false);
|
|
1531
|
+
return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false);
|
|
1566
1532
|
}
|
|
1567
1533
|
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
1568
1534
|
const velocity = halfVelocity * 2;
|
|
1569
1535
|
const channel = this.channels[channelNumber];
|
|
1570
1536
|
const promises = [];
|
|
1571
1537
|
for (let i = 0; i < channel.sustainNotes.length; i++) {
|
|
1572
|
-
const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i], velocity, scheduleTime);
|
|
1538
|
+
const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
|
|
1573
1539
|
promises.push(promise);
|
|
1574
1540
|
}
|
|
1575
1541
|
channel.sustainNotes = [];
|
|
@@ -1583,7 +1549,7 @@ class Midy {
|
|
|
1583
1549
|
channel.state.sostenutoPedal = 0;
|
|
1584
1550
|
for (let i = 0; i < sostenutoNotes.length; i++) {
|
|
1585
1551
|
const note = sostenutoNotes[i];
|
|
1586
|
-
const promise = this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime);
|
|
1552
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime);
|
|
1587
1553
|
promises.push(promise);
|
|
1588
1554
|
}
|
|
1589
1555
|
channel.sostenutoNotes = [];
|
|
@@ -1613,10 +1579,10 @@ class Midy {
|
|
|
1613
1579
|
}
|
|
1614
1580
|
handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure, scheduleTime) {
|
|
1615
1581
|
const channel = this.channels[channelNumber];
|
|
1616
|
-
channel.state.polyphonicKeyPressure = pressure / 127;
|
|
1617
1582
|
const table = channel.polyphonicKeyPressureTable;
|
|
1618
1583
|
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
1619
1584
|
if (note.noteNumber === noteNumber) {
|
|
1585
|
+
note.pressure = pressure;
|
|
1620
1586
|
this.setControllerParameters(channel, note, table);
|
|
1621
1587
|
}
|
|
1622
1588
|
});
|
|
@@ -1644,9 +1610,10 @@ class Midy {
|
|
|
1644
1610
|
const prev = channel.state.channelPressure;
|
|
1645
1611
|
const next = value / 127;
|
|
1646
1612
|
channel.state.channelPressure = next;
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1613
|
+
const channelPressureRaw = channel.channelPressureTable[0];
|
|
1614
|
+
if (0 <= channelPressureRaw) {
|
|
1615
|
+
const channelPressureDepth = (channelPressureRaw - 64) / 37.5; // 2400 / 64;
|
|
1616
|
+
channel.detune += channelPressureDepth * (next - prev);
|
|
1650
1617
|
}
|
|
1651
1618
|
const table = channel.channelPressureTable;
|
|
1652
1619
|
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
@@ -1706,10 +1673,15 @@ class Midy {
|
|
|
1706
1673
|
.setValueAtTime(volumeDepth, scheduleTime);
|
|
1707
1674
|
}
|
|
1708
1675
|
setReverbEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1676
|
+
let value = note.voiceParams.reverbEffectsSend;
|
|
1677
|
+
if (channel.isDrum) {
|
|
1678
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
|
|
1679
|
+
if (0 <= keyBasedValue) {
|
|
1680
|
+
value *= keyBasedValue / 127 / channel.state.reverbSendLevel;
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1709
1683
|
if (0 < prevValue) {
|
|
1710
|
-
if (0 <
|
|
1711
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
|
|
1712
|
-
const value = note.voiceParams.reverbEffectsSend + keyBasedValue;
|
|
1684
|
+
if (0 < value) {
|
|
1713
1685
|
note.reverbEffectsSend.gain
|
|
1714
1686
|
.cancelScheduledValues(scheduleTime)
|
|
1715
1687
|
.setValueAtTime(value, scheduleTime);
|
|
@@ -1719,22 +1691,27 @@ class Midy {
|
|
|
1719
1691
|
}
|
|
1720
1692
|
}
|
|
1721
1693
|
else {
|
|
1722
|
-
if (0 <
|
|
1694
|
+
if (0 < value) {
|
|
1723
1695
|
if (!note.reverbEffectsSend) {
|
|
1724
1696
|
note.reverbEffectsSend = new GainNode(this.audioContext, {
|
|
1725
|
-
gain:
|
|
1697
|
+
gain: value,
|
|
1726
1698
|
});
|
|
1727
|
-
note.
|
|
1699
|
+
note.volumeEnvelopeNode.connect(note.reverbEffectsSend);
|
|
1728
1700
|
}
|
|
1729
1701
|
note.reverbEffectsSend.connect(this.reverbEffect.input);
|
|
1730
1702
|
}
|
|
1731
1703
|
}
|
|
1732
1704
|
}
|
|
1733
1705
|
setChorusEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1706
|
+
let value = note.voiceParams.chorusEffectsSend;
|
|
1707
|
+
if (channel.isDrum) {
|
|
1708
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
|
|
1709
|
+
if (0 <= keyBasedValue) {
|
|
1710
|
+
value *= keyBasedValue / 127 / channel.state.chorusSendLevel;
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1734
1713
|
if (0 < prevValue) {
|
|
1735
|
-
if (0 <
|
|
1736
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
|
|
1737
|
-
const value = note.voiceParams.chorusEffectsSend + keyBasedValue;
|
|
1714
|
+
if (0 < vaule) {
|
|
1738
1715
|
note.chorusEffectsSend.gain
|
|
1739
1716
|
.cancelScheduledValues(scheduleTime)
|
|
1740
1717
|
.setValueAtTime(value, scheduleTime);
|
|
@@ -1744,12 +1721,12 @@ class Midy {
|
|
|
1744
1721
|
}
|
|
1745
1722
|
}
|
|
1746
1723
|
else {
|
|
1747
|
-
if (0 <
|
|
1724
|
+
if (0 < value) {
|
|
1748
1725
|
if (!note.chorusEffectsSend) {
|
|
1749
1726
|
note.chorusEffectsSend = new GainNode(this.audioContext, {
|
|
1750
|
-
gain:
|
|
1727
|
+
gain: value,
|
|
1751
1728
|
});
|
|
1752
|
-
note.
|
|
1729
|
+
note.volumeEnvelopeNode.connect(note.chorusEffectsSend);
|
|
1753
1730
|
}
|
|
1754
1731
|
note.chorusEffectsSend.connect(this.chorusEffect.input);
|
|
1755
1732
|
}
|
|
@@ -1824,21 +1801,22 @@ class Midy {
|
|
|
1824
1801
|
},
|
|
1825
1802
|
};
|
|
1826
1803
|
}
|
|
1827
|
-
getControllerState(channel, noteNumber, velocity) {
|
|
1804
|
+
getControllerState(channel, noteNumber, velocity, polyphonicKeyPressure) {
|
|
1828
1805
|
const state = new Float32Array(channel.state.array.length);
|
|
1829
1806
|
state.set(channel.state.array);
|
|
1830
1807
|
state[2] = velocity / 127;
|
|
1831
1808
|
state[3] = noteNumber / 127;
|
|
1832
|
-
state[10] =
|
|
1809
|
+
state[10] = polyphonicKeyPressure / 127;
|
|
1833
1810
|
state[13] = state.channelPressure / 127;
|
|
1834
1811
|
return state;
|
|
1835
1812
|
}
|
|
1836
1813
|
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
1837
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1838
|
-
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
|
|
1814
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1815
|
+
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity, note.pressure);
|
|
1839
1816
|
const voiceParams = note.voice.getParams(controllerType, controllerState);
|
|
1840
|
-
let
|
|
1841
|
-
let
|
|
1817
|
+
let applyVolumeEnvelope = false;
|
|
1818
|
+
let applyFilterEnvelope = false;
|
|
1819
|
+
let applyPitchEnvelope = false;
|
|
1842
1820
|
for (const [key, value] of Object.entries(voiceParams)) {
|
|
1843
1821
|
const prevValue = note.voiceParams[key];
|
|
1844
1822
|
if (value === prevValue)
|
|
@@ -1847,37 +1825,23 @@ class Midy {
|
|
|
1847
1825
|
if (key in this.voiceParamsHandlers) {
|
|
1848
1826
|
this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
|
|
1849
1827
|
}
|
|
1850
|
-
else
|
|
1851
|
-
if (
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
if (key in voiceParams)
|
|
1858
|
-
noteVoiceParams[key] = voiceParams[key];
|
|
1859
|
-
}
|
|
1860
|
-
if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
1861
|
-
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
1862
|
-
}
|
|
1863
|
-
else {
|
|
1864
|
-
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
1865
|
-
}
|
|
1866
|
-
this.setPitchEnvelope(note, scheduleTime);
|
|
1867
|
-
}
|
|
1868
|
-
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1869
|
-
if (appliedVolumeEnvelope)
|
|
1870
|
-
continue;
|
|
1871
|
-
appliedVolumeEnvelope = true;
|
|
1872
|
-
const noteVoiceParams = note.voiceParams;
|
|
1873
|
-
for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
|
|
1874
|
-
const key = volumeEnvelopeKeys[i];
|
|
1875
|
-
if (key in voiceParams)
|
|
1876
|
-
noteVoiceParams[key] = voiceParams[key];
|
|
1877
|
-
}
|
|
1878
|
-
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1828
|
+
else {
|
|
1829
|
+
if (volumeEnvelopeKeySet.has(key))
|
|
1830
|
+
applyVolumeEnvelope = true;
|
|
1831
|
+
if (filterEnvelopeKeySet.has(key))
|
|
1832
|
+
applyFilterEnvelope = true;
|
|
1833
|
+
if (pitchEnvelopeKeySet.has(key))
|
|
1834
|
+
applyPitchEnvelope = true;
|
|
1879
1835
|
}
|
|
1880
1836
|
}
|
|
1837
|
+
if (applyVolumeEnvelope) {
|
|
1838
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1839
|
+
}
|
|
1840
|
+
if (applyFilterEnvelope) {
|
|
1841
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
1842
|
+
}
|
|
1843
|
+
if (applyPitchEnvelope)
|
|
1844
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1881
1845
|
});
|
|
1882
1846
|
}
|
|
1883
1847
|
createControlChangeHandlers() {
|
|
@@ -1924,7 +1888,7 @@ class Midy {
|
|
|
1924
1888
|
handler.call(this, channelNumber, value, scheduleTime);
|
|
1925
1889
|
const channel = this.channels[channelNumber];
|
|
1926
1890
|
this.applyVoiceParams(channel, controllerType + 128, scheduleTime);
|
|
1927
|
-
this.applyControlTable(channel, controllerType);
|
|
1891
|
+
this.applyControlTable(channel, controllerType, scheduleTime);
|
|
1928
1892
|
}
|
|
1929
1893
|
else {
|
|
1930
1894
|
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
@@ -1935,7 +1899,7 @@ class Midy {
|
|
|
1935
1899
|
}
|
|
1936
1900
|
updateModulation(channel, scheduleTime) {
|
|
1937
1901
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1938
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1902
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1939
1903
|
if (note.modulationDepth) {
|
|
1940
1904
|
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1941
1905
|
}
|
|
@@ -1954,7 +1918,7 @@ class Midy {
|
|
|
1954
1918
|
this.updateModulation(channel, scheduleTime);
|
|
1955
1919
|
}
|
|
1956
1920
|
updatePortamento(channel, scheduleTime) {
|
|
1957
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1921
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1958
1922
|
if (0.5 <= channel.state.portamento) {
|
|
1959
1923
|
if (0 <= note.portamentoNoteNumber) {
|
|
1960
1924
|
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
@@ -1981,22 +1945,12 @@ class Midy {
|
|
|
1981
1945
|
return;
|
|
1982
1946
|
this.updatePortamento(channel, scheduleTime);
|
|
1983
1947
|
}
|
|
1984
|
-
setKeyBasedVolume(channel, scheduleTime) {
|
|
1985
|
-
this.processScheduledNotes(channel, (note) => {
|
|
1986
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1987
|
-
if (keyBasedValue !== 0) {
|
|
1988
|
-
note.volumeNode.gain
|
|
1989
|
-
.cancelScheduledValues(scheduleTime)
|
|
1990
|
-
.setValueAtTime(1 + keyBasedValue, scheduleTime);
|
|
1991
|
-
}
|
|
1992
|
-
});
|
|
1993
|
-
}
|
|
1994
1948
|
setVolume(channelNumber, volume, scheduleTime) {
|
|
1995
1949
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1996
1950
|
const channel = this.channels[channelNumber];
|
|
1997
1951
|
channel.state.volume = volume / 127;
|
|
1998
1952
|
this.updateChannelVolume(channel, scheduleTime);
|
|
1999
|
-
this.
|
|
1953
|
+
this.updateKeyBasedVolume(channel, scheduleTime);
|
|
2000
1954
|
}
|
|
2001
1955
|
panToGain(pan) {
|
|
2002
1956
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -2005,26 +1959,12 @@ class Midy {
|
|
|
2005
1959
|
gainRight: Math.sin(theta),
|
|
2006
1960
|
};
|
|
2007
1961
|
}
|
|
2008
|
-
setKeyBasedPan(channel, scheduleTime) {
|
|
2009
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2010
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
2011
|
-
if (keyBasedValue !== 0) {
|
|
2012
|
-
const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
|
|
2013
|
-
note.gainL.gain
|
|
2014
|
-
.cancelScheduledValues(scheduleTime)
|
|
2015
|
-
.setValueAtTime(gainLeft, scheduleTime);
|
|
2016
|
-
note.gainR.gain
|
|
2017
|
-
.cancelScheduledValues(scheduleTime)
|
|
2018
|
-
.setValueAtTime(gainRight, scheduleTime);
|
|
2019
|
-
}
|
|
2020
|
-
});
|
|
2021
|
-
}
|
|
2022
1962
|
setPan(channelNumber, pan, scheduleTime) {
|
|
2023
1963
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2024
1964
|
const channel = this.channels[channelNumber];
|
|
2025
1965
|
channel.state.pan = pan / 127;
|
|
2026
1966
|
this.updateChannelVolume(channel, scheduleTime);
|
|
2027
|
-
this.
|
|
1967
|
+
this.updateKeyBasedVolume(channel, scheduleTime);
|
|
2028
1968
|
}
|
|
2029
1969
|
setExpression(channelNumber, expression, scheduleTime) {
|
|
2030
1970
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -2050,6 +1990,34 @@ class Midy {
|
|
|
2050
1990
|
.cancelScheduledValues(scheduleTime)
|
|
2051
1991
|
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
2052
1992
|
}
|
|
1993
|
+
updateKeyBasedVolume(channel, scheduleTime) {
|
|
1994
|
+
if (!channel.isDrum)
|
|
1995
|
+
return;
|
|
1996
|
+
const state = channel.state;
|
|
1997
|
+
const defaultVolume = state.volume * state.expression;
|
|
1998
|
+
const defaultPan = state.pan;
|
|
1999
|
+
for (let i = 0; i < 128; i++) {
|
|
2000
|
+
const gainL = channel.keyBasedGainLs[i];
|
|
2001
|
+
const gainR = channel.keyBasedGainLs[i];
|
|
2002
|
+
if (!gainL)
|
|
2003
|
+
continue;
|
|
2004
|
+
if (!gainR)
|
|
2005
|
+
continue;
|
|
2006
|
+
const keyBasedVolume = this.getKeyBasedInstrumentControlValue(channel, i, 7);
|
|
2007
|
+
const volume = (0 <= keyBasedVolume)
|
|
2008
|
+
? defaultVolume * keyBasedVolume / 64
|
|
2009
|
+
: defaultVolume;
|
|
2010
|
+
const keyBasedPan = this.getKeyBasedInstrumentControlValue(channel, i, 10);
|
|
2011
|
+
const pan = (0 <= keyBasedPan) ? keyBasedPan / 127 : defaultPan;
|
|
2012
|
+
const { gainLeft, gainRight } = this.panToGain(pan);
|
|
2013
|
+
gainL.gain
|
|
2014
|
+
.cancelScheduledValues(scheduleTime)
|
|
2015
|
+
.setValueAtTime(volume * gainLeft, scheduleTime);
|
|
2016
|
+
gainR.gain
|
|
2017
|
+
.cancelScheduledValues(scheduleTime)
|
|
2018
|
+
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2053
2021
|
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
2054
2022
|
const channel = this.channels[channelNumber];
|
|
2055
2023
|
if (channel.isDrum)
|
|
@@ -2057,7 +2025,7 @@ class Midy {
|
|
|
2057
2025
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2058
2026
|
channel.state.sustainPedal = value / 127;
|
|
2059
2027
|
if (64 <= value) {
|
|
2060
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2028
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2061
2029
|
channel.sustainNotes.push(note);
|
|
2062
2030
|
});
|
|
2063
2031
|
}
|
|
@@ -2090,6 +2058,9 @@ class Midy {
|
|
|
2090
2058
|
this.releaseSostenutoPedal(channelNumber, value, scheduleTime);
|
|
2091
2059
|
}
|
|
2092
2060
|
}
|
|
2061
|
+
getSoftPedalFactor(channel, note) {
|
|
2062
|
+
return 1 - (0.1 + (note.noteNumber / 127) * 0.2) * channel.state.softPedal;
|
|
2063
|
+
}
|
|
2093
2064
|
setSoftPedal(channelNumber, softPedal, scheduleTime) {
|
|
2094
2065
|
const channel = this.channels[channelNumber];
|
|
2095
2066
|
if (channel.isDrum)
|
|
@@ -2097,7 +2068,7 @@ class Midy {
|
|
|
2097
2068
|
const state = channel.state;
|
|
2098
2069
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2099
2070
|
state.softPedal = softPedal / 127;
|
|
2100
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2071
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2101
2072
|
if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
2102
2073
|
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2103
2074
|
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
@@ -2115,7 +2086,7 @@ class Midy {
|
|
|
2115
2086
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2116
2087
|
const state = channel.state;
|
|
2117
2088
|
state.filterResonance = filterResonance / 127;
|
|
2118
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2089
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2119
2090
|
const Q = note.voiceParams.initialFilterQ / 5 * state.filterResonance;
|
|
2120
2091
|
note.filterNode.Q.setValueAtTime(Q, scheduleTime);
|
|
2121
2092
|
});
|
|
@@ -2133,7 +2104,7 @@ class Midy {
|
|
|
2133
2104
|
return;
|
|
2134
2105
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2135
2106
|
channel.state.attackTime = attackTime / 127;
|
|
2136
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2107
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2137
2108
|
if (note.startTime < scheduleTime)
|
|
2138
2109
|
return false;
|
|
2139
2110
|
this.setVolumeEnvelope(channel, note);
|
|
@@ -2146,7 +2117,7 @@ class Midy {
|
|
|
2146
2117
|
const state = channel.state;
|
|
2147
2118
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2148
2119
|
state.brightness = brightness / 127;
|
|
2149
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2120
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2150
2121
|
if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
2151
2122
|
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2152
2123
|
}
|
|
@@ -2161,7 +2132,7 @@ class Midy {
|
|
|
2161
2132
|
return;
|
|
2162
2133
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2163
2134
|
channel.state.decayTime = dacayTime / 127;
|
|
2164
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2135
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2165
2136
|
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
2166
2137
|
});
|
|
2167
2138
|
}
|
|
@@ -2173,7 +2144,7 @@ class Midy {
|
|
|
2173
2144
|
channel.state.vibratoRate = vibratoRate / 127;
|
|
2174
2145
|
if (channel.vibratoDepth <= 0)
|
|
2175
2146
|
return;
|
|
2176
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2147
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2177
2148
|
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
2178
2149
|
});
|
|
2179
2150
|
}
|
|
@@ -2185,12 +2156,12 @@ class Midy {
|
|
|
2185
2156
|
const prev = channel.state.vibratoDepth;
|
|
2186
2157
|
channel.state.vibratoDepth = vibratoDepth / 127;
|
|
2187
2158
|
if (0 < prev) {
|
|
2188
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2159
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2189
2160
|
this.setFreqVibLFO(channel, note, scheduleTime);
|
|
2190
2161
|
});
|
|
2191
2162
|
}
|
|
2192
2163
|
else {
|
|
2193
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2164
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2194
2165
|
this.startVibrato(channel, note, scheduleTime);
|
|
2195
2166
|
});
|
|
2196
2167
|
}
|
|
@@ -2202,7 +2173,7 @@ class Midy {
|
|
|
2202
2173
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2203
2174
|
channel.state.vibratoDelay = vibratoDelay / 127;
|
|
2204
2175
|
if (0 < channel.state.vibratoDepth) {
|
|
2205
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2176
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2206
2177
|
this.startVibrato(channel, note, scheduleTime);
|
|
2207
2178
|
});
|
|
2208
2179
|
}
|
|
@@ -2220,16 +2191,17 @@ class Midy {
|
|
|
2220
2191
|
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
2221
2192
|
}
|
|
2222
2193
|
else {
|
|
2223
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2194
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2224
2195
|
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
2225
2196
|
return false;
|
|
2226
|
-
note.reverbEffectsSend
|
|
2197
|
+
if (note.reverbEffectsSend)
|
|
2198
|
+
note.reverbEffectsSend.disconnect();
|
|
2227
2199
|
});
|
|
2228
2200
|
}
|
|
2229
2201
|
}
|
|
2230
2202
|
else {
|
|
2231
2203
|
if (0 < reverbSendLevel) {
|
|
2232
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2204
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2233
2205
|
this.setReverbEffectsSend(channel, note, 0, scheduleTime);
|
|
2234
2206
|
});
|
|
2235
2207
|
state.reverbSendLevel = reverbSendLevel / 127;
|
|
@@ -2252,16 +2224,17 @@ class Midy {
|
|
|
2252
2224
|
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
2253
2225
|
}
|
|
2254
2226
|
else {
|
|
2255
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2227
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2256
2228
|
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
2257
2229
|
return false;
|
|
2258
|
-
note.chorusEffectsSend
|
|
2230
|
+
if (note.chorusEffectsSend)
|
|
2231
|
+
note.chorusEffectsSend.disconnect();
|
|
2259
2232
|
});
|
|
2260
2233
|
}
|
|
2261
2234
|
}
|
|
2262
2235
|
else {
|
|
2263
2236
|
if (0 < chorusSendLevel) {
|
|
2264
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2237
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2265
2238
|
this.setChorusEffectsSend(channel, note, 0, scheduleTime);
|
|
2266
2239
|
});
|
|
2267
2240
|
state.chorusSendLevel = chorusSendLevel / 127;
|
|
@@ -2676,8 +2649,7 @@ class Midy {
|
|
|
2676
2649
|
setReverbType(type) {
|
|
2677
2650
|
this.reverb.time = this.getReverbTimeFromType(type);
|
|
2678
2651
|
this.reverb.feedback = (type === 8) ? 0.9 : 0.8;
|
|
2679
|
-
|
|
2680
|
-
this.reverbEffect = options.reverbAlgorithm(audioContext);
|
|
2652
|
+
this.reverbEffect = this.createReverbEffect(this.audioContext);
|
|
2681
2653
|
}
|
|
2682
2654
|
getReverbTimeFromType(type) {
|
|
2683
2655
|
switch (type) {
|
|
@@ -2699,8 +2671,7 @@ class Midy {
|
|
|
2699
2671
|
}
|
|
2700
2672
|
setReverbTime(value) {
|
|
2701
2673
|
this.reverb.time = this.getReverbTime(value);
|
|
2702
|
-
|
|
2703
|
-
this.reverbEffect = options.reverbAlgorithm(audioContext);
|
|
2674
|
+
this.reverbEffect = this.createReverbEffect(this.audioContext);
|
|
2704
2675
|
}
|
|
2705
2676
|
getReverbTime(value) {
|
|
2706
2677
|
return Math.exp((value - 40) * 0.025);
|
|
@@ -2895,65 +2866,88 @@ class Midy {
|
|
|
2895
2866
|
}
|
|
2896
2867
|
}
|
|
2897
2868
|
getPitchControl(channel, note) {
|
|
2898
|
-
const
|
|
2869
|
+
const polyphonicKeyPressureRaw = channel.polyphonicKeyPressureTable[0];
|
|
2870
|
+
if (polyphonicKeyPressureRaw < 0)
|
|
2871
|
+
return 0;
|
|
2872
|
+
const polyphonicKeyPressure = (polyphonicKeyPressureRaw - 64) *
|
|
2899
2873
|
note.pressure;
|
|
2900
2874
|
return polyphonicKeyPressure * note.pressure / 37.5; // 2400 / 64;
|
|
2901
2875
|
}
|
|
2902
2876
|
getFilterCutoffControl(channel, note) {
|
|
2903
|
-
const
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2877
|
+
const channelPressureRaw = channel.channelPressureTable[1];
|
|
2878
|
+
const channelPressure = (0 <= channelPressureRaw)
|
|
2879
|
+
? (channelPressureRaw - 64) * channel.state.channelPressure
|
|
2880
|
+
: 0;
|
|
2881
|
+
const polyphonicKeyPressureRaw = channel.polyphonicKeyPressureTable[1];
|
|
2882
|
+
const polyphonicKeyPressure = (0 <= polyphonicKeyPressureRaw)
|
|
2883
|
+
? (polyphonicKeyPressureRaw - 64) * note.pressure
|
|
2884
|
+
: 0;
|
|
2907
2885
|
return (channelPressure + polyphonicKeyPressure) * 15;
|
|
2908
2886
|
}
|
|
2909
2887
|
getAmplitudeControl(channel, note) {
|
|
2910
|
-
const
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2888
|
+
const channelPressureRaw = channel.channelPressureTable[2];
|
|
2889
|
+
const channelPressure = (0 <= channelPressureRaw)
|
|
2890
|
+
? channelPressureRaw * channel.state.channelPressure
|
|
2891
|
+
: 0;
|
|
2892
|
+
const polyphonicKeyPressureRaw = channel.polyphonicKeyPressureTable[2];
|
|
2893
|
+
const polyphonicKeyPressure = (0 <= polyphonicKeyPressureRaw)
|
|
2894
|
+
? polyphonicKeyPressureRaw * note.pressure
|
|
2895
|
+
: 0;
|
|
2914
2896
|
return (channelPressure + polyphonicKeyPressure) / 128;
|
|
2915
2897
|
}
|
|
2916
2898
|
getLFOPitchDepth(channel, note) {
|
|
2917
|
-
const
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2899
|
+
const channelPressureRaw = channel.channelPressureTable[3];
|
|
2900
|
+
const channelPressure = (0 <= channelPressureRaw)
|
|
2901
|
+
? channelPressureRaw * channel.state.channelPressure
|
|
2902
|
+
: 0;
|
|
2903
|
+
const polyphonicKeyPressureRaw = channel.polyphonicKeyPressureTable[3];
|
|
2904
|
+
const polyphonicKeyPressure = (0 <= polyphonicKeyPressureRaw)
|
|
2905
|
+
? polyphonicKeyPressureRaw * note.pressure
|
|
2906
|
+
: 0;
|
|
2921
2907
|
return (channelPressure + polyphonicKeyPressure) / 254 * 600;
|
|
2922
2908
|
}
|
|
2923
2909
|
getLFOFilterDepth(channel, note) {
|
|
2924
|
-
const
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2910
|
+
const channelPressureRaw = channel.channelPressureTable[4];
|
|
2911
|
+
const channelPressure = (0 <= channelPressureRaw)
|
|
2912
|
+
? channelPressureRaw * channel.state.channelPressure
|
|
2913
|
+
: 0;
|
|
2914
|
+
const polyphonicKeyPressureRaw = channel.polyphonicKeyPressureTable[4];
|
|
2915
|
+
const polyphonicKeyPressure = (0 <= polyphonicKeyPressureRaw)
|
|
2916
|
+
? polyphonicKeyPressureRaw * note.pressure
|
|
2917
|
+
: 0;
|
|
2928
2918
|
return (channelPressure + polyphonicKeyPressure) / 254 * 2400;
|
|
2929
2919
|
}
|
|
2930
2920
|
getLFOAmplitudeDepth(channel, note) {
|
|
2931
|
-
const
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2921
|
+
const channelPressureRaw = channel.channelPressureTable[5];
|
|
2922
|
+
const channelPressure = (0 <= channelPressureRaw)
|
|
2923
|
+
? channelPressureRaw * channel.state.channelPressure
|
|
2924
|
+
: 0;
|
|
2925
|
+
const polyphonicKeyPressureRaw = channel.polyphonicKeyPressureTable[5];
|
|
2926
|
+
const polyphonicKeyPressure = (0 <= polyphonicKeyPressureRaw)
|
|
2927
|
+
? polyphonicKeyPressureRaw * note.pressure
|
|
2928
|
+
: 0;
|
|
2935
2929
|
return (channelPressure + polyphonicKeyPressure) / 254;
|
|
2936
2930
|
}
|
|
2937
2931
|
setControllerParameters(channel, note, table) {
|
|
2938
|
-
if (table[0]
|
|
2932
|
+
if (0 <= table[0])
|
|
2939
2933
|
this.updateDetune(channel, note);
|
|
2940
2934
|
if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
|
|
2941
|
-
if (table[1]
|
|
2935
|
+
if (0 <= table[1])
|
|
2942
2936
|
this.setPortamentoFilterEnvelope(channel, note);
|
|
2943
|
-
if (table[2]
|
|
2937
|
+
if (0 <= table[2])
|
|
2944
2938
|
this.setPortamentoVolumeEnvelope(channel, note);
|
|
2945
2939
|
}
|
|
2946
2940
|
else {
|
|
2947
|
-
if (table[1]
|
|
2941
|
+
if (0 <= table[1])
|
|
2948
2942
|
this.setFilterEnvelope(channel, note);
|
|
2949
|
-
if (table[2]
|
|
2943
|
+
if (0 <= table[2])
|
|
2950
2944
|
this.setVolumeEnvelope(channel, note);
|
|
2951
2945
|
}
|
|
2952
|
-
if (table[3]
|
|
2946
|
+
if (0 <= table[3])
|
|
2953
2947
|
this.setModLfoToPitch(channel, note);
|
|
2954
|
-
if (table[4]
|
|
2948
|
+
if (0 <= table[4])
|
|
2955
2949
|
this.setModLfoToFilterFc(channel, note);
|
|
2956
|
-
if (table[5]
|
|
2950
|
+
if (0 <= table[5])
|
|
2957
2951
|
this.setModLfoToVolume(channel, note);
|
|
2958
2952
|
}
|
|
2959
2953
|
handlePressureSysEx(data, tableName) {
|
|
@@ -2969,26 +2963,15 @@ class Midy {
|
|
|
2969
2963
|
}
|
|
2970
2964
|
}
|
|
2971
2965
|
initControlTable() {
|
|
2972
|
-
const
|
|
2966
|
+
const ccCount = 128;
|
|
2973
2967
|
const slotSize = 6;
|
|
2974
|
-
|
|
2975
|
-
return this.resetControlTable(table);
|
|
2968
|
+
return new Int8Array(ccCount * slotSize).fill(-1);
|
|
2976
2969
|
}
|
|
2977
|
-
|
|
2978
|
-
const channelCount = 128;
|
|
2979
|
-
const slotSize = 6;
|
|
2980
|
-
const defaultValues = [64, 64, 64, 0, 0, 0];
|
|
2981
|
-
for (let ch = 0; ch < channelCount; ch++) {
|
|
2982
|
-
const offset = ch * slotSize;
|
|
2983
|
-
table.set(defaultValues, offset);
|
|
2984
|
-
}
|
|
2985
|
-
return table;
|
|
2986
|
-
}
|
|
2987
|
-
applyControlTable(channel, controllerType) {
|
|
2970
|
+
applyControlTable(channel, controllerType, scheduleTime) {
|
|
2988
2971
|
const slotSize = 6;
|
|
2989
2972
|
const offset = controllerType * slotSize;
|
|
2990
2973
|
const table = channel.controlTable.subarray(offset, offset + slotSize);
|
|
2991
|
-
this.processScheduledNotes(channel, (note) => {
|
|
2974
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2992
2975
|
this.setControllerParameters(channel, note, table);
|
|
2993
2976
|
});
|
|
2994
2977
|
}
|
|
@@ -3008,12 +2991,12 @@ class Midy {
|
|
|
3008
2991
|
getKeyBasedInstrumentControlValue(channel, keyNumber, controllerType) {
|
|
3009
2992
|
const index = keyNumber * 128 + controllerType;
|
|
3010
2993
|
const controlValue = channel.keyBasedInstrumentControlTable[index];
|
|
3011
|
-
return
|
|
2994
|
+
return controlValue;
|
|
3012
2995
|
}
|
|
3013
2996
|
handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
|
|
3014
2997
|
const channelNumber = data[4];
|
|
3015
2998
|
const channel = this.channels[channelNumber];
|
|
3016
|
-
if (channel.isDrum)
|
|
2999
|
+
if (!channel.isDrum)
|
|
3017
3000
|
return;
|
|
3018
3001
|
const keyNumber = data[5];
|
|
3019
3002
|
const table = channel.keyBasedInstrumentControlTable;
|
|
@@ -3021,7 +3004,7 @@ class Midy {
|
|
|
3021
3004
|
const controllerType = data[i];
|
|
3022
3005
|
const value = data[i + 1];
|
|
3023
3006
|
const index = keyNumber * 128 + controllerType;
|
|
3024
|
-
table[index] = value
|
|
3007
|
+
table[index] = value;
|
|
3025
3008
|
}
|
|
3026
3009
|
this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
|
|
3027
3010
|
}
|