@marmooo/midy 0.3.4 → 0.3.6
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/README.md +19 -11
- package/esm/midy-GM1.d.ts +14 -11
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +140 -106
- package/esm/midy-GM2.d.ts +26 -22
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +285 -279
- package/esm/midy-GMLite.d.ts +15 -11
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +139 -107
- package/esm/midy.d.ts +31 -27
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +304 -298
- package/package.json +2 -2
- package/script/midy-GM1.d.ts +14 -11
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +140 -106
- package/script/midy-GM2.d.ts +26 -22
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +285 -279
- package/script/midy-GMLite.d.ts +15 -11
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +139 -107
- package/script/midy.d.ts +31 -27
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +304 -298
package/script/midy.js
CHANGED
|
@@ -71,13 +71,13 @@ class Note {
|
|
|
71
71
|
writable: true,
|
|
72
72
|
value: void 0
|
|
73
73
|
});
|
|
74
|
-
Object.defineProperty(this, "
|
|
74
|
+
Object.defineProperty(this, "reverbSend", {
|
|
75
75
|
enumerable: true,
|
|
76
76
|
configurable: true,
|
|
77
77
|
writable: true,
|
|
78
78
|
value: void 0
|
|
79
79
|
});
|
|
80
|
-
Object.defineProperty(this, "
|
|
80
|
+
Object.defineProperty(this, "chorusSend", {
|
|
81
81
|
enumerable: true,
|
|
82
82
|
configurable: true,
|
|
83
83
|
writable: true,
|
|
@@ -332,13 +332,13 @@ class Midy {
|
|
|
332
332
|
writable: true,
|
|
333
333
|
value: this.initSoundFontTable()
|
|
334
334
|
});
|
|
335
|
-
Object.defineProperty(this, "
|
|
335
|
+
Object.defineProperty(this, "voiceCounter", {
|
|
336
336
|
enumerable: true,
|
|
337
337
|
configurable: true,
|
|
338
338
|
writable: true,
|
|
339
339
|
value: new Map()
|
|
340
340
|
});
|
|
341
|
-
Object.defineProperty(this, "
|
|
341
|
+
Object.defineProperty(this, "voiceCache", {
|
|
342
342
|
enumerable: true,
|
|
343
343
|
configurable: true,
|
|
344
344
|
writable: true,
|
|
@@ -380,17 +380,17 @@ class Midy {
|
|
|
380
380
|
writable: true,
|
|
381
381
|
value: []
|
|
382
382
|
});
|
|
383
|
-
Object.defineProperty(this, "
|
|
383
|
+
Object.defineProperty(this, "notePromises", {
|
|
384
384
|
enumerable: true,
|
|
385
385
|
configurable: true,
|
|
386
386
|
writable: true,
|
|
387
387
|
value: []
|
|
388
388
|
});
|
|
389
|
-
Object.defineProperty(this, "
|
|
389
|
+
Object.defineProperty(this, "instruments", {
|
|
390
390
|
enumerable: true,
|
|
391
391
|
configurable: true,
|
|
392
392
|
writable: true,
|
|
393
|
-
value:
|
|
393
|
+
value: new Set()
|
|
394
394
|
});
|
|
395
395
|
Object.defineProperty(this, "exclusiveClassNotes", {
|
|
396
396
|
enumerable: true,
|
|
@@ -435,13 +435,11 @@ class Midy {
|
|
|
435
435
|
const presetHeaders = soundFont.parsed.presetHeaders;
|
|
436
436
|
for (let i = 0; i < presetHeaders.length; i++) {
|
|
437
437
|
const presetHeader = presetHeaders[i];
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
banks.set(presetHeader.bank, index);
|
|
441
|
-
}
|
|
438
|
+
const banks = this.soundFontTable[presetHeader.preset];
|
|
439
|
+
banks.set(presetHeader.bank, index);
|
|
442
440
|
}
|
|
443
441
|
}
|
|
444
|
-
async
|
|
442
|
+
async toUint8Array(input) {
|
|
445
443
|
let uint8Array;
|
|
446
444
|
if (typeof input === "string") {
|
|
447
445
|
const response = await fetch(input);
|
|
@@ -454,23 +452,32 @@ class Midy {
|
|
|
454
452
|
else {
|
|
455
453
|
throw new TypeError("input must be a URL string or Uint8Array");
|
|
456
454
|
}
|
|
457
|
-
|
|
458
|
-
const soundFont = new soundfont_parser_1.SoundFont(parsed);
|
|
459
|
-
this.addSoundFont(soundFont);
|
|
455
|
+
return uint8Array;
|
|
460
456
|
}
|
|
461
|
-
async
|
|
462
|
-
|
|
463
|
-
if (
|
|
464
|
-
const
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
457
|
+
async loadSoundFont(input) {
|
|
458
|
+
this.voiceCounter.clear();
|
|
459
|
+
if (Array.isArray(input)) {
|
|
460
|
+
const promises = new Array(input.length);
|
|
461
|
+
for (let i = 0; i < input.length; i++) {
|
|
462
|
+
promises[i] = this.toUint8Array(input[i]);
|
|
463
|
+
}
|
|
464
|
+
const uint8Arrays = await Promise.all(promises);
|
|
465
|
+
for (let i = 0; i < uint8Arrays.length; i++) {
|
|
466
|
+
const parsed = (0, soundfont_parser_1.parse)(uint8Arrays[i]);
|
|
467
|
+
const soundFont = new soundfont_parser_1.SoundFont(parsed);
|
|
468
|
+
this.addSoundFont(soundFont);
|
|
469
|
+
}
|
|
470
470
|
}
|
|
471
471
|
else {
|
|
472
|
-
|
|
472
|
+
const uint8Array = await this.toUint8Array(input);
|
|
473
|
+
const parsed = (0, soundfont_parser_1.parse)(uint8Array);
|
|
474
|
+
const soundFont = new soundfont_parser_1.SoundFont(parsed);
|
|
475
|
+
this.addSoundFont(soundFont);
|
|
473
476
|
}
|
|
477
|
+
}
|
|
478
|
+
async loadMIDI(input) {
|
|
479
|
+
this.voiceCounter.clear();
|
|
480
|
+
const uint8Array = await this.toUint8Array(input);
|
|
474
481
|
const midi = (0, midi_file_1.parseMidi)(uint8Array);
|
|
475
482
|
this.ticksPerBeat = midi.header.ticksPerBeat;
|
|
476
483
|
const midiData = this.extractMidiData(midi);
|
|
@@ -478,6 +485,45 @@ class Midy {
|
|
|
478
485
|
this.timeline = midiData.timeline;
|
|
479
486
|
this.totalTime = this.calcTotalTime();
|
|
480
487
|
}
|
|
488
|
+
cacheVoiceIds() {
|
|
489
|
+
const timeline = this.timeline;
|
|
490
|
+
for (let i = 0; i < timeline.length; i++) {
|
|
491
|
+
const event = timeline[i];
|
|
492
|
+
switch (event.type) {
|
|
493
|
+
case "noteOn": {
|
|
494
|
+
const audioBufferId = this.getVoiceId(this.channels[event.channel], event.noteNumber, event.velocity);
|
|
495
|
+
this.voiceCounter.set(audioBufferId, (this.voiceCounter.get(audioBufferId) ?? 0) + 1);
|
|
496
|
+
break;
|
|
497
|
+
}
|
|
498
|
+
case "controller":
|
|
499
|
+
if (event.controllerType === 0) {
|
|
500
|
+
this.setBankMSB(event.channel, event.value);
|
|
501
|
+
}
|
|
502
|
+
else if (event.controllerType === 32) {
|
|
503
|
+
this.setBankLSB(event.channel, event.value);
|
|
504
|
+
}
|
|
505
|
+
break;
|
|
506
|
+
case "programChange":
|
|
507
|
+
this.setProgramChange(event.channel, event.programNumber, event.startTime);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
for (const [audioBufferId, count] of this.voiceCounter) {
|
|
511
|
+
if (count === 1)
|
|
512
|
+
this.voiceCounter.delete(audioBufferId);
|
|
513
|
+
}
|
|
514
|
+
this.GM2SystemOn();
|
|
515
|
+
}
|
|
516
|
+
getVoiceId(channel, noteNumber, velocity) {
|
|
517
|
+
const bankNumber = this.calcBank(channel);
|
|
518
|
+
const soundFontIndex = this.soundFontTable[channel.programNumber]
|
|
519
|
+
.get(bankNumber);
|
|
520
|
+
if (soundFontIndex === undefined)
|
|
521
|
+
return;
|
|
522
|
+
const soundFont = this.soundFonts[soundFontIndex];
|
|
523
|
+
const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
|
|
524
|
+
const { instrument, sampleID } = voice.generators;
|
|
525
|
+
return soundFontIndex * (2 ** 32) + (instrument << 16) + sampleID;
|
|
526
|
+
}
|
|
481
527
|
createChannelAudioNodes(audioContext) {
|
|
482
528
|
const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
|
|
483
529
|
const gainL = new GainNode(audioContext, { gain: gainLeft });
|
|
@@ -521,34 +567,12 @@ class Midy {
|
|
|
521
567
|
});
|
|
522
568
|
return channels;
|
|
523
569
|
}
|
|
524
|
-
async
|
|
570
|
+
async createAudioBuffer(voiceParams) {
|
|
571
|
+
const sample = voiceParams.sample;
|
|
525
572
|
const sampleStart = voiceParams.start;
|
|
526
|
-
const sampleEnd =
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
const start = sample.byteOffset + sampleStart;
|
|
530
|
-
const end = sample.byteOffset + sampleEnd;
|
|
531
|
-
const buffer = sample.buffer.slice(start, end);
|
|
532
|
-
const audioBuffer = await this.audioContext.decodeAudioData(buffer);
|
|
533
|
-
return audioBuffer;
|
|
534
|
-
}
|
|
535
|
-
else {
|
|
536
|
-
const sample = voiceParams.sample;
|
|
537
|
-
const start = sample.byteOffset + sampleStart;
|
|
538
|
-
const end = sample.byteOffset + sampleEnd;
|
|
539
|
-
const buffer = sample.buffer.slice(start, end);
|
|
540
|
-
const audioBuffer = new AudioBuffer({
|
|
541
|
-
numberOfChannels: 1,
|
|
542
|
-
length: sample.length,
|
|
543
|
-
sampleRate: voiceParams.sampleRate,
|
|
544
|
-
});
|
|
545
|
-
const channelData = audioBuffer.getChannelData(0);
|
|
546
|
-
const int16Array = new Int16Array(buffer);
|
|
547
|
-
for (let i = 0; i < int16Array.length; i++) {
|
|
548
|
-
channelData[i] = int16Array[i] / 32768;
|
|
549
|
-
}
|
|
550
|
-
return audioBuffer;
|
|
551
|
-
}
|
|
573
|
+
const sampleEnd = sample.data.length + voiceParams.end;
|
|
574
|
+
const audioBuffer = await sample.toAudioBuffer(this.audioContext, sampleStart, sampleEnd);
|
|
575
|
+
return audioBuffer;
|
|
552
576
|
}
|
|
553
577
|
isLoopDrum(channel, noteNumber) {
|
|
554
578
|
const programNumber = channel.programNumber;
|
|
@@ -585,16 +609,16 @@ class Midy {
|
|
|
585
609
|
break;
|
|
586
610
|
}
|
|
587
611
|
case "noteAftertouch":
|
|
588
|
-
this.
|
|
612
|
+
this.setPolyphonicKeyPressure(event.channel, event.noteNumber, event.amount, startTime);
|
|
589
613
|
break;
|
|
590
614
|
case "controller":
|
|
591
|
-
this.
|
|
615
|
+
this.setControlChange(event.channel, event.controllerType, event.value, startTime);
|
|
592
616
|
break;
|
|
593
617
|
case "programChange":
|
|
594
|
-
this.
|
|
618
|
+
this.setProgramChange(event.channel, event.programNumber, startTime);
|
|
595
619
|
break;
|
|
596
620
|
case "channelAftertouch":
|
|
597
|
-
this.
|
|
621
|
+
this.setChannelPressure(event.channel, event.amount, startTime);
|
|
598
622
|
break;
|
|
599
623
|
case "pitchBend":
|
|
600
624
|
this.setPitchBend(event.channel, event.value + 8192, startTime);
|
|
@@ -628,8 +652,9 @@ class Midy {
|
|
|
628
652
|
this.notePromises = [];
|
|
629
653
|
this.exclusiveClassNotes.fill(undefined);
|
|
630
654
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
631
|
-
this.
|
|
655
|
+
this.voiceCache.clear();
|
|
632
656
|
for (let i = 0; i < this.channels.length; i++) {
|
|
657
|
+
this.channels[i].scheduledNotes = [];
|
|
633
658
|
this.resetAllStates(i);
|
|
634
659
|
}
|
|
635
660
|
resolve();
|
|
@@ -651,8 +676,9 @@ class Midy {
|
|
|
651
676
|
this.notePromises = [];
|
|
652
677
|
this.exclusiveClassNotes.fill(undefined);
|
|
653
678
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
654
|
-
this.
|
|
679
|
+
this.voiceCache.clear();
|
|
655
680
|
for (let i = 0; i < this.channels.length; i++) {
|
|
681
|
+
this.channels[i].scheduledNotes = [];
|
|
656
682
|
this.resetAllStates(i);
|
|
657
683
|
}
|
|
658
684
|
this.isStopping = false;
|
|
@@ -685,11 +711,7 @@ class Midy {
|
|
|
685
711
|
secondToTicks(second, secondsPerBeat) {
|
|
686
712
|
return second * this.ticksPerBeat / secondsPerBeat;
|
|
687
713
|
}
|
|
688
|
-
getAudioBufferId(programNumber, noteNumber, velocity) {
|
|
689
|
-
return `${programNumber}:${noteNumber}:${velocity}`;
|
|
690
|
-
}
|
|
691
714
|
extractMidiData(midi) {
|
|
692
|
-
this.audioBufferCounter.clear();
|
|
693
715
|
const instruments = new Set();
|
|
694
716
|
const timeline = [];
|
|
695
717
|
const tmpChannels = new Array(this.channels.length);
|
|
@@ -710,8 +732,6 @@ class Midy {
|
|
|
710
732
|
switch (event.type) {
|
|
711
733
|
case "noteOn": {
|
|
712
734
|
const channel = tmpChannels[event.channel];
|
|
713
|
-
const audioBufferId = this.getAudioBufferId(channel.programNumber, event.noteNumber, event.velocity);
|
|
714
|
-
this.audioBufferCounter.set(audioBufferId, (this.audioBufferCounter.get(audioBufferId) ?? 0) + 1);
|
|
715
735
|
if (channel.programNumber < 0) {
|
|
716
736
|
channel.programNumber = event.programNumber;
|
|
717
737
|
switch (channel.bankMSB) {
|
|
@@ -761,10 +781,6 @@ class Midy {
|
|
|
761
781
|
timeline.push(event);
|
|
762
782
|
}
|
|
763
783
|
}
|
|
764
|
-
for (const [audioBufferId, count] of this.audioBufferCounter) {
|
|
765
|
-
if (count === 1)
|
|
766
|
-
this.audioBufferCounter.delete(audioBufferId);
|
|
767
|
-
}
|
|
768
784
|
const priority = {
|
|
769
785
|
controller: 0,
|
|
770
786
|
sysEx: 1,
|
|
@@ -804,12 +820,11 @@ class Midy {
|
|
|
804
820
|
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
805
821
|
const channel = this.channels[channelNumber];
|
|
806
822
|
const promises = [];
|
|
807
|
-
this.processScheduledNotes(channel,
|
|
823
|
+
this.processScheduledNotes(channel, (note) => {
|
|
808
824
|
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
809
825
|
this.notePromises.push(promise);
|
|
810
826
|
promises.push(promise);
|
|
811
827
|
});
|
|
812
|
-
channel.scheduledNotes = [];
|
|
813
828
|
return Promise.all(promises);
|
|
814
829
|
}
|
|
815
830
|
stopNotes(velocity, force, scheduleTime) {
|
|
@@ -823,6 +838,8 @@ class Midy {
|
|
|
823
838
|
if (this.isPlaying || this.isPaused)
|
|
824
839
|
return;
|
|
825
840
|
this.resumeTime = 0;
|
|
841
|
+
if (this.voiceCounter.size === 0)
|
|
842
|
+
this.cacheVoiceIds();
|
|
826
843
|
await this.playNotes();
|
|
827
844
|
this.isPlaying = false;
|
|
828
845
|
}
|
|
@@ -863,22 +880,20 @@ class Midy {
|
|
|
863
880
|
const now = this.audioContext.currentTime;
|
|
864
881
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
865
882
|
}
|
|
866
|
-
processScheduledNotes(channel,
|
|
883
|
+
processScheduledNotes(channel, callback) {
|
|
867
884
|
const scheduledNotes = channel.scheduledNotes;
|
|
868
|
-
for (let i =
|
|
885
|
+
for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
|
|
869
886
|
const note = scheduledNotes[i];
|
|
870
887
|
if (!note)
|
|
871
888
|
continue;
|
|
872
889
|
if (note.ending)
|
|
873
890
|
continue;
|
|
874
|
-
if (note.startTime < scheduleTime)
|
|
875
|
-
continue;
|
|
876
891
|
callback(note);
|
|
877
892
|
}
|
|
878
893
|
}
|
|
879
894
|
processActiveNotes(channel, scheduleTime, callback) {
|
|
880
895
|
const scheduledNotes = channel.scheduledNotes;
|
|
881
|
-
for (let i =
|
|
896
|
+
for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
|
|
882
897
|
const note = scheduledNotes[i];
|
|
883
898
|
if (!note)
|
|
884
899
|
continue;
|
|
@@ -912,13 +927,11 @@ class Midy {
|
|
|
912
927
|
return impulse;
|
|
913
928
|
}
|
|
914
929
|
createConvolutionReverb(audioContext, impulse) {
|
|
915
|
-
const input = new GainNode(audioContext);
|
|
916
930
|
const convolverNode = new ConvolverNode(audioContext, {
|
|
917
931
|
buffer: impulse,
|
|
918
932
|
});
|
|
919
|
-
input.connect(convolverNode);
|
|
920
933
|
return {
|
|
921
|
-
input,
|
|
934
|
+
input: convolverNode,
|
|
922
935
|
output: convolverNode,
|
|
923
936
|
convolverNode,
|
|
924
937
|
};
|
|
@@ -1069,7 +1082,7 @@ class Midy {
|
|
|
1069
1082
|
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
1070
1083
|
}
|
|
1071
1084
|
updateChannelDetune(channel, scheduleTime) {
|
|
1072
|
-
this.processScheduledNotes(channel,
|
|
1085
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1073
1086
|
this.updateDetune(channel, note, scheduleTime);
|
|
1074
1087
|
});
|
|
1075
1088
|
}
|
|
@@ -1297,31 +1310,31 @@ class Midy {
|
|
|
1297
1310
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
1298
1311
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
1299
1312
|
}
|
|
1300
|
-
async getAudioBuffer(
|
|
1301
|
-
const audioBufferId = this.
|
|
1302
|
-
const cache = this.
|
|
1313
|
+
async getAudioBuffer(channel, noteNumber, velocity, voiceParams) {
|
|
1314
|
+
const audioBufferId = this.getVoiceId(channel, noteNumber, velocity);
|
|
1315
|
+
const cache = this.voiceCache.get(audioBufferId);
|
|
1303
1316
|
if (cache) {
|
|
1304
1317
|
cache.counter += 1;
|
|
1305
1318
|
if (cache.maxCount <= cache.counter) {
|
|
1306
|
-
this.
|
|
1319
|
+
this.voiceCache.delete(audioBufferId);
|
|
1307
1320
|
}
|
|
1308
1321
|
return cache.audioBuffer;
|
|
1309
1322
|
}
|
|
1310
1323
|
else {
|
|
1311
|
-
const maxCount = this.
|
|
1312
|
-
const audioBuffer = await this.
|
|
1324
|
+
const maxCount = this.voiceCounter.get(audioBufferId) ?? 0;
|
|
1325
|
+
const audioBuffer = await this.createAudioBuffer(voiceParams);
|
|
1313
1326
|
const cache = { audioBuffer, maxCount, counter: 1 };
|
|
1314
|
-
this.
|
|
1327
|
+
this.voiceCache.set(audioBufferId, cache);
|
|
1315
1328
|
return audioBuffer;
|
|
1316
1329
|
}
|
|
1317
1330
|
}
|
|
1318
|
-
async createNote(channel, voice, noteNumber, velocity, startTime
|
|
1331
|
+
async createNote(channel, voice, noteNumber, velocity, startTime) {
|
|
1319
1332
|
const now = this.audioContext.currentTime;
|
|
1320
1333
|
const state = channel.state;
|
|
1321
1334
|
const controllerState = this.getControllerState(channel, noteNumber, velocity, 0);
|
|
1322
1335
|
const voiceParams = voice.getAllParams(controllerState);
|
|
1323
1336
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1324
|
-
const audioBuffer = await this.getAudioBuffer(channel
|
|
1337
|
+
const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams);
|
|
1325
1338
|
note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
|
|
1326
1339
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
1327
1340
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
@@ -1355,12 +1368,8 @@ class Midy {
|
|
|
1355
1368
|
}
|
|
1356
1369
|
note.bufferSource.connect(note.filterNode);
|
|
1357
1370
|
note.filterNode.connect(note.volumeEnvelopeNode);
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
}
|
|
1361
|
-
if (0 < state.reverbSendLevel) {
|
|
1362
|
-
this.setReverbEffectsSend(channel, note, 0, now);
|
|
1363
|
-
}
|
|
1371
|
+
this.setChorusSend(channel, note, now);
|
|
1372
|
+
this.setReverbSend(channel, note, now);
|
|
1364
1373
|
note.bufferSource.start(startTime);
|
|
1365
1374
|
return note;
|
|
1366
1375
|
}
|
|
@@ -1416,20 +1425,24 @@ class Midy {
|
|
|
1416
1425
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
1417
1426
|
const channel = this.channels[channelNumber];
|
|
1418
1427
|
const bankNumber = this.calcBank(channel, channelNumber);
|
|
1419
|
-
const soundFontIndex = this.soundFontTable[channel.programNumber]
|
|
1428
|
+
const soundFontIndex = this.soundFontTable[channel.programNumber]
|
|
1429
|
+
.get(bankNumber);
|
|
1420
1430
|
if (soundFontIndex === undefined)
|
|
1421
1431
|
return;
|
|
1422
1432
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
1423
1433
|
const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
|
|
1424
1434
|
if (!voice)
|
|
1425
1435
|
return;
|
|
1426
|
-
const
|
|
1427
|
-
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
1436
|
+
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
|
|
1428
1437
|
if (channel.isDrum) {
|
|
1429
|
-
const
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1438
|
+
const { keyBasedGainLs, keyBasedGainRs } = channel;
|
|
1439
|
+
let gainL = keyBasedGainLs[noteNumber];
|
|
1440
|
+
let gainR = keyBasedGainRs[noteNumber];
|
|
1441
|
+
if (!gainL) {
|
|
1442
|
+
const audioNodes = this.createChannelAudioNodes(this.audioContext);
|
|
1443
|
+
gainL = keyBasedGainLs[noteNumber] = audioNodes.gainL;
|
|
1444
|
+
gainR = keyBasedGainRs[noteNumber] = audioNodes.gainR;
|
|
1445
|
+
}
|
|
1433
1446
|
note.volumeEnvelopeNode.connect(gainL);
|
|
1434
1447
|
note.volumeEnvelopeNode.connect(gainR);
|
|
1435
1448
|
}
|
|
@@ -1463,11 +1476,11 @@ class Midy {
|
|
|
1463
1476
|
note.vibratoDepth.disconnect();
|
|
1464
1477
|
note.vibratoLFO.stop();
|
|
1465
1478
|
}
|
|
1466
|
-
if (note.
|
|
1467
|
-
note.
|
|
1479
|
+
if (note.reverbSend) {
|
|
1480
|
+
note.reverbSend.disconnect();
|
|
1468
1481
|
}
|
|
1469
|
-
if (note.
|
|
1470
|
-
note.
|
|
1482
|
+
if (note.chorusSend) {
|
|
1483
|
+
note.chorusSend.disconnect();
|
|
1471
1484
|
}
|
|
1472
1485
|
}
|
|
1473
1486
|
releaseNote(channel, note, endTime) {
|
|
@@ -1507,15 +1520,29 @@ class Midy {
|
|
|
1507
1520
|
return;
|
|
1508
1521
|
}
|
|
1509
1522
|
}
|
|
1510
|
-
const
|
|
1511
|
-
if (
|
|
1523
|
+
const index = this.findNoteOffIndex(channel, noteNumber);
|
|
1524
|
+
if (index < 0)
|
|
1512
1525
|
return;
|
|
1526
|
+
const note = channel.scheduledNotes[index];
|
|
1513
1527
|
note.ending = true;
|
|
1528
|
+
this.setNoteIndex(channel, index);
|
|
1514
1529
|
this.releaseNote(channel, note, endTime);
|
|
1515
1530
|
}
|
|
1516
|
-
|
|
1531
|
+
setNoteIndex(channel, index) {
|
|
1532
|
+
let allEnds = true;
|
|
1533
|
+
for (let i = channel.scheduleIndex; i < index; i++) {
|
|
1534
|
+
const note = channel.scheduledNotes[i];
|
|
1535
|
+
if (note && !note.ending) {
|
|
1536
|
+
allEnds = false;
|
|
1537
|
+
break;
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
if (allEnds)
|
|
1541
|
+
channel.scheduleIndex = index + 1;
|
|
1542
|
+
}
|
|
1543
|
+
findNoteOffIndex(channel, noteNumber) {
|
|
1517
1544
|
const scheduledNotes = channel.scheduledNotes;
|
|
1518
|
-
for (let i =
|
|
1545
|
+
for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
|
|
1519
1546
|
const note = scheduledNotes[i];
|
|
1520
1547
|
if (!note)
|
|
1521
1548
|
continue;
|
|
@@ -1523,8 +1550,9 @@ class Midy {
|
|
|
1523
1550
|
continue;
|
|
1524
1551
|
if (note.noteNumber !== noteNumber)
|
|
1525
1552
|
continue;
|
|
1526
|
-
return
|
|
1553
|
+
return i;
|
|
1527
1554
|
}
|
|
1555
|
+
return -1;
|
|
1528
1556
|
}
|
|
1529
1557
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1530
1558
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -1564,31 +1592,31 @@ class Midy {
|
|
|
1564
1592
|
case 0x90:
|
|
1565
1593
|
return this.noteOn(channelNumber, data1, data2, scheduleTime);
|
|
1566
1594
|
case 0xA0:
|
|
1567
|
-
return this.
|
|
1595
|
+
return this.setPolyphonicKeyPressure(channelNumber, data1, data2, scheduleTime);
|
|
1568
1596
|
case 0xB0:
|
|
1569
|
-
return this.
|
|
1597
|
+
return this.setControlChange(channelNumber, data1, data2, scheduleTime);
|
|
1570
1598
|
case 0xC0:
|
|
1571
|
-
return this.
|
|
1599
|
+
return this.setProgramChange(channelNumber, data1, scheduleTime);
|
|
1572
1600
|
case 0xD0:
|
|
1573
|
-
return this.
|
|
1601
|
+
return this.setChannelPressure(channelNumber, data1, scheduleTime);
|
|
1574
1602
|
case 0xE0:
|
|
1575
1603
|
return this.handlePitchBendMessage(channelNumber, data1, data2, scheduleTime);
|
|
1576
1604
|
default:
|
|
1577
1605
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
1578
1606
|
}
|
|
1579
1607
|
}
|
|
1580
|
-
|
|
1608
|
+
setPolyphonicKeyPressure(channelNumber, noteNumber, pressure, scheduleTime) {
|
|
1581
1609
|
const channel = this.channels[channelNumber];
|
|
1582
1610
|
const table = channel.polyphonicKeyPressureTable;
|
|
1583
1611
|
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
1584
1612
|
if (note.noteNumber === noteNumber) {
|
|
1585
1613
|
note.pressure = pressure;
|
|
1586
|
-
this.
|
|
1614
|
+
this.setEffects(channel, note, table, scheduleTime);
|
|
1587
1615
|
}
|
|
1588
1616
|
});
|
|
1589
1617
|
this.applyVoiceParams(channel, 10);
|
|
1590
1618
|
}
|
|
1591
|
-
|
|
1619
|
+
setProgramChange(channelNumber, programNumber, _scheduleTime) {
|
|
1592
1620
|
const channel = this.channels[channelNumber];
|
|
1593
1621
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1594
1622
|
channel.programNumber = programNumber;
|
|
@@ -1603,7 +1631,7 @@ class Midy {
|
|
|
1603
1631
|
}
|
|
1604
1632
|
}
|
|
1605
1633
|
}
|
|
1606
|
-
|
|
1634
|
+
setChannelPressure(channelNumber, value, scheduleTime) {
|
|
1607
1635
|
const channel = this.channels[channelNumber];
|
|
1608
1636
|
if (channel.isDrum)
|
|
1609
1637
|
return;
|
|
@@ -1617,7 +1645,7 @@ class Midy {
|
|
|
1617
1645
|
}
|
|
1618
1646
|
const table = channel.channelPressureTable;
|
|
1619
1647
|
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
1620
|
-
this.
|
|
1648
|
+
this.setEffects(channel, note, table, scheduleTime);
|
|
1621
1649
|
});
|
|
1622
1650
|
this.applyVoiceParams(channel, 13);
|
|
1623
1651
|
}
|
|
@@ -1639,13 +1667,18 @@ class Midy {
|
|
|
1639
1667
|
this.applyVoiceParams(channel, 14, scheduleTime);
|
|
1640
1668
|
}
|
|
1641
1669
|
setModLfoToPitch(channel, note, scheduleTime) {
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
.
|
|
1648
|
-
|
|
1670
|
+
if (note.modulationDepth) {
|
|
1671
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch +
|
|
1672
|
+
this.getLFOPitchDepth(channel, note);
|
|
1673
|
+
const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
|
|
1674
|
+
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
1675
|
+
note.modulationDepth.gain
|
|
1676
|
+
.cancelScheduledValues(scheduleTime)
|
|
1677
|
+
.setValueAtTime(modulationDepth, scheduleTime);
|
|
1678
|
+
}
|
|
1679
|
+
else {
|
|
1680
|
+
this.startModulation(channel, note, scheduleTime);
|
|
1681
|
+
}
|
|
1649
1682
|
}
|
|
1650
1683
|
setVibLfoToPitch(channel, note, scheduleTime) {
|
|
1651
1684
|
const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
|
|
@@ -1672,63 +1705,63 @@ class Midy {
|
|
|
1672
1705
|
.cancelScheduledValues(scheduleTime)
|
|
1673
1706
|
.setValueAtTime(volumeDepth, scheduleTime);
|
|
1674
1707
|
}
|
|
1675
|
-
|
|
1676
|
-
let value = note.voiceParams.reverbEffectsSend
|
|
1708
|
+
setReverbSend(channel, note, scheduleTime) {
|
|
1709
|
+
let value = note.voiceParams.reverbEffectsSend *
|
|
1710
|
+
channel.state.reverbSendLevel;
|
|
1677
1711
|
if (channel.isDrum) {
|
|
1678
|
-
const keyBasedValue = this.
|
|
1679
|
-
if (0 <= keyBasedValue)
|
|
1680
|
-
value
|
|
1681
|
-
}
|
|
1712
|
+
const keyBasedValue = this.getKeyBasedValue(channel, note.noteNumber, 91);
|
|
1713
|
+
if (0 <= keyBasedValue)
|
|
1714
|
+
value = keyBasedValue / 127;
|
|
1682
1715
|
}
|
|
1683
|
-
if (
|
|
1716
|
+
if (!note.reverbSend) {
|
|
1684
1717
|
if (0 < value) {
|
|
1685
|
-
note.
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
}
|
|
1689
|
-
else {
|
|
1690
|
-
note.reverbEffectsSend.disconnect();
|
|
1718
|
+
note.reverbSend = new GainNode(this.audioContext, { gain: value });
|
|
1719
|
+
note.volumeEnvelopeNode.connect(note.reverbSend);
|
|
1720
|
+
note.reverbSend.connect(this.reverbEffect.input);
|
|
1691
1721
|
}
|
|
1692
1722
|
}
|
|
1693
1723
|
else {
|
|
1724
|
+
note.reverbSend.gain
|
|
1725
|
+
.cancelScheduledValues(scheduleTime)
|
|
1726
|
+
.setValueAtTime(value, scheduleTime);
|
|
1694
1727
|
if (0 < value) {
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
note.volumeEnvelopeNode.
|
|
1728
|
+
note.volumeEnvelopeNode.connect(note.reverbSend);
|
|
1729
|
+
}
|
|
1730
|
+
else {
|
|
1731
|
+
try {
|
|
1732
|
+
note.volumeEnvelopeNode.disconnect(note.reverbSend);
|
|
1700
1733
|
}
|
|
1701
|
-
|
|
1734
|
+
catch { /* empty */ }
|
|
1702
1735
|
}
|
|
1703
1736
|
}
|
|
1704
1737
|
}
|
|
1705
|
-
|
|
1706
|
-
let value = note.voiceParams.chorusEffectsSend
|
|
1738
|
+
setChorusSend(channel, note, scheduleTime) {
|
|
1739
|
+
let value = note.voiceParams.chorusEffectsSend *
|
|
1740
|
+
channel.state.chorusSendLevel;
|
|
1707
1741
|
if (channel.isDrum) {
|
|
1708
|
-
const keyBasedValue = this.
|
|
1709
|
-
if (0 <= keyBasedValue)
|
|
1710
|
-
value
|
|
1711
|
-
}
|
|
1742
|
+
const keyBasedValue = this.getKeyBasedValue(channel, note.noteNumber, 93);
|
|
1743
|
+
if (0 <= keyBasedValue)
|
|
1744
|
+
value = keyBasedValue / 127;
|
|
1712
1745
|
}
|
|
1713
|
-
if (
|
|
1714
|
-
if (0 <
|
|
1715
|
-
note.
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
}
|
|
1719
|
-
else {
|
|
1720
|
-
note.chorusEffectsSend.disconnect();
|
|
1746
|
+
if (!note.chorusSend) {
|
|
1747
|
+
if (0 < value) {
|
|
1748
|
+
note.chorusSend = new GainNode(this.audioContext, { gain: value });
|
|
1749
|
+
note.volumeEnvelopeNode.connect(note.chorusSend);
|
|
1750
|
+
note.chorusSend.connect(this.chorusEffect.input);
|
|
1721
1751
|
}
|
|
1722
1752
|
}
|
|
1723
1753
|
else {
|
|
1754
|
+
note.chorusSend.gain
|
|
1755
|
+
.cancelScheduledValues(scheduleTime)
|
|
1756
|
+
.setValueAtTime(value, scheduleTime);
|
|
1724
1757
|
if (0 < value) {
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
note.volumeEnvelopeNode.
|
|
1758
|
+
note.volumeEnvelopeNode.connect(note.chorusSend);
|
|
1759
|
+
}
|
|
1760
|
+
else {
|
|
1761
|
+
try {
|
|
1762
|
+
note.volumeEnvelopeNode.disconnect(note.chorusSend);
|
|
1730
1763
|
}
|
|
1731
|
-
|
|
1764
|
+
catch { /* empty */ }
|
|
1732
1765
|
}
|
|
1733
1766
|
}
|
|
1734
1767
|
}
|
|
@@ -1774,11 +1807,11 @@ class Midy {
|
|
|
1774
1807
|
this.setModLfoToVolume(channel, note, scheduleTime);
|
|
1775
1808
|
}
|
|
1776
1809
|
},
|
|
1777
|
-
chorusEffectsSend: (channel, note,
|
|
1778
|
-
this.
|
|
1810
|
+
chorusEffectsSend: (channel, note, _prevValue, scheduleTime) => {
|
|
1811
|
+
this.setChorusSend(channel, note, scheduleTime);
|
|
1779
1812
|
},
|
|
1780
|
-
reverbEffectsSend: (channel, note,
|
|
1781
|
-
this.
|
|
1813
|
+
reverbEffectsSend: (channel, note, _prevValue, scheduleTime) => {
|
|
1814
|
+
this.setReverbSend(channel, note, scheduleTime);
|
|
1782
1815
|
},
|
|
1783
1816
|
delayModLFO: (_channel, note, _prevValue, scheduleTime) => this.setDelayModLFO(note, scheduleTime),
|
|
1784
1817
|
freqModLFO: (_channel, note, _prevValue, scheduleTime) => this.setFreqModLFO(note, scheduleTime),
|
|
@@ -1811,7 +1844,7 @@ class Midy {
|
|
|
1811
1844
|
return state;
|
|
1812
1845
|
}
|
|
1813
1846
|
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
1814
|
-
this.processScheduledNotes(channel,
|
|
1847
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1815
1848
|
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity, note.pressure);
|
|
1816
1849
|
const voiceParams = note.voice.getParams(controllerType, controllerState);
|
|
1817
1850
|
let applyVolumeEnvelope = false;
|
|
@@ -1882,13 +1915,13 @@ class Midy {
|
|
|
1882
1915
|
handlers[127] = this.polyOn;
|
|
1883
1916
|
return handlers;
|
|
1884
1917
|
}
|
|
1885
|
-
|
|
1918
|
+
setControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1886
1919
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1887
1920
|
if (handler) {
|
|
1888
1921
|
handler.call(this, channelNumber, value, scheduleTime);
|
|
1889
1922
|
const channel = this.channels[channelNumber];
|
|
1890
1923
|
this.applyVoiceParams(channel, controllerType + 128, scheduleTime);
|
|
1891
|
-
this.
|
|
1924
|
+
this.setControlChangeEffects(channel, controllerType, scheduleTime);
|
|
1892
1925
|
}
|
|
1893
1926
|
else {
|
|
1894
1927
|
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
@@ -1899,12 +1932,11 @@ class Midy {
|
|
|
1899
1932
|
}
|
|
1900
1933
|
updateModulation(channel, scheduleTime) {
|
|
1901
1934
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1902
|
-
this.processScheduledNotes(channel,
|
|
1935
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1903
1936
|
if (note.modulationDepth) {
|
|
1904
1937
|
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1905
1938
|
}
|
|
1906
1939
|
else {
|
|
1907
|
-
this.setPitchEnvelope(note, scheduleTime);
|
|
1908
1940
|
this.startModulation(channel, note, scheduleTime);
|
|
1909
1941
|
}
|
|
1910
1942
|
});
|
|
@@ -1918,7 +1950,7 @@ class Midy {
|
|
|
1918
1950
|
this.updateModulation(channel, scheduleTime);
|
|
1919
1951
|
}
|
|
1920
1952
|
updatePortamento(channel, scheduleTime) {
|
|
1921
|
-
this.processScheduledNotes(channel,
|
|
1953
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1922
1954
|
if (0.5 <= channel.state.portamento) {
|
|
1923
1955
|
if (0 <= note.portamentoNoteNumber) {
|
|
1924
1956
|
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
@@ -1949,8 +1981,14 @@ class Midy {
|
|
|
1949
1981
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1950
1982
|
const channel = this.channels[channelNumber];
|
|
1951
1983
|
channel.state.volume = volume / 127;
|
|
1952
|
-
|
|
1953
|
-
|
|
1984
|
+
if (channel.isDrum) {
|
|
1985
|
+
for (let i = 0; i < 128; i++) {
|
|
1986
|
+
this.updateKeyBasedVolume(channel, i, scheduleTime);
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
else {
|
|
1990
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1991
|
+
}
|
|
1954
1992
|
}
|
|
1955
1993
|
panToGain(pan) {
|
|
1956
1994
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1963,8 +2001,14 @@ class Midy {
|
|
|
1963
2001
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1964
2002
|
const channel = this.channels[channelNumber];
|
|
1965
2003
|
channel.state.pan = pan / 127;
|
|
1966
|
-
|
|
1967
|
-
|
|
2004
|
+
if (channel.isDrum) {
|
|
2005
|
+
for (let i = 0; i < 128; i++) {
|
|
2006
|
+
this.updateKeyBasedVolume(channel, i, scheduleTime);
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
else {
|
|
2010
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
2011
|
+
}
|
|
1968
2012
|
}
|
|
1969
2013
|
setExpression(channelNumber, expression, scheduleTime) {
|
|
1970
2014
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -1990,33 +2034,27 @@ class Midy {
|
|
|
1990
2034
|
.cancelScheduledValues(scheduleTime)
|
|
1991
2035
|
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1992
2036
|
}
|
|
1993
|
-
updateKeyBasedVolume(channel, scheduleTime) {
|
|
1994
|
-
if (!channel.isDrum)
|
|
1995
|
-
return;
|
|
2037
|
+
updateKeyBasedVolume(channel, keyNumber, scheduleTime) {
|
|
1996
2038
|
const state = channel.state;
|
|
1997
2039
|
const defaultVolume = state.volume * state.expression;
|
|
1998
2040
|
const defaultPan = state.pan;
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
gainR.gain
|
|
2017
|
-
.cancelScheduledValues(scheduleTime)
|
|
2018
|
-
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
2019
|
-
}
|
|
2041
|
+
const gainL = channel.keyBasedGainLs[keyNumber];
|
|
2042
|
+
const gainR = channel.keyBasedGainRs[keyNumber];
|
|
2043
|
+
if (!gainL)
|
|
2044
|
+
return;
|
|
2045
|
+
const keyBasedVolume = this.getKeyBasedValue(channel, keyNumber, 7);
|
|
2046
|
+
const volume = (0 <= keyBasedVolume)
|
|
2047
|
+
? defaultVolume * keyBasedVolume / 64
|
|
2048
|
+
: defaultVolume;
|
|
2049
|
+
const keyBasedPan = this.getKeyBasedValue(channel, keyNumber, 10);
|
|
2050
|
+
const pan = (0 <= keyBasedPan) ? keyBasedPan / 127 : defaultPan;
|
|
2051
|
+
const { gainLeft, gainRight } = this.panToGain(pan);
|
|
2052
|
+
gainL.gain
|
|
2053
|
+
.cancelScheduledValues(scheduleTime)
|
|
2054
|
+
.setValueAtTime(volume * gainLeft, scheduleTime);
|
|
2055
|
+
gainR.gain
|
|
2056
|
+
.cancelScheduledValues(scheduleTime)
|
|
2057
|
+
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
2020
2058
|
}
|
|
2021
2059
|
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
2022
2060
|
const channel = this.channels[channelNumber];
|
|
@@ -2025,7 +2063,7 @@ class Midy {
|
|
|
2025
2063
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2026
2064
|
channel.state.sustainPedal = value / 127;
|
|
2027
2065
|
if (64 <= value) {
|
|
2028
|
-
this.processScheduledNotes(channel,
|
|
2066
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2029
2067
|
channel.sustainNotes.push(note);
|
|
2030
2068
|
});
|
|
2031
2069
|
}
|
|
@@ -2068,7 +2106,7 @@ class Midy {
|
|
|
2068
2106
|
const state = channel.state;
|
|
2069
2107
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2070
2108
|
state.softPedal = softPedal / 127;
|
|
2071
|
-
this.processScheduledNotes(channel,
|
|
2109
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2072
2110
|
if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
2073
2111
|
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2074
2112
|
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
@@ -2086,7 +2124,7 @@ class Midy {
|
|
|
2086
2124
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2087
2125
|
const state = channel.state;
|
|
2088
2126
|
state.filterResonance = filterResonance / 127;
|
|
2089
|
-
this.processScheduledNotes(channel,
|
|
2127
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2090
2128
|
const Q = note.voiceParams.initialFilterQ / 5 * state.filterResonance;
|
|
2091
2129
|
note.filterNode.Q.setValueAtTime(Q, scheduleTime);
|
|
2092
2130
|
});
|
|
@@ -2104,10 +2142,10 @@ class Midy {
|
|
|
2104
2142
|
return;
|
|
2105
2143
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2106
2144
|
channel.state.attackTime = attackTime / 127;
|
|
2107
|
-
this.processScheduledNotes(channel,
|
|
2145
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2108
2146
|
if (note.startTime < scheduleTime)
|
|
2109
2147
|
return false;
|
|
2110
|
-
this.setVolumeEnvelope(channel, note);
|
|
2148
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
2111
2149
|
});
|
|
2112
2150
|
}
|
|
2113
2151
|
setBrightness(channelNumber, brightness, scheduleTime) {
|
|
@@ -2117,12 +2155,12 @@ class Midy {
|
|
|
2117
2155
|
const state = channel.state;
|
|
2118
2156
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2119
2157
|
state.brightness = brightness / 127;
|
|
2120
|
-
this.processScheduledNotes(channel,
|
|
2158
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2121
2159
|
if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
2122
2160
|
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2123
2161
|
}
|
|
2124
2162
|
else {
|
|
2125
|
-
this.setFilterEnvelope(channel, note);
|
|
2163
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
2126
2164
|
}
|
|
2127
2165
|
});
|
|
2128
2166
|
}
|
|
@@ -2132,7 +2170,7 @@ class Midy {
|
|
|
2132
2170
|
return;
|
|
2133
2171
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2134
2172
|
channel.state.decayTime = dacayTime / 127;
|
|
2135
|
-
this.processScheduledNotes(channel,
|
|
2173
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2136
2174
|
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
2137
2175
|
});
|
|
2138
2176
|
}
|
|
@@ -2144,7 +2182,7 @@ class Midy {
|
|
|
2144
2182
|
channel.state.vibratoRate = vibratoRate / 127;
|
|
2145
2183
|
if (channel.vibratoDepth <= 0)
|
|
2146
2184
|
return;
|
|
2147
|
-
this.processScheduledNotes(channel,
|
|
2185
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2148
2186
|
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
2149
2187
|
});
|
|
2150
2188
|
}
|
|
@@ -2156,12 +2194,12 @@ class Midy {
|
|
|
2156
2194
|
const prev = channel.state.vibratoDepth;
|
|
2157
2195
|
channel.state.vibratoDepth = vibratoDepth / 127;
|
|
2158
2196
|
if (0 < prev) {
|
|
2159
|
-
this.processScheduledNotes(channel,
|
|
2197
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2160
2198
|
this.setFreqVibLFO(channel, note, scheduleTime);
|
|
2161
2199
|
});
|
|
2162
2200
|
}
|
|
2163
2201
|
else {
|
|
2164
|
-
this.processScheduledNotes(channel,
|
|
2202
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2165
2203
|
this.startVibrato(channel, note, scheduleTime);
|
|
2166
2204
|
});
|
|
2167
2205
|
}
|
|
@@ -2173,7 +2211,7 @@ class Midy {
|
|
|
2173
2211
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2174
2212
|
channel.state.vibratoDelay = vibratoDelay / 127;
|
|
2175
2213
|
if (0 < channel.state.vibratoDepth) {
|
|
2176
|
-
this.processScheduledNotes(channel,
|
|
2214
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2177
2215
|
this.startVibrato(channel, note, scheduleTime);
|
|
2178
2216
|
});
|
|
2179
2217
|
}
|
|
@@ -2182,67 +2220,19 @@ class Midy {
|
|
|
2182
2220
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2183
2221
|
const channel = this.channels[channelNumber];
|
|
2184
2222
|
const state = channel.state;
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
reverbEffect.input.gain
|
|
2190
|
-
.cancelScheduledValues(scheduleTime)
|
|
2191
|
-
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
2192
|
-
}
|
|
2193
|
-
else {
|
|
2194
|
-
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2195
|
-
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
2196
|
-
return false;
|
|
2197
|
-
if (note.reverbEffectsSend)
|
|
2198
|
-
note.reverbEffectsSend.disconnect();
|
|
2199
|
-
});
|
|
2200
|
-
}
|
|
2201
|
-
}
|
|
2202
|
-
else {
|
|
2203
|
-
if (0 < reverbSendLevel) {
|
|
2204
|
-
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2205
|
-
this.setReverbEffectsSend(channel, note, 0, scheduleTime);
|
|
2206
|
-
});
|
|
2207
|
-
state.reverbSendLevel = reverbSendLevel / 127;
|
|
2208
|
-
reverbEffect.input.gain
|
|
2209
|
-
.cancelScheduledValues(scheduleTime)
|
|
2210
|
-
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
2211
|
-
}
|
|
2212
|
-
}
|
|
2223
|
+
state.reverbSendLevel = reverbSendLevel / 127;
|
|
2224
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2225
|
+
this.setReverbSend(channel, note, scheduleTime);
|
|
2226
|
+
});
|
|
2213
2227
|
}
|
|
2214
2228
|
setChorusSendLevel(channelNumber, chorusSendLevel, scheduleTime) {
|
|
2215
2229
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2216
2230
|
const channel = this.channels[channelNumber];
|
|
2217
2231
|
const state = channel.state;
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
chorusEffect.input.gain
|
|
2223
|
-
.cancelScheduledValues(scheduleTime)
|
|
2224
|
-
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
2225
|
-
}
|
|
2226
|
-
else {
|
|
2227
|
-
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2228
|
-
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
2229
|
-
return false;
|
|
2230
|
-
if (note.chorusEffectsSend)
|
|
2231
|
-
note.chorusEffectsSend.disconnect();
|
|
2232
|
-
});
|
|
2233
|
-
}
|
|
2234
|
-
}
|
|
2235
|
-
else {
|
|
2236
|
-
if (0 < chorusSendLevel) {
|
|
2237
|
-
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2238
|
-
this.setChorusEffectsSend(channel, note, 0, scheduleTime);
|
|
2239
|
-
});
|
|
2240
|
-
state.chorusSendLevel = chorusSendLevel / 127;
|
|
2241
|
-
chorusEffect.input.gain
|
|
2242
|
-
.cancelScheduledValues(scheduleTime)
|
|
2243
|
-
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
2244
|
-
}
|
|
2245
|
-
}
|
|
2232
|
+
state.chorusSendLevel = chorusSendLevel / 127;
|
|
2233
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2234
|
+
this.setChorusSend(channel, note, scheduleTime);
|
|
2235
|
+
});
|
|
2246
2236
|
}
|
|
2247
2237
|
limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
|
|
2248
2238
|
if (maxLSB < channel.dataLSB) {
|
|
@@ -2392,7 +2382,7 @@ class Midy {
|
|
|
2392
2382
|
const entries = Object.entries(defaultControllerState);
|
|
2393
2383
|
for (const [key, { type, defaultValue }] of entries) {
|
|
2394
2384
|
if (128 <= type) {
|
|
2395
|
-
this.
|
|
2385
|
+
this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
2396
2386
|
}
|
|
2397
2387
|
else {
|
|
2398
2388
|
state[key] = defaultValue;
|
|
@@ -2425,7 +2415,7 @@ class Midy {
|
|
|
2425
2415
|
const key = keys[i];
|
|
2426
2416
|
const { type, defaultValue } = defaultControllerState[key];
|
|
2427
2417
|
if (128 <= type) {
|
|
2428
|
-
this.
|
|
2418
|
+
this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
2429
2419
|
}
|
|
2430
2420
|
else {
|
|
2431
2421
|
state[key] = defaultValue;
|
|
@@ -2553,11 +2543,11 @@ class Midy {
|
|
|
2553
2543
|
case 9:
|
|
2554
2544
|
switch (data[3]) {
|
|
2555
2545
|
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
|
|
2556
|
-
return this.handlePressureSysEx(data, "channelPressureTable");
|
|
2546
|
+
return this.handlePressureSysEx(data, "channelPressureTable", scheduleTime);
|
|
2557
2547
|
case 2: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
|
|
2558
|
-
return this.handlePressureSysEx(data, "polyphonicKeyPressureTable");
|
|
2548
|
+
return this.handlePressureSysEx(data, "polyphonicKeyPressureTable", scheduleTime);
|
|
2559
2549
|
case 3: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
|
|
2560
|
-
return this.handleControlChangeSysEx(data);
|
|
2550
|
+
return this.handleControlChangeSysEx(data, scheduleTime);
|
|
2561
2551
|
default:
|
|
2562
2552
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2563
2553
|
}
|
|
@@ -2928,29 +2918,31 @@ class Midy {
|
|
|
2928
2918
|
: 0;
|
|
2929
2919
|
return (channelPressure + polyphonicKeyPressure) / 254;
|
|
2930
2920
|
}
|
|
2931
|
-
|
|
2921
|
+
setEffects(channel, note, table, scheduleTime) {
|
|
2932
2922
|
if (0 <= table[0])
|
|
2933
|
-
this.updateDetune(channel, note);
|
|
2923
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
2934
2924
|
if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
|
|
2935
|
-
if (0 <= table[1])
|
|
2936
|
-
this.setPortamentoFilterEnvelope(channel, note);
|
|
2937
|
-
|
|
2938
|
-
|
|
2925
|
+
if (0 <= table[1]) {
|
|
2926
|
+
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2927
|
+
}
|
|
2928
|
+
if (0 <= table[2]) {
|
|
2929
|
+
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2930
|
+
}
|
|
2939
2931
|
}
|
|
2940
2932
|
else {
|
|
2941
2933
|
if (0 <= table[1])
|
|
2942
|
-
this.setFilterEnvelope(channel, note);
|
|
2934
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
2943
2935
|
if (0 <= table[2])
|
|
2944
|
-
this.setVolumeEnvelope(channel, note);
|
|
2936
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
2945
2937
|
}
|
|
2946
2938
|
if (0 <= table[3])
|
|
2947
|
-
this.setModLfoToPitch(channel, note);
|
|
2939
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
2948
2940
|
if (0 <= table[4])
|
|
2949
|
-
this.setModLfoToFilterFc(channel, note);
|
|
2941
|
+
this.setModLfoToFilterFc(channel, note, scheduleTime);
|
|
2950
2942
|
if (0 <= table[5])
|
|
2951
|
-
this.setModLfoToVolume(channel, note);
|
|
2943
|
+
this.setModLfoToVolume(channel, note, scheduleTime);
|
|
2952
2944
|
}
|
|
2953
|
-
handlePressureSysEx(data, tableName) {
|
|
2945
|
+
handlePressureSysEx(data, tableName, scheduleTime) {
|
|
2954
2946
|
const channelNumber = data[4];
|
|
2955
2947
|
const channel = this.channels[channelNumber];
|
|
2956
2948
|
if (channel.isDrum)
|
|
@@ -2961,34 +2953,40 @@ class Midy {
|
|
|
2961
2953
|
const rr = data[i + 1];
|
|
2962
2954
|
table[pp] = rr;
|
|
2963
2955
|
}
|
|
2956
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
2957
|
+
this.setEffects(channel, note, table, scheduleTime);
|
|
2958
|
+
});
|
|
2964
2959
|
}
|
|
2965
2960
|
initControlTable() {
|
|
2966
2961
|
const ccCount = 128;
|
|
2967
2962
|
const slotSize = 6;
|
|
2968
2963
|
return new Int8Array(ccCount * slotSize).fill(-1);
|
|
2969
2964
|
}
|
|
2970
|
-
|
|
2965
|
+
setControlChangeEffects(channel, controllerType, scheduleTime) {
|
|
2971
2966
|
const slotSize = 6;
|
|
2972
2967
|
const offset = controllerType * slotSize;
|
|
2973
2968
|
const table = channel.controlTable.subarray(offset, offset + slotSize);
|
|
2974
|
-
this.processScheduledNotes(channel,
|
|
2975
|
-
this.
|
|
2969
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2970
|
+
this.setEffects(channel, note, table, scheduleTime);
|
|
2976
2971
|
});
|
|
2977
2972
|
}
|
|
2978
|
-
handleControlChangeSysEx(data) {
|
|
2973
|
+
handleControlChangeSysEx(data, scheduleTime) {
|
|
2979
2974
|
const channelNumber = data[4];
|
|
2980
2975
|
const channel = this.channels[channelNumber];
|
|
2981
2976
|
if (channel.isDrum)
|
|
2982
2977
|
return;
|
|
2978
|
+
const slotSize = 6;
|
|
2983
2979
|
const controllerType = data[5];
|
|
2984
|
-
const
|
|
2985
|
-
|
|
2980
|
+
const offset = controllerType * slotSize;
|
|
2981
|
+
const table = channel.controlTable;
|
|
2982
|
+
for (let i = 6; i < data.length; i += 2) {
|
|
2986
2983
|
const pp = data[i];
|
|
2987
2984
|
const rr = data[i + 1];
|
|
2988
|
-
table[pp] = rr;
|
|
2985
|
+
table[offset + pp] = rr;
|
|
2989
2986
|
}
|
|
2987
|
+
this.setControlChangeEffects(channel, controllerType, scheduleTime);
|
|
2990
2988
|
}
|
|
2991
|
-
|
|
2989
|
+
getKeyBasedValue(channel, keyNumber, controllerType) {
|
|
2992
2990
|
const index = keyNumber * 128 + controllerType;
|
|
2993
2991
|
const controlValue = channel.keyBasedInstrumentControlTable[index];
|
|
2994
2992
|
return controlValue;
|
|
@@ -3000,13 +2998,20 @@ class Midy {
|
|
|
3000
2998
|
return;
|
|
3001
2999
|
const keyNumber = data[5];
|
|
3002
3000
|
const table = channel.keyBasedInstrumentControlTable;
|
|
3003
|
-
for (let i = 6; i < data.length
|
|
3001
|
+
for (let i = 6; i < data.length; i += 2) {
|
|
3004
3002
|
const controllerType = data[i];
|
|
3005
3003
|
const value = data[i + 1];
|
|
3006
3004
|
const index = keyNumber * 128 + controllerType;
|
|
3007
3005
|
table[index] = value;
|
|
3006
|
+
switch (controllerType) {
|
|
3007
|
+
case 7:
|
|
3008
|
+
case 10:
|
|
3009
|
+
this.updateKeyBasedVolume(channel, keyNumber, scheduleTime);
|
|
3010
|
+
break;
|
|
3011
|
+
default: // TODO
|
|
3012
|
+
this.setControlChange(channelNumber, controllerType, value, scheduleTime);
|
|
3013
|
+
}
|
|
3008
3014
|
}
|
|
3009
|
-
this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
|
|
3010
3015
|
}
|
|
3011
3016
|
handleSysEx(data, scheduleTime) {
|
|
3012
3017
|
switch (data[0]) {
|
|
@@ -3044,6 +3049,7 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
3044
3049
|
configurable: true,
|
|
3045
3050
|
writable: true,
|
|
3046
3051
|
value: {
|
|
3052
|
+
scheduleIndex: 0,
|
|
3047
3053
|
detune: 0,
|
|
3048
3054
|
programNumber: 0,
|
|
3049
3055
|
bank: 121 * 128,
|