@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-GM2.js
CHANGED
|
@@ -314,13 +314,13 @@ export class MidyGM2 {
|
|
|
314
314
|
writable: true,
|
|
315
315
|
value: this.initSoundFontTable()
|
|
316
316
|
});
|
|
317
|
-
Object.defineProperty(this, "
|
|
317
|
+
Object.defineProperty(this, "voiceCounter", {
|
|
318
318
|
enumerable: true,
|
|
319
319
|
configurable: true,
|
|
320
320
|
writable: true,
|
|
321
321
|
value: new Map()
|
|
322
322
|
});
|
|
323
|
-
Object.defineProperty(this, "
|
|
323
|
+
Object.defineProperty(this, "voiceCache", {
|
|
324
324
|
enumerable: true,
|
|
325
325
|
configurable: true,
|
|
326
326
|
writable: true,
|
|
@@ -417,13 +417,11 @@ export class MidyGM2 {
|
|
|
417
417
|
const presetHeaders = soundFont.parsed.presetHeaders;
|
|
418
418
|
for (let i = 0; i < presetHeaders.length; i++) {
|
|
419
419
|
const presetHeader = presetHeaders[i];
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
banks.set(presetHeader.bank, index);
|
|
423
|
-
}
|
|
420
|
+
const banks = this.soundFontTable[presetHeader.preset];
|
|
421
|
+
banks.set(presetHeader.bank, index);
|
|
424
422
|
}
|
|
425
423
|
}
|
|
426
|
-
async
|
|
424
|
+
async toUint8Array(input) {
|
|
427
425
|
let uint8Array;
|
|
428
426
|
if (typeof input === "string") {
|
|
429
427
|
const response = await fetch(input);
|
|
@@ -436,23 +434,32 @@ export class MidyGM2 {
|
|
|
436
434
|
else {
|
|
437
435
|
throw new TypeError("input must be a URL string or Uint8Array");
|
|
438
436
|
}
|
|
439
|
-
|
|
440
|
-
const soundFont = new SoundFont(parsed);
|
|
441
|
-
this.addSoundFont(soundFont);
|
|
437
|
+
return uint8Array;
|
|
442
438
|
}
|
|
443
|
-
async
|
|
444
|
-
|
|
445
|
-
if (
|
|
446
|
-
const
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
439
|
+
async loadSoundFont(input) {
|
|
440
|
+
this.voiceCounter.clear();
|
|
441
|
+
if (Array.isArray(input)) {
|
|
442
|
+
const promises = new Array(input.length);
|
|
443
|
+
for (let i = 0; i < input.length; i++) {
|
|
444
|
+
promises[i] = this.toUint8Array(input[i]);
|
|
445
|
+
}
|
|
446
|
+
const uint8Arrays = await Promise.all(promises);
|
|
447
|
+
for (let i = 0; i < uint8Arrays.length; i++) {
|
|
448
|
+
const parsed = parse(uint8Arrays[i]);
|
|
449
|
+
const soundFont = new SoundFont(parsed);
|
|
450
|
+
this.addSoundFont(soundFont);
|
|
451
|
+
}
|
|
452
452
|
}
|
|
453
453
|
else {
|
|
454
|
-
|
|
454
|
+
const uint8Array = await this.toUint8Array(input);
|
|
455
|
+
const parsed = parse(uint8Array);
|
|
456
|
+
const soundFont = new SoundFont(parsed);
|
|
457
|
+
this.addSoundFont(soundFont);
|
|
455
458
|
}
|
|
459
|
+
}
|
|
460
|
+
async loadMIDI(input) {
|
|
461
|
+
this.voiceCounter.clear();
|
|
462
|
+
const uint8Array = await this.toUint8Array(input);
|
|
456
463
|
const midi = parseMidi(uint8Array);
|
|
457
464
|
this.ticksPerBeat = midi.header.ticksPerBeat;
|
|
458
465
|
const midiData = this.extractMidiData(midi);
|
|
@@ -460,6 +467,45 @@ export class MidyGM2 {
|
|
|
460
467
|
this.timeline = midiData.timeline;
|
|
461
468
|
this.totalTime = this.calcTotalTime();
|
|
462
469
|
}
|
|
470
|
+
cacheVoiceIds() {
|
|
471
|
+
const timeline = this.timeline;
|
|
472
|
+
for (let i = 0; i < timeline.length; i++) {
|
|
473
|
+
const event = timeline[i];
|
|
474
|
+
switch (event.type) {
|
|
475
|
+
case "noteOn": {
|
|
476
|
+
const audioBufferId = this.getVoiceId(this.channels[event.channel], event.noteNumber, event.velocity);
|
|
477
|
+
this.voiceCounter.set(audioBufferId, (this.voiceCounter.get(audioBufferId) ?? 0) + 1);
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
case "controller":
|
|
481
|
+
if (event.controllerType === 0) {
|
|
482
|
+
this.setBankMSB(event.channel, event.value);
|
|
483
|
+
}
|
|
484
|
+
else if (event.controllerType === 32) {
|
|
485
|
+
this.setBankLSB(event.channel, event.value);
|
|
486
|
+
}
|
|
487
|
+
break;
|
|
488
|
+
case "programChange":
|
|
489
|
+
this.setProgramChange(event.channel, event.programNumber, event.startTime);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
for (const [audioBufferId, count] of this.voiceCounter) {
|
|
493
|
+
if (count === 1)
|
|
494
|
+
this.voiceCounter.delete(audioBufferId);
|
|
495
|
+
}
|
|
496
|
+
this.GM2SystemOn();
|
|
497
|
+
}
|
|
498
|
+
getVoiceId(channel, noteNumber, velocity) {
|
|
499
|
+
const bankNumber = this.calcBank(channel);
|
|
500
|
+
const soundFontIndex = this.soundFontTable[channel.programNumber]
|
|
501
|
+
.get(bankNumber);
|
|
502
|
+
if (soundFontIndex === undefined)
|
|
503
|
+
return;
|
|
504
|
+
const soundFont = this.soundFonts[soundFontIndex];
|
|
505
|
+
const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
|
|
506
|
+
const { instrument, sampleID } = voice.generators;
|
|
507
|
+
return `${soundFontIndex}:${instrument}:${sampleID}`;
|
|
508
|
+
}
|
|
463
509
|
createChannelAudioNodes(audioContext) {
|
|
464
510
|
const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
|
|
465
511
|
const gainL = new GainNode(audioContext, { gain: gainLeft });
|
|
@@ -501,34 +547,12 @@ export class MidyGM2 {
|
|
|
501
547
|
});
|
|
502
548
|
return channels;
|
|
503
549
|
}
|
|
504
|
-
async
|
|
550
|
+
async createAudioBuffer(voiceParams) {
|
|
551
|
+
const sample = voiceParams.sample;
|
|
505
552
|
const sampleStart = voiceParams.start;
|
|
506
|
-
const sampleEnd =
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
const start = sample.byteOffset + sampleStart;
|
|
510
|
-
const end = sample.byteOffset + sampleEnd;
|
|
511
|
-
const buffer = sample.buffer.slice(start, end);
|
|
512
|
-
const audioBuffer = await this.audioContext.decodeAudioData(buffer);
|
|
513
|
-
return audioBuffer;
|
|
514
|
-
}
|
|
515
|
-
else {
|
|
516
|
-
const sample = voiceParams.sample;
|
|
517
|
-
const start = sample.byteOffset + sampleStart;
|
|
518
|
-
const end = sample.byteOffset + sampleEnd;
|
|
519
|
-
const buffer = sample.buffer.slice(start, end);
|
|
520
|
-
const audioBuffer = new AudioBuffer({
|
|
521
|
-
numberOfChannels: 1,
|
|
522
|
-
length: sample.length,
|
|
523
|
-
sampleRate: voiceParams.sampleRate,
|
|
524
|
-
});
|
|
525
|
-
const channelData = audioBuffer.getChannelData(0);
|
|
526
|
-
const int16Array = new Int16Array(buffer);
|
|
527
|
-
for (let i = 0; i < int16Array.length; i++) {
|
|
528
|
-
channelData[i] = int16Array[i] / 32768;
|
|
529
|
-
}
|
|
530
|
-
return audioBuffer;
|
|
531
|
-
}
|
|
553
|
+
const sampleEnd = sample.data.length + voiceParams.end;
|
|
554
|
+
const audioBuffer = await sample.toAudioBuffer(this.audioContext, sampleStart, sampleEnd);
|
|
555
|
+
return audioBuffer;
|
|
532
556
|
}
|
|
533
557
|
isLoopDrum(channel, noteNumber) {
|
|
534
558
|
const programNumber = channel.programNumber;
|
|
@@ -565,13 +589,13 @@ export class MidyGM2 {
|
|
|
565
589
|
break;
|
|
566
590
|
}
|
|
567
591
|
case "controller":
|
|
568
|
-
this.
|
|
592
|
+
this.setControlChange(event.channel, event.controllerType, event.value, startTime);
|
|
569
593
|
break;
|
|
570
594
|
case "programChange":
|
|
571
|
-
this.
|
|
595
|
+
this.setProgramChange(event.channel, event.programNumber, startTime);
|
|
572
596
|
break;
|
|
573
597
|
case "channelAftertouch":
|
|
574
|
-
this.
|
|
598
|
+
this.setChannelPressure(event.channel, event.amount, startTime);
|
|
575
599
|
break;
|
|
576
600
|
case "pitchBend":
|
|
577
601
|
this.setPitchBend(event.channel, event.value + 8192, startTime);
|
|
@@ -605,8 +629,9 @@ export class MidyGM2 {
|
|
|
605
629
|
this.notePromises = [];
|
|
606
630
|
this.exclusiveClassNotes.fill(undefined);
|
|
607
631
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
608
|
-
this.
|
|
632
|
+
this.voiceCache.clear();
|
|
609
633
|
for (let i = 0; i < this.channels.length; i++) {
|
|
634
|
+
this.channels[i].scheduledNotes = [];
|
|
610
635
|
this.resetAllStates(i);
|
|
611
636
|
}
|
|
612
637
|
resolve();
|
|
@@ -628,8 +653,9 @@ export class MidyGM2 {
|
|
|
628
653
|
this.notePromises = [];
|
|
629
654
|
this.exclusiveClassNotes.fill(undefined);
|
|
630
655
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
631
|
-
this.
|
|
656
|
+
this.voiceCache.clear();
|
|
632
657
|
for (let i = 0; i < this.channels.length; i++) {
|
|
658
|
+
this.channels[i].scheduledNotes = [];
|
|
633
659
|
this.resetAllStates(i);
|
|
634
660
|
}
|
|
635
661
|
this.isStopping = false;
|
|
@@ -662,11 +688,7 @@ export class MidyGM2 {
|
|
|
662
688
|
secondToTicks(second, secondsPerBeat) {
|
|
663
689
|
return second * this.ticksPerBeat / secondsPerBeat;
|
|
664
690
|
}
|
|
665
|
-
getAudioBufferId(programNumber, noteNumber, velocity) {
|
|
666
|
-
return `${programNumber}:${noteNumber}:${velocity}`;
|
|
667
|
-
}
|
|
668
691
|
extractMidiData(midi) {
|
|
669
|
-
this.audioBufferCounter.clear();
|
|
670
692
|
const instruments = new Set();
|
|
671
693
|
const timeline = [];
|
|
672
694
|
const tmpChannels = new Array(this.channels.length);
|
|
@@ -687,8 +709,6 @@ export class MidyGM2 {
|
|
|
687
709
|
switch (event.type) {
|
|
688
710
|
case "noteOn": {
|
|
689
711
|
const channel = tmpChannels[event.channel];
|
|
690
|
-
const audioBufferId = this.getAudioBufferId(channel.programNumber, event.noteNumber, event.velocity);
|
|
691
|
-
this.audioBufferCounter.set(audioBufferId, (this.audioBufferCounter.get(audioBufferId) ?? 0) + 1);
|
|
692
712
|
if (channel.programNumber < 0) {
|
|
693
713
|
channel.programNumber = event.programNumber;
|
|
694
714
|
switch (channel.bankMSB) {
|
|
@@ -738,10 +758,6 @@ export class MidyGM2 {
|
|
|
738
758
|
timeline.push(event);
|
|
739
759
|
}
|
|
740
760
|
}
|
|
741
|
-
for (const [audioBufferId, count] of this.audioBufferCounter) {
|
|
742
|
-
if (count === 1)
|
|
743
|
-
this.audioBufferCounter.delete(audioBufferId);
|
|
744
|
-
}
|
|
745
761
|
const priority = {
|
|
746
762
|
controller: 0,
|
|
747
763
|
sysEx: 1,
|
|
@@ -781,12 +797,11 @@ export class MidyGM2 {
|
|
|
781
797
|
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
782
798
|
const channel = this.channels[channelNumber];
|
|
783
799
|
const promises = [];
|
|
784
|
-
this.processScheduledNotes(channel,
|
|
800
|
+
this.processScheduledNotes(channel, (note) => {
|
|
785
801
|
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
786
802
|
this.notePromises.push(promise);
|
|
787
803
|
promises.push(promise);
|
|
788
804
|
});
|
|
789
|
-
channel.scheduledNotes = [];
|
|
790
805
|
return Promise.all(promises);
|
|
791
806
|
}
|
|
792
807
|
stopNotes(velocity, force, scheduleTime) {
|
|
@@ -800,6 +815,8 @@ export class MidyGM2 {
|
|
|
800
815
|
if (this.isPlaying || this.isPaused)
|
|
801
816
|
return;
|
|
802
817
|
this.resumeTime = 0;
|
|
818
|
+
if (this.voiceCounter.size === 0)
|
|
819
|
+
this.cacheVoiceIds();
|
|
803
820
|
await this.playNotes();
|
|
804
821
|
this.isPlaying = false;
|
|
805
822
|
}
|
|
@@ -840,22 +857,20 @@ export class MidyGM2 {
|
|
|
840
857
|
const now = this.audioContext.currentTime;
|
|
841
858
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
842
859
|
}
|
|
843
|
-
processScheduledNotes(channel,
|
|
860
|
+
processScheduledNotes(channel, callback) {
|
|
844
861
|
const scheduledNotes = channel.scheduledNotes;
|
|
845
|
-
for (let i =
|
|
862
|
+
for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
|
|
846
863
|
const note = scheduledNotes[i];
|
|
847
864
|
if (!note)
|
|
848
865
|
continue;
|
|
849
866
|
if (note.ending)
|
|
850
867
|
continue;
|
|
851
|
-
if (note.startTime < scheduleTime)
|
|
852
|
-
continue;
|
|
853
868
|
callback(note);
|
|
854
869
|
}
|
|
855
870
|
}
|
|
856
871
|
processActiveNotes(channel, scheduleTime, callback) {
|
|
857
872
|
const scheduledNotes = channel.scheduledNotes;
|
|
858
|
-
for (let i =
|
|
873
|
+
for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
|
|
859
874
|
const note = scheduledNotes[i];
|
|
860
875
|
if (!note)
|
|
861
876
|
continue;
|
|
@@ -1046,7 +1061,7 @@ export class MidyGM2 {
|
|
|
1046
1061
|
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
1047
1062
|
}
|
|
1048
1063
|
updateChannelDetune(channel, scheduleTime) {
|
|
1049
|
-
this.processScheduledNotes(channel,
|
|
1064
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1050
1065
|
this.updateDetune(channel, note, scheduleTime);
|
|
1051
1066
|
});
|
|
1052
1067
|
}
|
|
@@ -1267,31 +1282,31 @@ export class MidyGM2 {
|
|
|
1267
1282
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
1268
1283
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
1269
1284
|
}
|
|
1270
|
-
async getAudioBuffer(
|
|
1271
|
-
const audioBufferId = this.
|
|
1272
|
-
const cache = this.
|
|
1285
|
+
async getAudioBuffer(channel, noteNumber, velocity, voiceParams) {
|
|
1286
|
+
const audioBufferId = this.getVoiceId(channel, noteNumber, velocity);
|
|
1287
|
+
const cache = this.voiceCache.get(audioBufferId);
|
|
1273
1288
|
if (cache) {
|
|
1274
1289
|
cache.counter += 1;
|
|
1275
1290
|
if (cache.maxCount <= cache.counter) {
|
|
1276
|
-
this.
|
|
1291
|
+
this.voiceCache.delete(audioBufferId);
|
|
1277
1292
|
}
|
|
1278
1293
|
return cache.audioBuffer;
|
|
1279
1294
|
}
|
|
1280
1295
|
else {
|
|
1281
|
-
const maxCount = this.
|
|
1282
|
-
const audioBuffer = await this.
|
|
1296
|
+
const maxCount = this.voiceCounter.get(audioBufferId) ?? 0;
|
|
1297
|
+
const audioBuffer = await this.createAudioBuffer(voiceParams);
|
|
1283
1298
|
const cache = { audioBuffer, maxCount, counter: 1 };
|
|
1284
|
-
this.
|
|
1299
|
+
this.voiceCache.set(audioBufferId, cache);
|
|
1285
1300
|
return audioBuffer;
|
|
1286
1301
|
}
|
|
1287
1302
|
}
|
|
1288
|
-
async createNote(channel, voice, noteNumber, velocity, startTime
|
|
1303
|
+
async createNote(channel, voice, noteNumber, velocity, startTime) {
|
|
1289
1304
|
const now = this.audioContext.currentTime;
|
|
1290
1305
|
const state = channel.state;
|
|
1291
1306
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1292
1307
|
const voiceParams = voice.getAllParams(controllerState);
|
|
1293
1308
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1294
|
-
const audioBuffer = await this.getAudioBuffer(channel
|
|
1309
|
+
const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams);
|
|
1295
1310
|
note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
|
|
1296
1311
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
1297
1312
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
@@ -1386,15 +1401,15 @@ export class MidyGM2 {
|
|
|
1386
1401
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
1387
1402
|
const channel = this.channels[channelNumber];
|
|
1388
1403
|
const bankNumber = this.calcBank(channel, channelNumber);
|
|
1389
|
-
const soundFontIndex = this.soundFontTable[channel.programNumber]
|
|
1404
|
+
const soundFontIndex = this.soundFontTable[channel.programNumber]
|
|
1405
|
+
.get(bankNumber);
|
|
1390
1406
|
if (soundFontIndex === undefined)
|
|
1391
1407
|
return;
|
|
1392
1408
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
1393
1409
|
const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
|
|
1394
1410
|
if (!voice)
|
|
1395
1411
|
return;
|
|
1396
|
-
const
|
|
1397
|
-
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
1412
|
+
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
|
|
1398
1413
|
if (channel.isDrum) {
|
|
1399
1414
|
const audioContext = this.audioContext;
|
|
1400
1415
|
const { gainL, gainR } = this.createChannelAudioNodes(audioContext);
|
|
@@ -1476,15 +1491,29 @@ export class MidyGM2 {
|
|
|
1476
1491
|
return;
|
|
1477
1492
|
}
|
|
1478
1493
|
}
|
|
1479
|
-
const
|
|
1480
|
-
if (
|
|
1494
|
+
const index = this.findNoteOffIndex(channel, noteNumber);
|
|
1495
|
+
if (index < 0)
|
|
1481
1496
|
return;
|
|
1497
|
+
const note = channel.scheduledNotes[index];
|
|
1482
1498
|
note.ending = true;
|
|
1499
|
+
this.setNoteIndex(channel, index);
|
|
1483
1500
|
this.releaseNote(channel, note, endTime);
|
|
1484
1501
|
}
|
|
1485
|
-
|
|
1502
|
+
setNoteIndex(channel, index) {
|
|
1503
|
+
let allEnds = true;
|
|
1504
|
+
for (let i = channel.scheduleIndex; i < index; i++) {
|
|
1505
|
+
const note = channel.scheduledNotes[i];
|
|
1506
|
+
if (note && !note.ending) {
|
|
1507
|
+
allEnds = false;
|
|
1508
|
+
break;
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
if (allEnds)
|
|
1512
|
+
channel.scheduleIndex = index + 1;
|
|
1513
|
+
}
|
|
1514
|
+
findNoteOffIndex(channel, noteNumber) {
|
|
1486
1515
|
const scheduledNotes = channel.scheduledNotes;
|
|
1487
|
-
for (let i =
|
|
1516
|
+
for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
|
|
1488
1517
|
const note = scheduledNotes[i];
|
|
1489
1518
|
if (!note)
|
|
1490
1519
|
continue;
|
|
@@ -1492,8 +1521,9 @@ export class MidyGM2 {
|
|
|
1492
1521
|
continue;
|
|
1493
1522
|
if (note.noteNumber !== noteNumber)
|
|
1494
1523
|
continue;
|
|
1495
|
-
return
|
|
1524
|
+
return i;
|
|
1496
1525
|
}
|
|
1526
|
+
return -1;
|
|
1497
1527
|
}
|
|
1498
1528
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1499
1529
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -1533,18 +1563,18 @@ export class MidyGM2 {
|
|
|
1533
1563
|
case 0x90:
|
|
1534
1564
|
return this.noteOn(channelNumber, data1, data2, scheduleTime);
|
|
1535
1565
|
case 0xB0:
|
|
1536
|
-
return this.
|
|
1566
|
+
return this.setControlChange(channelNumber, data1, data2, scheduleTime);
|
|
1537
1567
|
case 0xC0:
|
|
1538
|
-
return this.
|
|
1568
|
+
return this.setProgramChange(channelNumber, data1, scheduleTime);
|
|
1539
1569
|
case 0xD0:
|
|
1540
|
-
return this.
|
|
1570
|
+
return this.setChannelPressure(channelNumber, data1, scheduleTime);
|
|
1541
1571
|
case 0xE0:
|
|
1542
1572
|
return this.handlePitchBendMessage(channelNumber, data1, data2, scheduleTime);
|
|
1543
1573
|
default:
|
|
1544
1574
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
1545
1575
|
}
|
|
1546
1576
|
}
|
|
1547
|
-
|
|
1577
|
+
setProgramChange(channelNumber, programNumber, _scheduleTime) {
|
|
1548
1578
|
const channel = this.channels[channelNumber];
|
|
1549
1579
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1550
1580
|
channel.programNumber = programNumber;
|
|
@@ -1559,7 +1589,7 @@ export class MidyGM2 {
|
|
|
1559
1589
|
}
|
|
1560
1590
|
}
|
|
1561
1591
|
}
|
|
1562
|
-
|
|
1592
|
+
setChannelPressure(channelNumber, value, scheduleTime) {
|
|
1563
1593
|
const channel = this.channels[channelNumber];
|
|
1564
1594
|
if (channel.isDrum)
|
|
1565
1595
|
return;
|
|
@@ -1631,7 +1661,7 @@ export class MidyGM2 {
|
|
|
1631
1661
|
setReverbEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1632
1662
|
let value = note.voiceParams.reverbEffectsSend;
|
|
1633
1663
|
if (channel.isDrum) {
|
|
1634
|
-
const keyBasedValue = this.
|
|
1664
|
+
const keyBasedValue = this.getKeyBasedValue(channel, note.noteNumber, 91);
|
|
1635
1665
|
if (0 <= keyBasedValue) {
|
|
1636
1666
|
value *= keyBasedValue / 127 / channel.state.reverbSendLevel;
|
|
1637
1667
|
}
|
|
@@ -1661,13 +1691,13 @@ export class MidyGM2 {
|
|
|
1661
1691
|
setChorusEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1662
1692
|
let value = note.voiceParams.chorusEffectsSend;
|
|
1663
1693
|
if (channel.isDrum) {
|
|
1664
|
-
const keyBasedValue = this.
|
|
1694
|
+
const keyBasedValue = this.getKeyBasedValue(channel, note.noteNumber, 93);
|
|
1665
1695
|
if (0 <= keyBasedValue) {
|
|
1666
1696
|
value *= keyBasedValue / 127 / channel.state.chorusSendLevel;
|
|
1667
1697
|
}
|
|
1668
1698
|
}
|
|
1669
1699
|
if (0 < prevValue) {
|
|
1670
|
-
if (0 <
|
|
1700
|
+
if (0 < value) {
|
|
1671
1701
|
note.chorusEffectsSend.gain
|
|
1672
1702
|
.cancelScheduledValues(scheduleTime)
|
|
1673
1703
|
.setValueAtTime(value, scheduleTime);
|
|
@@ -1766,7 +1796,7 @@ export class MidyGM2 {
|
|
|
1766
1796
|
return state;
|
|
1767
1797
|
}
|
|
1768
1798
|
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
1769
|
-
this.processScheduledNotes(channel,
|
|
1799
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1770
1800
|
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
|
|
1771
1801
|
const voiceParams = note.voice.getParams(controllerType, controllerState);
|
|
1772
1802
|
let applyVolumeEnvelope = false;
|
|
@@ -1827,7 +1857,7 @@ export class MidyGM2 {
|
|
|
1827
1857
|
handlers[127] = this.polyOn;
|
|
1828
1858
|
return handlers;
|
|
1829
1859
|
}
|
|
1830
|
-
|
|
1860
|
+
setControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1831
1861
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1832
1862
|
if (handler) {
|
|
1833
1863
|
handler.call(this, channelNumber, value, scheduleTime);
|
|
@@ -1844,7 +1874,7 @@ export class MidyGM2 {
|
|
|
1844
1874
|
}
|
|
1845
1875
|
updateModulation(channel, scheduleTime) {
|
|
1846
1876
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1847
|
-
this.processScheduledNotes(channel,
|
|
1877
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1848
1878
|
if (note.modulationDepth) {
|
|
1849
1879
|
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1850
1880
|
}
|
|
@@ -1863,7 +1893,7 @@ export class MidyGM2 {
|
|
|
1863
1893
|
this.updateModulation(channel, scheduleTime);
|
|
1864
1894
|
}
|
|
1865
1895
|
updatePortamento(channel, scheduleTime) {
|
|
1866
|
-
this.processScheduledNotes(channel,
|
|
1896
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1867
1897
|
if (0.5 <= channel.state.portamento) {
|
|
1868
1898
|
if (0 <= note.portamentoNoteNumber) {
|
|
1869
1899
|
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
@@ -1948,11 +1978,11 @@ export class MidyGM2 {
|
|
|
1948
1978
|
continue;
|
|
1949
1979
|
if (!gainR)
|
|
1950
1980
|
continue;
|
|
1951
|
-
const keyBasedVolume = this.
|
|
1981
|
+
const keyBasedVolume = this.getKeyBasedValue(channel, i, 7);
|
|
1952
1982
|
const volume = (0 <= keyBasedVolume)
|
|
1953
1983
|
? defaultVolume * keyBasedVolume / 64
|
|
1954
1984
|
: defaultVolume;
|
|
1955
|
-
const keyBasedPan = this.
|
|
1985
|
+
const keyBasedPan = this.getKeyBasedValue(channel, i, 10);
|
|
1956
1986
|
const pan = (0 <= keyBasedPan) ? keyBasedPan / 127 : defaultPan;
|
|
1957
1987
|
const { gainLeft, gainRight } = this.panToGain(pan);
|
|
1958
1988
|
gainL.gain
|
|
@@ -1970,7 +2000,7 @@ export class MidyGM2 {
|
|
|
1970
2000
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1971
2001
|
channel.state.sustainPedal = value / 127;
|
|
1972
2002
|
if (64 <= value) {
|
|
1973
|
-
this.processScheduledNotes(channel,
|
|
2003
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1974
2004
|
channel.sustainNotes.push(note);
|
|
1975
2005
|
});
|
|
1976
2006
|
}
|
|
@@ -2013,7 +2043,7 @@ export class MidyGM2 {
|
|
|
2013
2043
|
const state = channel.state;
|
|
2014
2044
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2015
2045
|
state.softPedal = softPedal / 127;
|
|
2016
|
-
this.processScheduledNotes(channel,
|
|
2046
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2017
2047
|
if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
2018
2048
|
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2019
2049
|
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
@@ -2037,7 +2067,7 @@ export class MidyGM2 {
|
|
|
2037
2067
|
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
2038
2068
|
}
|
|
2039
2069
|
else {
|
|
2040
|
-
this.processScheduledNotes(channel,
|
|
2070
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2041
2071
|
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
2042
2072
|
return false;
|
|
2043
2073
|
if (note.reverbEffectsSend)
|
|
@@ -2047,7 +2077,7 @@ export class MidyGM2 {
|
|
|
2047
2077
|
}
|
|
2048
2078
|
else {
|
|
2049
2079
|
if (0 < reverbSendLevel) {
|
|
2050
|
-
this.processScheduledNotes(channel,
|
|
2080
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2051
2081
|
this.setReverbEffectsSend(channel, note, 0, scheduleTime);
|
|
2052
2082
|
});
|
|
2053
2083
|
state.reverbSendLevel = reverbSendLevel / 127;
|
|
@@ -2070,7 +2100,7 @@ export class MidyGM2 {
|
|
|
2070
2100
|
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
2071
2101
|
}
|
|
2072
2102
|
else {
|
|
2073
|
-
this.processScheduledNotes(channel,
|
|
2103
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2074
2104
|
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
2075
2105
|
return false;
|
|
2076
2106
|
if (note.chorusEffectsSend)
|
|
@@ -2080,7 +2110,7 @@ export class MidyGM2 {
|
|
|
2080
2110
|
}
|
|
2081
2111
|
else {
|
|
2082
2112
|
if (0 < chorusSendLevel) {
|
|
2083
|
-
this.processScheduledNotes(channel,
|
|
2113
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2084
2114
|
this.setChorusEffectsSend(channel, note, 0, scheduleTime);
|
|
2085
2115
|
});
|
|
2086
2116
|
state.chorusSendLevel = chorusSendLevel / 127;
|
|
@@ -2224,7 +2254,7 @@ export class MidyGM2 {
|
|
|
2224
2254
|
const entries = Object.entries(defaultControllerState);
|
|
2225
2255
|
for (const [key, { type, defaultValue }] of entries) {
|
|
2226
2256
|
if (128 <= type) {
|
|
2227
|
-
this.
|
|
2257
|
+
this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
2228
2258
|
}
|
|
2229
2259
|
else {
|
|
2230
2260
|
state[key] = defaultValue;
|
|
@@ -2256,7 +2286,7 @@ export class MidyGM2 {
|
|
|
2256
2286
|
const key = keys[i];
|
|
2257
2287
|
const { type, defaultValue } = defaultControllerState[key];
|
|
2258
2288
|
if (128 <= type) {
|
|
2259
|
-
this.
|
|
2289
|
+
this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
2260
2290
|
}
|
|
2261
2291
|
else {
|
|
2262
2292
|
state[key] = defaultValue;
|
|
@@ -2691,27 +2721,29 @@ export class MidyGM2 {
|
|
|
2691
2721
|
: 0;
|
|
2692
2722
|
return channelPressure / 127;
|
|
2693
2723
|
}
|
|
2694
|
-
setControllerParameters(channel, note, table) {
|
|
2724
|
+
setControllerParameters(channel, note, table, scheduleTime) {
|
|
2695
2725
|
if (0 <= table[0])
|
|
2696
|
-
this.updateDetune(channel, note);
|
|
2726
|
+
this.updateDetune(channel, note, scueduleTime);
|
|
2697
2727
|
if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
|
|
2698
|
-
if (0 <= table[1])
|
|
2699
|
-
this.setPortamentoFilterEnvelope(channel, note);
|
|
2700
|
-
|
|
2701
|
-
|
|
2728
|
+
if (0 <= table[1]) {
|
|
2729
|
+
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2730
|
+
}
|
|
2731
|
+
if (0 <= table[2]) {
|
|
2732
|
+
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2733
|
+
}
|
|
2702
2734
|
}
|
|
2703
2735
|
else {
|
|
2704
2736
|
if (0 <= table[1])
|
|
2705
|
-
this.setFilterEnvelope(channel, note);
|
|
2737
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
2706
2738
|
if (0 <= table[2])
|
|
2707
|
-
this.setVolumeEnvelope(channel, note);
|
|
2739
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
2708
2740
|
}
|
|
2709
2741
|
if (0 <= table[3])
|
|
2710
|
-
this.setModLfoToPitch(channel, note);
|
|
2742
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
2711
2743
|
if (0 <= table[4])
|
|
2712
|
-
this.setModLfoToFilterFc(channel, note);
|
|
2744
|
+
this.setModLfoToFilterFc(channel, note, scheduleTime);
|
|
2713
2745
|
if (0 <= table[5])
|
|
2714
|
-
this.setModLfoToVolume(channel, note);
|
|
2746
|
+
this.setModLfoToVolume(channel, note, scheduleTime);
|
|
2715
2747
|
}
|
|
2716
2748
|
handlePressureSysEx(data, tableName) {
|
|
2717
2749
|
const channelNumber = data[4];
|
|
@@ -2734,8 +2766,8 @@ export class MidyGM2 {
|
|
|
2734
2766
|
const slotSize = 6;
|
|
2735
2767
|
const offset = controllerType * slotSize;
|
|
2736
2768
|
const table = channel.controlTable.subarray(offset, offset + slotSize);
|
|
2737
|
-
this.processScheduledNotes(channel,
|
|
2738
|
-
this.setControllerParameters(channel, note, table);
|
|
2769
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2770
|
+
this.setControllerParameters(channel, note, table, scheduleTime);
|
|
2739
2771
|
});
|
|
2740
2772
|
}
|
|
2741
2773
|
handleControlChangeSysEx(data) {
|
|
@@ -2751,7 +2783,7 @@ export class MidyGM2 {
|
|
|
2751
2783
|
table[pp] = rr;
|
|
2752
2784
|
}
|
|
2753
2785
|
}
|
|
2754
|
-
|
|
2786
|
+
getKeyBasedValue(channel, keyNumber, controllerType) {
|
|
2755
2787
|
const index = keyNumber * 128 + controllerType;
|
|
2756
2788
|
const controlValue = channel.keyBasedInstrumentControlTable[index];
|
|
2757
2789
|
return controlValue;
|
|
@@ -2769,7 +2801,7 @@ export class MidyGM2 {
|
|
|
2769
2801
|
const index = keyNumber * 128 + controllerType;
|
|
2770
2802
|
table[index] = value;
|
|
2771
2803
|
}
|
|
2772
|
-
this.
|
|
2804
|
+
this.setChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
|
|
2773
2805
|
}
|
|
2774
2806
|
handleSysEx(data, scheduleTime) {
|
|
2775
2807
|
switch (data[0]) {
|
|
@@ -2806,6 +2838,7 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
2806
2838
|
configurable: true,
|
|
2807
2839
|
writable: true,
|
|
2808
2840
|
value: {
|
|
2841
|
+
scheduleIndex: 0,
|
|
2809
2842
|
detune: 0,
|
|
2810
2843
|
programNumber: 0,
|
|
2811
2844
|
bank: 121 * 128,
|