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