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