@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-GM2.js
CHANGED
|
@@ -317,13 +317,13 @@ class MidyGM2 {
|
|
|
317
317
|
writable: true,
|
|
318
318
|
value: this.initSoundFontTable()
|
|
319
319
|
});
|
|
320
|
-
Object.defineProperty(this, "
|
|
320
|
+
Object.defineProperty(this, "voiceCounter", {
|
|
321
321
|
enumerable: true,
|
|
322
322
|
configurable: true,
|
|
323
323
|
writable: true,
|
|
324
324
|
value: new Map()
|
|
325
325
|
});
|
|
326
|
-
Object.defineProperty(this, "
|
|
326
|
+
Object.defineProperty(this, "voiceCache", {
|
|
327
327
|
enumerable: true,
|
|
328
328
|
configurable: true,
|
|
329
329
|
writable: true,
|
|
@@ -420,13 +420,11 @@ class MidyGM2 {
|
|
|
420
420
|
const presetHeaders = soundFont.parsed.presetHeaders;
|
|
421
421
|
for (let i = 0; i < presetHeaders.length; i++) {
|
|
422
422
|
const presetHeader = presetHeaders[i];
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
banks.set(presetHeader.bank, index);
|
|
426
|
-
}
|
|
423
|
+
const banks = this.soundFontTable[presetHeader.preset];
|
|
424
|
+
banks.set(presetHeader.bank, index);
|
|
427
425
|
}
|
|
428
426
|
}
|
|
429
|
-
async
|
|
427
|
+
async toUint8Array(input) {
|
|
430
428
|
let uint8Array;
|
|
431
429
|
if (typeof input === "string") {
|
|
432
430
|
const response = await fetch(input);
|
|
@@ -439,23 +437,32 @@ class MidyGM2 {
|
|
|
439
437
|
else {
|
|
440
438
|
throw new TypeError("input must be a URL string or Uint8Array");
|
|
441
439
|
}
|
|
442
|
-
|
|
443
|
-
const soundFont = new soundfont_parser_1.SoundFont(parsed);
|
|
444
|
-
this.addSoundFont(soundFont);
|
|
440
|
+
return uint8Array;
|
|
445
441
|
}
|
|
446
|
-
async
|
|
447
|
-
|
|
448
|
-
if (
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
442
|
+
async loadSoundFont(input) {
|
|
443
|
+
this.voiceCounter.clear();
|
|
444
|
+
if (Array.isArray(input)) {
|
|
445
|
+
const promises = new Array(input.length);
|
|
446
|
+
for (let i = 0; i < input.length; i++) {
|
|
447
|
+
promises[i] = this.toUint8Array(input[i]);
|
|
448
|
+
}
|
|
449
|
+
const uint8Arrays = await Promise.all(promises);
|
|
450
|
+
for (let i = 0; i < uint8Arrays.length; i++) {
|
|
451
|
+
const parsed = (0, soundfont_parser_1.parse)(uint8Arrays[i]);
|
|
452
|
+
const soundFont = new soundfont_parser_1.SoundFont(parsed);
|
|
453
|
+
this.addSoundFont(soundFont);
|
|
454
|
+
}
|
|
455
455
|
}
|
|
456
456
|
else {
|
|
457
|
-
|
|
457
|
+
const uint8Array = await this.toUint8Array(input);
|
|
458
|
+
const parsed = (0, soundfont_parser_1.parse)(uint8Array);
|
|
459
|
+
const soundFont = new soundfont_parser_1.SoundFont(parsed);
|
|
460
|
+
this.addSoundFont(soundFont);
|
|
458
461
|
}
|
|
462
|
+
}
|
|
463
|
+
async loadMIDI(input) {
|
|
464
|
+
this.voiceCounter.clear();
|
|
465
|
+
const uint8Array = await this.toUint8Array(input);
|
|
459
466
|
const midi = (0, midi_file_1.parseMidi)(uint8Array);
|
|
460
467
|
this.ticksPerBeat = midi.header.ticksPerBeat;
|
|
461
468
|
const midiData = this.extractMidiData(midi);
|
|
@@ -463,6 +470,45 @@ class MidyGM2 {
|
|
|
463
470
|
this.timeline = midiData.timeline;
|
|
464
471
|
this.totalTime = this.calcTotalTime();
|
|
465
472
|
}
|
|
473
|
+
cacheVoiceIds() {
|
|
474
|
+
const timeline = this.timeline;
|
|
475
|
+
for (let i = 0; i < timeline.length; i++) {
|
|
476
|
+
const event = timeline[i];
|
|
477
|
+
switch (event.type) {
|
|
478
|
+
case "noteOn": {
|
|
479
|
+
const audioBufferId = this.getVoiceId(this.channels[event.channel], event.noteNumber, event.velocity);
|
|
480
|
+
this.voiceCounter.set(audioBufferId, (this.voiceCounter.get(audioBufferId) ?? 0) + 1);
|
|
481
|
+
break;
|
|
482
|
+
}
|
|
483
|
+
case "controller":
|
|
484
|
+
if (event.controllerType === 0) {
|
|
485
|
+
this.setBankMSB(event.channel, event.value);
|
|
486
|
+
}
|
|
487
|
+
else if (event.controllerType === 32) {
|
|
488
|
+
this.setBankLSB(event.channel, event.value);
|
|
489
|
+
}
|
|
490
|
+
break;
|
|
491
|
+
case "programChange":
|
|
492
|
+
this.setProgramChange(event.channel, event.programNumber, event.startTime);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
for (const [audioBufferId, count] of this.voiceCounter) {
|
|
496
|
+
if (count === 1)
|
|
497
|
+
this.voiceCounter.delete(audioBufferId);
|
|
498
|
+
}
|
|
499
|
+
this.GM2SystemOn();
|
|
500
|
+
}
|
|
501
|
+
getVoiceId(channel, noteNumber, velocity) {
|
|
502
|
+
const bankNumber = this.calcBank(channel);
|
|
503
|
+
const soundFontIndex = this.soundFontTable[channel.programNumber]
|
|
504
|
+
.get(bankNumber);
|
|
505
|
+
if (soundFontIndex === undefined)
|
|
506
|
+
return;
|
|
507
|
+
const soundFont = this.soundFonts[soundFontIndex];
|
|
508
|
+
const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
|
|
509
|
+
const { instrument, sampleID } = voice.generators;
|
|
510
|
+
return `${soundFontIndex}:${instrument}:${sampleID}`;
|
|
511
|
+
}
|
|
466
512
|
createChannelAudioNodes(audioContext) {
|
|
467
513
|
const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
|
|
468
514
|
const gainL = new GainNode(audioContext, { gain: gainLeft });
|
|
@@ -504,34 +550,12 @@ class MidyGM2 {
|
|
|
504
550
|
});
|
|
505
551
|
return channels;
|
|
506
552
|
}
|
|
507
|
-
async
|
|
553
|
+
async createAudioBuffer(voiceParams) {
|
|
554
|
+
const sample = voiceParams.sample;
|
|
508
555
|
const sampleStart = voiceParams.start;
|
|
509
|
-
const sampleEnd =
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
const start = sample.byteOffset + sampleStart;
|
|
513
|
-
const end = sample.byteOffset + sampleEnd;
|
|
514
|
-
const buffer = sample.buffer.slice(start, end);
|
|
515
|
-
const audioBuffer = await this.audioContext.decodeAudioData(buffer);
|
|
516
|
-
return audioBuffer;
|
|
517
|
-
}
|
|
518
|
-
else {
|
|
519
|
-
const sample = voiceParams.sample;
|
|
520
|
-
const start = sample.byteOffset + sampleStart;
|
|
521
|
-
const end = sample.byteOffset + sampleEnd;
|
|
522
|
-
const buffer = sample.buffer.slice(start, end);
|
|
523
|
-
const audioBuffer = new AudioBuffer({
|
|
524
|
-
numberOfChannels: 1,
|
|
525
|
-
length: sample.length,
|
|
526
|
-
sampleRate: voiceParams.sampleRate,
|
|
527
|
-
});
|
|
528
|
-
const channelData = audioBuffer.getChannelData(0);
|
|
529
|
-
const int16Array = new Int16Array(buffer);
|
|
530
|
-
for (let i = 0; i < int16Array.length; i++) {
|
|
531
|
-
channelData[i] = int16Array[i] / 32768;
|
|
532
|
-
}
|
|
533
|
-
return audioBuffer;
|
|
534
|
-
}
|
|
556
|
+
const sampleEnd = sample.data.length + voiceParams.end;
|
|
557
|
+
const audioBuffer = await sample.toAudioBuffer(this.audioContext, sampleStart, sampleEnd);
|
|
558
|
+
return audioBuffer;
|
|
535
559
|
}
|
|
536
560
|
isLoopDrum(channel, noteNumber) {
|
|
537
561
|
const programNumber = channel.programNumber;
|
|
@@ -568,13 +592,13 @@ class MidyGM2 {
|
|
|
568
592
|
break;
|
|
569
593
|
}
|
|
570
594
|
case "controller":
|
|
571
|
-
this.
|
|
595
|
+
this.setControlChange(event.channel, event.controllerType, event.value, startTime);
|
|
572
596
|
break;
|
|
573
597
|
case "programChange":
|
|
574
|
-
this.
|
|
598
|
+
this.setProgramChange(event.channel, event.programNumber, startTime);
|
|
575
599
|
break;
|
|
576
600
|
case "channelAftertouch":
|
|
577
|
-
this.
|
|
601
|
+
this.setChannelPressure(event.channel, event.amount, startTime);
|
|
578
602
|
break;
|
|
579
603
|
case "pitchBend":
|
|
580
604
|
this.setPitchBend(event.channel, event.value + 8192, startTime);
|
|
@@ -608,8 +632,9 @@ class MidyGM2 {
|
|
|
608
632
|
this.notePromises = [];
|
|
609
633
|
this.exclusiveClassNotes.fill(undefined);
|
|
610
634
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
611
|
-
this.
|
|
635
|
+
this.voiceCache.clear();
|
|
612
636
|
for (let i = 0; i < this.channels.length; i++) {
|
|
637
|
+
this.channels[i].scheduledNotes = [];
|
|
613
638
|
this.resetAllStates(i);
|
|
614
639
|
}
|
|
615
640
|
resolve();
|
|
@@ -631,8 +656,9 @@ class MidyGM2 {
|
|
|
631
656
|
this.notePromises = [];
|
|
632
657
|
this.exclusiveClassNotes.fill(undefined);
|
|
633
658
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
634
|
-
this.
|
|
659
|
+
this.voiceCache.clear();
|
|
635
660
|
for (let i = 0; i < this.channels.length; i++) {
|
|
661
|
+
this.channels[i].scheduledNotes = [];
|
|
636
662
|
this.resetAllStates(i);
|
|
637
663
|
}
|
|
638
664
|
this.isStopping = false;
|
|
@@ -665,11 +691,7 @@ class MidyGM2 {
|
|
|
665
691
|
secondToTicks(second, secondsPerBeat) {
|
|
666
692
|
return second * this.ticksPerBeat / secondsPerBeat;
|
|
667
693
|
}
|
|
668
|
-
getAudioBufferId(programNumber, noteNumber, velocity) {
|
|
669
|
-
return `${programNumber}:${noteNumber}:${velocity}`;
|
|
670
|
-
}
|
|
671
694
|
extractMidiData(midi) {
|
|
672
|
-
this.audioBufferCounter.clear();
|
|
673
695
|
const instruments = new Set();
|
|
674
696
|
const timeline = [];
|
|
675
697
|
const tmpChannels = new Array(this.channels.length);
|
|
@@ -690,8 +712,6 @@ class MidyGM2 {
|
|
|
690
712
|
switch (event.type) {
|
|
691
713
|
case "noteOn": {
|
|
692
714
|
const channel = tmpChannels[event.channel];
|
|
693
|
-
const audioBufferId = this.getAudioBufferId(channel.programNumber, event.noteNumber, event.velocity);
|
|
694
|
-
this.audioBufferCounter.set(audioBufferId, (this.audioBufferCounter.get(audioBufferId) ?? 0) + 1);
|
|
695
715
|
if (channel.programNumber < 0) {
|
|
696
716
|
channel.programNumber = event.programNumber;
|
|
697
717
|
switch (channel.bankMSB) {
|
|
@@ -741,10 +761,6 @@ class MidyGM2 {
|
|
|
741
761
|
timeline.push(event);
|
|
742
762
|
}
|
|
743
763
|
}
|
|
744
|
-
for (const [audioBufferId, count] of this.audioBufferCounter) {
|
|
745
|
-
if (count === 1)
|
|
746
|
-
this.audioBufferCounter.delete(audioBufferId);
|
|
747
|
-
}
|
|
748
764
|
const priority = {
|
|
749
765
|
controller: 0,
|
|
750
766
|
sysEx: 1,
|
|
@@ -784,12 +800,11 @@ class MidyGM2 {
|
|
|
784
800
|
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
785
801
|
const channel = this.channels[channelNumber];
|
|
786
802
|
const promises = [];
|
|
787
|
-
this.processScheduledNotes(channel,
|
|
803
|
+
this.processScheduledNotes(channel, (note) => {
|
|
788
804
|
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
789
805
|
this.notePromises.push(promise);
|
|
790
806
|
promises.push(promise);
|
|
791
807
|
});
|
|
792
|
-
channel.scheduledNotes = [];
|
|
793
808
|
return Promise.all(promises);
|
|
794
809
|
}
|
|
795
810
|
stopNotes(velocity, force, scheduleTime) {
|
|
@@ -803,6 +818,8 @@ class MidyGM2 {
|
|
|
803
818
|
if (this.isPlaying || this.isPaused)
|
|
804
819
|
return;
|
|
805
820
|
this.resumeTime = 0;
|
|
821
|
+
if (this.voiceCounter.size === 0)
|
|
822
|
+
this.cacheVoiceIds();
|
|
806
823
|
await this.playNotes();
|
|
807
824
|
this.isPlaying = false;
|
|
808
825
|
}
|
|
@@ -843,22 +860,20 @@ class MidyGM2 {
|
|
|
843
860
|
const now = this.audioContext.currentTime;
|
|
844
861
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
845
862
|
}
|
|
846
|
-
processScheduledNotes(channel,
|
|
863
|
+
processScheduledNotes(channel, callback) {
|
|
847
864
|
const scheduledNotes = channel.scheduledNotes;
|
|
848
|
-
for (let i =
|
|
865
|
+
for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
|
|
849
866
|
const note = scheduledNotes[i];
|
|
850
867
|
if (!note)
|
|
851
868
|
continue;
|
|
852
869
|
if (note.ending)
|
|
853
870
|
continue;
|
|
854
|
-
if (note.startTime < scheduleTime)
|
|
855
|
-
continue;
|
|
856
871
|
callback(note);
|
|
857
872
|
}
|
|
858
873
|
}
|
|
859
874
|
processActiveNotes(channel, scheduleTime, callback) {
|
|
860
875
|
const scheduledNotes = channel.scheduledNotes;
|
|
861
|
-
for (let i =
|
|
876
|
+
for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
|
|
862
877
|
const note = scheduledNotes[i];
|
|
863
878
|
if (!note)
|
|
864
879
|
continue;
|
|
@@ -1049,7 +1064,7 @@ class MidyGM2 {
|
|
|
1049
1064
|
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
1050
1065
|
}
|
|
1051
1066
|
updateChannelDetune(channel, scheduleTime) {
|
|
1052
|
-
this.processScheduledNotes(channel,
|
|
1067
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1053
1068
|
this.updateDetune(channel, note, scheduleTime);
|
|
1054
1069
|
});
|
|
1055
1070
|
}
|
|
@@ -1270,31 +1285,31 @@ class MidyGM2 {
|
|
|
1270
1285
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
1271
1286
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
1272
1287
|
}
|
|
1273
|
-
async getAudioBuffer(
|
|
1274
|
-
const audioBufferId = this.
|
|
1275
|
-
const cache = this.
|
|
1288
|
+
async getAudioBuffer(channel, noteNumber, velocity, voiceParams) {
|
|
1289
|
+
const audioBufferId = this.getVoiceId(channel, noteNumber, velocity);
|
|
1290
|
+
const cache = this.voiceCache.get(audioBufferId);
|
|
1276
1291
|
if (cache) {
|
|
1277
1292
|
cache.counter += 1;
|
|
1278
1293
|
if (cache.maxCount <= cache.counter) {
|
|
1279
|
-
this.
|
|
1294
|
+
this.voiceCache.delete(audioBufferId);
|
|
1280
1295
|
}
|
|
1281
1296
|
return cache.audioBuffer;
|
|
1282
1297
|
}
|
|
1283
1298
|
else {
|
|
1284
|
-
const maxCount = this.
|
|
1285
|
-
const audioBuffer = await this.
|
|
1299
|
+
const maxCount = this.voiceCounter.get(audioBufferId) ?? 0;
|
|
1300
|
+
const audioBuffer = await this.createAudioBuffer(voiceParams);
|
|
1286
1301
|
const cache = { audioBuffer, maxCount, counter: 1 };
|
|
1287
|
-
this.
|
|
1302
|
+
this.voiceCache.set(audioBufferId, cache);
|
|
1288
1303
|
return audioBuffer;
|
|
1289
1304
|
}
|
|
1290
1305
|
}
|
|
1291
|
-
async createNote(channel, voice, noteNumber, velocity, startTime
|
|
1306
|
+
async createNote(channel, voice, noteNumber, velocity, startTime) {
|
|
1292
1307
|
const now = this.audioContext.currentTime;
|
|
1293
1308
|
const state = channel.state;
|
|
1294
1309
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1295
1310
|
const voiceParams = voice.getAllParams(controllerState);
|
|
1296
1311
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1297
|
-
const audioBuffer = await this.getAudioBuffer(channel
|
|
1312
|
+
const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams);
|
|
1298
1313
|
note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
|
|
1299
1314
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
1300
1315
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
@@ -1389,15 +1404,15 @@ class MidyGM2 {
|
|
|
1389
1404
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
1390
1405
|
const channel = this.channels[channelNumber];
|
|
1391
1406
|
const bankNumber = this.calcBank(channel, channelNumber);
|
|
1392
|
-
const soundFontIndex = this.soundFontTable[channel.programNumber]
|
|
1407
|
+
const soundFontIndex = this.soundFontTable[channel.programNumber]
|
|
1408
|
+
.get(bankNumber);
|
|
1393
1409
|
if (soundFontIndex === undefined)
|
|
1394
1410
|
return;
|
|
1395
1411
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
1396
1412
|
const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
|
|
1397
1413
|
if (!voice)
|
|
1398
1414
|
return;
|
|
1399
|
-
const
|
|
1400
|
-
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
1415
|
+
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
|
|
1401
1416
|
if (channel.isDrum) {
|
|
1402
1417
|
const audioContext = this.audioContext;
|
|
1403
1418
|
const { gainL, gainR } = this.createChannelAudioNodes(audioContext);
|
|
@@ -1479,15 +1494,29 @@ class MidyGM2 {
|
|
|
1479
1494
|
return;
|
|
1480
1495
|
}
|
|
1481
1496
|
}
|
|
1482
|
-
const
|
|
1483
|
-
if (
|
|
1497
|
+
const index = this.findNoteOffIndex(channel, noteNumber);
|
|
1498
|
+
if (index < 0)
|
|
1484
1499
|
return;
|
|
1500
|
+
const note = channel.scheduledNotes[index];
|
|
1485
1501
|
note.ending = true;
|
|
1502
|
+
this.setNoteIndex(channel, index);
|
|
1486
1503
|
this.releaseNote(channel, note, endTime);
|
|
1487
1504
|
}
|
|
1488
|
-
|
|
1505
|
+
setNoteIndex(channel, index) {
|
|
1506
|
+
let allEnds = true;
|
|
1507
|
+
for (let i = channel.scheduleIndex; i < index; i++) {
|
|
1508
|
+
const note = channel.scheduledNotes[i];
|
|
1509
|
+
if (note && !note.ending) {
|
|
1510
|
+
allEnds = false;
|
|
1511
|
+
break;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
if (allEnds)
|
|
1515
|
+
channel.scheduleIndex = index + 1;
|
|
1516
|
+
}
|
|
1517
|
+
findNoteOffIndex(channel, noteNumber) {
|
|
1489
1518
|
const scheduledNotes = channel.scheduledNotes;
|
|
1490
|
-
for (let i =
|
|
1519
|
+
for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
|
|
1491
1520
|
const note = scheduledNotes[i];
|
|
1492
1521
|
if (!note)
|
|
1493
1522
|
continue;
|
|
@@ -1495,8 +1524,9 @@ class MidyGM2 {
|
|
|
1495
1524
|
continue;
|
|
1496
1525
|
if (note.noteNumber !== noteNumber)
|
|
1497
1526
|
continue;
|
|
1498
|
-
return
|
|
1527
|
+
return i;
|
|
1499
1528
|
}
|
|
1529
|
+
return -1;
|
|
1500
1530
|
}
|
|
1501
1531
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1502
1532
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -1536,18 +1566,18 @@ class MidyGM2 {
|
|
|
1536
1566
|
case 0x90:
|
|
1537
1567
|
return this.noteOn(channelNumber, data1, data2, scheduleTime);
|
|
1538
1568
|
case 0xB0:
|
|
1539
|
-
return this.
|
|
1569
|
+
return this.setControlChange(channelNumber, data1, data2, scheduleTime);
|
|
1540
1570
|
case 0xC0:
|
|
1541
|
-
return this.
|
|
1571
|
+
return this.setProgramChange(channelNumber, data1, scheduleTime);
|
|
1542
1572
|
case 0xD0:
|
|
1543
|
-
return this.
|
|
1573
|
+
return this.setChannelPressure(channelNumber, data1, scheduleTime);
|
|
1544
1574
|
case 0xE0:
|
|
1545
1575
|
return this.handlePitchBendMessage(channelNumber, data1, data2, scheduleTime);
|
|
1546
1576
|
default:
|
|
1547
1577
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
1548
1578
|
}
|
|
1549
1579
|
}
|
|
1550
|
-
|
|
1580
|
+
setProgramChange(channelNumber, programNumber, _scheduleTime) {
|
|
1551
1581
|
const channel = this.channels[channelNumber];
|
|
1552
1582
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1553
1583
|
channel.programNumber = programNumber;
|
|
@@ -1562,7 +1592,7 @@ class MidyGM2 {
|
|
|
1562
1592
|
}
|
|
1563
1593
|
}
|
|
1564
1594
|
}
|
|
1565
|
-
|
|
1595
|
+
setChannelPressure(channelNumber, value, scheduleTime) {
|
|
1566
1596
|
const channel = this.channels[channelNumber];
|
|
1567
1597
|
if (channel.isDrum)
|
|
1568
1598
|
return;
|
|
@@ -1634,7 +1664,7 @@ class MidyGM2 {
|
|
|
1634
1664
|
setReverbEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1635
1665
|
let value = note.voiceParams.reverbEffectsSend;
|
|
1636
1666
|
if (channel.isDrum) {
|
|
1637
|
-
const keyBasedValue = this.
|
|
1667
|
+
const keyBasedValue = this.getKeyBasedValue(channel, note.noteNumber, 91);
|
|
1638
1668
|
if (0 <= keyBasedValue) {
|
|
1639
1669
|
value *= keyBasedValue / 127 / channel.state.reverbSendLevel;
|
|
1640
1670
|
}
|
|
@@ -1664,13 +1694,13 @@ class MidyGM2 {
|
|
|
1664
1694
|
setChorusEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1665
1695
|
let value = note.voiceParams.chorusEffectsSend;
|
|
1666
1696
|
if (channel.isDrum) {
|
|
1667
|
-
const keyBasedValue = this.
|
|
1697
|
+
const keyBasedValue = this.getKeyBasedValue(channel, note.noteNumber, 93);
|
|
1668
1698
|
if (0 <= keyBasedValue) {
|
|
1669
1699
|
value *= keyBasedValue / 127 / channel.state.chorusSendLevel;
|
|
1670
1700
|
}
|
|
1671
1701
|
}
|
|
1672
1702
|
if (0 < prevValue) {
|
|
1673
|
-
if (0 <
|
|
1703
|
+
if (0 < value) {
|
|
1674
1704
|
note.chorusEffectsSend.gain
|
|
1675
1705
|
.cancelScheduledValues(scheduleTime)
|
|
1676
1706
|
.setValueAtTime(value, scheduleTime);
|
|
@@ -1769,7 +1799,7 @@ class MidyGM2 {
|
|
|
1769
1799
|
return state;
|
|
1770
1800
|
}
|
|
1771
1801
|
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
1772
|
-
this.processScheduledNotes(channel,
|
|
1802
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1773
1803
|
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
|
|
1774
1804
|
const voiceParams = note.voice.getParams(controllerType, controllerState);
|
|
1775
1805
|
let applyVolumeEnvelope = false;
|
|
@@ -1830,7 +1860,7 @@ class MidyGM2 {
|
|
|
1830
1860
|
handlers[127] = this.polyOn;
|
|
1831
1861
|
return handlers;
|
|
1832
1862
|
}
|
|
1833
|
-
|
|
1863
|
+
setControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1834
1864
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1835
1865
|
if (handler) {
|
|
1836
1866
|
handler.call(this, channelNumber, value, scheduleTime);
|
|
@@ -1847,7 +1877,7 @@ class MidyGM2 {
|
|
|
1847
1877
|
}
|
|
1848
1878
|
updateModulation(channel, scheduleTime) {
|
|
1849
1879
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1850
|
-
this.processScheduledNotes(channel,
|
|
1880
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1851
1881
|
if (note.modulationDepth) {
|
|
1852
1882
|
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1853
1883
|
}
|
|
@@ -1866,7 +1896,7 @@ class MidyGM2 {
|
|
|
1866
1896
|
this.updateModulation(channel, scheduleTime);
|
|
1867
1897
|
}
|
|
1868
1898
|
updatePortamento(channel, scheduleTime) {
|
|
1869
|
-
this.processScheduledNotes(channel,
|
|
1899
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1870
1900
|
if (0.5 <= channel.state.portamento) {
|
|
1871
1901
|
if (0 <= note.portamentoNoteNumber) {
|
|
1872
1902
|
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
@@ -1951,11 +1981,11 @@ class MidyGM2 {
|
|
|
1951
1981
|
continue;
|
|
1952
1982
|
if (!gainR)
|
|
1953
1983
|
continue;
|
|
1954
|
-
const keyBasedVolume = this.
|
|
1984
|
+
const keyBasedVolume = this.getKeyBasedValue(channel, i, 7);
|
|
1955
1985
|
const volume = (0 <= keyBasedVolume)
|
|
1956
1986
|
? defaultVolume * keyBasedVolume / 64
|
|
1957
1987
|
: defaultVolume;
|
|
1958
|
-
const keyBasedPan = this.
|
|
1988
|
+
const keyBasedPan = this.getKeyBasedValue(channel, i, 10);
|
|
1959
1989
|
const pan = (0 <= keyBasedPan) ? keyBasedPan / 127 : defaultPan;
|
|
1960
1990
|
const { gainLeft, gainRight } = this.panToGain(pan);
|
|
1961
1991
|
gainL.gain
|
|
@@ -1973,7 +2003,7 @@ class MidyGM2 {
|
|
|
1973
2003
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1974
2004
|
channel.state.sustainPedal = value / 127;
|
|
1975
2005
|
if (64 <= value) {
|
|
1976
|
-
this.processScheduledNotes(channel,
|
|
2006
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1977
2007
|
channel.sustainNotes.push(note);
|
|
1978
2008
|
});
|
|
1979
2009
|
}
|
|
@@ -2016,7 +2046,7 @@ class MidyGM2 {
|
|
|
2016
2046
|
const state = channel.state;
|
|
2017
2047
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2018
2048
|
state.softPedal = softPedal / 127;
|
|
2019
|
-
this.processScheduledNotes(channel,
|
|
2049
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2020
2050
|
if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
2021
2051
|
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2022
2052
|
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
@@ -2040,7 +2070,7 @@ class MidyGM2 {
|
|
|
2040
2070
|
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
2041
2071
|
}
|
|
2042
2072
|
else {
|
|
2043
|
-
this.processScheduledNotes(channel,
|
|
2073
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2044
2074
|
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
2045
2075
|
return false;
|
|
2046
2076
|
if (note.reverbEffectsSend)
|
|
@@ -2050,7 +2080,7 @@ class MidyGM2 {
|
|
|
2050
2080
|
}
|
|
2051
2081
|
else {
|
|
2052
2082
|
if (0 < reverbSendLevel) {
|
|
2053
|
-
this.processScheduledNotes(channel,
|
|
2083
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2054
2084
|
this.setReverbEffectsSend(channel, note, 0, scheduleTime);
|
|
2055
2085
|
});
|
|
2056
2086
|
state.reverbSendLevel = reverbSendLevel / 127;
|
|
@@ -2073,7 +2103,7 @@ class MidyGM2 {
|
|
|
2073
2103
|
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
2074
2104
|
}
|
|
2075
2105
|
else {
|
|
2076
|
-
this.processScheduledNotes(channel,
|
|
2106
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2077
2107
|
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
2078
2108
|
return false;
|
|
2079
2109
|
if (note.chorusEffectsSend)
|
|
@@ -2083,7 +2113,7 @@ class MidyGM2 {
|
|
|
2083
2113
|
}
|
|
2084
2114
|
else {
|
|
2085
2115
|
if (0 < chorusSendLevel) {
|
|
2086
|
-
this.processScheduledNotes(channel,
|
|
2116
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2087
2117
|
this.setChorusEffectsSend(channel, note, 0, scheduleTime);
|
|
2088
2118
|
});
|
|
2089
2119
|
state.chorusSendLevel = chorusSendLevel / 127;
|
|
@@ -2227,7 +2257,7 @@ class MidyGM2 {
|
|
|
2227
2257
|
const entries = Object.entries(defaultControllerState);
|
|
2228
2258
|
for (const [key, { type, defaultValue }] of entries) {
|
|
2229
2259
|
if (128 <= type) {
|
|
2230
|
-
this.
|
|
2260
|
+
this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
2231
2261
|
}
|
|
2232
2262
|
else {
|
|
2233
2263
|
state[key] = defaultValue;
|
|
@@ -2259,7 +2289,7 @@ class MidyGM2 {
|
|
|
2259
2289
|
const key = keys[i];
|
|
2260
2290
|
const { type, defaultValue } = defaultControllerState[key];
|
|
2261
2291
|
if (128 <= type) {
|
|
2262
|
-
this.
|
|
2292
|
+
this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
2263
2293
|
}
|
|
2264
2294
|
else {
|
|
2265
2295
|
state[key] = defaultValue;
|
|
@@ -2694,27 +2724,29 @@ class MidyGM2 {
|
|
|
2694
2724
|
: 0;
|
|
2695
2725
|
return channelPressure / 127;
|
|
2696
2726
|
}
|
|
2697
|
-
setControllerParameters(channel, note, table) {
|
|
2727
|
+
setControllerParameters(channel, note, table, scheduleTime) {
|
|
2698
2728
|
if (0 <= table[0])
|
|
2699
|
-
this.updateDetune(channel, note);
|
|
2729
|
+
this.updateDetune(channel, note, scueduleTime);
|
|
2700
2730
|
if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
|
|
2701
|
-
if (0 <= table[1])
|
|
2702
|
-
this.setPortamentoFilterEnvelope(channel, note);
|
|
2703
|
-
|
|
2704
|
-
|
|
2731
|
+
if (0 <= table[1]) {
|
|
2732
|
+
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2733
|
+
}
|
|
2734
|
+
if (0 <= table[2]) {
|
|
2735
|
+
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2736
|
+
}
|
|
2705
2737
|
}
|
|
2706
2738
|
else {
|
|
2707
2739
|
if (0 <= table[1])
|
|
2708
|
-
this.setFilterEnvelope(channel, note);
|
|
2740
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
2709
2741
|
if (0 <= table[2])
|
|
2710
|
-
this.setVolumeEnvelope(channel, note);
|
|
2742
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
2711
2743
|
}
|
|
2712
2744
|
if (0 <= table[3])
|
|
2713
|
-
this.setModLfoToPitch(channel, note);
|
|
2745
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
2714
2746
|
if (0 <= table[4])
|
|
2715
|
-
this.setModLfoToFilterFc(channel, note);
|
|
2747
|
+
this.setModLfoToFilterFc(channel, note, scheduleTime);
|
|
2716
2748
|
if (0 <= table[5])
|
|
2717
|
-
this.setModLfoToVolume(channel, note);
|
|
2749
|
+
this.setModLfoToVolume(channel, note, scheduleTime);
|
|
2718
2750
|
}
|
|
2719
2751
|
handlePressureSysEx(data, tableName) {
|
|
2720
2752
|
const channelNumber = data[4];
|
|
@@ -2737,8 +2769,8 @@ class MidyGM2 {
|
|
|
2737
2769
|
const slotSize = 6;
|
|
2738
2770
|
const offset = controllerType * slotSize;
|
|
2739
2771
|
const table = channel.controlTable.subarray(offset, offset + slotSize);
|
|
2740
|
-
this.processScheduledNotes(channel,
|
|
2741
|
-
this.setControllerParameters(channel, note, table);
|
|
2772
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2773
|
+
this.setControllerParameters(channel, note, table, scheduleTime);
|
|
2742
2774
|
});
|
|
2743
2775
|
}
|
|
2744
2776
|
handleControlChangeSysEx(data) {
|
|
@@ -2754,7 +2786,7 @@ class MidyGM2 {
|
|
|
2754
2786
|
table[pp] = rr;
|
|
2755
2787
|
}
|
|
2756
2788
|
}
|
|
2757
|
-
|
|
2789
|
+
getKeyBasedValue(channel, keyNumber, controllerType) {
|
|
2758
2790
|
const index = keyNumber * 128 + controllerType;
|
|
2759
2791
|
const controlValue = channel.keyBasedInstrumentControlTable[index];
|
|
2760
2792
|
return controlValue;
|
|
@@ -2772,7 +2804,7 @@ class MidyGM2 {
|
|
|
2772
2804
|
const index = keyNumber * 128 + controllerType;
|
|
2773
2805
|
table[index] = value;
|
|
2774
2806
|
}
|
|
2775
|
-
this.
|
|
2807
|
+
this.setChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
|
|
2776
2808
|
}
|
|
2777
2809
|
handleSysEx(data, scheduleTime) {
|
|
2778
2810
|
switch (data[0]) {
|
|
@@ -2810,6 +2842,7 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
2810
2842
|
configurable: true,
|
|
2811
2843
|
writable: true,
|
|
2812
2844
|
value: {
|
|
2845
|
+
scheduleIndex: 0,
|
|
2813
2846
|
detune: 0,
|
|
2814
2847
|
programNumber: 0,
|
|
2815
2848
|
bank: 121 * 128,
|