@marmooo/midy 0.3.4 → 0.3.6
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 +14 -11
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +140 -106
- package/esm/midy-GM2.d.ts +26 -22
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +285 -279
- package/esm/midy-GMLite.d.ts +15 -11
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +139 -107
- package/esm/midy.d.ts +31 -27
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +304 -298
- package/package.json +2 -2
- package/script/midy-GM1.d.ts +14 -11
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +140 -106
- package/script/midy-GM2.d.ts +26 -22
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +285 -279
- package/script/midy-GMLite.d.ts +15 -11
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +139 -107
- package/script/midy.d.ts +31 -27
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +304 -298
package/esm/midy-GM2.js
CHANGED
|
@@ -68,13 +68,13 @@ class Note {
|
|
|
68
68
|
writable: true,
|
|
69
69
|
value: void 0
|
|
70
70
|
});
|
|
71
|
-
Object.defineProperty(this, "
|
|
71
|
+
Object.defineProperty(this, "reverbSend", {
|
|
72
72
|
enumerable: true,
|
|
73
73
|
configurable: true,
|
|
74
74
|
writable: true,
|
|
75
75
|
value: void 0
|
|
76
76
|
});
|
|
77
|
-
Object.defineProperty(this, "
|
|
77
|
+
Object.defineProperty(this, "chorusSend", {
|
|
78
78
|
enumerable: true,
|
|
79
79
|
configurable: true,
|
|
80
80
|
writable: true,
|
|
@@ -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,
|
|
@@ -362,17 +362,17 @@ export class MidyGM2 {
|
|
|
362
362
|
writable: true,
|
|
363
363
|
value: []
|
|
364
364
|
});
|
|
365
|
-
Object.defineProperty(this, "
|
|
365
|
+
Object.defineProperty(this, "notePromises", {
|
|
366
366
|
enumerable: true,
|
|
367
367
|
configurable: true,
|
|
368
368
|
writable: true,
|
|
369
369
|
value: []
|
|
370
370
|
});
|
|
371
|
-
Object.defineProperty(this, "
|
|
371
|
+
Object.defineProperty(this, "instruments", {
|
|
372
372
|
enumerable: true,
|
|
373
373
|
configurable: true,
|
|
374
374
|
writable: true,
|
|
375
|
-
value:
|
|
375
|
+
value: new Set()
|
|
376
376
|
});
|
|
377
377
|
Object.defineProperty(this, "exclusiveClassNotes", {
|
|
378
378
|
enumerable: 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 * (2 ** 32) + (instrument << 16) + 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;
|
|
@@ -889,13 +904,11 @@ export class MidyGM2 {
|
|
|
889
904
|
return impulse;
|
|
890
905
|
}
|
|
891
906
|
createConvolutionReverb(audioContext, impulse) {
|
|
892
|
-
const input = new GainNode(audioContext);
|
|
893
907
|
const convolverNode = new ConvolverNode(audioContext, {
|
|
894
908
|
buffer: impulse,
|
|
895
909
|
});
|
|
896
|
-
input.connect(convolverNode);
|
|
897
910
|
return {
|
|
898
|
-
input,
|
|
911
|
+
input: convolverNode,
|
|
899
912
|
output: convolverNode,
|
|
900
913
|
convolverNode,
|
|
901
914
|
};
|
|
@@ -1046,7 +1059,7 @@ export class MidyGM2 {
|
|
|
1046
1059
|
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
1047
1060
|
}
|
|
1048
1061
|
updateChannelDetune(channel, scheduleTime) {
|
|
1049
|
-
this.processScheduledNotes(channel,
|
|
1062
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1050
1063
|
this.updateDetune(channel, note, scheduleTime);
|
|
1051
1064
|
});
|
|
1052
1065
|
}
|
|
@@ -1267,31 +1280,31 @@ export class MidyGM2 {
|
|
|
1267
1280
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
1268
1281
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
1269
1282
|
}
|
|
1270
|
-
async getAudioBuffer(
|
|
1271
|
-
const audioBufferId = this.
|
|
1272
|
-
const cache = this.
|
|
1283
|
+
async getAudioBuffer(channel, noteNumber, velocity, voiceParams) {
|
|
1284
|
+
const audioBufferId = this.getVoiceId(channel, noteNumber, velocity);
|
|
1285
|
+
const cache = this.voiceCache.get(audioBufferId);
|
|
1273
1286
|
if (cache) {
|
|
1274
1287
|
cache.counter += 1;
|
|
1275
1288
|
if (cache.maxCount <= cache.counter) {
|
|
1276
|
-
this.
|
|
1289
|
+
this.voiceCache.delete(audioBufferId);
|
|
1277
1290
|
}
|
|
1278
1291
|
return cache.audioBuffer;
|
|
1279
1292
|
}
|
|
1280
1293
|
else {
|
|
1281
|
-
const maxCount = this.
|
|
1282
|
-
const audioBuffer = await this.
|
|
1294
|
+
const maxCount = this.voiceCounter.get(audioBufferId) ?? 0;
|
|
1295
|
+
const audioBuffer = await this.createAudioBuffer(voiceParams);
|
|
1283
1296
|
const cache = { audioBuffer, maxCount, counter: 1 };
|
|
1284
|
-
this.
|
|
1297
|
+
this.voiceCache.set(audioBufferId, cache);
|
|
1285
1298
|
return audioBuffer;
|
|
1286
1299
|
}
|
|
1287
1300
|
}
|
|
1288
|
-
async createNote(channel, voice, noteNumber, velocity, startTime
|
|
1301
|
+
async createNote(channel, voice, noteNumber, velocity, startTime) {
|
|
1289
1302
|
const now = this.audioContext.currentTime;
|
|
1290
1303
|
const state = channel.state;
|
|
1291
1304
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1292
1305
|
const voiceParams = voice.getAllParams(controllerState);
|
|
1293
1306
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1294
|
-
const audioBuffer = await this.getAudioBuffer(channel
|
|
1307
|
+
const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams);
|
|
1295
1308
|
note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
|
|
1296
1309
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
1297
1310
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
@@ -1325,12 +1338,8 @@ export class MidyGM2 {
|
|
|
1325
1338
|
}
|
|
1326
1339
|
note.bufferSource.connect(note.filterNode);
|
|
1327
1340
|
note.filterNode.connect(note.volumeEnvelopeNode);
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
}
|
|
1331
|
-
if (0 < state.reverbSendLevel) {
|
|
1332
|
-
this.setReverbEffectsSend(channel, note, 0, now);
|
|
1333
|
-
}
|
|
1341
|
+
this.setChorusSend(channel, note, now);
|
|
1342
|
+
this.setReverbSend(channel, note, now);
|
|
1334
1343
|
note.bufferSource.start(startTime);
|
|
1335
1344
|
return note;
|
|
1336
1345
|
}
|
|
@@ -1386,20 +1395,24 @@ export class MidyGM2 {
|
|
|
1386
1395
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
1387
1396
|
const channel = this.channels[channelNumber];
|
|
1388
1397
|
const bankNumber = this.calcBank(channel, channelNumber);
|
|
1389
|
-
const soundFontIndex = this.soundFontTable[channel.programNumber]
|
|
1398
|
+
const soundFontIndex = this.soundFontTable[channel.programNumber]
|
|
1399
|
+
.get(bankNumber);
|
|
1390
1400
|
if (soundFontIndex === undefined)
|
|
1391
1401
|
return;
|
|
1392
1402
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
1393
1403
|
const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
|
|
1394
1404
|
if (!voice)
|
|
1395
1405
|
return;
|
|
1396
|
-
const
|
|
1397
|
-
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
1406
|
+
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
|
|
1398
1407
|
if (channel.isDrum) {
|
|
1399
|
-
const
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1408
|
+
const { keyBasedGainLs, keyBasedGainRs } = channel;
|
|
1409
|
+
let gainL = keyBasedGainLs[noteNumber];
|
|
1410
|
+
let gainR = keyBasedGainRs[noteNumber];
|
|
1411
|
+
if (!gainL) {
|
|
1412
|
+
const audioNodes = this.createChannelAudioNodes(this.audioContext);
|
|
1413
|
+
gainL = keyBasedGainLs[noteNumber] = audioNodes.gainL;
|
|
1414
|
+
gainR = keyBasedGainRs[noteNumber] = audioNodes.gainR;
|
|
1415
|
+
}
|
|
1403
1416
|
note.volumeEnvelopeNode.connect(gainL);
|
|
1404
1417
|
note.volumeEnvelopeNode.connect(gainR);
|
|
1405
1418
|
}
|
|
@@ -1433,11 +1446,11 @@ export class MidyGM2 {
|
|
|
1433
1446
|
note.vibratoDepth.disconnect();
|
|
1434
1447
|
note.vibratoLFO.stop();
|
|
1435
1448
|
}
|
|
1436
|
-
if (note.
|
|
1437
|
-
note.
|
|
1449
|
+
if (note.reverbSend) {
|
|
1450
|
+
note.reverbSend.disconnect();
|
|
1438
1451
|
}
|
|
1439
|
-
if (note.
|
|
1440
|
-
note.
|
|
1452
|
+
if (note.chorusSend) {
|
|
1453
|
+
note.chorusSend.disconnect();
|
|
1441
1454
|
}
|
|
1442
1455
|
}
|
|
1443
1456
|
releaseNote(channel, note, endTime) {
|
|
@@ -1476,15 +1489,29 @@ export class MidyGM2 {
|
|
|
1476
1489
|
return;
|
|
1477
1490
|
}
|
|
1478
1491
|
}
|
|
1479
|
-
const
|
|
1480
|
-
if (
|
|
1492
|
+
const index = this.findNoteOffIndex(channel, noteNumber);
|
|
1493
|
+
if (index < 0)
|
|
1481
1494
|
return;
|
|
1495
|
+
const note = channel.scheduledNotes[index];
|
|
1482
1496
|
note.ending = true;
|
|
1497
|
+
this.setNoteIndex(channel, index);
|
|
1483
1498
|
this.releaseNote(channel, note, endTime);
|
|
1484
1499
|
}
|
|
1485
|
-
|
|
1500
|
+
setNoteIndex(channel, index) {
|
|
1501
|
+
let allEnds = true;
|
|
1502
|
+
for (let i = channel.scheduleIndex; i < index; i++) {
|
|
1503
|
+
const note = channel.scheduledNotes[i];
|
|
1504
|
+
if (note && !note.ending) {
|
|
1505
|
+
allEnds = false;
|
|
1506
|
+
break;
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
if (allEnds)
|
|
1510
|
+
channel.scheduleIndex = index + 1;
|
|
1511
|
+
}
|
|
1512
|
+
findNoteOffIndex(channel, noteNumber) {
|
|
1486
1513
|
const scheduledNotes = channel.scheduledNotes;
|
|
1487
|
-
for (let i =
|
|
1514
|
+
for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
|
|
1488
1515
|
const note = scheduledNotes[i];
|
|
1489
1516
|
if (!note)
|
|
1490
1517
|
continue;
|
|
@@ -1492,8 +1519,9 @@ export class MidyGM2 {
|
|
|
1492
1519
|
continue;
|
|
1493
1520
|
if (note.noteNumber !== noteNumber)
|
|
1494
1521
|
continue;
|
|
1495
|
-
return
|
|
1522
|
+
return i;
|
|
1496
1523
|
}
|
|
1524
|
+
return -1;
|
|
1497
1525
|
}
|
|
1498
1526
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1499
1527
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -1533,18 +1561,18 @@ export class MidyGM2 {
|
|
|
1533
1561
|
case 0x90:
|
|
1534
1562
|
return this.noteOn(channelNumber, data1, data2, scheduleTime);
|
|
1535
1563
|
case 0xB0:
|
|
1536
|
-
return this.
|
|
1564
|
+
return this.setControlChange(channelNumber, data1, data2, scheduleTime);
|
|
1537
1565
|
case 0xC0:
|
|
1538
|
-
return this.
|
|
1566
|
+
return this.setProgramChange(channelNumber, data1, scheduleTime);
|
|
1539
1567
|
case 0xD0:
|
|
1540
|
-
return this.
|
|
1568
|
+
return this.setChannelPressure(channelNumber, data1, scheduleTime);
|
|
1541
1569
|
case 0xE0:
|
|
1542
1570
|
return this.handlePitchBendMessage(channelNumber, data1, data2, scheduleTime);
|
|
1543
1571
|
default:
|
|
1544
1572
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
1545
1573
|
}
|
|
1546
1574
|
}
|
|
1547
|
-
|
|
1575
|
+
setProgramChange(channelNumber, programNumber, _scheduleTime) {
|
|
1548
1576
|
const channel = this.channels[channelNumber];
|
|
1549
1577
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1550
1578
|
channel.programNumber = programNumber;
|
|
@@ -1559,7 +1587,7 @@ export class MidyGM2 {
|
|
|
1559
1587
|
}
|
|
1560
1588
|
}
|
|
1561
1589
|
}
|
|
1562
|
-
|
|
1590
|
+
setChannelPressure(channelNumber, value, scheduleTime) {
|
|
1563
1591
|
const channel = this.channels[channelNumber];
|
|
1564
1592
|
if (channel.isDrum)
|
|
1565
1593
|
return;
|
|
@@ -1573,7 +1601,7 @@ export class MidyGM2 {
|
|
|
1573
1601
|
}
|
|
1574
1602
|
const table = channel.channelPressureTable;
|
|
1575
1603
|
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
1576
|
-
this.
|
|
1604
|
+
this.setEffects(channel, note, table);
|
|
1577
1605
|
});
|
|
1578
1606
|
this.applyVoiceParams(channel, 13);
|
|
1579
1607
|
}
|
|
@@ -1595,13 +1623,18 @@ export class MidyGM2 {
|
|
|
1595
1623
|
this.applyVoiceParams(channel, 14, scheduleTime);
|
|
1596
1624
|
}
|
|
1597
1625
|
setModLfoToPitch(channel, note, scheduleTime) {
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
.
|
|
1604
|
-
|
|
1626
|
+
if (note.modulationDepth) {
|
|
1627
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch +
|
|
1628
|
+
this.getLFOPitchDepth(channel, note);
|
|
1629
|
+
const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
|
|
1630
|
+
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
1631
|
+
note.modulationDepth.gain
|
|
1632
|
+
.cancelScheduledValues(scheduleTime)
|
|
1633
|
+
.setValueAtTime(modulationDepth, scheduleTime);
|
|
1634
|
+
}
|
|
1635
|
+
else {
|
|
1636
|
+
this.startModulation(channel, note, scheduleTime);
|
|
1637
|
+
}
|
|
1605
1638
|
}
|
|
1606
1639
|
setVibLfoToPitch(channel, note, scheduleTime) {
|
|
1607
1640
|
const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
|
|
@@ -1628,63 +1661,63 @@ export class MidyGM2 {
|
|
|
1628
1661
|
.cancelScheduledValues(scheduleTime)
|
|
1629
1662
|
.setValueAtTime(volumeDepth, scheduleTime);
|
|
1630
1663
|
}
|
|
1631
|
-
|
|
1632
|
-
let value = note.voiceParams.reverbEffectsSend
|
|
1664
|
+
setReverbSend(channel, note, scheduleTime) {
|
|
1665
|
+
let value = note.voiceParams.reverbEffectsSend *
|
|
1666
|
+
channel.state.reverbSendLevel;
|
|
1633
1667
|
if (channel.isDrum) {
|
|
1634
|
-
const keyBasedValue = this.
|
|
1635
|
-
if (0 <= keyBasedValue)
|
|
1636
|
-
value
|
|
1637
|
-
}
|
|
1668
|
+
const keyBasedValue = this.getKeyBasedValue(channel, note.noteNumber, 91);
|
|
1669
|
+
if (0 <= keyBasedValue)
|
|
1670
|
+
value = keyBasedValue / 127;
|
|
1638
1671
|
}
|
|
1639
|
-
if (
|
|
1672
|
+
if (!note.reverbSend) {
|
|
1640
1673
|
if (0 < value) {
|
|
1641
|
-
note.
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
}
|
|
1645
|
-
else {
|
|
1646
|
-
note.reverbEffectsSend.disconnect();
|
|
1674
|
+
note.reverbSend = new GainNode(this.audioContext, { gain: value });
|
|
1675
|
+
note.volumeEnvelopeNode.connect(note.reverbSend);
|
|
1676
|
+
note.reverbSend.connect(this.reverbEffect.input);
|
|
1647
1677
|
}
|
|
1648
1678
|
}
|
|
1649
1679
|
else {
|
|
1680
|
+
note.reverbSend.gain
|
|
1681
|
+
.cancelScheduledValues(scheduleTime)
|
|
1682
|
+
.setValueAtTime(value, scheduleTime);
|
|
1650
1683
|
if (0 < value) {
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
note.
|
|
1684
|
+
note.volumeEnvelopeNode.connect(note.reverbSend);
|
|
1685
|
+
}
|
|
1686
|
+
else {
|
|
1687
|
+
try {
|
|
1688
|
+
note.volumeEnvelopeNode.disconnect(note.reverbSend);
|
|
1656
1689
|
}
|
|
1657
|
-
|
|
1690
|
+
catch { /* empty */ }
|
|
1658
1691
|
}
|
|
1659
1692
|
}
|
|
1660
1693
|
}
|
|
1661
|
-
|
|
1662
|
-
let value = note.voiceParams.chorusEffectsSend
|
|
1694
|
+
setChorusSend(channel, note, scheduleTime) {
|
|
1695
|
+
let value = note.voiceParams.chorusEffectsSend *
|
|
1696
|
+
channel.state.chorusSendLevel;
|
|
1663
1697
|
if (channel.isDrum) {
|
|
1664
|
-
const keyBasedValue = this.
|
|
1665
|
-
if (0 <= keyBasedValue)
|
|
1666
|
-
value
|
|
1667
|
-
}
|
|
1698
|
+
const keyBasedValue = this.getKeyBasedValue(channel, note.noteNumber, 93);
|
|
1699
|
+
if (0 <= keyBasedValue)
|
|
1700
|
+
value = keyBasedValue / 127;
|
|
1668
1701
|
}
|
|
1669
|
-
if (
|
|
1670
|
-
if (0 <
|
|
1671
|
-
note.
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
}
|
|
1675
|
-
else {
|
|
1676
|
-
note.chorusEffectsSend.disconnect();
|
|
1702
|
+
if (!note.chorusSend) {
|
|
1703
|
+
if (0 < value) {
|
|
1704
|
+
note.chorusSend = new GainNode(this.audioContext, { gain: value });
|
|
1705
|
+
note.volumeEnvelopeNode.connect(note.chorusSend);
|
|
1706
|
+
note.chorusSend.connect(this.chorusEffect.input);
|
|
1677
1707
|
}
|
|
1678
1708
|
}
|
|
1679
1709
|
else {
|
|
1710
|
+
note.chorusSend.gain
|
|
1711
|
+
.cancelScheduledValues(scheduleTime)
|
|
1712
|
+
.setValueAtTime(value, scheduleTime);
|
|
1680
1713
|
if (0 < value) {
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
note.volumeEnvelopeNode.
|
|
1714
|
+
note.volumeEnvelopeNode.connect(note.chorusSend);
|
|
1715
|
+
}
|
|
1716
|
+
else {
|
|
1717
|
+
try {
|
|
1718
|
+
note.volumeEnvelopeNode.disconnect(note.chorusSend);
|
|
1686
1719
|
}
|
|
1687
|
-
|
|
1720
|
+
catch { /* empty */ }
|
|
1688
1721
|
}
|
|
1689
1722
|
}
|
|
1690
1723
|
}
|
|
@@ -1766,7 +1799,7 @@ export class MidyGM2 {
|
|
|
1766
1799
|
return state;
|
|
1767
1800
|
}
|
|
1768
1801
|
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
1769
|
-
this.processScheduledNotes(channel,
|
|
1802
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1770
1803
|
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
|
|
1771
1804
|
const voiceParams = note.voice.getParams(controllerType, controllerState);
|
|
1772
1805
|
let applyVolumeEnvelope = false;
|
|
@@ -1827,13 +1860,13 @@ export class MidyGM2 {
|
|
|
1827
1860
|
handlers[127] = this.polyOn;
|
|
1828
1861
|
return handlers;
|
|
1829
1862
|
}
|
|
1830
|
-
|
|
1863
|
+
setControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1831
1864
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1832
1865
|
if (handler) {
|
|
1833
1866
|
handler.call(this, channelNumber, value, scheduleTime);
|
|
1834
1867
|
const channel = this.channels[channelNumber];
|
|
1835
1868
|
this.applyVoiceParams(channel, controllerType + 128, scheduleTime);
|
|
1836
|
-
this.
|
|
1869
|
+
this.setControlChangeEffects(channel, controllerType, scheduleTime);
|
|
1837
1870
|
}
|
|
1838
1871
|
else {
|
|
1839
1872
|
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
@@ -1844,12 +1877,11 @@ export class MidyGM2 {
|
|
|
1844
1877
|
}
|
|
1845
1878
|
updateModulation(channel, scheduleTime) {
|
|
1846
1879
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1847
|
-
this.processScheduledNotes(channel,
|
|
1880
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1848
1881
|
if (note.modulationDepth) {
|
|
1849
1882
|
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1850
1883
|
}
|
|
1851
1884
|
else {
|
|
1852
|
-
this.setPitchEnvelope(note, scheduleTime);
|
|
1853
1885
|
this.startModulation(channel, note, scheduleTime);
|
|
1854
1886
|
}
|
|
1855
1887
|
});
|
|
@@ -1863,7 +1895,7 @@ export class MidyGM2 {
|
|
|
1863
1895
|
this.updateModulation(channel, scheduleTime);
|
|
1864
1896
|
}
|
|
1865
1897
|
updatePortamento(channel, scheduleTime) {
|
|
1866
|
-
this.processScheduledNotes(channel,
|
|
1898
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1867
1899
|
if (0.5 <= channel.state.portamento) {
|
|
1868
1900
|
if (0 <= note.portamentoNoteNumber) {
|
|
1869
1901
|
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
@@ -1894,8 +1926,14 @@ export class MidyGM2 {
|
|
|
1894
1926
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1895
1927
|
const channel = this.channels[channelNumber];
|
|
1896
1928
|
channel.state.volume = volume / 127;
|
|
1897
|
-
|
|
1898
|
-
|
|
1929
|
+
if (channel.isDrum) {
|
|
1930
|
+
for (let i = 0; i < 128; i++) {
|
|
1931
|
+
this.updateKeyBasedVolume(channel, i, scheduleTime);
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
else {
|
|
1935
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1936
|
+
}
|
|
1899
1937
|
}
|
|
1900
1938
|
panToGain(pan) {
|
|
1901
1939
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1908,8 +1946,14 @@ export class MidyGM2 {
|
|
|
1908
1946
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1909
1947
|
const channel = this.channels[channelNumber];
|
|
1910
1948
|
channel.state.pan = pan / 127;
|
|
1911
|
-
|
|
1912
|
-
|
|
1949
|
+
if (channel.isDrum) {
|
|
1950
|
+
for (let i = 0; i < 128; i++) {
|
|
1951
|
+
this.updateKeyBasedVolume(channel, i, scheduleTime);
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
else {
|
|
1955
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1956
|
+
}
|
|
1913
1957
|
}
|
|
1914
1958
|
setExpression(channelNumber, expression, scheduleTime) {
|
|
1915
1959
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -1935,33 +1979,27 @@ export class MidyGM2 {
|
|
|
1935
1979
|
.cancelScheduledValues(scheduleTime)
|
|
1936
1980
|
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1937
1981
|
}
|
|
1938
|
-
updateKeyBasedVolume(channel, scheduleTime) {
|
|
1939
|
-
if (!channel.isDrum)
|
|
1940
|
-
return;
|
|
1982
|
+
updateKeyBasedVolume(channel, keyNumber, scheduleTime) {
|
|
1941
1983
|
const state = channel.state;
|
|
1942
1984
|
const defaultVolume = state.volume * state.expression;
|
|
1943
1985
|
const defaultPan = state.pan;
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
gainR.gain
|
|
1962
|
-
.cancelScheduledValues(scheduleTime)
|
|
1963
|
-
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1964
|
-
}
|
|
1986
|
+
const gainL = channel.keyBasedGainLs[keyNumber];
|
|
1987
|
+
const gainR = channel.keyBasedGainRs[keyNumber];
|
|
1988
|
+
if (!gainL)
|
|
1989
|
+
return;
|
|
1990
|
+
const keyBasedVolume = this.getKeyBasedValue(channel, keyNumber, 7);
|
|
1991
|
+
const volume = (0 <= keyBasedVolume)
|
|
1992
|
+
? defaultVolume * keyBasedVolume / 64
|
|
1993
|
+
: defaultVolume;
|
|
1994
|
+
const keyBasedPan = this.getKeyBasedValue(channel, keyNumber, 10);
|
|
1995
|
+
const pan = (0 <= keyBasedPan) ? keyBasedPan / 127 : defaultPan;
|
|
1996
|
+
const { gainLeft, gainRight } = this.panToGain(pan);
|
|
1997
|
+
gainL.gain
|
|
1998
|
+
.cancelScheduledValues(scheduleTime)
|
|
1999
|
+
.setValueAtTime(volume * gainLeft, scheduleTime);
|
|
2000
|
+
gainR.gain
|
|
2001
|
+
.cancelScheduledValues(scheduleTime)
|
|
2002
|
+
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1965
2003
|
}
|
|
1966
2004
|
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
1967
2005
|
const channel = this.channels[channelNumber];
|
|
@@ -1970,7 +2008,7 @@ export class MidyGM2 {
|
|
|
1970
2008
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1971
2009
|
channel.state.sustainPedal = value / 127;
|
|
1972
2010
|
if (64 <= value) {
|
|
1973
|
-
this.processScheduledNotes(channel,
|
|
2011
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1974
2012
|
channel.sustainNotes.push(note);
|
|
1975
2013
|
});
|
|
1976
2014
|
}
|
|
@@ -2013,7 +2051,7 @@ export class MidyGM2 {
|
|
|
2013
2051
|
const state = channel.state;
|
|
2014
2052
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2015
2053
|
state.softPedal = softPedal / 127;
|
|
2016
|
-
this.processScheduledNotes(channel,
|
|
2054
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2017
2055
|
if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
2018
2056
|
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2019
2057
|
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
@@ -2028,67 +2066,19 @@ export class MidyGM2 {
|
|
|
2028
2066
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2029
2067
|
const channel = this.channels[channelNumber];
|
|
2030
2068
|
const state = channel.state;
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
reverbEffect.input.gain
|
|
2036
|
-
.cancelScheduledValues(scheduleTime)
|
|
2037
|
-
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
2038
|
-
}
|
|
2039
|
-
else {
|
|
2040
|
-
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2041
|
-
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
2042
|
-
return false;
|
|
2043
|
-
if (note.reverbEffectsSend)
|
|
2044
|
-
note.reverbEffectsSend.disconnect();
|
|
2045
|
-
});
|
|
2046
|
-
}
|
|
2047
|
-
}
|
|
2048
|
-
else {
|
|
2049
|
-
if (0 < reverbSendLevel) {
|
|
2050
|
-
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2051
|
-
this.setReverbEffectsSend(channel, note, 0, scheduleTime);
|
|
2052
|
-
});
|
|
2053
|
-
state.reverbSendLevel = reverbSendLevel / 127;
|
|
2054
|
-
reverbEffect.input.gain
|
|
2055
|
-
.cancelScheduledValues(scheduleTime)
|
|
2056
|
-
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
2057
|
-
}
|
|
2058
|
-
}
|
|
2069
|
+
state.reverbSendLevel = reverbSendLevel / 127;
|
|
2070
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2071
|
+
this.setReverbSend(channel, note, scheduleTime);
|
|
2072
|
+
});
|
|
2059
2073
|
}
|
|
2060
2074
|
setChorusSendLevel(channelNumber, chorusSendLevel, scheduleTime) {
|
|
2061
2075
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2062
2076
|
const channel = this.channels[channelNumber];
|
|
2063
2077
|
const state = channel.state;
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
chorusEffect.input.gain
|
|
2069
|
-
.cancelScheduledValues(scheduleTime)
|
|
2070
|
-
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
2071
|
-
}
|
|
2072
|
-
else {
|
|
2073
|
-
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2074
|
-
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
2075
|
-
return false;
|
|
2076
|
-
if (note.chorusEffectsSend)
|
|
2077
|
-
note.chorusEffectsSend.disconnect();
|
|
2078
|
-
});
|
|
2079
|
-
}
|
|
2080
|
-
}
|
|
2081
|
-
else {
|
|
2082
|
-
if (0 < chorusSendLevel) {
|
|
2083
|
-
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
2084
|
-
this.setChorusEffectsSend(channel, note, 0, scheduleTime);
|
|
2085
|
-
});
|
|
2086
|
-
state.chorusSendLevel = chorusSendLevel / 127;
|
|
2087
|
-
chorusEffect.input.gain
|
|
2088
|
-
.cancelScheduledValues(scheduleTime)
|
|
2089
|
-
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
2090
|
-
}
|
|
2091
|
-
}
|
|
2078
|
+
state.chorusSendLevel = chorusSendLevel / 127;
|
|
2079
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2080
|
+
this.setChorusSend(channel, note, scheduleTime);
|
|
2081
|
+
});
|
|
2092
2082
|
}
|
|
2093
2083
|
limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
|
|
2094
2084
|
if (maxLSB < channel.dataLSB) {
|
|
@@ -2224,7 +2214,7 @@ export class MidyGM2 {
|
|
|
2224
2214
|
const entries = Object.entries(defaultControllerState);
|
|
2225
2215
|
for (const [key, { type, defaultValue }] of entries) {
|
|
2226
2216
|
if (128 <= type) {
|
|
2227
|
-
this.
|
|
2217
|
+
this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
2228
2218
|
}
|
|
2229
2219
|
else {
|
|
2230
2220
|
state[key] = defaultValue;
|
|
@@ -2256,7 +2246,7 @@ export class MidyGM2 {
|
|
|
2256
2246
|
const key = keys[i];
|
|
2257
2247
|
const { type, defaultValue } = defaultControllerState[key];
|
|
2258
2248
|
if (128 <= type) {
|
|
2259
|
-
this.
|
|
2249
|
+
this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
2260
2250
|
}
|
|
2261
2251
|
else {
|
|
2262
2252
|
state[key] = defaultValue;
|
|
@@ -2370,9 +2360,9 @@ export class MidyGM2 {
|
|
|
2370
2360
|
case 9:
|
|
2371
2361
|
switch (data[3]) {
|
|
2372
2362
|
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
|
|
2373
|
-
return this.handlePressureSysEx(data, "channelPressureTable");
|
|
2363
|
+
return this.handlePressureSysEx(data, "channelPressureTable", scheduleTime);
|
|
2374
2364
|
case 3: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
|
|
2375
|
-
return this.handleControlChangeSysEx(data);
|
|
2365
|
+
return this.handleControlChangeSysEx(data, scheduleTime);
|
|
2376
2366
|
default:
|
|
2377
2367
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2378
2368
|
}
|
|
@@ -2691,29 +2681,31 @@ export class MidyGM2 {
|
|
|
2691
2681
|
: 0;
|
|
2692
2682
|
return channelPressure / 127;
|
|
2693
2683
|
}
|
|
2694
|
-
|
|
2684
|
+
setEffects(channel, note, table, scheduleTime) {
|
|
2695
2685
|
if (0 <= table[0])
|
|
2696
|
-
this.updateDetune(channel, note);
|
|
2686
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
2697
2687
|
if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
|
|
2698
|
-
if (0 <= table[1])
|
|
2699
|
-
this.setPortamentoFilterEnvelope(channel, note);
|
|
2700
|
-
|
|
2701
|
-
|
|
2688
|
+
if (0 <= table[1]) {
|
|
2689
|
+
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2690
|
+
}
|
|
2691
|
+
if (0 <= table[2]) {
|
|
2692
|
+
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2693
|
+
}
|
|
2702
2694
|
}
|
|
2703
2695
|
else {
|
|
2704
2696
|
if (0 <= table[1])
|
|
2705
|
-
this.setFilterEnvelope(channel, note);
|
|
2697
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
2706
2698
|
if (0 <= table[2])
|
|
2707
|
-
this.setVolumeEnvelope(channel, note);
|
|
2699
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
2708
2700
|
}
|
|
2709
2701
|
if (0 <= table[3])
|
|
2710
|
-
this.setModLfoToPitch(channel, note);
|
|
2702
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
2711
2703
|
if (0 <= table[4])
|
|
2712
|
-
this.setModLfoToFilterFc(channel, note);
|
|
2704
|
+
this.setModLfoToFilterFc(channel, note, scheduleTime);
|
|
2713
2705
|
if (0 <= table[5])
|
|
2714
|
-
this.setModLfoToVolume(channel, note);
|
|
2706
|
+
this.setModLfoToVolume(channel, note, scheduleTime);
|
|
2715
2707
|
}
|
|
2716
|
-
handlePressureSysEx(data, tableName) {
|
|
2708
|
+
handlePressureSysEx(data, tableName, scheduleTime) {
|
|
2717
2709
|
const channelNumber = data[4];
|
|
2718
2710
|
const channel = this.channels[channelNumber];
|
|
2719
2711
|
if (channel.isDrum)
|
|
@@ -2724,34 +2716,40 @@ export class MidyGM2 {
|
|
|
2724
2716
|
const rr = data[i + 1];
|
|
2725
2717
|
table[pp] = rr;
|
|
2726
2718
|
}
|
|
2719
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
2720
|
+
this.setEffects(channel, note, table, scheduleTime);
|
|
2721
|
+
});
|
|
2727
2722
|
}
|
|
2728
2723
|
initControlTable() {
|
|
2729
2724
|
const ccCount = 128;
|
|
2730
2725
|
const slotSize = 6;
|
|
2731
2726
|
return new Int8Array(ccCount * slotSize).fill(-1);
|
|
2732
2727
|
}
|
|
2733
|
-
|
|
2728
|
+
setControlChangeEffects(channel, controllerType, scheduleTime) {
|
|
2734
2729
|
const slotSize = 6;
|
|
2735
2730
|
const offset = controllerType * slotSize;
|
|
2736
2731
|
const table = channel.controlTable.subarray(offset, offset + slotSize);
|
|
2737
|
-
this.processScheduledNotes(channel,
|
|
2738
|
-
this.
|
|
2732
|
+
this.processScheduledNotes(channel, (note) => {
|
|
2733
|
+
this.setEffects(channel, note, table, scheduleTime);
|
|
2739
2734
|
});
|
|
2740
2735
|
}
|
|
2741
|
-
handleControlChangeSysEx(data) {
|
|
2736
|
+
handleControlChangeSysEx(data, scheduleTime) {
|
|
2742
2737
|
const channelNumber = data[4];
|
|
2743
2738
|
const channel = this.channels[channelNumber];
|
|
2744
2739
|
if (channel.isDrum)
|
|
2745
2740
|
return;
|
|
2741
|
+
const slotSize = 6;
|
|
2746
2742
|
const controllerType = data[5];
|
|
2747
|
-
const
|
|
2748
|
-
|
|
2743
|
+
const offset = controllerType * slotSize;
|
|
2744
|
+
const table = channel.controlTable;
|
|
2745
|
+
for (let i = 6; i < data.length; i += 2) {
|
|
2749
2746
|
const pp = data[i];
|
|
2750
2747
|
const rr = data[i + 1];
|
|
2751
|
-
table[pp] = rr;
|
|
2748
|
+
table[offset + pp] = rr;
|
|
2752
2749
|
}
|
|
2750
|
+
this.setControlChangeEffects(channel, controllerType, scheduleTime);
|
|
2753
2751
|
}
|
|
2754
|
-
|
|
2752
|
+
getKeyBasedValue(channel, keyNumber, controllerType) {
|
|
2755
2753
|
const index = keyNumber * 128 + controllerType;
|
|
2756
2754
|
const controlValue = channel.keyBasedInstrumentControlTable[index];
|
|
2757
2755
|
return controlValue;
|
|
@@ -2763,13 +2761,20 @@ export class MidyGM2 {
|
|
|
2763
2761
|
return;
|
|
2764
2762
|
const keyNumber = data[5];
|
|
2765
2763
|
const table = channel.keyBasedInstrumentControlTable;
|
|
2766
|
-
for (let i = 6; i < data.length
|
|
2764
|
+
for (let i = 6; i < data.length; i += 2) {
|
|
2767
2765
|
const controllerType = data[i];
|
|
2768
2766
|
const value = data[i + 1];
|
|
2769
2767
|
const index = keyNumber * 128 + controllerType;
|
|
2770
2768
|
table[index] = value;
|
|
2769
|
+
switch (controllerType) {
|
|
2770
|
+
case 7:
|
|
2771
|
+
case 10:
|
|
2772
|
+
this.updateKeyBasedVolume(channel, keyNumber, scheduleTime);
|
|
2773
|
+
break;
|
|
2774
|
+
default: // TODO
|
|
2775
|
+
this.setControlChange(channelNumber, controllerType, value, scheduleTime);
|
|
2776
|
+
}
|
|
2771
2777
|
}
|
|
2772
|
-
this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
|
|
2773
2778
|
}
|
|
2774
2779
|
handleSysEx(data, scheduleTime) {
|
|
2775
2780
|
switch (data[0]) {
|
|
@@ -2806,6 +2811,7 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
2806
2811
|
configurable: true,
|
|
2807
2812
|
writable: true,
|
|
2808
2813
|
value: {
|
|
2814
|
+
scheduleIndex: 0,
|
|
2809
2815
|
detune: 0,
|
|
2810
2816
|
programNumber: 0,
|
|
2811
2817
|
bank: 121 * 128,
|