@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/esm/midy.js
CHANGED
|
@@ -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,
|
|
@@ -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}:${instrument}:${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;
|
|
@@ -1066,7 +1081,7 @@ export class Midy {
|
|
|
1066
1081
|
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
1067
1082
|
}
|
|
1068
1083
|
updateChannelDetune(channel, scheduleTime) {
|
|
1069
|
-
this.processScheduledNotes(channel,
|
|
1084
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1070
1085
|
this.updateDetune(channel, note, scheduleTime);
|
|
1071
1086
|
});
|
|
1072
1087
|
}
|
|
@@ -1294,31 +1309,31 @@ export class Midy {
|
|
|
1294
1309
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
1295
1310
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
1296
1311
|
}
|
|
1297
|
-
async getAudioBuffer(
|
|
1298
|
-
const audioBufferId = this.
|
|
1299
|
-
const cache = this.
|
|
1312
|
+
async getAudioBuffer(channel, noteNumber, velocity, voiceParams) {
|
|
1313
|
+
const audioBufferId = this.getVoiceId(channel, noteNumber, velocity);
|
|
1314
|
+
const cache = this.voiceCache.get(audioBufferId);
|
|
1300
1315
|
if (cache) {
|
|
1301
1316
|
cache.counter += 1;
|
|
1302
1317
|
if (cache.maxCount <= cache.counter) {
|
|
1303
|
-
this.
|
|
1318
|
+
this.voiceCache.delete(audioBufferId);
|
|
1304
1319
|
}
|
|
1305
1320
|
return cache.audioBuffer;
|
|
1306
1321
|
}
|
|
1307
1322
|
else {
|
|
1308
|
-
const maxCount = this.
|
|
1309
|
-
const audioBuffer = await this.
|
|
1323
|
+
const maxCount = this.voiceCounter.get(audioBufferId) ?? 0;
|
|
1324
|
+
const audioBuffer = await this.createAudioBuffer(voiceParams);
|
|
1310
1325
|
const cache = { audioBuffer, maxCount, counter: 1 };
|
|
1311
|
-
this.
|
|
1326
|
+
this.voiceCache.set(audioBufferId, cache);
|
|
1312
1327
|
return audioBuffer;
|
|
1313
1328
|
}
|
|
1314
1329
|
}
|
|
1315
|
-
async createNote(channel, voice, noteNumber, velocity, startTime
|
|
1330
|
+
async createNote(channel, voice, noteNumber, velocity, startTime) {
|
|
1316
1331
|
const now = this.audioContext.currentTime;
|
|
1317
1332
|
const state = channel.state;
|
|
1318
1333
|
const controllerState = this.getControllerState(channel, noteNumber, velocity, 0);
|
|
1319
1334
|
const voiceParams = voice.getAllParams(controllerState);
|
|
1320
1335
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1321
|
-
const audioBuffer = await this.getAudioBuffer(channel
|
|
1336
|
+
const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams);
|
|
1322
1337
|
note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
|
|
1323
1338
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
1324
1339
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
@@ -1413,15 +1428,15 @@ export class Midy {
|
|
|
1413
1428
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
1414
1429
|
const channel = this.channels[channelNumber];
|
|
1415
1430
|
const bankNumber = this.calcBank(channel, channelNumber);
|
|
1416
|
-
const soundFontIndex = this.soundFontTable[channel.programNumber]
|
|
1431
|
+
const soundFontIndex = this.soundFontTable[channel.programNumber]
|
|
1432
|
+
.get(bankNumber);
|
|
1417
1433
|
if (soundFontIndex === undefined)
|
|
1418
1434
|
return;
|
|
1419
1435
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
1420
1436
|
const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
|
|
1421
1437
|
if (!voice)
|
|
1422
1438
|
return;
|
|
1423
|
-
const
|
|
1424
|
-
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
1439
|
+
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
|
|
1425
1440
|
if (channel.isDrum) {
|
|
1426
1441
|
const audioContext = this.audioContext;
|
|
1427
1442
|
const { gainL, gainR } = this.createChannelAudioNodes(audioContext);
|
|
@@ -1504,15 +1519,29 @@ export class Midy {
|
|
|
1504
1519
|
return;
|
|
1505
1520
|
}
|
|
1506
1521
|
}
|
|
1507
|
-
const
|
|
1508
|
-
if (
|
|
1522
|
+
const index = this.findNoteOffIndex(channel, noteNumber);
|
|
1523
|
+
if (index < 0)
|
|
1509
1524
|
return;
|
|
1525
|
+
const note = channel.scheduledNotes[index];
|
|
1510
1526
|
note.ending = true;
|
|
1527
|
+
this.setNoteIndex(channel, index);
|
|
1511
1528
|
this.releaseNote(channel, note, endTime);
|
|
1512
1529
|
}
|
|
1513
|
-
|
|
1530
|
+
setNoteIndex(channel, index) {
|
|
1531
|
+
let allEnds = true;
|
|
1532
|
+
for (let i = channel.scheduleIndex; i < index; i++) {
|
|
1533
|
+
const note = channel.scheduledNotes[i];
|
|
1534
|
+
if (note && !note.ending) {
|
|
1535
|
+
allEnds = false;
|
|
1536
|
+
break;
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
if (allEnds)
|
|
1540
|
+
channel.scheduleIndex = index + 1;
|
|
1541
|
+
}
|
|
1542
|
+
findNoteOffIndex(channel, noteNumber) {
|
|
1514
1543
|
const scheduledNotes = channel.scheduledNotes;
|
|
1515
|
-
for (let i =
|
|
1544
|
+
for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
|
|
1516
1545
|
const note = scheduledNotes[i];
|
|
1517
1546
|
if (!note)
|
|
1518
1547
|
continue;
|
|
@@ -1520,8 +1549,9 @@ export class Midy {
|
|
|
1520
1549
|
continue;
|
|
1521
1550
|
if (note.noteNumber !== noteNumber)
|
|
1522
1551
|
continue;
|
|
1523
|
-
return
|
|
1552
|
+
return i;
|
|
1524
1553
|
}
|
|
1554
|
+
return -1;
|
|
1525
1555
|
}
|
|
1526
1556
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1527
1557
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -1561,31 +1591,31 @@ export class Midy {
|
|
|
1561
1591
|
case 0x90:
|
|
1562
1592
|
return this.noteOn(channelNumber, data1, data2, scheduleTime);
|
|
1563
1593
|
case 0xA0:
|
|
1564
|
-
return this.
|
|
1594
|
+
return this.setPolyphonicKeyPressure(channelNumber, data1, data2, scheduleTime);
|
|
1565
1595
|
case 0xB0:
|
|
1566
|
-
return this.
|
|
1596
|
+
return this.setControlChange(channelNumber, data1, data2, scheduleTime);
|
|
1567
1597
|
case 0xC0:
|
|
1568
|
-
return this.
|
|
1598
|
+
return this.setProgramChange(channelNumber, data1, scheduleTime);
|
|
1569
1599
|
case 0xD0:
|
|
1570
|
-
return this.
|
|
1600
|
+
return this.setChannelPressure(channelNumber, data1, scheduleTime);
|
|
1571
1601
|
case 0xE0:
|
|
1572
1602
|
return this.handlePitchBendMessage(channelNumber, data1, data2, scheduleTime);
|
|
1573
1603
|
default:
|
|
1574
1604
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
1575
1605
|
}
|
|
1576
1606
|
}
|
|
1577
|
-
|
|
1607
|
+
setPolyphonicKeyPressure(channelNumber, noteNumber, pressure, scheduleTime) {
|
|
1578
1608
|
const channel = this.channels[channelNumber];
|
|
1579
1609
|
const table = channel.polyphonicKeyPressureTable;
|
|
1580
1610
|
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
1581
1611
|
if (note.noteNumber === noteNumber) {
|
|
1582
1612
|
note.pressure = pressure;
|
|
1583
|
-
this.setControllerParameters(channel, note, table);
|
|
1613
|
+
this.setControllerParameters(channel, note, table, scheduleTime);
|
|
1584
1614
|
}
|
|
1585
1615
|
});
|
|
1586
1616
|
this.applyVoiceParams(channel, 10);
|
|
1587
1617
|
}
|
|
1588
|
-
|
|
1618
|
+
setProgramChange(channelNumber, programNumber, _scheduleTime) {
|
|
1589
1619
|
const channel = this.channels[channelNumber];
|
|
1590
1620
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1591
1621
|
channel.programNumber = programNumber;
|
|
@@ -1600,7 +1630,7 @@ export class Midy {
|
|
|
1600
1630
|
}
|
|
1601
1631
|
}
|
|
1602
1632
|
}
|
|
1603
|
-
|
|
1633
|
+
setChannelPressure(channelNumber, value, scheduleTime) {
|
|
1604
1634
|
const channel = this.channels[channelNumber];
|
|
1605
1635
|
if (channel.isDrum)
|
|
1606
1636
|
return;
|
|
@@ -1614,7 +1644,7 @@ export class Midy {
|
|
|
1614
1644
|
}
|
|
1615
1645
|
const table = channel.channelPressureTable;
|
|
1616
1646
|
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
1617
|
-
this.setControllerParameters(channel, note, table);
|
|
1647
|
+
this.setControllerParameters(channel, note, table, scheduleTime);
|
|
1618
1648
|
});
|
|
1619
1649
|
this.applyVoiceParams(channel, 13);
|
|
1620
1650
|
}
|
|
@@ -1672,7 +1702,7 @@ export class Midy {
|
|
|
1672
1702
|
setReverbEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1673
1703
|
let value = note.voiceParams.reverbEffectsSend;
|
|
1674
1704
|
if (channel.isDrum) {
|
|
1675
|
-
const keyBasedValue = this.
|
|
1705
|
+
const keyBasedValue = this.getKeyBasedValue(channel, note.noteNumber, 91);
|
|
1676
1706
|
if (0 <= keyBasedValue) {
|
|
1677
1707
|
value *= keyBasedValue / 127 / channel.state.reverbSendLevel;
|
|
1678
1708
|
}
|
|
@@ -1702,13 +1732,13 @@ export class Midy {
|
|
|
1702
1732
|
setChorusEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1703
1733
|
let value = note.voiceParams.chorusEffectsSend;
|
|
1704
1734
|
if (channel.isDrum) {
|
|
1705
|
-
const keyBasedValue = this.
|
|
1735
|
+
const keyBasedValue = this.getKeyBasedValue(channel, note.noteNumber, 93);
|
|
1706
1736
|
if (0 <= keyBasedValue) {
|
|
1707
1737
|
value *= keyBasedValue / 127 / channel.state.chorusSendLevel;
|
|
1708
1738
|
}
|
|
1709
1739
|
}
|
|
1710
1740
|
if (0 < prevValue) {
|
|
1711
|
-
if (0 <
|
|
1741
|
+
if (0 < value) {
|
|
1712
1742
|
note.chorusEffectsSend.gain
|
|
1713
1743
|
.cancelScheduledValues(scheduleTime)
|
|
1714
1744
|
.setValueAtTime(value, scheduleTime);
|
|
@@ -1808,7 +1838,7 @@ export class Midy {
|
|
|
1808
1838
|
return state;
|
|
1809
1839
|
}
|
|
1810
1840
|
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
1811
|
-
this.processScheduledNotes(channel,
|
|
1841
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1812
1842
|
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity, note.pressure);
|
|
1813
1843
|
const voiceParams = note.voice.getParams(controllerType, controllerState);
|
|
1814
1844
|
let applyVolumeEnvelope = false;
|
|
@@ -1879,7 +1909,7 @@ export class Midy {
|
|
|
1879
1909
|
handlers[127] = this.polyOn;
|
|
1880
1910
|
return handlers;
|
|
1881
1911
|
}
|
|
1882
|
-
|
|
1912
|
+
setControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1883
1913
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1884
1914
|
if (handler) {
|
|
1885
1915
|
handler.call(this, channelNumber, value, scheduleTime);
|
|
@@ -1896,7 +1926,7 @@ export class Midy {
|
|
|
1896
1926
|
}
|
|
1897
1927
|
updateModulation(channel, scheduleTime) {
|
|
1898
1928
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1899
|
-
this.processScheduledNotes(channel,
|
|
1929
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1900
1930
|
if (note.modulationDepth) {
|
|
1901
1931
|
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1902
1932
|
}
|
|
@@ -1915,7 +1945,7 @@ export class Midy {
|
|
|
1915
1945
|
this.updateModulation(channel, scheduleTime);
|
|
1916
1946
|
}
|
|
1917
1947
|
updatePortamento(channel, scheduleTime) {
|
|
1918
|
-
this.processScheduledNotes(channel,
|
|
1948
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1919
1949
|
if (0.5 <= channel.state.portamento) {
|
|
1920
1950
|
if (0 <= note.portamentoNoteNumber) {
|
|
1921
1951
|
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
@@ -2000,11 +2030,11 @@ export class Midy {
|
|
|
2000
2030
|
continue;
|
|
2001
2031
|
if (!gainR)
|
|
2002
2032
|
continue;
|
|
2003
|
-
const keyBasedVolume = this.
|
|
2033
|
+
const keyBasedVolume = this.getKeyBasedValue(channel, i, 7);
|
|
2004
2034
|
const volume = (0 <= keyBasedVolume)
|
|
2005
2035
|
? defaultVolume * keyBasedVolume / 64
|
|
2006
2036
|
: defaultVolume;
|
|
2007
|
-
const keyBasedPan = this.
|
|
2037
|
+
const keyBasedPan = this.getKeyBasedValue(channel, i, 10);
|
|
2008
2038
|
const pan = (0 <= keyBasedPan) ? keyBasedPan / 127 : defaultPan;
|
|
2009
2039
|
const { gainLeft, gainRight } = this.panToGain(pan);
|
|
2010
2040
|
gainL.gain
|
|
@@ -2022,7 +2052,7 @@ export class Midy {
|
|
|
2022
2052
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2023
2053
|
channel.state.sustainPedal = value / 127;
|
|
2024
2054
|
if (64 <= value) {
|
|
2025
|
-
this.processScheduledNotes(channel,
|
|
2055
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2026
2056
|
channel.sustainNotes.push(note);
|
|
2027
2057
|
});
|
|
2028
2058
|
}
|
|
@@ -2065,7 +2095,7 @@ export class Midy {
|
|
|
2065
2095
|
const state = channel.state;
|
|
2066
2096
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2067
2097
|
state.softPedal = softPedal / 127;
|
|
2068
|
-
this.processScheduledNotes(channel,
|
|
2098
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2069
2099
|
if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
2070
2100
|
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2071
2101
|
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
@@ -2083,7 +2113,7 @@ export class Midy {
|
|
|
2083
2113
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2084
2114
|
const state = channel.state;
|
|
2085
2115
|
state.filterResonance = filterResonance / 127;
|
|
2086
|
-
this.processScheduledNotes(channel,
|
|
2116
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2087
2117
|
const Q = note.voiceParams.initialFilterQ / 5 * state.filterResonance;
|
|
2088
2118
|
note.filterNode.Q.setValueAtTime(Q, scheduleTime);
|
|
2089
2119
|
});
|
|
@@ -2101,10 +2131,10 @@ export class Midy {
|
|
|
2101
2131
|
return;
|
|
2102
2132
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2103
2133
|
channel.state.attackTime = attackTime / 127;
|
|
2104
|
-
this.processScheduledNotes(channel,
|
|
2134
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2105
2135
|
if (note.startTime < scheduleTime)
|
|
2106
2136
|
return false;
|
|
2107
|
-
this.setVolumeEnvelope(channel, note);
|
|
2137
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
2108
2138
|
});
|
|
2109
2139
|
}
|
|
2110
2140
|
setBrightness(channelNumber, brightness, scheduleTime) {
|
|
@@ -2114,12 +2144,12 @@ export class Midy {
|
|
|
2114
2144
|
const state = channel.state;
|
|
2115
2145
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2116
2146
|
state.brightness = brightness / 127;
|
|
2117
|
-
this.processScheduledNotes(channel,
|
|
2147
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2118
2148
|
if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
2119
2149
|
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2120
2150
|
}
|
|
2121
2151
|
else {
|
|
2122
|
-
this.setFilterEnvelope(channel, note);
|
|
2152
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
2123
2153
|
}
|
|
2124
2154
|
});
|
|
2125
2155
|
}
|
|
@@ -2129,7 +2159,7 @@ export class Midy {
|
|
|
2129
2159
|
return;
|
|
2130
2160
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2131
2161
|
channel.state.decayTime = dacayTime / 127;
|
|
2132
|
-
this.processScheduledNotes(channel,
|
|
2162
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2133
2163
|
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
2134
2164
|
});
|
|
2135
2165
|
}
|
|
@@ -2141,7 +2171,7 @@ export class Midy {
|
|
|
2141
2171
|
channel.state.vibratoRate = vibratoRate / 127;
|
|
2142
2172
|
if (channel.vibratoDepth <= 0)
|
|
2143
2173
|
return;
|
|
2144
|
-
this.processScheduledNotes(channel,
|
|
2174
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2145
2175
|
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
2146
2176
|
});
|
|
2147
2177
|
}
|
|
@@ -2153,12 +2183,12 @@ export class Midy {
|
|
|
2153
2183
|
const prev = channel.state.vibratoDepth;
|
|
2154
2184
|
channel.state.vibratoDepth = vibratoDepth / 127;
|
|
2155
2185
|
if (0 < prev) {
|
|
2156
|
-
this.processScheduledNotes(channel,
|
|
2186
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2157
2187
|
this.setFreqVibLFO(channel, note, scheduleTime);
|
|
2158
2188
|
});
|
|
2159
2189
|
}
|
|
2160
2190
|
else {
|
|
2161
|
-
this.processScheduledNotes(channel,
|
|
2191
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2162
2192
|
this.startVibrato(channel, note, scheduleTime);
|
|
2163
2193
|
});
|
|
2164
2194
|
}
|
|
@@ -2170,7 +2200,7 @@ export class Midy {
|
|
|
2170
2200
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2171
2201
|
channel.state.vibratoDelay = vibratoDelay / 127;
|
|
2172
2202
|
if (0 < channel.state.vibratoDepth) {
|
|
2173
|
-
this.processScheduledNotes(channel,
|
|
2203
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2174
2204
|
this.startVibrato(channel, note, scheduleTime);
|
|
2175
2205
|
});
|
|
2176
2206
|
}
|
|
@@ -2188,7 +2218,7 @@ export class Midy {
|
|
|
2188
2218
|
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
2189
2219
|
}
|
|
2190
2220
|
else {
|
|
2191
|
-
this.processScheduledNotes(channel,
|
|
2221
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2192
2222
|
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
2193
2223
|
return false;
|
|
2194
2224
|
if (note.reverbEffectsSend)
|
|
@@ -2198,7 +2228,7 @@ export class Midy {
|
|
|
2198
2228
|
}
|
|
2199
2229
|
else {
|
|
2200
2230
|
if (0 < reverbSendLevel) {
|
|
2201
|
-
this.processScheduledNotes(channel,
|
|
2231
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2202
2232
|
this.setReverbEffectsSend(channel, note, 0, scheduleTime);
|
|
2203
2233
|
});
|
|
2204
2234
|
state.reverbSendLevel = reverbSendLevel / 127;
|
|
@@ -2221,7 +2251,7 @@ export class Midy {
|
|
|
2221
2251
|
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
2222
2252
|
}
|
|
2223
2253
|
else {
|
|
2224
|
-
this.processScheduledNotes(channel,
|
|
2254
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2225
2255
|
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
2226
2256
|
return false;
|
|
2227
2257
|
if (note.chorusEffectsSend)
|
|
@@ -2231,7 +2261,7 @@ export class Midy {
|
|
|
2231
2261
|
}
|
|
2232
2262
|
else {
|
|
2233
2263
|
if (0 < chorusSendLevel) {
|
|
2234
|
-
this.processScheduledNotes(channel,
|
|
2264
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2235
2265
|
this.setChorusEffectsSend(channel, note, 0, scheduleTime);
|
|
2236
2266
|
});
|
|
2237
2267
|
state.chorusSendLevel = chorusSendLevel / 127;
|
|
@@ -2389,7 +2419,7 @@ export class Midy {
|
|
|
2389
2419
|
const entries = Object.entries(defaultControllerState);
|
|
2390
2420
|
for (const [key, { type, defaultValue }] of entries) {
|
|
2391
2421
|
if (128 <= type) {
|
|
2392
|
-
this.
|
|
2422
|
+
this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
2393
2423
|
}
|
|
2394
2424
|
else {
|
|
2395
2425
|
state[key] = defaultValue;
|
|
@@ -2422,7 +2452,7 @@ export class Midy {
|
|
|
2422
2452
|
const key = keys[i];
|
|
2423
2453
|
const { type, defaultValue } = defaultControllerState[key];
|
|
2424
2454
|
if (128 <= type) {
|
|
2425
|
-
this.
|
|
2455
|
+
this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
2426
2456
|
}
|
|
2427
2457
|
else {
|
|
2428
2458
|
state[key] = defaultValue;
|
|
@@ -2925,27 +2955,29 @@ export class Midy {
|
|
|
2925
2955
|
: 0;
|
|
2926
2956
|
return (channelPressure + polyphonicKeyPressure) / 254;
|
|
2927
2957
|
}
|
|
2928
|
-
setControllerParameters(channel, note, table) {
|
|
2958
|
+
setControllerParameters(channel, note, table, scheduleTime) {
|
|
2929
2959
|
if (0 <= table[0])
|
|
2930
|
-
this.updateDetune(channel, note);
|
|
2960
|
+
this.updateDetune(channel, note, scueduleTime);
|
|
2931
2961
|
if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
|
|
2932
|
-
if (0 <= table[1])
|
|
2933
|
-
this.setPortamentoFilterEnvelope(channel, note);
|
|
2934
|
-
|
|
2935
|
-
|
|
2962
|
+
if (0 <= table[1]) {
|
|
2963
|
+
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2964
|
+
}
|
|
2965
|
+
if (0 <= table[2]) {
|
|
2966
|
+
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2967
|
+
}
|
|
2936
2968
|
}
|
|
2937
2969
|
else {
|
|
2938
2970
|
if (0 <= table[1])
|
|
2939
|
-
this.setFilterEnvelope(channel, note);
|
|
2971
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
2940
2972
|
if (0 <= table[2])
|
|
2941
|
-
this.setVolumeEnvelope(channel, note);
|
|
2973
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
2942
2974
|
}
|
|
2943
2975
|
if (0 <= table[3])
|
|
2944
|
-
this.setModLfoToPitch(channel, note);
|
|
2976
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
2945
2977
|
if (0 <= table[4])
|
|
2946
|
-
this.setModLfoToFilterFc(channel, note);
|
|
2978
|
+
this.setModLfoToFilterFc(channel, note, scheduleTime);
|
|
2947
2979
|
if (0 <= table[5])
|
|
2948
|
-
this.setModLfoToVolume(channel, note);
|
|
2980
|
+
this.setModLfoToVolume(channel, note, scheduleTime);
|
|
2949
2981
|
}
|
|
2950
2982
|
handlePressureSysEx(data, tableName) {
|
|
2951
2983
|
const channelNumber = data[4];
|
|
@@ -2968,8 +3000,8 @@ export class Midy {
|
|
|
2968
3000
|
const slotSize = 6;
|
|
2969
3001
|
const offset = controllerType * slotSize;
|
|
2970
3002
|
const table = channel.controlTable.subarray(offset, offset + slotSize);
|
|
2971
|
-
this.processScheduledNotes(channel,
|
|
2972
|
-
this.setControllerParameters(channel, note, table);
|
|
3003
|
+
this.processScheduledNotes(channel, (note) => {
|
|
3004
|
+
this.setControllerParameters(channel, note, table, scheduleTime);
|
|
2973
3005
|
});
|
|
2974
3006
|
}
|
|
2975
3007
|
handleControlChangeSysEx(data) {
|
|
@@ -2985,7 +3017,7 @@ export class Midy {
|
|
|
2985
3017
|
table[pp] = rr;
|
|
2986
3018
|
}
|
|
2987
3019
|
}
|
|
2988
|
-
|
|
3020
|
+
getKeyBasedValue(channel, keyNumber, controllerType) {
|
|
2989
3021
|
const index = keyNumber * 128 + controllerType;
|
|
2990
3022
|
const controlValue = channel.keyBasedInstrumentControlTable[index];
|
|
2991
3023
|
return controlValue;
|
|
@@ -3003,7 +3035,7 @@ export class Midy {
|
|
|
3003
3035
|
const index = keyNumber * 128 + controllerType;
|
|
3004
3036
|
table[index] = value;
|
|
3005
3037
|
}
|
|
3006
|
-
this.
|
|
3038
|
+
this.setChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
|
|
3007
3039
|
}
|
|
3008
3040
|
handleSysEx(data, scheduleTime) {
|
|
3009
3041
|
switch (data[0]) {
|
|
@@ -3040,6 +3072,7 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
3040
3072
|
configurable: true,
|
|
3041
3073
|
writable: true,
|
|
3042
3074
|
value: {
|
|
3075
|
+
scheduleIndex: 0,
|
|
3043
3076
|
detune: 0,
|
|
3044
3077
|
programNumber: 0,
|
|
3045
3078
|
bank: 121 * 128,
|