@marmooo/midy 0.3.4 → 0.3.5
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 +13 -10
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +126 -95
- package/esm/midy-GM2.d.ts +17 -13
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +157 -124
- package/esm/midy-GMLite.d.ts +14 -10
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +125 -96
- package/esm/midy.d.ts +18 -14
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +172 -139
- package/package.json +2 -2
- package/script/midy-GM1.d.ts +13 -10
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +126 -95
- package/script/midy-GM2.d.ts +17 -13
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +157 -124
- package/script/midy-GMLite.d.ts +14 -10
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +125 -96
- package/script/midy.d.ts +18 -14
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +172 -139
package/script/midy.js
CHANGED
|
@@ -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,
|
|
@@ -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}:${instrument}:${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;
|
|
@@ -1069,7 +1084,7 @@ class Midy {
|
|
|
1069
1084
|
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
1070
1085
|
}
|
|
1071
1086
|
updateChannelDetune(channel, scheduleTime) {
|
|
1072
|
-
this.processScheduledNotes(channel,
|
|
1087
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1073
1088
|
this.updateDetune(channel, note, scheduleTime);
|
|
1074
1089
|
});
|
|
1075
1090
|
}
|
|
@@ -1297,31 +1312,31 @@ class Midy {
|
|
|
1297
1312
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
1298
1313
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
1299
1314
|
}
|
|
1300
|
-
async getAudioBuffer(
|
|
1301
|
-
const audioBufferId = this.
|
|
1302
|
-
const cache = this.
|
|
1315
|
+
async getAudioBuffer(channel, noteNumber, velocity, voiceParams) {
|
|
1316
|
+
const audioBufferId = this.getVoiceId(channel, noteNumber, velocity);
|
|
1317
|
+
const cache = this.voiceCache.get(audioBufferId);
|
|
1303
1318
|
if (cache) {
|
|
1304
1319
|
cache.counter += 1;
|
|
1305
1320
|
if (cache.maxCount <= cache.counter) {
|
|
1306
|
-
this.
|
|
1321
|
+
this.voiceCache.delete(audioBufferId);
|
|
1307
1322
|
}
|
|
1308
1323
|
return cache.audioBuffer;
|
|
1309
1324
|
}
|
|
1310
1325
|
else {
|
|
1311
|
-
const maxCount = this.
|
|
1312
|
-
const audioBuffer = await this.
|
|
1326
|
+
const maxCount = this.voiceCounter.get(audioBufferId) ?? 0;
|
|
1327
|
+
const audioBuffer = await this.createAudioBuffer(voiceParams);
|
|
1313
1328
|
const cache = { audioBuffer, maxCount, counter: 1 };
|
|
1314
|
-
this.
|
|
1329
|
+
this.voiceCache.set(audioBufferId, cache);
|
|
1315
1330
|
return audioBuffer;
|
|
1316
1331
|
}
|
|
1317
1332
|
}
|
|
1318
|
-
async createNote(channel, voice, noteNumber, velocity, startTime
|
|
1333
|
+
async createNote(channel, voice, noteNumber, velocity, startTime) {
|
|
1319
1334
|
const now = this.audioContext.currentTime;
|
|
1320
1335
|
const state = channel.state;
|
|
1321
1336
|
const controllerState = this.getControllerState(channel, noteNumber, velocity, 0);
|
|
1322
1337
|
const voiceParams = voice.getAllParams(controllerState);
|
|
1323
1338
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1324
|
-
const audioBuffer = await this.getAudioBuffer(channel
|
|
1339
|
+
const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams);
|
|
1325
1340
|
note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
|
|
1326
1341
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
1327
1342
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
@@ -1416,15 +1431,15 @@ class Midy {
|
|
|
1416
1431
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
1417
1432
|
const channel = this.channels[channelNumber];
|
|
1418
1433
|
const bankNumber = this.calcBank(channel, channelNumber);
|
|
1419
|
-
const soundFontIndex = this.soundFontTable[channel.programNumber]
|
|
1434
|
+
const soundFontIndex = this.soundFontTable[channel.programNumber]
|
|
1435
|
+
.get(bankNumber);
|
|
1420
1436
|
if (soundFontIndex === undefined)
|
|
1421
1437
|
return;
|
|
1422
1438
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
1423
1439
|
const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
|
|
1424
1440
|
if (!voice)
|
|
1425
1441
|
return;
|
|
1426
|
-
const
|
|
1427
|
-
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
1442
|
+
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
|
|
1428
1443
|
if (channel.isDrum) {
|
|
1429
1444
|
const audioContext = this.audioContext;
|
|
1430
1445
|
const { gainL, gainR } = this.createChannelAudioNodes(audioContext);
|
|
@@ -1507,15 +1522,29 @@ class Midy {
|
|
|
1507
1522
|
return;
|
|
1508
1523
|
}
|
|
1509
1524
|
}
|
|
1510
|
-
const
|
|
1511
|
-
if (
|
|
1525
|
+
const index = this.findNoteOffIndex(channel, noteNumber);
|
|
1526
|
+
if (index < 0)
|
|
1512
1527
|
return;
|
|
1528
|
+
const note = channel.scheduledNotes[index];
|
|
1513
1529
|
note.ending = true;
|
|
1530
|
+
this.setNoteIndex(channel, index);
|
|
1514
1531
|
this.releaseNote(channel, note, endTime);
|
|
1515
1532
|
}
|
|
1516
|
-
|
|
1533
|
+
setNoteIndex(channel, index) {
|
|
1534
|
+
let allEnds = true;
|
|
1535
|
+
for (let i = channel.scheduleIndex; i < index; i++) {
|
|
1536
|
+
const note = channel.scheduledNotes[i];
|
|
1537
|
+
if (note && !note.ending) {
|
|
1538
|
+
allEnds = false;
|
|
1539
|
+
break;
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
if (allEnds)
|
|
1543
|
+
channel.scheduleIndex = index + 1;
|
|
1544
|
+
}
|
|
1545
|
+
findNoteOffIndex(channel, noteNumber) {
|
|
1517
1546
|
const scheduledNotes = channel.scheduledNotes;
|
|
1518
|
-
for (let i =
|
|
1547
|
+
for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
|
|
1519
1548
|
const note = scheduledNotes[i];
|
|
1520
1549
|
if (!note)
|
|
1521
1550
|
continue;
|
|
@@ -1523,8 +1552,9 @@ class Midy {
|
|
|
1523
1552
|
continue;
|
|
1524
1553
|
if (note.noteNumber !== noteNumber)
|
|
1525
1554
|
continue;
|
|
1526
|
-
return
|
|
1555
|
+
return i;
|
|
1527
1556
|
}
|
|
1557
|
+
return -1;
|
|
1528
1558
|
}
|
|
1529
1559
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1530
1560
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -1564,31 +1594,31 @@ class Midy {
|
|
|
1564
1594
|
case 0x90:
|
|
1565
1595
|
return this.noteOn(channelNumber, data1, data2, scheduleTime);
|
|
1566
1596
|
case 0xA0:
|
|
1567
|
-
return this.
|
|
1597
|
+
return this.setPolyphonicKeyPressure(channelNumber, data1, data2, scheduleTime);
|
|
1568
1598
|
case 0xB0:
|
|
1569
|
-
return this.
|
|
1599
|
+
return this.setControlChange(channelNumber, data1, data2, scheduleTime);
|
|
1570
1600
|
case 0xC0:
|
|
1571
|
-
return this.
|
|
1601
|
+
return this.setProgramChange(channelNumber, data1, scheduleTime);
|
|
1572
1602
|
case 0xD0:
|
|
1573
|
-
return this.
|
|
1603
|
+
return this.setChannelPressure(channelNumber, data1, scheduleTime);
|
|
1574
1604
|
case 0xE0:
|
|
1575
1605
|
return this.handlePitchBendMessage(channelNumber, data1, data2, scheduleTime);
|
|
1576
1606
|
default:
|
|
1577
1607
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
1578
1608
|
}
|
|
1579
1609
|
}
|
|
1580
|
-
|
|
1610
|
+
setPolyphonicKeyPressure(channelNumber, noteNumber, pressure, scheduleTime) {
|
|
1581
1611
|
const channel = this.channels[channelNumber];
|
|
1582
1612
|
const table = channel.polyphonicKeyPressureTable;
|
|
1583
1613
|
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
1584
1614
|
if (note.noteNumber === noteNumber) {
|
|
1585
1615
|
note.pressure = pressure;
|
|
1586
|
-
this.setControllerParameters(channel, note, table);
|
|
1616
|
+
this.setControllerParameters(channel, note, table, scheduleTime);
|
|
1587
1617
|
}
|
|
1588
1618
|
});
|
|
1589
1619
|
this.applyVoiceParams(channel, 10);
|
|
1590
1620
|
}
|
|
1591
|
-
|
|
1621
|
+
setProgramChange(channelNumber, programNumber, _scheduleTime) {
|
|
1592
1622
|
const channel = this.channels[channelNumber];
|
|
1593
1623
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1594
1624
|
channel.programNumber = programNumber;
|
|
@@ -1603,7 +1633,7 @@ class Midy {
|
|
|
1603
1633
|
}
|
|
1604
1634
|
}
|
|
1605
1635
|
}
|
|
1606
|
-
|
|
1636
|
+
setChannelPressure(channelNumber, value, scheduleTime) {
|
|
1607
1637
|
const channel = this.channels[channelNumber];
|
|
1608
1638
|
if (channel.isDrum)
|
|
1609
1639
|
return;
|
|
@@ -1617,7 +1647,7 @@ class Midy {
|
|
|
1617
1647
|
}
|
|
1618
1648
|
const table = channel.channelPressureTable;
|
|
1619
1649
|
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
1620
|
-
this.setControllerParameters(channel, note, table);
|
|
1650
|
+
this.setControllerParameters(channel, note, table, scheduleTime);
|
|
1621
1651
|
});
|
|
1622
1652
|
this.applyVoiceParams(channel, 13);
|
|
1623
1653
|
}
|
|
@@ -1675,7 +1705,7 @@ class Midy {
|
|
|
1675
1705
|
setReverbEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1676
1706
|
let value = note.voiceParams.reverbEffectsSend;
|
|
1677
1707
|
if (channel.isDrum) {
|
|
1678
|
-
const keyBasedValue = this.
|
|
1708
|
+
const keyBasedValue = this.getKeyBasedValue(channel, note.noteNumber, 91);
|
|
1679
1709
|
if (0 <= keyBasedValue) {
|
|
1680
1710
|
value *= keyBasedValue / 127 / channel.state.reverbSendLevel;
|
|
1681
1711
|
}
|
|
@@ -1705,13 +1735,13 @@ class Midy {
|
|
|
1705
1735
|
setChorusEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1706
1736
|
let value = note.voiceParams.chorusEffectsSend;
|
|
1707
1737
|
if (channel.isDrum) {
|
|
1708
|
-
const keyBasedValue = this.
|
|
1738
|
+
const keyBasedValue = this.getKeyBasedValue(channel, note.noteNumber, 93);
|
|
1709
1739
|
if (0 <= keyBasedValue) {
|
|
1710
1740
|
value *= keyBasedValue / 127 / channel.state.chorusSendLevel;
|
|
1711
1741
|
}
|
|
1712
1742
|
}
|
|
1713
1743
|
if (0 < prevValue) {
|
|
1714
|
-
if (0 <
|
|
1744
|
+
if (0 < value) {
|
|
1715
1745
|
note.chorusEffectsSend.gain
|
|
1716
1746
|
.cancelScheduledValues(scheduleTime)
|
|
1717
1747
|
.setValueAtTime(value, scheduleTime);
|
|
@@ -1811,7 +1841,7 @@ class Midy {
|
|
|
1811
1841
|
return state;
|
|
1812
1842
|
}
|
|
1813
1843
|
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
1814
|
-
this.processScheduledNotes(channel,
|
|
1844
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1815
1845
|
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity, note.pressure);
|
|
1816
1846
|
const voiceParams = note.voice.getParams(controllerType, controllerState);
|
|
1817
1847
|
let applyVolumeEnvelope = false;
|
|
@@ -1882,7 +1912,7 @@ class Midy {
|
|
|
1882
1912
|
handlers[127] = this.polyOn;
|
|
1883
1913
|
return handlers;
|
|
1884
1914
|
}
|
|
1885
|
-
|
|
1915
|
+
setControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1886
1916
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1887
1917
|
if (handler) {
|
|
1888
1918
|
handler.call(this, channelNumber, value, scheduleTime);
|
|
@@ -1899,7 +1929,7 @@ class Midy {
|
|
|
1899
1929
|
}
|
|
1900
1930
|
updateModulation(channel, scheduleTime) {
|
|
1901
1931
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1902
|
-
this.processScheduledNotes(channel,
|
|
1932
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1903
1933
|
if (note.modulationDepth) {
|
|
1904
1934
|
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1905
1935
|
}
|
|
@@ -1918,7 +1948,7 @@ class Midy {
|
|
|
1918
1948
|
this.updateModulation(channel, scheduleTime);
|
|
1919
1949
|
}
|
|
1920
1950
|
updatePortamento(channel, scheduleTime) {
|
|
1921
|
-
this.processScheduledNotes(channel,
|
|
1951
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1922
1952
|
if (0.5 <= channel.state.portamento) {
|
|
1923
1953
|
if (0 <= note.portamentoNoteNumber) {
|
|
1924
1954
|
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
@@ -2003,11 +2033,11 @@ class Midy {
|
|
|
2003
2033
|
continue;
|
|
2004
2034
|
if (!gainR)
|
|
2005
2035
|
continue;
|
|
2006
|
-
const keyBasedVolume = this.
|
|
2036
|
+
const keyBasedVolume = this.getKeyBasedValue(channel, i, 7);
|
|
2007
2037
|
const volume = (0 <= keyBasedVolume)
|
|
2008
2038
|
? defaultVolume * keyBasedVolume / 64
|
|
2009
2039
|
: defaultVolume;
|
|
2010
|
-
const keyBasedPan = this.
|
|
2040
|
+
const keyBasedPan = this.getKeyBasedValue(channel, i, 10);
|
|
2011
2041
|
const pan = (0 <= keyBasedPan) ? keyBasedPan / 127 : defaultPan;
|
|
2012
2042
|
const { gainLeft, gainRight } = this.panToGain(pan);
|
|
2013
2043
|
gainL.gain
|
|
@@ -2025,7 +2055,7 @@ class Midy {
|
|
|
2025
2055
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2026
2056
|
channel.state.sustainPedal = value / 127;
|
|
2027
2057
|
if (64 <= value) {
|
|
2028
|
-
this.processScheduledNotes(channel,
|
|
2058
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2029
2059
|
channel.sustainNotes.push(note);
|
|
2030
2060
|
});
|
|
2031
2061
|
}
|
|
@@ -2068,7 +2098,7 @@ class Midy {
|
|
|
2068
2098
|
const state = channel.state;
|
|
2069
2099
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2070
2100
|
state.softPedal = softPedal / 127;
|
|
2071
|
-
this.processScheduledNotes(channel,
|
|
2101
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2072
2102
|
if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
2073
2103
|
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2074
2104
|
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
@@ -2086,7 +2116,7 @@ class Midy {
|
|
|
2086
2116
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2087
2117
|
const state = channel.state;
|
|
2088
2118
|
state.filterResonance = filterResonance / 127;
|
|
2089
|
-
this.processScheduledNotes(channel,
|
|
2119
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2090
2120
|
const Q = note.voiceParams.initialFilterQ / 5 * state.filterResonance;
|
|
2091
2121
|
note.filterNode.Q.setValueAtTime(Q, scheduleTime);
|
|
2092
2122
|
});
|
|
@@ -2104,10 +2134,10 @@ class Midy {
|
|
|
2104
2134
|
return;
|
|
2105
2135
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2106
2136
|
channel.state.attackTime = attackTime / 127;
|
|
2107
|
-
this.processScheduledNotes(channel,
|
|
2137
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2108
2138
|
if (note.startTime < scheduleTime)
|
|
2109
2139
|
return false;
|
|
2110
|
-
this.setVolumeEnvelope(channel, note);
|
|
2140
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
2111
2141
|
});
|
|
2112
2142
|
}
|
|
2113
2143
|
setBrightness(channelNumber, brightness, scheduleTime) {
|
|
@@ -2117,12 +2147,12 @@ class Midy {
|
|
|
2117
2147
|
const state = channel.state;
|
|
2118
2148
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2119
2149
|
state.brightness = brightness / 127;
|
|
2120
|
-
this.processScheduledNotes(channel,
|
|
2150
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2121
2151
|
if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
2122
2152
|
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2123
2153
|
}
|
|
2124
2154
|
else {
|
|
2125
|
-
this.setFilterEnvelope(channel, note);
|
|
2155
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
2126
2156
|
}
|
|
2127
2157
|
});
|
|
2128
2158
|
}
|
|
@@ -2132,7 +2162,7 @@ class Midy {
|
|
|
2132
2162
|
return;
|
|
2133
2163
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2134
2164
|
channel.state.decayTime = dacayTime / 127;
|
|
2135
|
-
this.processScheduledNotes(channel,
|
|
2165
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2136
2166
|
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
2137
2167
|
});
|
|
2138
2168
|
}
|
|
@@ -2144,7 +2174,7 @@ class Midy {
|
|
|
2144
2174
|
channel.state.vibratoRate = vibratoRate / 127;
|
|
2145
2175
|
if (channel.vibratoDepth <= 0)
|
|
2146
2176
|
return;
|
|
2147
|
-
this.processScheduledNotes(channel,
|
|
2177
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2148
2178
|
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
2149
2179
|
});
|
|
2150
2180
|
}
|
|
@@ -2156,12 +2186,12 @@ class Midy {
|
|
|
2156
2186
|
const prev = channel.state.vibratoDepth;
|
|
2157
2187
|
channel.state.vibratoDepth = vibratoDepth / 127;
|
|
2158
2188
|
if (0 < prev) {
|
|
2159
|
-
this.processScheduledNotes(channel,
|
|
2189
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2160
2190
|
this.setFreqVibLFO(channel, note, scheduleTime);
|
|
2161
2191
|
});
|
|
2162
2192
|
}
|
|
2163
2193
|
else {
|
|
2164
|
-
this.processScheduledNotes(channel,
|
|
2194
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2165
2195
|
this.startVibrato(channel, note, scheduleTime);
|
|
2166
2196
|
});
|
|
2167
2197
|
}
|
|
@@ -2173,7 +2203,7 @@ class Midy {
|
|
|
2173
2203
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2174
2204
|
channel.state.vibratoDelay = vibratoDelay / 127;
|
|
2175
2205
|
if (0 < channel.state.vibratoDepth) {
|
|
2176
|
-
this.processScheduledNotes(channel,
|
|
2206
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2177
2207
|
this.startVibrato(channel, note, scheduleTime);
|
|
2178
2208
|
});
|
|
2179
2209
|
}
|
|
@@ -2191,7 +2221,7 @@ class Midy {
|
|
|
2191
2221
|
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
2192
2222
|
}
|
|
2193
2223
|
else {
|
|
2194
|
-
this.processScheduledNotes(channel,
|
|
2224
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2195
2225
|
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
2196
2226
|
return false;
|
|
2197
2227
|
if (note.reverbEffectsSend)
|
|
@@ -2201,7 +2231,7 @@ class Midy {
|
|
|
2201
2231
|
}
|
|
2202
2232
|
else {
|
|
2203
2233
|
if (0 < reverbSendLevel) {
|
|
2204
|
-
this.processScheduledNotes(channel,
|
|
2234
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2205
2235
|
this.setReverbEffectsSend(channel, note, 0, scheduleTime);
|
|
2206
2236
|
});
|
|
2207
2237
|
state.reverbSendLevel = reverbSendLevel / 127;
|
|
@@ -2224,7 +2254,7 @@ class Midy {
|
|
|
2224
2254
|
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
2225
2255
|
}
|
|
2226
2256
|
else {
|
|
2227
|
-
this.processScheduledNotes(channel,
|
|
2257
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2228
2258
|
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
2229
2259
|
return false;
|
|
2230
2260
|
if (note.chorusEffectsSend)
|
|
@@ -2234,7 +2264,7 @@ class Midy {
|
|
|
2234
2264
|
}
|
|
2235
2265
|
else {
|
|
2236
2266
|
if (0 < chorusSendLevel) {
|
|
2237
|
-
this.processScheduledNotes(channel,
|
|
2267
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2238
2268
|
this.setChorusEffectsSend(channel, note, 0, scheduleTime);
|
|
2239
2269
|
});
|
|
2240
2270
|
state.chorusSendLevel = chorusSendLevel / 127;
|
|
@@ -2392,7 +2422,7 @@ class Midy {
|
|
|
2392
2422
|
const entries = Object.entries(defaultControllerState);
|
|
2393
2423
|
for (const [key, { type, defaultValue }] of entries) {
|
|
2394
2424
|
if (128 <= type) {
|
|
2395
|
-
this.
|
|
2425
|
+
this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
2396
2426
|
}
|
|
2397
2427
|
else {
|
|
2398
2428
|
state[key] = defaultValue;
|
|
@@ -2425,7 +2455,7 @@ class Midy {
|
|
|
2425
2455
|
const key = keys[i];
|
|
2426
2456
|
const { type, defaultValue } = defaultControllerState[key];
|
|
2427
2457
|
if (128 <= type) {
|
|
2428
|
-
this.
|
|
2458
|
+
this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
2429
2459
|
}
|
|
2430
2460
|
else {
|
|
2431
2461
|
state[key] = defaultValue;
|
|
@@ -2928,27 +2958,29 @@ class Midy {
|
|
|
2928
2958
|
: 0;
|
|
2929
2959
|
return (channelPressure + polyphonicKeyPressure) / 254;
|
|
2930
2960
|
}
|
|
2931
|
-
setControllerParameters(channel, note, table) {
|
|
2961
|
+
setControllerParameters(channel, note, table, scheduleTime) {
|
|
2932
2962
|
if (0 <= table[0])
|
|
2933
|
-
this.updateDetune(channel, note);
|
|
2963
|
+
this.updateDetune(channel, note, scueduleTime);
|
|
2934
2964
|
if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
|
|
2935
|
-
if (0 <= table[1])
|
|
2936
|
-
this.setPortamentoFilterEnvelope(channel, note);
|
|
2937
|
-
|
|
2938
|
-
|
|
2965
|
+
if (0 <= table[1]) {
|
|
2966
|
+
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2967
|
+
}
|
|
2968
|
+
if (0 <= table[2]) {
|
|
2969
|
+
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2970
|
+
}
|
|
2939
2971
|
}
|
|
2940
2972
|
else {
|
|
2941
2973
|
if (0 <= table[1])
|
|
2942
|
-
this.setFilterEnvelope(channel, note);
|
|
2974
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
2943
2975
|
if (0 <= table[2])
|
|
2944
|
-
this.setVolumeEnvelope(channel, note);
|
|
2976
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
2945
2977
|
}
|
|
2946
2978
|
if (0 <= table[3])
|
|
2947
|
-
this.setModLfoToPitch(channel, note);
|
|
2979
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
2948
2980
|
if (0 <= table[4])
|
|
2949
|
-
this.setModLfoToFilterFc(channel, note);
|
|
2981
|
+
this.setModLfoToFilterFc(channel, note, scheduleTime);
|
|
2950
2982
|
if (0 <= table[5])
|
|
2951
|
-
this.setModLfoToVolume(channel, note);
|
|
2983
|
+
this.setModLfoToVolume(channel, note, scheduleTime);
|
|
2952
2984
|
}
|
|
2953
2985
|
handlePressureSysEx(data, tableName) {
|
|
2954
2986
|
const channelNumber = data[4];
|
|
@@ -2971,8 +3003,8 @@ class Midy {
|
|
|
2971
3003
|
const slotSize = 6;
|
|
2972
3004
|
const offset = controllerType * slotSize;
|
|
2973
3005
|
const table = channel.controlTable.subarray(offset, offset + slotSize);
|
|
2974
|
-
this.processScheduledNotes(channel,
|
|
2975
|
-
this.setControllerParameters(channel, note, table);
|
|
3006
|
+
this.processScheduledNotes(channel, (note) => {
|
|
3007
|
+
this.setControllerParameters(channel, note, table, scheduleTime);
|
|
2976
3008
|
});
|
|
2977
3009
|
}
|
|
2978
3010
|
handleControlChangeSysEx(data) {
|
|
@@ -2988,7 +3020,7 @@ class Midy {
|
|
|
2988
3020
|
table[pp] = rr;
|
|
2989
3021
|
}
|
|
2990
3022
|
}
|
|
2991
|
-
|
|
3023
|
+
getKeyBasedValue(channel, keyNumber, controllerType) {
|
|
2992
3024
|
const index = keyNumber * 128 + controllerType;
|
|
2993
3025
|
const controlValue = channel.keyBasedInstrumentControlTable[index];
|
|
2994
3026
|
return controlValue;
|
|
@@ -3006,7 +3038,7 @@ class Midy {
|
|
|
3006
3038
|
const index = keyNumber * 128 + controllerType;
|
|
3007
3039
|
table[index] = value;
|
|
3008
3040
|
}
|
|
3009
|
-
this.
|
|
3041
|
+
this.setChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
|
|
3010
3042
|
}
|
|
3011
3043
|
handleSysEx(data, scheduleTime) {
|
|
3012
3044
|
switch (data[0]) {
|
|
@@ -3044,6 +3076,7 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
3044
3076
|
configurable: true,
|
|
3045
3077
|
writable: true,
|
|
3046
3078
|
value: {
|
|
3079
|
+
scheduleIndex: 0,
|
|
3047
3080
|
detune: 0,
|
|
3048
3081
|
programNumber: 0,
|
|
3049
3082
|
bank: 121 * 128,
|