@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/esm/midy.js
CHANGED
|
@@ -68,13 +68,13 @@ class Note {
|
|
|
68
68
|
writable: true,
|
|
69
69
|
value: void 0
|
|
70
70
|
});
|
|
71
|
-
Object.defineProperty(this, "
|
|
71
|
+
Object.defineProperty(this, "reverbSend", {
|
|
72
72
|
enumerable: true,
|
|
73
73
|
configurable: true,
|
|
74
74
|
writable: true,
|
|
75
75
|
value: void 0
|
|
76
76
|
});
|
|
77
|
-
Object.defineProperty(this, "
|
|
77
|
+
Object.defineProperty(this, "chorusSend", {
|
|
78
78
|
enumerable: true,
|
|
79
79
|
configurable: true,
|
|
80
80
|
writable: true,
|
|
@@ -329,13 +329,13 @@ export class Midy {
|
|
|
329
329
|
writable: true,
|
|
330
330
|
value: this.initSoundFontTable()
|
|
331
331
|
});
|
|
332
|
-
Object.defineProperty(this, "
|
|
332
|
+
Object.defineProperty(this, "voiceCounter", {
|
|
333
333
|
enumerable: true,
|
|
334
334
|
configurable: true,
|
|
335
335
|
writable: true,
|
|
336
336
|
value: new Map()
|
|
337
337
|
});
|
|
338
|
-
Object.defineProperty(this, "
|
|
338
|
+
Object.defineProperty(this, "voiceCache", {
|
|
339
339
|
enumerable: true,
|
|
340
340
|
configurable: true,
|
|
341
341
|
writable: true,
|
|
@@ -377,17 +377,17 @@ export class Midy {
|
|
|
377
377
|
writable: true,
|
|
378
378
|
value: []
|
|
379
379
|
});
|
|
380
|
-
Object.defineProperty(this, "
|
|
380
|
+
Object.defineProperty(this, "notePromises", {
|
|
381
381
|
enumerable: true,
|
|
382
382
|
configurable: true,
|
|
383
383
|
writable: true,
|
|
384
384
|
value: []
|
|
385
385
|
});
|
|
386
|
-
Object.defineProperty(this, "
|
|
386
|
+
Object.defineProperty(this, "instruments", {
|
|
387
387
|
enumerable: true,
|
|
388
388
|
configurable: true,
|
|
389
389
|
writable: true,
|
|
390
|
-
value:
|
|
390
|
+
value: new Set()
|
|
391
391
|
});
|
|
392
392
|
Object.defineProperty(this, "exclusiveClassNotes", {
|
|
393
393
|
enumerable: true,
|
|
@@ -432,13 +432,11 @@ export class Midy {
|
|
|
432
432
|
const presetHeaders = soundFont.parsed.presetHeaders;
|
|
433
433
|
for (let i = 0; i < presetHeaders.length; i++) {
|
|
434
434
|
const presetHeader = presetHeaders[i];
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
banks.set(presetHeader.bank, index);
|
|
438
|
-
}
|
|
435
|
+
const banks = this.soundFontTable[presetHeader.preset];
|
|
436
|
+
banks.set(presetHeader.bank, index);
|
|
439
437
|
}
|
|
440
438
|
}
|
|
441
|
-
async
|
|
439
|
+
async toUint8Array(input) {
|
|
442
440
|
let uint8Array;
|
|
443
441
|
if (typeof input === "string") {
|
|
444
442
|
const response = await fetch(input);
|
|
@@ -451,23 +449,32 @@ export class Midy {
|
|
|
451
449
|
else {
|
|
452
450
|
throw new TypeError("input must be a URL string or Uint8Array");
|
|
453
451
|
}
|
|
454
|
-
|
|
455
|
-
const soundFont = new SoundFont(parsed);
|
|
456
|
-
this.addSoundFont(soundFont);
|
|
452
|
+
return uint8Array;
|
|
457
453
|
}
|
|
458
|
-
async
|
|
459
|
-
|
|
460
|
-
if (
|
|
461
|
-
const
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
454
|
+
async loadSoundFont(input) {
|
|
455
|
+
this.voiceCounter.clear();
|
|
456
|
+
if (Array.isArray(input)) {
|
|
457
|
+
const promises = new Array(input.length);
|
|
458
|
+
for (let i = 0; i < input.length; i++) {
|
|
459
|
+
promises[i] = this.toUint8Array(input[i]);
|
|
460
|
+
}
|
|
461
|
+
const uint8Arrays = await Promise.all(promises);
|
|
462
|
+
for (let i = 0; i < uint8Arrays.length; i++) {
|
|
463
|
+
const parsed = parse(uint8Arrays[i]);
|
|
464
|
+
const soundFont = new SoundFont(parsed);
|
|
465
|
+
this.addSoundFont(soundFont);
|
|
466
|
+
}
|
|
467
467
|
}
|
|
468
468
|
else {
|
|
469
|
-
|
|
469
|
+
const uint8Array = await this.toUint8Array(input);
|
|
470
|
+
const parsed = parse(uint8Array);
|
|
471
|
+
const soundFont = new SoundFont(parsed);
|
|
472
|
+
this.addSoundFont(soundFont);
|
|
470
473
|
}
|
|
474
|
+
}
|
|
475
|
+
async loadMIDI(input) {
|
|
476
|
+
this.voiceCounter.clear();
|
|
477
|
+
const uint8Array = await this.toUint8Array(input);
|
|
471
478
|
const midi = parseMidi(uint8Array);
|
|
472
479
|
this.ticksPerBeat = midi.header.ticksPerBeat;
|
|
473
480
|
const midiData = this.extractMidiData(midi);
|
|
@@ -475,6 +482,45 @@ export class Midy {
|
|
|
475
482
|
this.timeline = midiData.timeline;
|
|
476
483
|
this.totalTime = this.calcTotalTime();
|
|
477
484
|
}
|
|
485
|
+
cacheVoiceIds() {
|
|
486
|
+
const timeline = this.timeline;
|
|
487
|
+
for (let i = 0; i < timeline.length; i++) {
|
|
488
|
+
const event = timeline[i];
|
|
489
|
+
switch (event.type) {
|
|
490
|
+
case "noteOn": {
|
|
491
|
+
const audioBufferId = this.getVoiceId(this.channels[event.channel], event.noteNumber, event.velocity);
|
|
492
|
+
this.voiceCounter.set(audioBufferId, (this.voiceCounter.get(audioBufferId) ?? 0) + 1);
|
|
493
|
+
break;
|
|
494
|
+
}
|
|
495
|
+
case "controller":
|
|
496
|
+
if (event.controllerType === 0) {
|
|
497
|
+
this.setBankMSB(event.channel, event.value);
|
|
498
|
+
}
|
|
499
|
+
else if (event.controllerType === 32) {
|
|
500
|
+
this.setBankLSB(event.channel, event.value);
|
|
501
|
+
}
|
|
502
|
+
break;
|
|
503
|
+
case "programChange":
|
|
504
|
+
this.setProgramChange(event.channel, event.programNumber, event.startTime);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
for (const [audioBufferId, count] of this.voiceCounter) {
|
|
508
|
+
if (count === 1)
|
|
509
|
+
this.voiceCounter.delete(audioBufferId);
|
|
510
|
+
}
|
|
511
|
+
this.GM2SystemOn();
|
|
512
|
+
}
|
|
513
|
+
getVoiceId(channel, noteNumber, velocity) {
|
|
514
|
+
const bankNumber = this.calcBank(channel);
|
|
515
|
+
const soundFontIndex = this.soundFontTable[channel.programNumber]
|
|
516
|
+
.get(bankNumber);
|
|
517
|
+
if (soundFontIndex === undefined)
|
|
518
|
+
return;
|
|
519
|
+
const soundFont = this.soundFonts[soundFontIndex];
|
|
520
|
+
const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
|
|
521
|
+
const { instrument, sampleID } = voice.generators;
|
|
522
|
+
return soundFontIndex * (2 ** 32) + (instrument << 16) + sampleID;
|
|
523
|
+
}
|
|
478
524
|
createChannelAudioNodes(audioContext) {
|
|
479
525
|
const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
|
|
480
526
|
const gainL = new GainNode(audioContext, { gain: gainLeft });
|
|
@@ -518,34 +564,12 @@ export class Midy {
|
|
|
518
564
|
});
|
|
519
565
|
return channels;
|
|
520
566
|
}
|
|
521
|
-
async
|
|
567
|
+
async createAudioBuffer(voiceParams) {
|
|
568
|
+
const sample = voiceParams.sample;
|
|
522
569
|
const sampleStart = voiceParams.start;
|
|
523
|
-
const sampleEnd =
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
const start = sample.byteOffset + sampleStart;
|
|
527
|
-
const end = sample.byteOffset + sampleEnd;
|
|
528
|
-
const buffer = sample.buffer.slice(start, end);
|
|
529
|
-
const audioBuffer = await this.audioContext.decodeAudioData(buffer);
|
|
530
|
-
return audioBuffer;
|
|
531
|
-
}
|
|
532
|
-
else {
|
|
533
|
-
const sample = voiceParams.sample;
|
|
534
|
-
const start = sample.byteOffset + sampleStart;
|
|
535
|
-
const end = sample.byteOffset + sampleEnd;
|
|
536
|
-
const buffer = sample.buffer.slice(start, end);
|
|
537
|
-
const audioBuffer = new AudioBuffer({
|
|
538
|
-
numberOfChannels: 1,
|
|
539
|
-
length: sample.length,
|
|
540
|
-
sampleRate: voiceParams.sampleRate,
|
|
541
|
-
});
|
|
542
|
-
const channelData = audioBuffer.getChannelData(0);
|
|
543
|
-
const int16Array = new Int16Array(buffer);
|
|
544
|
-
for (let i = 0; i < int16Array.length; i++) {
|
|
545
|
-
channelData[i] = int16Array[i] / 32768;
|
|
546
|
-
}
|
|
547
|
-
return audioBuffer;
|
|
548
|
-
}
|
|
570
|
+
const sampleEnd = sample.data.length + voiceParams.end;
|
|
571
|
+
const audioBuffer = await sample.toAudioBuffer(this.audioContext, sampleStart, sampleEnd);
|
|
572
|
+
return audioBuffer;
|
|
549
573
|
}
|
|
550
574
|
isLoopDrum(channel, noteNumber) {
|
|
551
575
|
const programNumber = channel.programNumber;
|
|
@@ -582,16 +606,16 @@ export class Midy {
|
|
|
582
606
|
break;
|
|
583
607
|
}
|
|
584
608
|
case "noteAftertouch":
|
|
585
|
-
this.
|
|
609
|
+
this.setPolyphonicKeyPressure(event.channel, event.noteNumber, event.amount, startTime);
|
|
586
610
|
break;
|
|
587
611
|
case "controller":
|
|
588
|
-
this.
|
|
612
|
+
this.setControlChange(event.channel, event.controllerType, event.value, startTime);
|
|
589
613
|
break;
|
|
590
614
|
case "programChange":
|
|
591
|
-
this.
|
|
615
|
+
this.setProgramChange(event.channel, event.programNumber, startTime);
|
|
592
616
|
break;
|
|
593
617
|
case "channelAftertouch":
|
|
594
|
-
this.
|
|
618
|
+
this.setChannelPressure(event.channel, event.amount, startTime);
|
|
595
619
|
break;
|
|
596
620
|
case "pitchBend":
|
|
597
621
|
this.setPitchBend(event.channel, event.value + 8192, startTime);
|
|
@@ -625,8 +649,9 @@ export class Midy {
|
|
|
625
649
|
this.notePromises = [];
|
|
626
650
|
this.exclusiveClassNotes.fill(undefined);
|
|
627
651
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
628
|
-
this.
|
|
652
|
+
this.voiceCache.clear();
|
|
629
653
|
for (let i = 0; i < this.channels.length; i++) {
|
|
654
|
+
this.channels[i].scheduledNotes = [];
|
|
630
655
|
this.resetAllStates(i);
|
|
631
656
|
}
|
|
632
657
|
resolve();
|
|
@@ -648,8 +673,9 @@ export class Midy {
|
|
|
648
673
|
this.notePromises = [];
|
|
649
674
|
this.exclusiveClassNotes.fill(undefined);
|
|
650
675
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
651
|
-
this.
|
|
676
|
+
this.voiceCache.clear();
|
|
652
677
|
for (let i = 0; i < this.channels.length; i++) {
|
|
678
|
+
this.channels[i].scheduledNotes = [];
|
|
653
679
|
this.resetAllStates(i);
|
|
654
680
|
}
|
|
655
681
|
this.isStopping = false;
|
|
@@ -682,11 +708,7 @@ export class Midy {
|
|
|
682
708
|
secondToTicks(second, secondsPerBeat) {
|
|
683
709
|
return second * this.ticksPerBeat / secondsPerBeat;
|
|
684
710
|
}
|
|
685
|
-
getAudioBufferId(programNumber, noteNumber, velocity) {
|
|
686
|
-
return `${programNumber}:${noteNumber}:${velocity}`;
|
|
687
|
-
}
|
|
688
711
|
extractMidiData(midi) {
|
|
689
|
-
this.audioBufferCounter.clear();
|
|
690
712
|
const instruments = new Set();
|
|
691
713
|
const timeline = [];
|
|
692
714
|
const tmpChannels = new Array(this.channels.length);
|
|
@@ -707,8 +729,6 @@ export class Midy {
|
|
|
707
729
|
switch (event.type) {
|
|
708
730
|
case "noteOn": {
|
|
709
731
|
const channel = tmpChannels[event.channel];
|
|
710
|
-
const audioBufferId = this.getAudioBufferId(channel.programNumber, event.noteNumber, event.velocity);
|
|
711
|
-
this.audioBufferCounter.set(audioBufferId, (this.audioBufferCounter.get(audioBufferId) ?? 0) + 1);
|
|
712
732
|
if (channel.programNumber < 0) {
|
|
713
733
|
channel.programNumber = event.programNumber;
|
|
714
734
|
switch (channel.bankMSB) {
|
|
@@ -758,10 +778,6 @@ export class Midy {
|
|
|
758
778
|
timeline.push(event);
|
|
759
779
|
}
|
|
760
780
|
}
|
|
761
|
-
for (const [audioBufferId, count] of this.audioBufferCounter) {
|
|
762
|
-
if (count === 1)
|
|
763
|
-
this.audioBufferCounter.delete(audioBufferId);
|
|
764
|
-
}
|
|
765
781
|
const priority = {
|
|
766
782
|
controller: 0,
|
|
767
783
|
sysEx: 1,
|
|
@@ -801,12 +817,11 @@ export class Midy {
|
|
|
801
817
|
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
802
818
|
const channel = this.channels[channelNumber];
|
|
803
819
|
const promises = [];
|
|
804
|
-
this.processScheduledNotes(channel,
|
|
820
|
+
this.processScheduledNotes(channel, (note) => {
|
|
805
821
|
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
806
822
|
this.notePromises.push(promise);
|
|
807
823
|
promises.push(promise);
|
|
808
824
|
});
|
|
809
|
-
channel.scheduledNotes = [];
|
|
810
825
|
return Promise.all(promises);
|
|
811
826
|
}
|
|
812
827
|
stopNotes(velocity, force, scheduleTime) {
|
|
@@ -820,6 +835,8 @@ export class Midy {
|
|
|
820
835
|
if (this.isPlaying || this.isPaused)
|
|
821
836
|
return;
|
|
822
837
|
this.resumeTime = 0;
|
|
838
|
+
if (this.voiceCounter.size === 0)
|
|
839
|
+
this.cacheVoiceIds();
|
|
823
840
|
await this.playNotes();
|
|
824
841
|
this.isPlaying = false;
|
|
825
842
|
}
|
|
@@ -860,22 +877,20 @@ export class Midy {
|
|
|
860
877
|
const now = this.audioContext.currentTime;
|
|
861
878
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
862
879
|
}
|
|
863
|
-
processScheduledNotes(channel,
|
|
880
|
+
processScheduledNotes(channel, callback) {
|
|
864
881
|
const scheduledNotes = channel.scheduledNotes;
|
|
865
|
-
for (let i =
|
|
882
|
+
for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
|
|
866
883
|
const note = scheduledNotes[i];
|
|
867
884
|
if (!note)
|
|
868
885
|
continue;
|
|
869
886
|
if (note.ending)
|
|
870
887
|
continue;
|
|
871
|
-
if (note.startTime < scheduleTime)
|
|
872
|
-
continue;
|
|
873
888
|
callback(note);
|
|
874
889
|
}
|
|
875
890
|
}
|
|
876
891
|
processActiveNotes(channel, scheduleTime, callback) {
|
|
877
892
|
const scheduledNotes = channel.scheduledNotes;
|
|
878
|
-
for (let i =
|
|
893
|
+
for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
|
|
879
894
|
const note = scheduledNotes[i];
|
|
880
895
|
if (!note)
|
|
881
896
|
continue;
|
|
@@ -909,13 +924,11 @@ export class Midy {
|
|
|
909
924
|
return impulse;
|
|
910
925
|
}
|
|
911
926
|
createConvolutionReverb(audioContext, impulse) {
|
|
912
|
-
const input = new GainNode(audioContext);
|
|
913
927
|
const convolverNode = new ConvolverNode(audioContext, {
|
|
914
928
|
buffer: impulse,
|
|
915
929
|
});
|
|
916
|
-
input.connect(convolverNode);
|
|
917
930
|
return {
|
|
918
|
-
input,
|
|
931
|
+
input: convolverNode,
|
|
919
932
|
output: convolverNode,
|
|
920
933
|
convolverNode,
|
|
921
934
|
};
|
|
@@ -1066,7 +1079,7 @@ export class Midy {
|
|
|
1066
1079
|
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
1067
1080
|
}
|
|
1068
1081
|
updateChannelDetune(channel, scheduleTime) {
|
|
1069
|
-
this.processScheduledNotes(channel,
|
|
1082
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1070
1083
|
this.updateDetune(channel, note, scheduleTime);
|
|
1071
1084
|
});
|
|
1072
1085
|
}
|
|
@@ -1294,31 +1307,31 @@ export class Midy {
|
|
|
1294
1307
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
1295
1308
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
1296
1309
|
}
|
|
1297
|
-
async getAudioBuffer(
|
|
1298
|
-
const audioBufferId = this.
|
|
1299
|
-
const cache = this.
|
|
1310
|
+
async getAudioBuffer(channel, noteNumber, velocity, voiceParams) {
|
|
1311
|
+
const audioBufferId = this.getVoiceId(channel, noteNumber, velocity);
|
|
1312
|
+
const cache = this.voiceCache.get(audioBufferId);
|
|
1300
1313
|
if (cache) {
|
|
1301
1314
|
cache.counter += 1;
|
|
1302
1315
|
if (cache.maxCount <= cache.counter) {
|
|
1303
|
-
this.
|
|
1316
|
+
this.voiceCache.delete(audioBufferId);
|
|
1304
1317
|
}
|
|
1305
1318
|
return cache.audioBuffer;
|
|
1306
1319
|
}
|
|
1307
1320
|
else {
|
|
1308
|
-
const maxCount = this.
|
|
1309
|
-
const audioBuffer = await this.
|
|
1321
|
+
const maxCount = this.voiceCounter.get(audioBufferId) ?? 0;
|
|
1322
|
+
const audioBuffer = await this.createAudioBuffer(voiceParams);
|
|
1310
1323
|
const cache = { audioBuffer, maxCount, counter: 1 };
|
|
1311
|
-
this.
|
|
1324
|
+
this.voiceCache.set(audioBufferId, cache);
|
|
1312
1325
|
return audioBuffer;
|
|
1313
1326
|
}
|
|
1314
1327
|
}
|
|
1315
|
-
async createNote(channel, voice, noteNumber, velocity, startTime
|
|
1328
|
+
async createNote(channel, voice, noteNumber, velocity, startTime) {
|
|
1316
1329
|
const now = this.audioContext.currentTime;
|
|
1317
1330
|
const state = channel.state;
|
|
1318
1331
|
const controllerState = this.getControllerState(channel, noteNumber, velocity, 0);
|
|
1319
1332
|
const voiceParams = voice.getAllParams(controllerState);
|
|
1320
1333
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1321
|
-
const audioBuffer = await this.getAudioBuffer(channel
|
|
1334
|
+
const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams);
|
|
1322
1335
|
note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
|
|
1323
1336
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
1324
1337
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
@@ -1352,12 +1365,8 @@ export class Midy {
|
|
|
1352
1365
|
}
|
|
1353
1366
|
note.bufferSource.connect(note.filterNode);
|
|
1354
1367
|
note.filterNode.connect(note.volumeEnvelopeNode);
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
}
|
|
1358
|
-
if (0 < state.reverbSendLevel) {
|
|
1359
|
-
this.setReverbEffectsSend(channel, note, 0, now);
|
|
1360
|
-
}
|
|
1368
|
+
this.setChorusSend(channel, note, now);
|
|
1369
|
+
this.setReverbSend(channel, note, now);
|
|
1361
1370
|
note.bufferSource.start(startTime);
|
|
1362
1371
|
return note;
|
|
1363
1372
|
}
|
|
@@ -1413,20 +1422,24 @@ export class Midy {
|
|
|
1413
1422
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
1414
1423
|
const channel = this.channels[channelNumber];
|
|
1415
1424
|
const bankNumber = this.calcBank(channel, channelNumber);
|
|
1416
|
-
const soundFontIndex = this.soundFontTable[channel.programNumber]
|
|
1425
|
+
const soundFontIndex = this.soundFontTable[channel.programNumber]
|
|
1426
|
+
.get(bankNumber);
|
|
1417
1427
|
if (soundFontIndex === undefined)
|
|
1418
1428
|
return;
|
|
1419
1429
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
1420
1430
|
const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
|
|
1421
1431
|
if (!voice)
|
|
1422
1432
|
return;
|
|
1423
|
-
const
|
|
1424
|
-
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
1433
|
+
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
|
|
1425
1434
|
if (channel.isDrum) {
|
|
1426
|
-
const
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1435
|
+
const { keyBasedGainLs, keyBasedGainRs } = channel;
|
|
1436
|
+
let gainL = keyBasedGainLs[noteNumber];
|
|
1437
|
+
let gainR = keyBasedGainRs[noteNumber];
|
|
1438
|
+
if (!gainL) {
|
|
1439
|
+
const audioNodes = this.createChannelAudioNodes(this.audioContext);
|
|
1440
|
+
gainL = keyBasedGainLs[noteNumber] = audioNodes.gainL;
|
|
1441
|
+
gainR = keyBasedGainRs[noteNumber] = audioNodes.gainR;
|
|
1442
|
+
}
|
|
1430
1443
|
note.volumeEnvelopeNode.connect(gainL);
|
|
1431
1444
|
note.volumeEnvelopeNode.connect(gainR);
|
|
1432
1445
|
}
|
|
@@ -1460,11 +1473,11 @@ export class Midy {
|
|
|
1460
1473
|
note.vibratoDepth.disconnect();
|
|
1461
1474
|
note.vibratoLFO.stop();
|
|
1462
1475
|
}
|
|
1463
|
-
if (note.
|
|
1464
|
-
note.
|
|
1476
|
+
if (note.reverbSend) {
|
|
1477
|
+
note.reverbSend.disconnect();
|
|
1465
1478
|
}
|
|
1466
|
-
if (note.
|
|
1467
|
-
note.
|
|
1479
|
+
if (note.chorusSend) {
|
|
1480
|
+
note.chorusSend.disconnect();
|
|
1468
1481
|
}
|
|
1469
1482
|
}
|
|
1470
1483
|
releaseNote(channel, note, endTime) {
|
|
@@ -1504,15 +1517,29 @@ export class Midy {
|
|
|
1504
1517
|
return;
|
|
1505
1518
|
}
|
|
1506
1519
|
}
|
|
1507
|
-
const
|
|
1508
|
-
if (
|
|
1520
|
+
const index = this.findNoteOffIndex(channel, noteNumber);
|
|
1521
|
+
if (index < 0)
|
|
1509
1522
|
return;
|
|
1523
|
+
const note = channel.scheduledNotes[index];
|
|
1510
1524
|
note.ending = true;
|
|
1525
|
+
this.setNoteIndex(channel, index);
|
|
1511
1526
|
this.releaseNote(channel, note, endTime);
|
|
1512
1527
|
}
|
|
1513
|
-
|
|
1528
|
+
setNoteIndex(channel, index) {
|
|
1529
|
+
let allEnds = true;
|
|
1530
|
+
for (let i = channel.scheduleIndex; i < index; i++) {
|
|
1531
|
+
const note = channel.scheduledNotes[i];
|
|
1532
|
+
if (note && !note.ending) {
|
|
1533
|
+
allEnds = false;
|
|
1534
|
+
break;
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
if (allEnds)
|
|
1538
|
+
channel.scheduleIndex = index + 1;
|
|
1539
|
+
}
|
|
1540
|
+
findNoteOffIndex(channel, noteNumber) {
|
|
1514
1541
|
const scheduledNotes = channel.scheduledNotes;
|
|
1515
|
-
for (let i =
|
|
1542
|
+
for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
|
|
1516
1543
|
const note = scheduledNotes[i];
|
|
1517
1544
|
if (!note)
|
|
1518
1545
|
continue;
|
|
@@ -1520,8 +1547,9 @@ export class Midy {
|
|
|
1520
1547
|
continue;
|
|
1521
1548
|
if (note.noteNumber !== noteNumber)
|
|
1522
1549
|
continue;
|
|
1523
|
-
return
|
|
1550
|
+
return i;
|
|
1524
1551
|
}
|
|
1552
|
+
return -1;
|
|
1525
1553
|
}
|
|
1526
1554
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1527
1555
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -1561,31 +1589,31 @@ export class Midy {
|
|
|
1561
1589
|
case 0x90:
|
|
1562
1590
|
return this.noteOn(channelNumber, data1, data2, scheduleTime);
|
|
1563
1591
|
case 0xA0:
|
|
1564
|
-
return this.
|
|
1592
|
+
return this.setPolyphonicKeyPressure(channelNumber, data1, data2, scheduleTime);
|
|
1565
1593
|
case 0xB0:
|
|
1566
|
-
return this.
|
|
1594
|
+
return this.setControlChange(channelNumber, data1, data2, scheduleTime);
|
|
1567
1595
|
case 0xC0:
|
|
1568
|
-
return this.
|
|
1596
|
+
return this.setProgramChange(channelNumber, data1, scheduleTime);
|
|
1569
1597
|
case 0xD0:
|
|
1570
|
-
return this.
|
|
1598
|
+
return this.setChannelPressure(channelNumber, data1, scheduleTime);
|
|
1571
1599
|
case 0xE0:
|
|
1572
1600
|
return this.handlePitchBendMessage(channelNumber, data1, data2, scheduleTime);
|
|
1573
1601
|
default:
|
|
1574
1602
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
1575
1603
|
}
|
|
1576
1604
|
}
|
|
1577
|
-
|
|
1605
|
+
setPolyphonicKeyPressure(channelNumber, noteNumber, pressure, scheduleTime) {
|
|
1578
1606
|
const channel = this.channels[channelNumber];
|
|
1579
1607
|
const table = channel.polyphonicKeyPressureTable;
|
|
1580
1608
|
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
1581
1609
|
if (note.noteNumber === noteNumber) {
|
|
1582
1610
|
note.pressure = pressure;
|
|
1583
|
-
this.
|
|
1611
|
+
this.setEffects(channel, note, table, scheduleTime);
|
|
1584
1612
|
}
|
|
1585
1613
|
});
|
|
1586
1614
|
this.applyVoiceParams(channel, 10);
|
|
1587
1615
|
}
|
|
1588
|
-
|
|
1616
|
+
setProgramChange(channelNumber, programNumber, _scheduleTime) {
|
|
1589
1617
|
const channel = this.channels[channelNumber];
|
|
1590
1618
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1591
1619
|
channel.programNumber = programNumber;
|
|
@@ -1600,7 +1628,7 @@ export class Midy {
|
|
|
1600
1628
|
}
|
|
1601
1629
|
}
|
|
1602
1630
|
}
|
|
1603
|
-
|
|
1631
|
+
setChannelPressure(channelNumber, value, scheduleTime) {
|
|
1604
1632
|
const channel = this.channels[channelNumber];
|
|
1605
1633
|
if (channel.isDrum)
|
|
1606
1634
|
return;
|
|
@@ -1614,7 +1642,7 @@ export class Midy {
|
|
|
1614
1642
|
}
|
|
1615
1643
|
const table = channel.channelPressureTable;
|
|
1616
1644
|
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
1617
|
-
this.
|
|
1645
|
+
this.setEffects(channel, note, table, scheduleTime);
|
|
1618
1646
|
});
|
|
1619
1647
|
this.applyVoiceParams(channel, 13);
|
|
1620
1648
|
}
|
|
@@ -1636,13 +1664,18 @@ export class Midy {
|
|
|
1636
1664
|
this.applyVoiceParams(channel, 14, scheduleTime);
|
|
1637
1665
|
}
|
|
1638
1666
|
setModLfoToPitch(channel, note, scheduleTime) {
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
.
|
|
1645
|
-
|
|
1667
|
+
if (note.modulationDepth) {
|
|
1668
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch +
|
|
1669
|
+
this.getLFOPitchDepth(channel, note);
|
|
1670
|
+
const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
|
|
1671
|
+
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
1672
|
+
note.modulationDepth.gain
|
|
1673
|
+
.cancelScheduledValues(scheduleTime)
|
|
1674
|
+
.setValueAtTime(modulationDepth, scheduleTime);
|
|
1675
|
+
}
|
|
1676
|
+
else {
|
|
1677
|
+
this.startModulation(channel, note, scheduleTime);
|
|
1678
|
+
}
|
|
1646
1679
|
}
|
|
1647
1680
|
setVibLfoToPitch(channel, note, scheduleTime) {
|
|
1648
1681
|
const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
|
|
@@ -1669,63 +1702,63 @@ export class Midy {
|
|
|
1669
1702
|
.cancelScheduledValues(scheduleTime)
|
|
1670
1703
|
.setValueAtTime(volumeDepth, scheduleTime);
|
|
1671
1704
|
}
|
|
1672
|
-
|
|
1673
|
-
let value = note.voiceParams.reverbEffectsSend
|
|
1705
|
+
setReverbSend(channel, note, scheduleTime) {
|
|
1706
|
+
let value = note.voiceParams.reverbEffectsSend *
|
|
1707
|
+
channel.state.reverbSendLevel;
|
|
1674
1708
|
if (channel.isDrum) {
|
|
1675
|
-
const keyBasedValue = this.
|
|
1676
|
-
if (0 <= keyBasedValue)
|
|
1677
|
-
value
|
|
1678
|
-
}
|
|
1709
|
+
const keyBasedValue = this.getKeyBasedValue(channel, note.noteNumber, 91);
|
|
1710
|
+
if (0 <= keyBasedValue)
|
|
1711
|
+
value = keyBasedValue / 127;
|
|
1679
1712
|
}
|
|
1680
|
-
if (
|
|
1713
|
+
if (!note.reverbSend) {
|
|
1681
1714
|
if (0 < value) {
|
|
1682
|
-
note.
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
}
|
|
1686
|
-
else {
|
|
1687
|
-
note.reverbEffectsSend.disconnect();
|
|
1715
|
+
note.reverbSend = new GainNode(this.audioContext, { gain: value });
|
|
1716
|
+
note.volumeEnvelopeNode.connect(note.reverbSend);
|
|
1717
|
+
note.reverbSend.connect(this.reverbEffect.input);
|
|
1688
1718
|
}
|
|
1689
1719
|
}
|
|
1690
1720
|
else {
|
|
1721
|
+
note.reverbSend.gain
|
|
1722
|
+
.cancelScheduledValues(scheduleTime)
|
|
1723
|
+
.setValueAtTime(value, scheduleTime);
|
|
1691
1724
|
if (0 < value) {
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
note.volumeEnvelopeNode.
|
|
1725
|
+
note.volumeEnvelopeNode.connect(note.reverbSend);
|
|
1726
|
+
}
|
|
1727
|
+
else {
|
|
1728
|
+
try {
|
|
1729
|
+
note.volumeEnvelopeNode.disconnect(note.reverbSend);
|
|
1697
1730
|
}
|
|
1698
|
-
|
|
1731
|
+
catch { /* empty */ }
|
|
1699
1732
|
}
|
|
1700
1733
|
}
|
|
1701
1734
|
}
|
|
1702
|
-
|
|
1703
|
-
let value = note.voiceParams.chorusEffectsSend
|
|
1735
|
+
setChorusSend(channel, note, scheduleTime) {
|
|
1736
|
+
let value = note.voiceParams.chorusEffectsSend *
|
|
1737
|
+
channel.state.chorusSendLevel;
|
|
1704
1738
|
if (channel.isDrum) {
|
|
1705
|
-
const keyBasedValue = this.
|
|
1706
|
-
if (0 <= keyBasedValue)
|
|
1707
|
-
value
|
|
1708
|
-
}
|
|
1739
|
+
const keyBasedValue = this.getKeyBasedValue(channel, note.noteNumber, 93);
|
|
1740
|
+
if (0 <= keyBasedValue)
|
|
1741
|
+
value = keyBasedValue / 127;
|
|
1709
1742
|
}
|
|
1710
|
-
if (
|
|
1711
|
-
if (0 <
|
|
1712
|
-
note.
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
}
|
|
1716
|
-
else {
|
|
1717
|
-
note.chorusEffectsSend.disconnect();
|
|
1743
|
+
if (!note.chorusSend) {
|
|
1744
|
+
if (0 < value) {
|
|
1745
|
+
note.chorusSend = new GainNode(this.audioContext, { gain: value });
|
|
1746
|
+
note.volumeEnvelopeNode.connect(note.chorusSend);
|
|
1747
|
+
note.chorusSend.connect(this.chorusEffect.input);
|
|
1718
1748
|
}
|
|
1719
1749
|
}
|
|
1720
1750
|
else {
|
|
1751
|
+
note.chorusSend.gain
|
|
1752
|
+
.cancelScheduledValues(scheduleTime)
|
|
1753
|
+
.setValueAtTime(value, scheduleTime);
|
|
1721
1754
|
if (0 < value) {
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
note.volumeEnvelopeNode.
|
|
1755
|
+
note.volumeEnvelopeNode.connect(note.chorusSend);
|
|
1756
|
+
}
|
|
1757
|
+
else {
|
|
1758
|
+
try {
|
|
1759
|
+
note.volumeEnvelopeNode.disconnect(note.chorusSend);
|
|
1727
1760
|
}
|
|
1728
|
-
|
|
1761
|
+
catch { /* empty */ }
|
|
1729
1762
|
}
|
|
1730
1763
|
}
|
|
1731
1764
|
}
|
|
@@ -1771,11 +1804,11 @@ export class Midy {
|
|
|
1771
1804
|
this.setModLfoToVolume(channel, note, scheduleTime);
|
|
1772
1805
|
}
|
|
1773
1806
|
},
|
|
1774
|
-
chorusEffectsSend: (channel, note,
|
|
1775
|
-
this.
|
|
1807
|
+
chorusEffectsSend: (channel, note, _prevValue, scheduleTime) => {
|
|
1808
|
+
this.setChorusSend(channel, note, scheduleTime);
|
|
1776
1809
|
},
|
|
1777
|
-
reverbEffectsSend: (channel, note,
|
|
1778
|
-
this.
|
|
1810
|
+
reverbEffectsSend: (channel, note, _prevValue, scheduleTime) => {
|
|
1811
|
+
this.setReverbSend(channel, note, scheduleTime);
|
|
1779
1812
|
},
|
|
1780
1813
|
delayModLFO: (_channel, note, _prevValue, scheduleTime) => this.setDelayModLFO(note, scheduleTime),
|
|
1781
1814
|
freqModLFO: (_channel, note, _prevValue, scheduleTime) => this.setFreqModLFO(note, scheduleTime),
|
|
@@ -1808,7 +1841,7 @@ export class Midy {
|
|
|
1808
1841
|
return state;
|
|
1809
1842
|
}
|
|
1810
1843
|
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
1811
|
-
this.processScheduledNotes(channel,
|
|
1844
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1812
1845
|
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity, note.pressure);
|
|
1813
1846
|
const voiceParams = note.voice.getParams(controllerType, controllerState);
|
|
1814
1847
|
let applyVolumeEnvelope = false;
|
|
@@ -1879,13 +1912,13 @@ export class Midy {
|
|
|
1879
1912
|
handlers[127] = this.polyOn;
|
|
1880
1913
|
return handlers;
|
|
1881
1914
|
}
|
|
1882
|
-
|
|
1915
|
+
setControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1883
1916
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1884
1917
|
if (handler) {
|
|
1885
1918
|
handler.call(this, channelNumber, value, scheduleTime);
|
|
1886
1919
|
const channel = this.channels[channelNumber];
|
|
1887
1920
|
this.applyVoiceParams(channel, controllerType + 128, scheduleTime);
|
|
1888
|
-
this.
|
|
1921
|
+
this.setControlChangeEffects(channel, controllerType, scheduleTime);
|
|
1889
1922
|
}
|
|
1890
1923
|
else {
|
|
1891
1924
|
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
@@ -1896,12 +1929,11 @@ export class Midy {
|
|
|
1896
1929
|
}
|
|
1897
1930
|
updateModulation(channel, scheduleTime) {
|
|
1898
1931
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1899
|
-
this.processScheduledNotes(channel,
|
|
1932
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1900
1933
|
if (note.modulationDepth) {
|
|
1901
1934
|
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1902
1935
|
}
|
|
1903
1936
|
else {
|
|
1904
|
-
this.setPitchEnvelope(note, scheduleTime);
|
|
1905
1937
|
this.startModulation(channel, note, scheduleTime);
|
|
1906
1938
|
}
|
|
1907
1939
|
});
|
|
@@ -1915,7 +1947,7 @@ export class Midy {
|
|
|
1915
1947
|
this.updateModulation(channel, scheduleTime);
|
|
1916
1948
|
}
|
|
1917
1949
|
updatePortamento(channel, scheduleTime) {
|
|
1918
|
-
this.processScheduledNotes(channel,
|
|
1950
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1919
1951
|
if (0.5 <= channel.state.portamento) {
|
|
1920
1952
|
if (0 <= note.portamentoNoteNumber) {
|
|
1921
1953
|
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
@@ -1946,8 +1978,14 @@ export class Midy {
|
|
|
1946
1978
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1947
1979
|
const channel = this.channels[channelNumber];
|
|
1948
1980
|
channel.state.volume = volume / 127;
|
|
1949
|
-
|
|
1950
|
-
|
|
1981
|
+
if (channel.isDrum) {
|
|
1982
|
+
for (let i = 0; i < 128; i++) {
|
|
1983
|
+
this.updateKeyBasedVolume(channel, i, scheduleTime);
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
else {
|
|
1987
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1988
|
+
}
|
|
1951
1989
|
}
|
|
1952
1990
|
panToGain(pan) {
|
|
1953
1991
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1960,8 +1998,14 @@ export class Midy {
|
|
|
1960
1998
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1961
1999
|
const channel = this.channels[channelNumber];
|
|
1962
2000
|
channel.state.pan = pan / 127;
|
|
1963
|
-
|
|
1964
|
-
|
|
2001
|
+
if (channel.isDrum) {
|
|
2002
|
+
for (let i = 0; i < 128; i++) {
|
|
2003
|
+
this.updateKeyBasedVolume(channel, i, scheduleTime);
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
else {
|
|
2007
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
2008
|
+
}
|
|
1965
2009
|
}
|
|
1966
2010
|
setExpression(channelNumber, expression, scheduleTime) {
|
|
1967
2011
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -1987,33 +2031,27 @@ export class Midy {
|
|
|
1987
2031
|
.cancelScheduledValues(scheduleTime)
|
|
1988
2032
|
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1989
2033
|
}
|
|
1990
|
-
updateKeyBasedVolume(channel, scheduleTime) {
|
|
1991
|
-
if (!channel.isDrum)
|
|
1992
|
-
return;
|
|
2034
|
+
updateKeyBasedVolume(channel, keyNumber, scheduleTime) {
|
|
1993
2035
|
const state = channel.state;
|
|
1994
2036
|
const defaultVolume = state.volume * state.expression;
|
|
1995
2037
|
const defaultPan = state.pan;
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
gainR.gain
|
|
2014
|
-
.cancelScheduledValues(scheduleTime)
|
|
2015
|
-
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
2016
|
-
}
|
|
2038
|
+
const gainL = channel.keyBasedGainLs[keyNumber];
|
|
2039
|
+
const gainR = channel.keyBasedGainRs[keyNumber];
|
|
2040
|
+
if (!gainL)
|
|
2041
|
+
return;
|
|
2042
|
+
const keyBasedVolume = this.getKeyBasedValue(channel, keyNumber, 7);
|
|
2043
|
+
const volume = (0 <= keyBasedVolume)
|
|
2044
|
+
? defaultVolume * keyBasedVolume / 64
|
|
2045
|
+
: defaultVolume;
|
|
2046
|
+
const keyBasedPan = this.getKeyBasedValue(channel, keyNumber, 10);
|
|
2047
|
+
const pan = (0 <= keyBasedPan) ? keyBasedPan / 127 : defaultPan;
|
|
2048
|
+
const { gainLeft, gainRight } = this.panToGain(pan);
|
|
2049
|
+
gainL.gain
|
|
2050
|
+
.cancelScheduledValues(scheduleTime)
|
|
2051
|
+
.setValueAtTime(volume * gainLeft, scheduleTime);
|
|
2052
|
+
gainR.gain
|
|
2053
|
+
.cancelScheduledValues(scheduleTime)
|
|
2054
|
+
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
2017
2055
|
}
|
|
2018
2056
|
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
2019
2057
|
const channel = this.channels[channelNumber];
|
|
@@ -2022,7 +2060,7 @@ export class Midy {
|
|
|
2022
2060
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2023
2061
|
channel.state.sustainPedal = value / 127;
|
|
2024
2062
|
if (64 <= value) {
|
|
2025
|
-
this.processScheduledNotes(channel,
|
|
2063
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2026
2064
|
channel.sustainNotes.push(note);
|
|
2027
2065
|
});
|
|
2028
2066
|
}
|
|
@@ -2065,7 +2103,7 @@ export class Midy {
|
|
|
2065
2103
|
const state = channel.state;
|
|
2066
2104
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2067
2105
|
state.softPedal = softPedal / 127;
|
|
2068
|
-
this.processScheduledNotes(channel,
|
|
2106
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2069
2107
|
if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
2070
2108
|
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2071
2109
|
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
@@ -2083,7 +2121,7 @@ export class Midy {
|
|
|
2083
2121
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2084
2122
|
const state = channel.state;
|
|
2085
2123
|
state.filterResonance = filterResonance / 127;
|
|
2086
|
-
this.processScheduledNotes(channel,
|
|
2124
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2087
2125
|
const Q = note.voiceParams.initialFilterQ / 5 * state.filterResonance;
|
|
2088
2126
|
note.filterNode.Q.setValueAtTime(Q, scheduleTime);
|
|
2089
2127
|
});
|
|
@@ -2101,10 +2139,10 @@ export class Midy {
|
|
|
2101
2139
|
return;
|
|
2102
2140
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2103
2141
|
channel.state.attackTime = attackTime / 127;
|
|
2104
|
-
this.processScheduledNotes(channel,
|
|
2142
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2105
2143
|
if (note.startTime < scheduleTime)
|
|
2106
2144
|
return false;
|
|
2107
|
-
this.setVolumeEnvelope(channel, note);
|
|
2145
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
2108
2146
|
});
|
|
2109
2147
|
}
|
|
2110
2148
|
setBrightness(channelNumber, brightness, scheduleTime) {
|
|
@@ -2114,12 +2152,12 @@ export class Midy {
|
|
|
2114
2152
|
const state = channel.state;
|
|
2115
2153
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2116
2154
|
state.brightness = brightness / 127;
|
|
2117
|
-
this.processScheduledNotes(channel,
|
|
2155
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2118
2156
|
if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
2119
2157
|
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2120
2158
|
}
|
|
2121
2159
|
else {
|
|
2122
|
-
this.setFilterEnvelope(channel, note);
|
|
2160
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
2123
2161
|
}
|
|
2124
2162
|
});
|
|
2125
2163
|
}
|
|
@@ -2129,7 +2167,7 @@ export class Midy {
|
|
|
2129
2167
|
return;
|
|
2130
2168
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2131
2169
|
channel.state.decayTime = dacayTime / 127;
|
|
2132
|
-
this.processScheduledNotes(channel,
|
|
2170
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2133
2171
|
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
2134
2172
|
});
|
|
2135
2173
|
}
|
|
@@ -2141,7 +2179,7 @@ export class Midy {
|
|
|
2141
2179
|
channel.state.vibratoRate = vibratoRate / 127;
|
|
2142
2180
|
if (channel.vibratoDepth <= 0)
|
|
2143
2181
|
return;
|
|
2144
|
-
this.processScheduledNotes(channel,
|
|
2182
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2145
2183
|
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
2146
2184
|
});
|
|
2147
2185
|
}
|
|
@@ -2153,12 +2191,12 @@ export class Midy {
|
|
|
2153
2191
|
const prev = channel.state.vibratoDepth;
|
|
2154
2192
|
channel.state.vibratoDepth = vibratoDepth / 127;
|
|
2155
2193
|
if (0 < prev) {
|
|
2156
|
-
this.processScheduledNotes(channel,
|
|
2194
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2157
2195
|
this.setFreqVibLFO(channel, note, scheduleTime);
|
|
2158
2196
|
});
|
|
2159
2197
|
}
|
|
2160
2198
|
else {
|
|
2161
|
-
this.processScheduledNotes(channel,
|
|
2199
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2162
2200
|
this.startVibrato(channel, note, scheduleTime);
|
|
2163
2201
|
});
|
|
2164
2202
|
}
|
|
@@ -2170,7 +2208,7 @@ export class Midy {
|
|
|
2170
2208
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2171
2209
|
channel.state.vibratoDelay = vibratoDelay / 127;
|
|
2172
2210
|
if (0 < channel.state.vibratoDepth) {
|
|
2173
|
-
this.processScheduledNotes(channel,
|
|
2211
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2174
2212
|
this.startVibrato(channel, note, scheduleTime);
|
|
2175
2213
|
});
|
|
2176
2214
|
}
|
|
@@ -2179,67 +2217,19 @@ export class Midy {
|
|
|
2179
2217
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2180
2218
|
const channel = this.channels[channelNumber];
|
|
2181
2219
|
const state = channel.state;
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
reverbEffect.input.gain
|
|
2187
|
-
.cancelScheduledValues(scheduleTime)
|
|
2188
|
-
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
2189
|
-
}
|
|
2190
|
-
else {
|
|
2191
|
-
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2192
|
-
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
2193
|
-
return false;
|
|
2194
|
-
if (note.reverbEffectsSend)
|
|
2195
|
-
note.reverbEffectsSend.disconnect();
|
|
2196
|
-
});
|
|
2197
|
-
}
|
|
2198
|
-
}
|
|
2199
|
-
else {
|
|
2200
|
-
if (0 < reverbSendLevel) {
|
|
2201
|
-
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2202
|
-
this.setReverbEffectsSend(channel, note, 0, scheduleTime);
|
|
2203
|
-
});
|
|
2204
|
-
state.reverbSendLevel = reverbSendLevel / 127;
|
|
2205
|
-
reverbEffect.input.gain
|
|
2206
|
-
.cancelScheduledValues(scheduleTime)
|
|
2207
|
-
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
2208
|
-
}
|
|
2209
|
-
}
|
|
2220
|
+
state.reverbSendLevel = reverbSendLevel / 127;
|
|
2221
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2222
|
+
this.setReverbSend(channel, note, scheduleTime);
|
|
2223
|
+
});
|
|
2210
2224
|
}
|
|
2211
2225
|
setChorusSendLevel(channelNumber, chorusSendLevel, scheduleTime) {
|
|
2212
2226
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2213
2227
|
const channel = this.channels[channelNumber];
|
|
2214
2228
|
const state = channel.state;
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
chorusEffect.input.gain
|
|
2220
|
-
.cancelScheduledValues(scheduleTime)
|
|
2221
|
-
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
2222
|
-
}
|
|
2223
|
-
else {
|
|
2224
|
-
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2225
|
-
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
2226
|
-
return false;
|
|
2227
|
-
if (note.chorusEffectsSend)
|
|
2228
|
-
note.chorusEffectsSend.disconnect();
|
|
2229
|
-
});
|
|
2230
|
-
}
|
|
2231
|
-
}
|
|
2232
|
-
else {
|
|
2233
|
-
if (0 < chorusSendLevel) {
|
|
2234
|
-
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2235
|
-
this.setChorusEffectsSend(channel, note, 0, scheduleTime);
|
|
2236
|
-
});
|
|
2237
|
-
state.chorusSendLevel = chorusSendLevel / 127;
|
|
2238
|
-
chorusEffect.input.gain
|
|
2239
|
-
.cancelScheduledValues(scheduleTime)
|
|
2240
|
-
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
2241
|
-
}
|
|
2242
|
-
}
|
|
2229
|
+
state.chorusSendLevel = chorusSendLevel / 127;
|
|
2230
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2231
|
+
this.setChorusSend(channel, note, scheduleTime);
|
|
2232
|
+
});
|
|
2243
2233
|
}
|
|
2244
2234
|
limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
|
|
2245
2235
|
if (maxLSB < channel.dataLSB) {
|
|
@@ -2389,7 +2379,7 @@ export class Midy {
|
|
|
2389
2379
|
const entries = Object.entries(defaultControllerState);
|
|
2390
2380
|
for (const [key, { type, defaultValue }] of entries) {
|
|
2391
2381
|
if (128 <= type) {
|
|
2392
|
-
this.
|
|
2382
|
+
this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
2393
2383
|
}
|
|
2394
2384
|
else {
|
|
2395
2385
|
state[key] = defaultValue;
|
|
@@ -2422,7 +2412,7 @@ export class Midy {
|
|
|
2422
2412
|
const key = keys[i];
|
|
2423
2413
|
const { type, defaultValue } = defaultControllerState[key];
|
|
2424
2414
|
if (128 <= type) {
|
|
2425
|
-
this.
|
|
2415
|
+
this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
2426
2416
|
}
|
|
2427
2417
|
else {
|
|
2428
2418
|
state[key] = defaultValue;
|
|
@@ -2550,11 +2540,11 @@ export class Midy {
|
|
|
2550
2540
|
case 9:
|
|
2551
2541
|
switch (data[3]) {
|
|
2552
2542
|
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
|
|
2553
|
-
return this.handlePressureSysEx(data, "channelPressureTable");
|
|
2543
|
+
return this.handlePressureSysEx(data, "channelPressureTable", scheduleTime);
|
|
2554
2544
|
case 2: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
|
|
2555
|
-
return this.handlePressureSysEx(data, "polyphonicKeyPressureTable");
|
|
2545
|
+
return this.handlePressureSysEx(data, "polyphonicKeyPressureTable", scheduleTime);
|
|
2556
2546
|
case 3: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
|
|
2557
|
-
return this.handleControlChangeSysEx(data);
|
|
2547
|
+
return this.handleControlChangeSysEx(data, scheduleTime);
|
|
2558
2548
|
default:
|
|
2559
2549
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2560
2550
|
}
|
|
@@ -2925,29 +2915,31 @@ export class Midy {
|
|
|
2925
2915
|
: 0;
|
|
2926
2916
|
return (channelPressure + polyphonicKeyPressure) / 254;
|
|
2927
2917
|
}
|
|
2928
|
-
|
|
2918
|
+
setEffects(channel, note, table, scheduleTime) {
|
|
2929
2919
|
if (0 <= table[0])
|
|
2930
|
-
this.updateDetune(channel, note);
|
|
2920
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
2931
2921
|
if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
|
|
2932
|
-
if (0 <= table[1])
|
|
2933
|
-
this.setPortamentoFilterEnvelope(channel, note);
|
|
2934
|
-
|
|
2935
|
-
|
|
2922
|
+
if (0 <= table[1]) {
|
|
2923
|
+
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2924
|
+
}
|
|
2925
|
+
if (0 <= table[2]) {
|
|
2926
|
+
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2927
|
+
}
|
|
2936
2928
|
}
|
|
2937
2929
|
else {
|
|
2938
2930
|
if (0 <= table[1])
|
|
2939
|
-
this.setFilterEnvelope(channel, note);
|
|
2931
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
2940
2932
|
if (0 <= table[2])
|
|
2941
|
-
this.setVolumeEnvelope(channel, note);
|
|
2933
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
2942
2934
|
}
|
|
2943
2935
|
if (0 <= table[3])
|
|
2944
|
-
this.setModLfoToPitch(channel, note);
|
|
2936
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
2945
2937
|
if (0 <= table[4])
|
|
2946
|
-
this.setModLfoToFilterFc(channel, note);
|
|
2938
|
+
this.setModLfoToFilterFc(channel, note, scheduleTime);
|
|
2947
2939
|
if (0 <= table[5])
|
|
2948
|
-
this.setModLfoToVolume(channel, note);
|
|
2940
|
+
this.setModLfoToVolume(channel, note, scheduleTime);
|
|
2949
2941
|
}
|
|
2950
|
-
handlePressureSysEx(data, tableName) {
|
|
2942
|
+
handlePressureSysEx(data, tableName, scheduleTime) {
|
|
2951
2943
|
const channelNumber = data[4];
|
|
2952
2944
|
const channel = this.channels[channelNumber];
|
|
2953
2945
|
if (channel.isDrum)
|
|
@@ -2958,34 +2950,40 @@ export class Midy {
|
|
|
2958
2950
|
const rr = data[i + 1];
|
|
2959
2951
|
table[pp] = rr;
|
|
2960
2952
|
}
|
|
2953
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
2954
|
+
this.setEffects(channel, note, table, scheduleTime);
|
|
2955
|
+
});
|
|
2961
2956
|
}
|
|
2962
2957
|
initControlTable() {
|
|
2963
2958
|
const ccCount = 128;
|
|
2964
2959
|
const slotSize = 6;
|
|
2965
2960
|
return new Int8Array(ccCount * slotSize).fill(-1);
|
|
2966
2961
|
}
|
|
2967
|
-
|
|
2962
|
+
setControlChangeEffects(channel, controllerType, scheduleTime) {
|
|
2968
2963
|
const slotSize = 6;
|
|
2969
2964
|
const offset = controllerType * slotSize;
|
|
2970
2965
|
const table = channel.controlTable.subarray(offset, offset + slotSize);
|
|
2971
|
-
this.processScheduledNotes(channel,
|
|
2972
|
-
this.
|
|
2966
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2967
|
+
this.setEffects(channel, note, table, scheduleTime);
|
|
2973
2968
|
});
|
|
2974
2969
|
}
|
|
2975
|
-
handleControlChangeSysEx(data) {
|
|
2970
|
+
handleControlChangeSysEx(data, scheduleTime) {
|
|
2976
2971
|
const channelNumber = data[4];
|
|
2977
2972
|
const channel = this.channels[channelNumber];
|
|
2978
2973
|
if (channel.isDrum)
|
|
2979
2974
|
return;
|
|
2975
|
+
const slotSize = 6;
|
|
2980
2976
|
const controllerType = data[5];
|
|
2981
|
-
const
|
|
2982
|
-
|
|
2977
|
+
const offset = controllerType * slotSize;
|
|
2978
|
+
const table = channel.controlTable;
|
|
2979
|
+
for (let i = 6; i < data.length; i += 2) {
|
|
2983
2980
|
const pp = data[i];
|
|
2984
2981
|
const rr = data[i + 1];
|
|
2985
|
-
table[pp] = rr;
|
|
2982
|
+
table[offset + pp] = rr;
|
|
2986
2983
|
}
|
|
2984
|
+
this.setControlChangeEffects(channel, controllerType, scheduleTime);
|
|
2987
2985
|
}
|
|
2988
|
-
|
|
2986
|
+
getKeyBasedValue(channel, keyNumber, controllerType) {
|
|
2989
2987
|
const index = keyNumber * 128 + controllerType;
|
|
2990
2988
|
const controlValue = channel.keyBasedInstrumentControlTable[index];
|
|
2991
2989
|
return controlValue;
|
|
@@ -2997,13 +2995,20 @@ export class Midy {
|
|
|
2997
2995
|
return;
|
|
2998
2996
|
const keyNumber = data[5];
|
|
2999
2997
|
const table = channel.keyBasedInstrumentControlTable;
|
|
3000
|
-
for (let i = 6; i < data.length
|
|
2998
|
+
for (let i = 6; i < data.length; i += 2) {
|
|
3001
2999
|
const controllerType = data[i];
|
|
3002
3000
|
const value = data[i + 1];
|
|
3003
3001
|
const index = keyNumber * 128 + controllerType;
|
|
3004
3002
|
table[index] = value;
|
|
3003
|
+
switch (controllerType) {
|
|
3004
|
+
case 7:
|
|
3005
|
+
case 10:
|
|
3006
|
+
this.updateKeyBasedVolume(channel, keyNumber, scheduleTime);
|
|
3007
|
+
break;
|
|
3008
|
+
default: // TODO
|
|
3009
|
+
this.setControlChange(channelNumber, controllerType, value, scheduleTime);
|
|
3010
|
+
}
|
|
3005
3011
|
}
|
|
3006
|
-
this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
|
|
3007
3012
|
}
|
|
3008
3013
|
handleSysEx(data, scheduleTime) {
|
|
3009
3014
|
switch (data[0]) {
|
|
@@ -3040,6 +3045,7 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
3040
3045
|
configurable: true,
|
|
3041
3046
|
writable: true,
|
|
3042
3047
|
value: {
|
|
3048
|
+
scheduleIndex: 0,
|
|
3043
3049
|
detune: 0,
|
|
3044
3050
|
programNumber: 0,
|
|
3045
3051
|
bank: 121 * 128,
|