@marmooo/midy 0.3.6 → 0.3.8
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 +38 -20
- package/esm/midy-GM1.d.ts +36 -30
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +184 -142
- package/esm/midy-GM2.d.ts +43 -33
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +324 -279
- package/esm/midy-GMLite.d.ts +35 -30
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +178 -139
- package/esm/midy.d.ts +45 -34
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +414 -302
- package/package.json +2 -2
- package/script/midy-GM1.d.ts +36 -30
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +184 -142
- package/script/midy-GM2.d.ts +43 -33
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +324 -279
- package/script/midy-GMLite.d.ts +35 -30
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +178 -139
- package/script/midy.d.ts +45 -34
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +414 -302
package/script/midy-GM2.js
CHANGED
|
@@ -228,13 +228,13 @@ class MidyGM2 {
|
|
|
228
228
|
configurable: true,
|
|
229
229
|
writable: true,
|
|
230
230
|
value: 0
|
|
231
|
-
}); //
|
|
231
|
+
}); // cent
|
|
232
232
|
Object.defineProperty(this, "masterCoarseTuning", {
|
|
233
233
|
enumerable: true,
|
|
234
234
|
configurable: true,
|
|
235
235
|
writable: true,
|
|
236
236
|
value: 0
|
|
237
|
-
}); //
|
|
237
|
+
}); // cent
|
|
238
238
|
Object.defineProperty(this, "reverb", {
|
|
239
239
|
enumerable: true,
|
|
240
240
|
configurable: true,
|
|
@@ -275,6 +275,18 @@ class MidyGM2 {
|
|
|
275
275
|
writable: true,
|
|
276
276
|
value: 0
|
|
277
277
|
});
|
|
278
|
+
Object.defineProperty(this, "lastActiveSensing", {
|
|
279
|
+
enumerable: true,
|
|
280
|
+
configurable: true,
|
|
281
|
+
writable: true,
|
|
282
|
+
value: 0
|
|
283
|
+
});
|
|
284
|
+
Object.defineProperty(this, "activeSensingThreshold", {
|
|
285
|
+
enumerable: true,
|
|
286
|
+
configurable: true,
|
|
287
|
+
writable: true,
|
|
288
|
+
value: 0.3
|
|
289
|
+
});
|
|
278
290
|
Object.defineProperty(this, "noteCheckInterval", {
|
|
279
291
|
enumerable: true,
|
|
280
292
|
configurable: true,
|
|
@@ -315,7 +327,7 @@ class MidyGM2 {
|
|
|
315
327
|
enumerable: true,
|
|
316
328
|
configurable: true,
|
|
317
329
|
writable: true,
|
|
318
|
-
value:
|
|
330
|
+
value: Array.from({ length: 128 }, () => [])
|
|
319
331
|
});
|
|
320
332
|
Object.defineProperty(this, "voiceCounter", {
|
|
321
333
|
enumerable: true,
|
|
@@ -359,6 +371,12 @@ class MidyGM2 {
|
|
|
359
371
|
writable: true,
|
|
360
372
|
value: false
|
|
361
373
|
});
|
|
374
|
+
Object.defineProperty(this, "playPromise", {
|
|
375
|
+
enumerable: true,
|
|
376
|
+
configurable: true,
|
|
377
|
+
writable: true,
|
|
378
|
+
value: void 0
|
|
379
|
+
});
|
|
362
380
|
Object.defineProperty(this, "timeline", {
|
|
363
381
|
enumerable: true,
|
|
364
382
|
configurable: true,
|
|
@@ -396,8 +414,10 @@ class MidyGM2 {
|
|
|
396
414
|
length: 1,
|
|
397
415
|
sampleRate: audioContext.sampleRate,
|
|
398
416
|
});
|
|
417
|
+
this.messageHandlers = this.createMessageHandlers();
|
|
399
418
|
this.voiceParamsHandlers = this.createVoiceParamsHandlers();
|
|
400
419
|
this.controlChangeHandlers = this.createControlChangeHandlers();
|
|
420
|
+
this.keyBasedControllerHandlers = this.createKeyBasedControllerHandlers();
|
|
401
421
|
this.channels = this.createChannels(audioContext);
|
|
402
422
|
this.reverbEffect = this.createReverbEffect(audioContext);
|
|
403
423
|
this.chorusEffect = this.createChorusEffect(audioContext);
|
|
@@ -407,21 +427,14 @@ class MidyGM2 {
|
|
|
407
427
|
this.scheduler.connect(audioContext.destination);
|
|
408
428
|
this.GM2SystemOn();
|
|
409
429
|
}
|
|
410
|
-
initSoundFontTable() {
|
|
411
|
-
const table = new Array(128);
|
|
412
|
-
for (let i = 0; i < 128; i++) {
|
|
413
|
-
table[i] = new Map();
|
|
414
|
-
}
|
|
415
|
-
return table;
|
|
416
|
-
}
|
|
417
430
|
addSoundFont(soundFont) {
|
|
418
431
|
const index = this.soundFonts.length;
|
|
419
432
|
this.soundFonts.push(soundFont);
|
|
420
433
|
const presetHeaders = soundFont.parsed.presetHeaders;
|
|
434
|
+
const soundFontTable = this.soundFontTable;
|
|
421
435
|
for (let i = 0; i < presetHeaders.length; i++) {
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
banks.set(presetHeader.bank, index);
|
|
436
|
+
const { preset, bank } = presetHeaders[i];
|
|
437
|
+
soundFontTable[preset][bank] = index;
|
|
425
438
|
}
|
|
426
439
|
}
|
|
427
440
|
async toUint8Array(input) {
|
|
@@ -499,13 +512,17 @@ class MidyGM2 {
|
|
|
499
512
|
this.GM2SystemOn();
|
|
500
513
|
}
|
|
501
514
|
getVoiceId(channel, noteNumber, velocity) {
|
|
502
|
-
const
|
|
503
|
-
const
|
|
504
|
-
|
|
515
|
+
const programNumber = channel.programNumber;
|
|
516
|
+
const bankTable = this.soundFontTable[programNumber];
|
|
517
|
+
if (!bankTable)
|
|
518
|
+
return;
|
|
519
|
+
const bankLSB = channel.isDrum ? 128 : channel.bankLSB;
|
|
520
|
+
const bank = bankTable[bankLSB] !== undefined ? bankLSB : 0;
|
|
521
|
+
const soundFontIndex = bankTable[bank];
|
|
505
522
|
if (soundFontIndex === undefined)
|
|
506
523
|
return;
|
|
507
524
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
508
|
-
const voice = soundFont.getVoice(
|
|
525
|
+
const voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
|
|
509
526
|
const { instrument, sampleID } = voice.generators;
|
|
510
527
|
return soundFontIndex * (2 ** 32) + (instrument << 16) + sampleID;
|
|
511
528
|
}
|
|
@@ -527,7 +544,7 @@ class MidyGM2 {
|
|
|
527
544
|
channel.controlTable.fill(-1);
|
|
528
545
|
channel.scaleOctaveTuningTable.fill(0); // [-100, 100] cent
|
|
529
546
|
channel.channelPressureTable.fill(-1);
|
|
530
|
-
channel.
|
|
547
|
+
channel.keyBasedTable.fill(-1);
|
|
531
548
|
}
|
|
532
549
|
createChannels(audioContext) {
|
|
533
550
|
const channels = Array.from({ length: this.numChannels }, () => {
|
|
@@ -543,7 +560,7 @@ class MidyGM2 {
|
|
|
543
560
|
controlTable: this.initControlTable(),
|
|
544
561
|
scaleOctaveTuningTable: new Int8Array(12), // [-64, 63] cent
|
|
545
562
|
channelPressureTable: new Int8Array(6).fill(-1),
|
|
546
|
-
|
|
563
|
+
keyBasedTable: new Int8Array(128 * 128).fill(-1),
|
|
547
564
|
keyBasedGainLs: new Array(128),
|
|
548
565
|
keyBasedGainRs: new Array(128),
|
|
549
566
|
};
|
|
@@ -574,13 +591,16 @@ class MidyGM2 {
|
|
|
574
591
|
}
|
|
575
592
|
return bufferSource;
|
|
576
593
|
}
|
|
577
|
-
async scheduleTimelineEvents(
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
594
|
+
async scheduleTimelineEvents(scheduleTime, queueIndex) {
|
|
595
|
+
const timeOffset = this.resumeTime - this.startTime;
|
|
596
|
+
const lookAheadCheckTime = scheduleTime + timeOffset + this.lookAhead;
|
|
597
|
+
const schedulingOffset = this.startDelay - timeOffset;
|
|
598
|
+
const timeline = this.timeline;
|
|
599
|
+
while (queueIndex < timeline.length) {
|
|
600
|
+
const event = timeline[queueIndex];
|
|
601
|
+
if (lookAheadCheckTime < event.startTime)
|
|
581
602
|
break;
|
|
582
|
-
const
|
|
583
|
-
const startTime = event.startTime + delay;
|
|
603
|
+
const startTime = event.startTime + schedulingOffset;
|
|
584
604
|
switch (event.type) {
|
|
585
605
|
case "noteOn":
|
|
586
606
|
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
|
|
@@ -618,72 +638,85 @@ class MidyGM2 {
|
|
|
618
638
|
}
|
|
619
639
|
return 0;
|
|
620
640
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
641
|
+
resetAllStates() {
|
|
642
|
+
this.exclusiveClassNotes.fill(undefined);
|
|
643
|
+
this.drumExclusiveClassNotes.fill(undefined);
|
|
644
|
+
this.voiceCache.clear();
|
|
645
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
646
|
+
this.channels[i].scheduledNotes = [];
|
|
647
|
+
this.resetChannelStates(i);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
updateStates(queueIndex, nextQueueIndex) {
|
|
651
|
+
if (nextQueueIndex < queueIndex)
|
|
652
|
+
queueIndex = 0;
|
|
653
|
+
for (let i = queueIndex; i < nextQueueIndex; i++) {
|
|
654
|
+
const event = this.timeline[i];
|
|
655
|
+
switch (event.type) {
|
|
656
|
+
case "controller":
|
|
657
|
+
this.setControlChange(event.channel, event.controllerType, event.value, 0);
|
|
658
|
+
break;
|
|
659
|
+
case "programChange":
|
|
660
|
+
this.setProgramChange(event.channel, event.programNumber, 0);
|
|
661
|
+
break;
|
|
662
|
+
case "pitchBend":
|
|
663
|
+
this.setPitchBend(event.channel, event.value + 8192, 0);
|
|
664
|
+
break;
|
|
665
|
+
case "sysEx":
|
|
666
|
+
this.handleSysEx(event.data, 0);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
async playNotes() {
|
|
671
|
+
if (this.audioContext.state === "suspended") {
|
|
672
|
+
await this.audioContext.resume();
|
|
673
|
+
}
|
|
674
|
+
this.isPlaying = true;
|
|
675
|
+
this.isPaused = false;
|
|
676
|
+
this.startTime = this.audioContext.currentTime;
|
|
677
|
+
let queueIndex = this.getQueueIndex(this.resumeTime);
|
|
678
|
+
let finished = false;
|
|
679
|
+
this.notePromises = [];
|
|
680
|
+
while (queueIndex < this.timeline.length) {
|
|
681
|
+
const now = this.audioContext.currentTime;
|
|
682
|
+
if (0 < this.lastActiveSensing &&
|
|
683
|
+
this.activeSensingThreshold < performance.now() - this.lastActiveSensing) {
|
|
684
|
+
await this.stopNotes(0, true, now);
|
|
685
|
+
await this.audioContext.suspend();
|
|
686
|
+
finished = true;
|
|
687
|
+
break;
|
|
688
|
+
}
|
|
689
|
+
queueIndex = await this.scheduleTimelineEvents(now, queueIndex);
|
|
690
|
+
if (this.isPausing) {
|
|
691
|
+
await this.stopNotes(0, true, now);
|
|
692
|
+
await this.audioContext.suspend();
|
|
693
|
+
this.notePromises = [];
|
|
694
|
+
break;
|
|
695
|
+
}
|
|
696
|
+
else if (this.isStopping) {
|
|
697
|
+
await this.stopNotes(0, true, now);
|
|
698
|
+
await this.audioContext.suspend();
|
|
699
|
+
finished = true;
|
|
700
|
+
break;
|
|
701
|
+
}
|
|
702
|
+
else if (this.isSeeking) {
|
|
703
|
+
await this.stopNotes(0, true, now);
|
|
704
|
+
this.startTime = this.audioContext.currentTime;
|
|
705
|
+
const nextQueueIndex = this.getQueueIndex(this.resumeTime);
|
|
706
|
+
this.updateStates(queueIndex, nextQueueIndex);
|
|
707
|
+
queueIndex = nextQueueIndex;
|
|
708
|
+
this.isSeeking = false;
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
711
|
+
const waitTime = now + this.noteCheckInterval;
|
|
712
|
+
await this.scheduleTask(() => { }, waitTime);
|
|
713
|
+
}
|
|
714
|
+
if (finished) {
|
|
628
715
|
this.notePromises = [];
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
this.exclusiveClassNotes.fill(undefined);
|
|
634
|
-
this.drumExclusiveClassNotes.fill(undefined);
|
|
635
|
-
this.voiceCache.clear();
|
|
636
|
-
for (let i = 0; i < this.channels.length; i++) {
|
|
637
|
-
this.channels[i].scheduledNotes = [];
|
|
638
|
-
this.resetAllStates(i);
|
|
639
|
-
}
|
|
640
|
-
resolve();
|
|
641
|
-
return;
|
|
642
|
-
}
|
|
643
|
-
const now = this.audioContext.currentTime;
|
|
644
|
-
const t = now + resumeTime;
|
|
645
|
-
queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
|
|
646
|
-
if (this.isPausing) {
|
|
647
|
-
await this.stopNotes(0, true, now);
|
|
648
|
-
this.notePromises = [];
|
|
649
|
-
this.isPausing = false;
|
|
650
|
-
this.isPaused = true;
|
|
651
|
-
resolve();
|
|
652
|
-
return;
|
|
653
|
-
}
|
|
654
|
-
else if (this.isStopping) {
|
|
655
|
-
await this.stopNotes(0, true, now);
|
|
656
|
-
this.notePromises = [];
|
|
657
|
-
this.exclusiveClassNotes.fill(undefined);
|
|
658
|
-
this.drumExclusiveClassNotes.fill(undefined);
|
|
659
|
-
this.voiceCache.clear();
|
|
660
|
-
for (let i = 0; i < this.channels.length; i++) {
|
|
661
|
-
this.channels[i].scheduledNotes = [];
|
|
662
|
-
this.resetAllStates(i);
|
|
663
|
-
}
|
|
664
|
-
this.isStopping = false;
|
|
665
|
-
this.isPaused = false;
|
|
666
|
-
resolve();
|
|
667
|
-
return;
|
|
668
|
-
}
|
|
669
|
-
else if (this.isSeeking) {
|
|
670
|
-
this.stopNotes(0, true, now);
|
|
671
|
-
this.exclusiveClassNotes.fill(undefined);
|
|
672
|
-
this.drumExclusiveClassNotes.fill(undefined);
|
|
673
|
-
this.startTime = this.audioContext.currentTime;
|
|
674
|
-
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
675
|
-
resumeTime = this.resumeTime - this.startTime;
|
|
676
|
-
this.isSeeking = false;
|
|
677
|
-
await schedulePlayback();
|
|
678
|
-
}
|
|
679
|
-
else {
|
|
680
|
-
const waitTime = now + this.noteCheckInterval;
|
|
681
|
-
await this.scheduleTask(() => { }, waitTime);
|
|
682
|
-
await schedulePlayback();
|
|
683
|
-
}
|
|
684
|
-
};
|
|
685
|
-
schedulePlayback();
|
|
686
|
-
});
|
|
716
|
+
this.resetAllStates();
|
|
717
|
+
this.lastActiveSensing = 0;
|
|
718
|
+
}
|
|
719
|
+
this.isPlaying = false;
|
|
687
720
|
}
|
|
688
721
|
ticksToSecond(ticks, secondsPerBeat) {
|
|
689
722
|
return ticks * secondsPerBeat / this.ticksPerBeat;
|
|
@@ -691,17 +724,17 @@ class MidyGM2 {
|
|
|
691
724
|
secondToTicks(second, secondsPerBeat) {
|
|
692
725
|
return second * this.ticksPerBeat / secondsPerBeat;
|
|
693
726
|
}
|
|
727
|
+
getSoundFontId(channel) {
|
|
728
|
+
const programNumber = channel.programNumber;
|
|
729
|
+
const bankNumber = channel.isDrum ? 128 : channel.bankLSB;
|
|
730
|
+
const bank = bankNumber.toString().padStart(3, "0");
|
|
731
|
+
const program = programNumber.toString().padStart(3, "0");
|
|
732
|
+
return `${bank}:${program}`;
|
|
733
|
+
}
|
|
694
734
|
extractMidiData(midi) {
|
|
695
735
|
const instruments = new Set();
|
|
696
736
|
const timeline = [];
|
|
697
|
-
const
|
|
698
|
-
for (let i = 0; i < tmpChannels.length; i++) {
|
|
699
|
-
tmpChannels[i] = {
|
|
700
|
-
programNumber: -1,
|
|
701
|
-
bankMSB: this.channels[i].bankMSB,
|
|
702
|
-
bankLSB: this.channels[i].bankLSB,
|
|
703
|
-
};
|
|
704
|
-
}
|
|
737
|
+
const channels = this.channels;
|
|
705
738
|
for (let i = 0; i < midi.tracks.length; i++) {
|
|
706
739
|
const track = midi.tracks[i];
|
|
707
740
|
let currentTicks = 0;
|
|
@@ -711,48 +744,40 @@ class MidyGM2 {
|
|
|
711
744
|
event.ticks = currentTicks;
|
|
712
745
|
switch (event.type) {
|
|
713
746
|
case "noteOn": {
|
|
714
|
-
const channel =
|
|
715
|
-
|
|
716
|
-
channel.programNumber = event.programNumber;
|
|
717
|
-
switch (channel.bankMSB) {
|
|
718
|
-
case 120:
|
|
719
|
-
instruments.add(`128:0`);
|
|
720
|
-
break;
|
|
721
|
-
case 121:
|
|
722
|
-
instruments.add(`${channel.bankLSB}:0`);
|
|
723
|
-
break;
|
|
724
|
-
default: {
|
|
725
|
-
const bankNumber = channel.bankMSB * 128 + channel.bankLSB;
|
|
726
|
-
instruments.add(`${bankNumber}:0`);
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
channel.programNumber = 0;
|
|
730
|
-
}
|
|
747
|
+
const channel = channels[event.channel];
|
|
748
|
+
instruments.add(this.getSoundFontId(channel));
|
|
731
749
|
break;
|
|
732
750
|
}
|
|
733
751
|
case "controller":
|
|
734
752
|
switch (event.controllerType) {
|
|
735
753
|
case 0:
|
|
736
|
-
|
|
754
|
+
this.setBankMSB(event.channel, event.value);
|
|
737
755
|
break;
|
|
738
756
|
case 32:
|
|
739
|
-
|
|
757
|
+
this.setBankLSB(event.channel, event.value);
|
|
740
758
|
break;
|
|
741
759
|
}
|
|
742
760
|
break;
|
|
743
761
|
case "programChange": {
|
|
744
|
-
const channel =
|
|
745
|
-
channel
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
762
|
+
const channel = channels[event.channel];
|
|
763
|
+
this.setProgramChange(event.channel, event.programNumber);
|
|
764
|
+
instruments.add(this.getSoundFontId(channel));
|
|
765
|
+
break;
|
|
766
|
+
}
|
|
767
|
+
case "sysEx": {
|
|
768
|
+
const data = event.data;
|
|
769
|
+
if (data[0] === 126 && data[1] === 9 && data[2] === 3) {
|
|
770
|
+
switch (data[3]) {
|
|
771
|
+
case 1:
|
|
772
|
+
this.GM1SystemOn(scheduleTime);
|
|
773
|
+
break;
|
|
774
|
+
case 2: // GM System Off
|
|
775
|
+
break;
|
|
776
|
+
case 3:
|
|
777
|
+
this.GM2SystemOn(scheduleTime);
|
|
778
|
+
break;
|
|
779
|
+
default:
|
|
780
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
756
781
|
}
|
|
757
782
|
}
|
|
758
783
|
}
|
|
@@ -820,26 +845,32 @@ class MidyGM2 {
|
|
|
820
845
|
this.resumeTime = 0;
|
|
821
846
|
if (this.voiceCounter.size === 0)
|
|
822
847
|
this.cacheVoiceIds();
|
|
823
|
-
|
|
824
|
-
this.
|
|
848
|
+
this.playPromise = this.playNotes();
|
|
849
|
+
await this.playPromise;
|
|
825
850
|
}
|
|
826
|
-
stop() {
|
|
851
|
+
async stop() {
|
|
827
852
|
if (!this.isPlaying)
|
|
828
853
|
return;
|
|
829
854
|
this.isStopping = true;
|
|
855
|
+
await this.playPromise;
|
|
856
|
+
this.isStopping = false;
|
|
830
857
|
}
|
|
831
|
-
pause() {
|
|
858
|
+
async pause() {
|
|
832
859
|
if (!this.isPlaying || this.isPaused)
|
|
833
860
|
return;
|
|
834
861
|
const now = this.audioContext.currentTime;
|
|
835
862
|
this.resumeTime += now - this.startTime - this.startDelay;
|
|
836
863
|
this.isPausing = true;
|
|
864
|
+
await this.playPromise;
|
|
865
|
+
this.isPausing = false;
|
|
866
|
+
this.isPaused = true;
|
|
837
867
|
}
|
|
838
868
|
async resume() {
|
|
839
869
|
if (!this.isPaused)
|
|
840
870
|
return;
|
|
841
|
-
|
|
842
|
-
this.
|
|
871
|
+
this.playPromise = this.playNotes();
|
|
872
|
+
await this.playPromise;
|
|
873
|
+
this.isPaused = false;
|
|
843
874
|
}
|
|
844
875
|
seekTo(second) {
|
|
845
876
|
this.resumeTime = second;
|
|
@@ -1069,7 +1100,7 @@ class MidyGM2 {
|
|
|
1069
1100
|
updateDetune(channel, note, scheduleTime) {
|
|
1070
1101
|
const noteDetune = this.calcNoteDetune(channel, note);
|
|
1071
1102
|
const detune = channel.detune + noteDetune;
|
|
1072
|
-
if (
|
|
1103
|
+
if (this.isPortamento(channel, note)) {
|
|
1073
1104
|
const startTime = note.startTime;
|
|
1074
1105
|
const deltaCent = (note.noteNumber - note.portamentoNoteNumber) * 100;
|
|
1075
1106
|
const portamentoTime = startTime + this.getPortamentoTime(channel, note);
|
|
@@ -1274,10 +1305,12 @@ class MidyGM2 {
|
|
|
1274
1305
|
startVibrato(channel, note, scheduleTime) {
|
|
1275
1306
|
const { voiceParams } = note;
|
|
1276
1307
|
const state = channel.state;
|
|
1308
|
+
const vibratoRate = state.vibratoRate * 2;
|
|
1309
|
+
const vibratoDelay = state.vibratoDelay * 2;
|
|
1277
1310
|
note.vibratoLFO = new OscillatorNode(this.audioContext, {
|
|
1278
|
-
frequency: this.centToHz(voiceParams.freqVibLFO) *
|
|
1311
|
+
frequency: this.centToHz(voiceParams.freqVibLFO) * vibratoRate,
|
|
1279
1312
|
});
|
|
1280
|
-
note.vibratoLFO.start(note.startTime + voiceParams.delayVibLFO *
|
|
1313
|
+
note.vibratoLFO.start(note.startTime + voiceParams.delayVibLFO * vibratoDelay);
|
|
1281
1314
|
note.vibratoDepth = new GainNode(this.audioContext);
|
|
1282
1315
|
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
1283
1316
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
@@ -1318,7 +1351,7 @@ class MidyGM2 {
|
|
|
1318
1351
|
if (prevNote && prevNote.noteNumber !== noteNumber) {
|
|
1319
1352
|
note.portamentoNoteNumber = prevNote.noteNumber;
|
|
1320
1353
|
}
|
|
1321
|
-
if (
|
|
1354
|
+
if (!channel.isDrum && this.isPortamento(channel, note)) {
|
|
1322
1355
|
this.setPortamentoVolumeEnvelope(channel, note, now);
|
|
1323
1356
|
this.setPortamentoFilterEnvelope(channel, note, now);
|
|
1324
1357
|
this.setPortamentoPitchEnvelope(note, now);
|
|
@@ -1346,22 +1379,6 @@ class MidyGM2 {
|
|
|
1346
1379
|
note.bufferSource.start(startTime);
|
|
1347
1380
|
return note;
|
|
1348
1381
|
}
|
|
1349
|
-
calcBank(channel) {
|
|
1350
|
-
switch (this.mode) {
|
|
1351
|
-
case "GM1":
|
|
1352
|
-
if (channel.isDrum)
|
|
1353
|
-
return 128;
|
|
1354
|
-
return 0;
|
|
1355
|
-
case "GM2":
|
|
1356
|
-
if (channel.bankMSB === 121)
|
|
1357
|
-
return 0;
|
|
1358
|
-
if (channel.isDrum)
|
|
1359
|
-
return 128;
|
|
1360
|
-
return channel.bank;
|
|
1361
|
-
default:
|
|
1362
|
-
return channel.bank;
|
|
1363
|
-
}
|
|
1364
|
-
}
|
|
1365
1382
|
handleExclusiveClass(note, channelNumber, startTime) {
|
|
1366
1383
|
const exclusiveClass = note.voiceParams.exclusiveClass;
|
|
1367
1384
|
if (exclusiveClass === 0)
|
|
@@ -1397,13 +1414,17 @@ class MidyGM2 {
|
|
|
1397
1414
|
}
|
|
1398
1415
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
1399
1416
|
const channel = this.channels[channelNumber];
|
|
1400
|
-
const
|
|
1401
|
-
const
|
|
1402
|
-
|
|
1417
|
+
const programNumber = channel.programNumber;
|
|
1418
|
+
const bankTable = this.soundFontTable[programNumber];
|
|
1419
|
+
if (!bankTable)
|
|
1420
|
+
return;
|
|
1421
|
+
const bankLSB = channel.isDrum ? 128 : channel.bankLSB;
|
|
1422
|
+
const bank = bankTable[bankLSB] !== undefined ? bankLSB : 0;
|
|
1423
|
+
const soundFontIndex = bankTable[bank];
|
|
1403
1424
|
if (soundFontIndex === undefined)
|
|
1404
1425
|
return;
|
|
1405
1426
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
1406
|
-
const voice = soundFont.getVoice(
|
|
1427
|
+
const voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
|
|
1407
1428
|
if (!voice)
|
|
1408
1429
|
return;
|
|
1409
1430
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
|
|
@@ -1555,34 +1576,39 @@ class MidyGM2 {
|
|
|
1555
1576
|
channel.sostenutoNotes = [];
|
|
1556
1577
|
return promises;
|
|
1557
1578
|
}
|
|
1558
|
-
|
|
1559
|
-
const
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
1579
|
+
createMessageHandlers() {
|
|
1580
|
+
const handlers = new Array(256);
|
|
1581
|
+
// Channel Message
|
|
1582
|
+
handlers[0x80] = (data, scheduleTime) => this.noteOff(data[0] & 0x0F, data[1], data[2], scheduleTime);
|
|
1583
|
+
handlers[0x90] = (data, scheduleTime) => this.noteOn(data[0] & 0x0F, data[1], data[2], scheduleTime);
|
|
1584
|
+
handlers[0xB0] = (data, scheduleTime) => this.setControlChange(data[0] & 0x0F, data[1], data[2], scheduleTime);
|
|
1585
|
+
handlers[0xC0] = (data, scheduleTime) => this.setProgramChange(data[0] & 0x0F, data[1], scheduleTime);
|
|
1586
|
+
handlers[0xD0] = (data, scheduleTime) => this.setChannelPressure(data[0] & 0x0F, data[1], scheduleTime);
|
|
1587
|
+
handlers[0xE0] = (data, scheduleTime) => this.handlePitchBendMessage(data[0] & 0x0F, data[1], data[2], scheduleTime);
|
|
1588
|
+
// System Real Time Message
|
|
1589
|
+
handlers[0xFE] = (_data, _scheduleTime) => this.activeSensing();
|
|
1590
|
+
return handlers;
|
|
1591
|
+
}
|
|
1592
|
+
handleMessage(data, scheduleTime) {
|
|
1593
|
+
const status = data[0];
|
|
1594
|
+
if (status === 0xF0) {
|
|
1595
|
+
return this.handleSysEx(data.subarray(1), scheduleTime);
|
|
1576
1596
|
}
|
|
1597
|
+
const handler = this.messageHandlers[status];
|
|
1598
|
+
if (handler)
|
|
1599
|
+
handler(data, scheduleTime);
|
|
1600
|
+
}
|
|
1601
|
+
activeSensing() {
|
|
1602
|
+
this.lastActiveSensing = performance.now();
|
|
1577
1603
|
}
|
|
1578
1604
|
setProgramChange(channelNumber, programNumber, _scheduleTime) {
|
|
1579
1605
|
const channel = this.channels[channelNumber];
|
|
1580
|
-
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1581
1606
|
channel.programNumber = programNumber;
|
|
1582
1607
|
if (this.mode === "GM2") {
|
|
1583
1608
|
switch (channel.bankMSB) {
|
|
1584
1609
|
case 120:
|
|
1585
1610
|
channel.isDrum = true;
|
|
1611
|
+
channel.keyBasedTable.fill(-1);
|
|
1586
1612
|
break;
|
|
1587
1613
|
case 121:
|
|
1588
1614
|
channel.isDrum = false;
|
|
@@ -1604,7 +1630,7 @@ class MidyGM2 {
|
|
|
1604
1630
|
}
|
|
1605
1631
|
const table = channel.channelPressureTable;
|
|
1606
1632
|
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
1607
|
-
this.setEffects(channel, note, table);
|
|
1633
|
+
this.setEffects(channel, note, table, scheduleTime);
|
|
1608
1634
|
});
|
|
1609
1635
|
this.applyVoiceParams(channel, 13);
|
|
1610
1636
|
}
|
|
@@ -1630,23 +1656,28 @@ class MidyGM2 {
|
|
|
1630
1656
|
const modLfoToPitch = note.voiceParams.modLfoToPitch +
|
|
1631
1657
|
this.getLFOPitchDepth(channel, note);
|
|
1632
1658
|
const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
|
|
1633
|
-
const
|
|
1659
|
+
const depth = baseDepth * Math.sign(modLfoToPitch);
|
|
1634
1660
|
note.modulationDepth.gain
|
|
1635
1661
|
.cancelScheduledValues(scheduleTime)
|
|
1636
|
-
.setValueAtTime(
|
|
1662
|
+
.setValueAtTime(depth, scheduleTime);
|
|
1637
1663
|
}
|
|
1638
1664
|
else {
|
|
1639
1665
|
this.startModulation(channel, note, scheduleTime);
|
|
1640
1666
|
}
|
|
1641
1667
|
}
|
|
1642
1668
|
setVibLfoToPitch(channel, note, scheduleTime) {
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
.
|
|
1649
|
-
|
|
1669
|
+
if (note.vibratoDepth) {
|
|
1670
|
+
const vibratoDepth = channel.state.vibratoDepth * 2;
|
|
1671
|
+
const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
|
|
1672
|
+
const baseDepth = Math.abs(vibLfoToPitch) * vibratoDepth;
|
|
1673
|
+
const depth = baseDepth * Math.sign(vibLfoToPitch);
|
|
1674
|
+
note.vibratoDepth.gain
|
|
1675
|
+
.cancelScheduledValues(scheduleTime)
|
|
1676
|
+
.setValueAtTime(depth, scheduleTime);
|
|
1677
|
+
}
|
|
1678
|
+
else {
|
|
1679
|
+
this.startVibrato(channel, note, scheduleTime);
|
|
1680
|
+
}
|
|
1650
1681
|
}
|
|
1651
1682
|
setModLfoToFilterFc(channel, note, scheduleTime) {
|
|
1652
1683
|
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc +
|
|
@@ -1724,13 +1755,12 @@ class MidyGM2 {
|
|
|
1724
1755
|
}
|
|
1725
1756
|
}
|
|
1726
1757
|
}
|
|
1727
|
-
setDelayModLFO(note
|
|
1728
|
-
const startTime = note.startTime;
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
note.modulationLFO.connect(note.filterDepth);
|
|
1758
|
+
setDelayModLFO(note) {
|
|
1759
|
+
const startTime = note.startTime + note.voiceParams.delayModLFO;
|
|
1760
|
+
try {
|
|
1761
|
+
note.modulationLFO.start(startTime);
|
|
1762
|
+
}
|
|
1763
|
+
catch { /* empty */ }
|
|
1734
1764
|
}
|
|
1735
1765
|
setFreqModLFO(note, scheduleTime) {
|
|
1736
1766
|
const freqModLFO = note.voiceParams.freqModLFO;
|
|
@@ -1739,54 +1769,65 @@ class MidyGM2 {
|
|
|
1739
1769
|
.setValueAtTime(freqModLFO, scheduleTime);
|
|
1740
1770
|
}
|
|
1741
1771
|
setFreqVibLFO(channel, note, scheduleTime) {
|
|
1772
|
+
const vibratoRate = channel.state.vibratoRate * 2;
|
|
1742
1773
|
const freqVibLFO = note.voiceParams.freqVibLFO;
|
|
1743
1774
|
note.vibratoLFO.frequency
|
|
1744
1775
|
.cancelScheduledValues(scheduleTime)
|
|
1745
|
-
.setValueAtTime(freqVibLFO *
|
|
1776
|
+
.setValueAtTime(freqVibLFO * vibratoRate, scheduleTime);
|
|
1777
|
+
}
|
|
1778
|
+
setDelayVibLFO(channel, note) {
|
|
1779
|
+
const vibratoDelay = channel.state.vibratoDelay * 2;
|
|
1780
|
+
const value = note.voiceParams.delayVibLFO;
|
|
1781
|
+
const startTime = note.startTime + value * vibratoDelay;
|
|
1782
|
+
try {
|
|
1783
|
+
note.vibratoLFO.start(startTime);
|
|
1784
|
+
}
|
|
1785
|
+
catch { /* empty */ }
|
|
1746
1786
|
}
|
|
1747
1787
|
createVoiceParamsHandlers() {
|
|
1748
1788
|
return {
|
|
1749
|
-
modLfoToPitch: (channel, note,
|
|
1789
|
+
modLfoToPitch: (channel, note, scheduleTime) => {
|
|
1750
1790
|
if (0 < channel.state.modulationDepth) {
|
|
1751
1791
|
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
1752
1792
|
}
|
|
1753
1793
|
},
|
|
1754
|
-
vibLfoToPitch: (channel, note,
|
|
1794
|
+
vibLfoToPitch: (channel, note, scheduleTime) => {
|
|
1755
1795
|
if (0 < channel.state.vibratoDepth) {
|
|
1756
1796
|
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
1757
1797
|
}
|
|
1758
1798
|
},
|
|
1759
|
-
modLfoToFilterFc: (channel, note,
|
|
1799
|
+
modLfoToFilterFc: (channel, note, scheduleTime) => {
|
|
1760
1800
|
if (0 < channel.state.modulationDepth) {
|
|
1761
1801
|
this.setModLfoToFilterFc(channel, note, scheduleTime);
|
|
1762
1802
|
}
|
|
1763
1803
|
},
|
|
1764
|
-
modLfoToVolume: (channel, note,
|
|
1804
|
+
modLfoToVolume: (channel, note, scheduleTime) => {
|
|
1765
1805
|
if (0 < channel.state.modulationDepth) {
|
|
1766
1806
|
this.setModLfoToVolume(channel, note, scheduleTime);
|
|
1767
1807
|
}
|
|
1768
1808
|
},
|
|
1769
|
-
chorusEffectsSend: (channel, note,
|
|
1770
|
-
this.
|
|
1809
|
+
chorusEffectsSend: (channel, note, scheduleTime) => {
|
|
1810
|
+
this.setChorusSend(channel, note, scheduleTime);
|
|
1771
1811
|
},
|
|
1772
|
-
reverbEffectsSend: (channel, note,
|
|
1773
|
-
this.
|
|
1812
|
+
reverbEffectsSend: (channel, note, scheduleTime) => {
|
|
1813
|
+
this.setReverbSend(channel, note, scheduleTime);
|
|
1774
1814
|
},
|
|
1775
|
-
delayModLFO: (_channel, note,
|
|
1776
|
-
|
|
1777
|
-
|
|
1815
|
+
delayModLFO: (_channel, note, _scheduleTime) => {
|
|
1816
|
+
if (0 < channel.state.modulationDepth) {
|
|
1817
|
+
this.setDelayModLFO(note);
|
|
1818
|
+
}
|
|
1819
|
+
},
|
|
1820
|
+
freqModLFO: (_channel, note, scheduleTime) => {
|
|
1821
|
+
if (0 < channel.state.modulationDepth) {
|
|
1822
|
+
this.setFreqModLFO(note, scheduleTime);
|
|
1823
|
+
}
|
|
1824
|
+
},
|
|
1825
|
+
delayVibLFO: (channel, note, _scheduleTime) => {
|
|
1778
1826
|
if (0 < channel.state.vibratoDepth) {
|
|
1779
|
-
|
|
1780
|
-
const prevStartTime = note.startTime + prevValue * vibratoDelay;
|
|
1781
|
-
if (scheduleTime < prevStartTime)
|
|
1782
|
-
return;
|
|
1783
|
-
const value = note.voiceParams.delayVibLFO;
|
|
1784
|
-
const startTime = note.startTime + value * vibratoDelay;
|
|
1785
|
-
note.vibratoLFO.stop(scheduleTime);
|
|
1786
|
-
note.vibratoLFO.start(startTime);
|
|
1827
|
+
setDelayVibLFO(channel, note);
|
|
1787
1828
|
}
|
|
1788
1829
|
},
|
|
1789
|
-
freqVibLFO: (channel, note,
|
|
1830
|
+
freqVibLFO: (channel, note, scheduleTime) => {
|
|
1790
1831
|
if (0 < channel.state.vibratoDepth) {
|
|
1791
1832
|
this.setFreqVibLFO(channel, note, scheduleTime);
|
|
1792
1833
|
}
|
|
@@ -1814,7 +1855,7 @@ class MidyGM2 {
|
|
|
1814
1855
|
continue;
|
|
1815
1856
|
note.voiceParams[key] = value;
|
|
1816
1857
|
if (key in this.voiceParamsHandlers) {
|
|
1817
|
-
this.voiceParamsHandlers[key](channel, note,
|
|
1858
|
+
this.voiceParamsHandlers[key](channel, note, scheduleTime);
|
|
1818
1859
|
}
|
|
1819
1860
|
else {
|
|
1820
1861
|
if (volumeEnvelopeKeySet.has(key))
|
|
@@ -1898,22 +1939,20 @@ class MidyGM2 {
|
|
|
1898
1939
|
this.updateModulation(channel, scheduleTime);
|
|
1899
1940
|
}
|
|
1900
1941
|
updatePortamento(channel, scheduleTime) {
|
|
1942
|
+
if (channel.isDrum)
|
|
1943
|
+
return;
|
|
1901
1944
|
this.processScheduledNotes(channel, (note) => {
|
|
1902
|
-
if (
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
this.updateDetune(channel, note, scheduleTime);
|
|
1908
|
-
}
|
|
1945
|
+
if (this.isPortamento(channel, note)) {
|
|
1946
|
+
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
1947
|
+
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
1948
|
+
this.setPortamentoPitchEnvelope(note, scheduleTime);
|
|
1949
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
1909
1950
|
}
|
|
1910
1951
|
else {
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
this.updateDetune(channel, note, scheduleTime);
|
|
1916
|
-
}
|
|
1952
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1953
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
1954
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1955
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
1917
1956
|
}
|
|
1918
1957
|
});
|
|
1919
1958
|
}
|
|
@@ -1983,13 +2022,13 @@ class MidyGM2 {
|
|
|
1983
2022
|
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1984
2023
|
}
|
|
1985
2024
|
updateKeyBasedVolume(channel, keyNumber, scheduleTime) {
|
|
1986
|
-
const state = channel.state;
|
|
1987
|
-
const defaultVolume = state.volume * state.expression;
|
|
1988
|
-
const defaultPan = state.pan;
|
|
1989
2025
|
const gainL = channel.keyBasedGainLs[keyNumber];
|
|
1990
|
-
const gainR = channel.keyBasedGainRs[keyNumber];
|
|
1991
2026
|
if (!gainL)
|
|
1992
2027
|
return;
|
|
2028
|
+
const gainR = channel.keyBasedGainRs[keyNumber];
|
|
2029
|
+
const state = channel.state;
|
|
2030
|
+
const defaultVolume = state.volume * state.expression;
|
|
2031
|
+
const defaultPan = state.pan;
|
|
1993
2032
|
const keyBasedVolume = this.getKeyBasedValue(channel, keyNumber, 7);
|
|
1994
2033
|
const volume = (0 <= keyBasedVolume)
|
|
1995
2034
|
? defaultVolume * keyBasedVolume / 64
|
|
@@ -2019,6 +2058,9 @@ class MidyGM2 {
|
|
|
2019
2058
|
this.releaseSustainPedal(channelNumber, value, scheduleTime);
|
|
2020
2059
|
}
|
|
2021
2060
|
}
|
|
2061
|
+
isPortamento(channel, note) {
|
|
2062
|
+
return 0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber;
|
|
2063
|
+
}
|
|
2022
2064
|
setPortamento(channelNumber, value, scheduleTime) {
|
|
2023
2065
|
const channel = this.channels[channelNumber];
|
|
2024
2066
|
if (channel.isDrum)
|
|
@@ -2055,7 +2097,7 @@ class MidyGM2 {
|
|
|
2055
2097
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2056
2098
|
state.softPedal = softPedal / 127;
|
|
2057
2099
|
this.processScheduledNotes(channel, (note) => {
|
|
2058
|
-
if (
|
|
2100
|
+
if (this.isPortamento(channel, note)) {
|
|
2059
2101
|
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2060
2102
|
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2061
2103
|
}
|
|
@@ -2141,8 +2183,8 @@ class MidyGM2 {
|
|
|
2141
2183
|
}
|
|
2142
2184
|
handlePitchBendRangeRPN(channelNumber, scheduleTime) {
|
|
2143
2185
|
const channel = this.channels[channelNumber];
|
|
2144
|
-
this.limitData(channel, 0, 127, 0,
|
|
2145
|
-
const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
|
|
2186
|
+
this.limitData(channel, 0, 127, 0, 127);
|
|
2187
|
+
const pitchBendRange = (channel.dataMSB + channel.dataLSB / 128) * 100;
|
|
2146
2188
|
this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
|
|
2147
2189
|
}
|
|
2148
2190
|
setPitchBendRange(channelNumber, value, scheduleTime) {
|
|
@@ -2152,7 +2194,7 @@ class MidyGM2 {
|
|
|
2152
2194
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2153
2195
|
const state = channel.state;
|
|
2154
2196
|
const prev = state.pitchWheelSensitivity;
|
|
2155
|
-
const next = value /
|
|
2197
|
+
const next = value / 12800;
|
|
2156
2198
|
state.pitchWheelSensitivity = next;
|
|
2157
2199
|
channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
|
|
2158
2200
|
this.updateChannelDetune(channel, scheduleTime);
|
|
@@ -2161,7 +2203,8 @@ class MidyGM2 {
|
|
|
2161
2203
|
handleFineTuningRPN(channelNumber, scheduleTime) {
|
|
2162
2204
|
const channel = this.channels[channelNumber];
|
|
2163
2205
|
this.limitData(channel, 0, 127, 0, 127);
|
|
2164
|
-
const
|
|
2206
|
+
const value = channel.dataMSB * 128 + channel.dataLSB;
|
|
2207
|
+
const fineTuning = (value - 8192) / 8192 * 100;
|
|
2165
2208
|
this.setFineTuning(channelNumber, fineTuning, scheduleTime);
|
|
2166
2209
|
}
|
|
2167
2210
|
setFineTuning(channelNumber, value, scheduleTime) {
|
|
@@ -2170,7 +2213,7 @@ class MidyGM2 {
|
|
|
2170
2213
|
return;
|
|
2171
2214
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2172
2215
|
const prev = channel.fineTuning;
|
|
2173
|
-
const next =
|
|
2216
|
+
const next = value;
|
|
2174
2217
|
channel.fineTuning = next;
|
|
2175
2218
|
channel.detune += next - prev;
|
|
2176
2219
|
this.updateChannelDetune(channel, scheduleTime);
|
|
@@ -2178,7 +2221,7 @@ class MidyGM2 {
|
|
|
2178
2221
|
handleCoarseTuningRPN(channelNumber, scheduleTime) {
|
|
2179
2222
|
const channel = this.channels[channelNumber];
|
|
2180
2223
|
this.limitDataMSB(channel, 0, 127);
|
|
2181
|
-
const coarseTuning = channel.dataMSB;
|
|
2224
|
+
const coarseTuning = (channel.dataMSB - 64) * 100;
|
|
2182
2225
|
this.setCoarseTuning(channelNumber, coarseTuning, scheduleTime);
|
|
2183
2226
|
}
|
|
2184
2227
|
setCoarseTuning(channelNumber, value, scheduleTime) {
|
|
@@ -2187,7 +2230,7 @@ class MidyGM2 {
|
|
|
2187
2230
|
return;
|
|
2188
2231
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2189
2232
|
const prev = channel.coarseTuning;
|
|
2190
|
-
const next =
|
|
2233
|
+
const next = value;
|
|
2191
2234
|
channel.coarseTuning = next;
|
|
2192
2235
|
channel.detune += next - prev;
|
|
2193
2236
|
this.updateChannelDetune(channel, scheduleTime);
|
|
@@ -2195,22 +2238,22 @@ class MidyGM2 {
|
|
|
2195
2238
|
handleModulationDepthRangeRPN(channelNumber, scheduleTime) {
|
|
2196
2239
|
const channel = this.channels[channelNumber];
|
|
2197
2240
|
this.limitData(channel, 0, 127, 0, 127);
|
|
2198
|
-
const
|
|
2199
|
-
this.setModulationDepthRange(channelNumber,
|
|
2241
|
+
const value = (channel.dataMSB + channel.dataLSB / 128) * 100;
|
|
2242
|
+
this.setModulationDepthRange(channelNumber, value, scheduleTime);
|
|
2200
2243
|
}
|
|
2201
|
-
setModulationDepthRange(channelNumber,
|
|
2244
|
+
setModulationDepthRange(channelNumber, value, scheduleTime) {
|
|
2202
2245
|
const channel = this.channels[channelNumber];
|
|
2203
2246
|
if (channel.isDrum)
|
|
2204
2247
|
return;
|
|
2205
2248
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2206
|
-
channel.modulationDepthRange =
|
|
2249
|
+
channel.modulationDepthRange = value;
|
|
2207
2250
|
this.updateModulation(channel, scheduleTime);
|
|
2208
2251
|
}
|
|
2209
2252
|
allSoundOff(channelNumber, _value, scheduleTime) {
|
|
2210
2253
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2211
2254
|
return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
|
|
2212
2255
|
}
|
|
2213
|
-
|
|
2256
|
+
resetChannelStates(channelNumber) {
|
|
2214
2257
|
const scheduleTime = this.audioContext.currentTime;
|
|
2215
2258
|
const channel = this.channels[channelNumber];
|
|
2216
2259
|
const state = channel.state;
|
|
@@ -2228,8 +2271,8 @@ class MidyGM2 {
|
|
|
2228
2271
|
}
|
|
2229
2272
|
this.resetChannelTable(channel);
|
|
2230
2273
|
this.mode = "GM2";
|
|
2231
|
-
this.masterFineTuning = 0; //
|
|
2232
|
-
this.masterCoarseTuning = 0; //
|
|
2274
|
+
this.masterFineTuning = 0; // cent
|
|
2275
|
+
this.masterCoarseTuning = 0; // cent
|
|
2233
2276
|
}
|
|
2234
2277
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
|
|
2235
2278
|
resetAllControllers(channelNumber, _value, scheduleTime) {
|
|
@@ -2322,11 +2365,9 @@ class MidyGM2 {
|
|
|
2322
2365
|
const channel = this.channels[i];
|
|
2323
2366
|
channel.bankMSB = 0;
|
|
2324
2367
|
channel.bankLSB = 0;
|
|
2325
|
-
channel.bank = 0;
|
|
2326
2368
|
channel.isDrum = false;
|
|
2327
2369
|
}
|
|
2328
2370
|
this.channels[9].bankMSB = 1;
|
|
2329
|
-
this.channels[9].bank = 128;
|
|
2330
2371
|
this.channels[9].isDrum = true;
|
|
2331
2372
|
}
|
|
2332
2373
|
GM2SystemOn(scheduleTime) {
|
|
@@ -2337,11 +2378,9 @@ class MidyGM2 {
|
|
|
2337
2378
|
const channel = this.channels[i];
|
|
2338
2379
|
channel.bankMSB = 121;
|
|
2339
2380
|
channel.bankLSB = 0;
|
|
2340
|
-
channel.bank = 121 * 128;
|
|
2341
2381
|
channel.isDrum = false;
|
|
2342
2382
|
}
|
|
2343
2383
|
this.channels[9].bankMSB = 120;
|
|
2344
|
-
this.channels[9].bank = 120 * 128;
|
|
2345
2384
|
this.channels[9].isDrum = true;
|
|
2346
2385
|
}
|
|
2347
2386
|
handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
|
|
@@ -2386,24 +2425,20 @@ class MidyGM2 {
|
|
|
2386
2425
|
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
2387
2426
|
this.setMasterVolume(volume, scheduleTime);
|
|
2388
2427
|
}
|
|
2389
|
-
setMasterVolume(
|
|
2428
|
+
setMasterVolume(value, scheduleTime) {
|
|
2390
2429
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
else {
|
|
2395
|
-
this.masterVolume.gain
|
|
2396
|
-
.cancelScheduledValues(scheduleTime)
|
|
2397
|
-
.setValueAtTime(volume * volume, scheduleTime);
|
|
2398
|
-
}
|
|
2430
|
+
this.masterVolume.gain
|
|
2431
|
+
.cancelScheduledValues(scheduleTime)
|
|
2432
|
+
.setValueAtTime(value * value, scheduleTime);
|
|
2399
2433
|
}
|
|
2400
2434
|
handleMasterFineTuningSysEx(data, scheduleTime) {
|
|
2401
|
-
const
|
|
2435
|
+
const value = (data[5] * 128 + data[4]) / 16383;
|
|
2436
|
+
const fineTuning = (value - 8192) / 8192 * 100;
|
|
2402
2437
|
this.setMasterFineTuning(fineTuning, scheduleTime);
|
|
2403
2438
|
}
|
|
2404
2439
|
setMasterFineTuning(value, scheduleTime) {
|
|
2405
2440
|
const prev = this.masterFineTuning;
|
|
2406
|
-
const next =
|
|
2441
|
+
const next = value;
|
|
2407
2442
|
this.masterFineTuning = next;
|
|
2408
2443
|
const detuneChange = next - prev;
|
|
2409
2444
|
for (let i = 0; i < this.channels.length; i++) {
|
|
@@ -2415,12 +2450,12 @@ class MidyGM2 {
|
|
|
2415
2450
|
}
|
|
2416
2451
|
}
|
|
2417
2452
|
handleMasterCoarseTuningSysEx(data, scheduleTime) {
|
|
2418
|
-
const coarseTuning = data[4];
|
|
2453
|
+
const coarseTuning = (data[4] - 64) * 100;
|
|
2419
2454
|
this.setMasterCoarseTuning(coarseTuning, scheduleTime);
|
|
2420
2455
|
}
|
|
2421
2456
|
setMasterCoarseTuning(value, scheduleTime) {
|
|
2422
2457
|
const prev = this.masterCoarseTuning;
|
|
2423
|
-
const next =
|
|
2458
|
+
const next = value;
|
|
2424
2459
|
this.masterCoarseTuning = next;
|
|
2425
2460
|
const detuneChange = next - prev;
|
|
2426
2461
|
for (let i = 0; i < this.channels.length; i++) {
|
|
@@ -2754,29 +2789,40 @@ class MidyGM2 {
|
|
|
2754
2789
|
}
|
|
2755
2790
|
getKeyBasedValue(channel, keyNumber, controllerType) {
|
|
2756
2791
|
const index = keyNumber * 128 + controllerType;
|
|
2757
|
-
const controlValue = channel.
|
|
2792
|
+
const controlValue = channel.keyBasedTable[index];
|
|
2758
2793
|
return controlValue;
|
|
2759
2794
|
}
|
|
2795
|
+
createKeyBasedControllerHandlers() {
|
|
2796
|
+
const handlers = new Array(128);
|
|
2797
|
+
handlers[7] = (channel, keyNumber, scheduleTime) => this.updateKeyBasedVolume(channel, keyNumber, scheduleTime);
|
|
2798
|
+
handlers[10] = (channel, keyNumber, scheduleTime) => this.updateKeyBasedVolume(channel, keyNumber, scheduleTime);
|
|
2799
|
+
handlers[91] = (channel, keyNumber, scheduleTime) => this.processScheduledNotes(channel, (note) => {
|
|
2800
|
+
if (note.noteNumber === keyNumber) {
|
|
2801
|
+
this.setReverbSend(channel, note, scheduleTime);
|
|
2802
|
+
}
|
|
2803
|
+
});
|
|
2804
|
+
handlers[93] = (channel, keyNumber, scheduleTime) => this.processScheduledNotes(channel, (note) => {
|
|
2805
|
+
if (note.noteNumber === keyNumber) {
|
|
2806
|
+
this.setChorusSend(channel, note, scheduleTime);
|
|
2807
|
+
}
|
|
2808
|
+
});
|
|
2809
|
+
return handlers;
|
|
2810
|
+
}
|
|
2760
2811
|
handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
|
|
2761
2812
|
const channelNumber = data[4];
|
|
2762
2813
|
const channel = this.channels[channelNumber];
|
|
2763
2814
|
if (!channel.isDrum)
|
|
2764
2815
|
return;
|
|
2765
2816
|
const keyNumber = data[5];
|
|
2766
|
-
const table = channel.
|
|
2817
|
+
const table = channel.keyBasedTable;
|
|
2767
2818
|
for (let i = 6; i < data.length; i += 2) {
|
|
2768
2819
|
const controllerType = data[i];
|
|
2769
2820
|
const value = data[i + 1];
|
|
2770
2821
|
const index = keyNumber * 128 + controllerType;
|
|
2771
2822
|
table[index] = value;
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
this.updateKeyBasedVolume(channel, keyNumber, scheduleTime);
|
|
2776
|
-
break;
|
|
2777
|
-
default: // TODO
|
|
2778
|
-
this.setControlChange(channelNumber, controllerType, value, scheduleTime);
|
|
2779
|
-
}
|
|
2823
|
+
const handler = this.keyBasedControllerHandlers[controllerType];
|
|
2824
|
+
if (handler)
|
|
2825
|
+
handler(channel, keyNumber, scheduleTime);
|
|
2780
2826
|
}
|
|
2781
2827
|
}
|
|
2782
2828
|
handleSysEx(data, scheduleTime) {
|
|
@@ -2818,7 +2864,6 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
2818
2864
|
scheduleIndex: 0,
|
|
2819
2865
|
detune: 0,
|
|
2820
2866
|
programNumber: 0,
|
|
2821
|
-
bank: 121 * 128,
|
|
2822
2867
|
bankMSB: 121,
|
|
2823
2868
|
bankLSB: 0,
|
|
2824
2869
|
dataMSB: 0,
|
|
@@ -2827,7 +2872,7 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
2827
2872
|
rpnLSB: 127,
|
|
2828
2873
|
mono: false, // CC#124, CC#125
|
|
2829
2874
|
modulationDepthRange: 50, // cent
|
|
2830
|
-
fineTuning: 0, //
|
|
2831
|
-
coarseTuning: 0, //
|
|
2875
|
+
fineTuning: 0, // cent
|
|
2876
|
+
coarseTuning: 0, // cent
|
|
2832
2877
|
}
|
|
2833
2878
|
});
|