@marmooo/midy 0.3.1 → 0.3.3
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/esm/midy-GM1.d.ts +17 -55
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +159 -184
- package/esm/midy-GM2.d.ts +34 -90
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +421 -342
- package/esm/midy-GMLite.d.ts +17 -55
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +169 -199
- package/esm/midy.d.ts +37 -114
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +451 -376
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +17 -55
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +159 -184
- package/script/midy-GM2.d.ts +34 -90
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +421 -342
- package/script/midy-GMLite.d.ts +17 -55
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +169 -199
- package/script/midy.d.ts +37 -114
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +451 -376
package/esm/midy-GM2.js
CHANGED
|
@@ -1,59 +1,19 @@
|
|
|
1
1
|
import { parseMidi } from "midi-file";
|
|
2
2
|
import { parse, SoundFont } from "@marmooo/soundfont-parser";
|
|
3
|
-
// 2-3 times faster than Map
|
|
4
|
-
class SparseMap {
|
|
5
|
-
constructor(size) {
|
|
6
|
-
this.data = new Array(size);
|
|
7
|
-
this.activeIndices = [];
|
|
8
|
-
}
|
|
9
|
-
set(key, value) {
|
|
10
|
-
if (this.data[key] === undefined) {
|
|
11
|
-
this.activeIndices.push(key);
|
|
12
|
-
}
|
|
13
|
-
this.data[key] = value;
|
|
14
|
-
}
|
|
15
|
-
get(key) {
|
|
16
|
-
return this.data[key];
|
|
17
|
-
}
|
|
18
|
-
delete(key) {
|
|
19
|
-
if (this.data[key] !== undefined) {
|
|
20
|
-
this.data[key] = undefined;
|
|
21
|
-
const index = this.activeIndices.indexOf(key);
|
|
22
|
-
if (index !== -1) {
|
|
23
|
-
this.activeIndices.splice(index, 1);
|
|
24
|
-
}
|
|
25
|
-
return true;
|
|
26
|
-
}
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
has(key) {
|
|
30
|
-
return this.data[key] !== undefined;
|
|
31
|
-
}
|
|
32
|
-
get size() {
|
|
33
|
-
return this.activeIndices.length;
|
|
34
|
-
}
|
|
35
|
-
clear() {
|
|
36
|
-
for (let i = 0; i < this.activeIndices.length; i++) {
|
|
37
|
-
const key = this.activeIndices[i];
|
|
38
|
-
this.data[key] = undefined;
|
|
39
|
-
}
|
|
40
|
-
this.activeIndices = [];
|
|
41
|
-
}
|
|
42
|
-
*[Symbol.iterator]() {
|
|
43
|
-
for (let i = 0; i < this.activeIndices.length; i++) {
|
|
44
|
-
const key = this.activeIndices[i];
|
|
45
|
-
yield [key, this.data[key]];
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
forEach(callback) {
|
|
49
|
-
for (let i = 0; i < this.activeIndices.length; i++) {
|
|
50
|
-
const key = this.activeIndices[i];
|
|
51
|
-
callback(this.data[key], key, this);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
3
|
class Note {
|
|
56
4
|
constructor(noteNumber, velocity, startTime, voice, voiceParams) {
|
|
5
|
+
Object.defineProperty(this, "index", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
configurable: true,
|
|
8
|
+
writable: true,
|
|
9
|
+
value: -1
|
|
10
|
+
});
|
|
11
|
+
Object.defineProperty(this, "ending", {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
configurable: true,
|
|
14
|
+
writable: true,
|
|
15
|
+
value: false
|
|
16
|
+
});
|
|
57
17
|
Object.defineProperty(this, "bufferSource", {
|
|
58
18
|
enumerable: true,
|
|
59
19
|
configurable: true,
|
|
@@ -138,11 +98,11 @@ class Note {
|
|
|
138
98
|
writable: true,
|
|
139
99
|
value: void 0
|
|
140
100
|
});
|
|
141
|
-
Object.defineProperty(this, "
|
|
101
|
+
Object.defineProperty(this, "portamentoNoteNumber", {
|
|
142
102
|
enumerable: true,
|
|
143
103
|
configurable: true,
|
|
144
104
|
writable: true,
|
|
145
|
-
value:
|
|
105
|
+
value: -1
|
|
146
106
|
});
|
|
147
107
|
this.noteNumber = noteNumber;
|
|
148
108
|
this.velocity = velocity;
|
|
@@ -197,7 +157,7 @@ const defaultControllerState = {
|
|
|
197
157
|
portamentoTime: { type: 128 + 5, defaultValue: 0 },
|
|
198
158
|
// dataMSB: { type: 128 + 6, defaultValue: 0, },
|
|
199
159
|
volume: { type: 128 + 7, defaultValue: 100 / 127 },
|
|
200
|
-
pan: { type: 128 + 10, defaultValue:
|
|
160
|
+
pan: { type: 128 + 10, defaultValue: 64 / 127 },
|
|
201
161
|
expression: { type: 128 + 11, defaultValue: 1 },
|
|
202
162
|
// bankLSB: { type: 128 + 32, defaultValue: 0, },
|
|
203
163
|
// dataLSB: { type: 128 + 38, defaultValue: 0, },
|
|
@@ -205,14 +165,6 @@ const defaultControllerState = {
|
|
|
205
165
|
portamento: { type: 128 + 65, defaultValue: 0 },
|
|
206
166
|
sostenutoPedal: { type: 128 + 66, defaultValue: 0 },
|
|
207
167
|
softPedal: { type: 128 + 67, defaultValue: 0 },
|
|
208
|
-
filterResonance: { type: 128 + 71, defaultValue: 0.5 },
|
|
209
|
-
releaseTime: { type: 128 + 72, defaultValue: 0.5 },
|
|
210
|
-
attackTime: { type: 128 + 73, defaultValue: 0.5 },
|
|
211
|
-
brightness: { type: 128 + 74, defaultValue: 0.5 },
|
|
212
|
-
decayTime: { type: 128 + 75, defaultValue: 0.5 },
|
|
213
|
-
vibratoRate: { type: 128 + 76, defaultValue: 0.5 },
|
|
214
|
-
vibratoDepth: { type: 128 + 77, defaultValue: 0.5 },
|
|
215
|
-
vibratoDelay: { type: 128 + 78, defaultValue: 0.5 },
|
|
216
168
|
reverbSendLevel: { type: 128 + 91, defaultValue: 0 },
|
|
217
169
|
chorusSendLevel: { type: 128 + 93, defaultValue: 0 },
|
|
218
170
|
// dataIncrement: { type: 128 + 96, defaultValue: 0 },
|
|
@@ -487,7 +439,7 @@ export class MidyGM2 {
|
|
|
487
439
|
initSoundFontTable() {
|
|
488
440
|
const table = new Array(128);
|
|
489
441
|
for (let i = 0; i < 128; i++) {
|
|
490
|
-
table[i] = new
|
|
442
|
+
table[i] = new Map();
|
|
491
443
|
}
|
|
492
444
|
return table;
|
|
493
445
|
}
|
|
@@ -503,17 +455,37 @@ export class MidyGM2 {
|
|
|
503
455
|
}
|
|
504
456
|
}
|
|
505
457
|
}
|
|
506
|
-
async loadSoundFont(
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
458
|
+
async loadSoundFont(input) {
|
|
459
|
+
let uint8Array;
|
|
460
|
+
if (typeof input === "string") {
|
|
461
|
+
const response = await fetch(input);
|
|
462
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
463
|
+
uint8Array = new Uint8Array(arrayBuffer);
|
|
464
|
+
}
|
|
465
|
+
else if (input instanceof Uint8Array) {
|
|
466
|
+
uint8Array = input;
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
throw new TypeError("input must be a URL string or Uint8Array");
|
|
470
|
+
}
|
|
471
|
+
const parsed = parse(uint8Array);
|
|
510
472
|
const soundFont = new SoundFont(parsed);
|
|
511
473
|
this.addSoundFont(soundFont);
|
|
512
474
|
}
|
|
513
|
-
async loadMIDI(
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
475
|
+
async loadMIDI(input) {
|
|
476
|
+
let uint8Array;
|
|
477
|
+
if (typeof input === "string") {
|
|
478
|
+
const response = await fetch(input);
|
|
479
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
480
|
+
uint8Array = new Uint8Array(arrayBuffer);
|
|
481
|
+
}
|
|
482
|
+
else if (input instanceof Uint8Array) {
|
|
483
|
+
uint8Array = input;
|
|
484
|
+
}
|
|
485
|
+
else {
|
|
486
|
+
throw new TypeError("input must be a URL string or Uint8Array");
|
|
487
|
+
}
|
|
488
|
+
const midi = parseMidi(uint8Array);
|
|
517
489
|
this.ticksPerBeat = midi.header.ticksPerBeat;
|
|
518
490
|
const midiData = this.extractMidiData(midi);
|
|
519
491
|
this.instruments = midiData.instruments;
|
|
@@ -534,21 +506,28 @@ export class MidyGM2 {
|
|
|
534
506
|
merger,
|
|
535
507
|
};
|
|
536
508
|
}
|
|
509
|
+
resetChannelTable(channel) {
|
|
510
|
+
this.resetControlTable(channel.controlTable);
|
|
511
|
+
channel.scaleOctaveTuningTable.fill(0); // [-100, 100] cent
|
|
512
|
+
channel.channelPressureTable.set([64, 64, 64, 0, 0, 0]);
|
|
513
|
+
channel.polyphonicKeyPressureTable.set([64, 64, 64, 0, 0, 0]);
|
|
514
|
+
channel.keyBasedInstrumentControlTable.fill(-1);
|
|
515
|
+
}
|
|
537
516
|
createChannels(audioContext) {
|
|
538
517
|
const channels = Array.from({ length: this.numChannels }, () => {
|
|
539
518
|
return {
|
|
540
519
|
currentBufferSource: null,
|
|
541
520
|
isDrum: false,
|
|
542
|
-
...this.constructor.channelSettings,
|
|
543
521
|
state: new ControllerState(),
|
|
544
|
-
|
|
522
|
+
...this.constructor.channelSettings,
|
|
545
523
|
...this.setChannelAudioNodes(audioContext),
|
|
546
|
-
scheduledNotes:
|
|
524
|
+
scheduledNotes: [],
|
|
547
525
|
sustainNotes: [],
|
|
548
|
-
sostenutoNotes:
|
|
526
|
+
sostenutoNotes: [],
|
|
527
|
+
controlTable: this.initControlTable(),
|
|
549
528
|
scaleOctaveTuningTable: new Int8Array(12), // [-64, 63] cent
|
|
550
529
|
channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
551
|
-
keyBasedInstrumentControlTable: new Int8Array(128 * 128)
|
|
530
|
+
keyBasedInstrumentControlTable: new Int8Array(128 * 128).fill(-1),
|
|
552
531
|
};
|
|
553
532
|
});
|
|
554
533
|
return channels;
|
|
@@ -582,56 +561,39 @@ export class MidyGM2 {
|
|
|
582
561
|
return audioBuffer;
|
|
583
562
|
}
|
|
584
563
|
}
|
|
585
|
-
|
|
564
|
+
isLoopDrum(channel, noteNumber) {
|
|
565
|
+
const programNumber = channel.programNumber;
|
|
566
|
+
return ((programNumber === 48 && noteNumber === 88) ||
|
|
567
|
+
(programNumber === 56 && 47 <= noteNumber && noteNumber <= 84));
|
|
568
|
+
}
|
|
569
|
+
createBufferSource(channel, noteNumber, voiceParams, audioBuffer) {
|
|
586
570
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
587
571
|
bufferSource.buffer = audioBuffer;
|
|
588
572
|
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
573
|
+
if (channel.isDrum) {
|
|
574
|
+
bufferSource.loop = this.isLoopDrum(channel, noteNumber);
|
|
575
|
+
}
|
|
589
576
|
if (bufferSource.loop) {
|
|
590
577
|
bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
|
|
591
578
|
bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
|
|
592
579
|
}
|
|
593
580
|
return bufferSource;
|
|
594
581
|
}
|
|
595
|
-
|
|
596
|
-
const endEvent = this.timeline[queueIndex];
|
|
597
|
-
if (!this.channels[endEvent.channel].portamento)
|
|
598
|
-
return;
|
|
599
|
-
const endTime = endEvent.startTime;
|
|
600
|
-
let target;
|
|
601
|
-
while (++queueIndex < this.timeline.length) {
|
|
602
|
-
const event = this.timeline[queueIndex];
|
|
603
|
-
if (endTime !== event.startTime)
|
|
604
|
-
break;
|
|
605
|
-
if (event.type !== "noteOn")
|
|
606
|
-
continue;
|
|
607
|
-
if (!target || event.noteNumber < target.noteNumber) {
|
|
608
|
-
target = event;
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
return target;
|
|
612
|
-
}
|
|
613
|
-
async scheduleTimelineEvents(t, offset, queueIndex) {
|
|
582
|
+
async scheduleTimelineEvents(t, resumeTime, queueIndex) {
|
|
614
583
|
while (queueIndex < this.timeline.length) {
|
|
615
584
|
const event = this.timeline[queueIndex];
|
|
616
585
|
if (event.startTime > t + this.lookAhead)
|
|
617
586
|
break;
|
|
618
|
-
const
|
|
587
|
+
const delay = this.startDelay - resumeTime;
|
|
588
|
+
const startTime = event.startTime + delay;
|
|
619
589
|
switch (event.type) {
|
|
620
590
|
case "noteOn":
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
break;
|
|
624
|
-
}
|
|
625
|
-
/* falls through */
|
|
591
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
|
|
592
|
+
break;
|
|
626
593
|
case "noteOff": {
|
|
627
|
-
const
|
|
628
|
-
if (
|
|
629
|
-
portamentoTarget.portamento = true;
|
|
630
|
-
const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false, // force
|
|
631
|
-
portamentoTarget?.noteNumber);
|
|
632
|
-
if (notePromise) {
|
|
594
|
+
const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
|
|
595
|
+
if (notePromise)
|
|
633
596
|
this.notePromises.push(notePromise);
|
|
634
|
-
}
|
|
635
597
|
break;
|
|
636
598
|
}
|
|
637
599
|
case "controller":
|
|
@@ -667,7 +629,7 @@ export class MidyGM2 {
|
|
|
667
629
|
this.isPaused = false;
|
|
668
630
|
this.startTime = this.audioContext.currentTime;
|
|
669
631
|
let queueIndex = this.getQueueIndex(this.resumeTime);
|
|
670
|
-
let
|
|
632
|
+
let resumeTime = this.resumeTime - this.startTime;
|
|
671
633
|
this.notePromises = [];
|
|
672
634
|
const schedulePlayback = async () => {
|
|
673
635
|
if (queueIndex >= this.timeline.length) {
|
|
@@ -676,18 +638,21 @@ export class MidyGM2 {
|
|
|
676
638
|
this.exclusiveClassNotes.fill(undefined);
|
|
677
639
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
678
640
|
this.audioBufferCache.clear();
|
|
641
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
642
|
+
this.resetAllStates(i);
|
|
643
|
+
}
|
|
679
644
|
resolve();
|
|
680
645
|
return;
|
|
681
646
|
}
|
|
682
647
|
const now = this.audioContext.currentTime;
|
|
683
|
-
const t = now +
|
|
684
|
-
queueIndex = await this.scheduleTimelineEvents(t,
|
|
648
|
+
const t = now + resumeTime;
|
|
649
|
+
queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
|
|
685
650
|
if (this.isPausing) {
|
|
686
651
|
await this.stopNotes(0, true, now);
|
|
687
652
|
this.notePromises = [];
|
|
688
|
-
resolve();
|
|
689
653
|
this.isPausing = false;
|
|
690
654
|
this.isPaused = true;
|
|
655
|
+
resolve();
|
|
691
656
|
return;
|
|
692
657
|
}
|
|
693
658
|
else if (this.isStopping) {
|
|
@@ -696,9 +661,12 @@ export class MidyGM2 {
|
|
|
696
661
|
this.exclusiveClassNotes.fill(undefined);
|
|
697
662
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
698
663
|
this.audioBufferCache.clear();
|
|
699
|
-
|
|
664
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
665
|
+
this.resetAllStates(i);
|
|
666
|
+
}
|
|
700
667
|
this.isStopping = false;
|
|
701
668
|
this.isPaused = false;
|
|
669
|
+
resolve();
|
|
702
670
|
return;
|
|
703
671
|
}
|
|
704
672
|
else if (this.isSeeking) {
|
|
@@ -707,7 +675,7 @@ export class MidyGM2 {
|
|
|
707
675
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
708
676
|
this.startTime = this.audioContext.currentTime;
|
|
709
677
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
710
|
-
|
|
678
|
+
resumeTime = this.resumeTime - this.startTime;
|
|
711
679
|
this.isSeeking = false;
|
|
712
680
|
await schedulePlayback();
|
|
713
681
|
}
|
|
@@ -730,6 +698,7 @@ export class MidyGM2 {
|
|
|
730
698
|
return `${programNumber}:${noteNumber}:${velocity}`;
|
|
731
699
|
}
|
|
732
700
|
extractMidiData(midi) {
|
|
701
|
+
this.audioBufferCounter.clear();
|
|
733
702
|
const instruments = new Set();
|
|
734
703
|
const timeline = [];
|
|
735
704
|
const tmpChannels = new Array(this.channels.length);
|
|
@@ -834,9 +803,8 @@ export class MidyGM2 {
|
|
|
834
803
|
stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
|
|
835
804
|
const channel = this.channels[channelNumber];
|
|
836
805
|
const promises = [];
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
|
|
806
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
807
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
840
808
|
this.notePromises.push(promise);
|
|
841
809
|
promises.push(promise);
|
|
842
810
|
});
|
|
@@ -846,11 +814,11 @@ export class MidyGM2 {
|
|
|
846
814
|
const channel = this.channels[channelNumber];
|
|
847
815
|
const promises = [];
|
|
848
816
|
this.processScheduledNotes(channel, (note) => {
|
|
849
|
-
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force
|
|
817
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
|
|
850
818
|
this.notePromises.push(promise);
|
|
851
819
|
promises.push(promise);
|
|
852
820
|
});
|
|
853
|
-
channel.scheduledNotes
|
|
821
|
+
channel.scheduledNotes = [];
|
|
854
822
|
return Promise.all(promises);
|
|
855
823
|
}
|
|
856
824
|
stopNotes(velocity, force, scheduleTime) {
|
|
@@ -871,9 +839,6 @@ export class MidyGM2 {
|
|
|
871
839
|
if (!this.isPlaying)
|
|
872
840
|
return;
|
|
873
841
|
this.isStopping = true;
|
|
874
|
-
for (let i = 0; i < this.channels.length; i++) {
|
|
875
|
-
this.resetAllStates(i);
|
|
876
|
-
}
|
|
877
842
|
}
|
|
878
843
|
pause() {
|
|
879
844
|
if (!this.isPlaying || this.isPaused)
|
|
@@ -908,37 +873,28 @@ export class MidyGM2 {
|
|
|
908
873
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
909
874
|
}
|
|
910
875
|
processScheduledNotes(channel, callback) {
|
|
911
|
-
channel.scheduledNotes
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
});
|
|
921
|
-
}
|
|
922
|
-
getActiveNotes(channel, scheduleTime) {
|
|
923
|
-
const activeNotes = new SparseMap(128);
|
|
924
|
-
channel.scheduledNotes.forEach((noteList) => {
|
|
925
|
-
const activeNote = this.getActiveNote(noteList, scheduleTime);
|
|
926
|
-
if (activeNote) {
|
|
927
|
-
activeNotes.set(activeNote.noteNumber, activeNote);
|
|
928
|
-
}
|
|
929
|
-
});
|
|
930
|
-
return activeNotes;
|
|
876
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
877
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
878
|
+
const note = scheduledNotes[i];
|
|
879
|
+
if (!note)
|
|
880
|
+
continue;
|
|
881
|
+
if (note.ending)
|
|
882
|
+
continue;
|
|
883
|
+
callback(note);
|
|
884
|
+
}
|
|
931
885
|
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
886
|
+
processActiveNotes(channel, scheduleTime, callback) {
|
|
887
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
888
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
889
|
+
const note = scheduledNotes[i];
|
|
935
890
|
if (!note)
|
|
936
|
-
|
|
891
|
+
continue;
|
|
892
|
+
if (note.ending)
|
|
893
|
+
continue;
|
|
937
894
|
if (scheduleTime < note.startTime)
|
|
938
895
|
continue;
|
|
939
|
-
|
|
896
|
+
callback(note);
|
|
940
897
|
}
|
|
941
|
-
return noteList[0];
|
|
942
898
|
}
|
|
943
899
|
createConvolutionReverbImpulse(audioContext, decay, preDecay) {
|
|
944
900
|
const sampleRate = audioContext.sampleRate;
|
|
@@ -1104,24 +1060,93 @@ export class MidyGM2 {
|
|
|
1104
1060
|
updateDetune(channel, note, scheduleTime) {
|
|
1105
1061
|
const noteDetune = this.calcNoteDetune(channel, note);
|
|
1106
1062
|
const detune = channel.detune + noteDetune;
|
|
1107
|
-
note.
|
|
1108
|
-
.
|
|
1109
|
-
.
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1063
|
+
if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
1064
|
+
const startTime = note.startTime;
|
|
1065
|
+
const deltaCent = (note.noteNumber - note.portamentoNoteNumber) * 100;
|
|
1066
|
+
const portamentoTime = startTime + this.getPortamentoTime(channel, note);
|
|
1067
|
+
note.bufferSource.detune
|
|
1068
|
+
.cancelScheduledValues(scheduleTime)
|
|
1069
|
+
.setValueAtTime(detune - deltaCent, scheduleTime)
|
|
1070
|
+
.linearRampToValueAtTime(detune, portamentoTime);
|
|
1071
|
+
}
|
|
1072
|
+
else {
|
|
1073
|
+
note.bufferSource.detune
|
|
1074
|
+
.cancelScheduledValues(scheduleTime)
|
|
1075
|
+
.setValueAtTime(detune, scheduleTime);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
getPortamentoTime(channel, note) {
|
|
1079
|
+
const deltaSemitone = Math.abs(note.noteNumber - note.portamentoNoteNumber);
|
|
1080
|
+
const value = Math.ceil(channel.state.portamentoTime * 127);
|
|
1081
|
+
return deltaSemitone / this.getPitchIncrementSpeed(value) / 10;
|
|
1082
|
+
}
|
|
1083
|
+
getPitchIncrementSpeed(value) {
|
|
1084
|
+
const points = [
|
|
1085
|
+
[0, 1000],
|
|
1086
|
+
[6, 100],
|
|
1087
|
+
[16, 20],
|
|
1088
|
+
[32, 10],
|
|
1089
|
+
[48, 5],
|
|
1090
|
+
[64, 2.5],
|
|
1091
|
+
[80, 1],
|
|
1092
|
+
[96, 0.4],
|
|
1093
|
+
[112, 0.15],
|
|
1094
|
+
[127, 0.01],
|
|
1095
|
+
];
|
|
1096
|
+
const logPoints = new Array(points.length);
|
|
1097
|
+
for (let i = 0; i < points.length; i++) {
|
|
1098
|
+
const [x, y] = points[i];
|
|
1099
|
+
if (value === x)
|
|
1100
|
+
return y;
|
|
1101
|
+
logPoints[i] = [x, Math.log(y)];
|
|
1102
|
+
}
|
|
1103
|
+
let startIndex = 0;
|
|
1104
|
+
for (let i = 1; i < logPoints.length; i++) {
|
|
1105
|
+
if (value <= logPoints[i][0]) {
|
|
1106
|
+
startIndex = i - 1;
|
|
1107
|
+
break;
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
const [x0, y0] = logPoints[startIndex];
|
|
1111
|
+
const [x1, y1] = logPoints[startIndex + 1];
|
|
1112
|
+
const h = x1 - x0;
|
|
1113
|
+
const t = (value - x0) / h;
|
|
1114
|
+
let m0, m1;
|
|
1115
|
+
if (startIndex === 0) {
|
|
1116
|
+
m0 = (y1 - y0) / h;
|
|
1117
|
+
}
|
|
1118
|
+
else {
|
|
1119
|
+
const [xPrev, yPrev] = logPoints[startIndex - 1];
|
|
1120
|
+
m0 = (y1 - yPrev) / (x1 - xPrev);
|
|
1121
|
+
}
|
|
1122
|
+
if (startIndex === logPoints.length - 2) {
|
|
1123
|
+
m1 = (y1 - y0) / h;
|
|
1124
|
+
}
|
|
1125
|
+
else {
|
|
1126
|
+
const [xNext, yNext] = logPoints[startIndex + 2];
|
|
1127
|
+
m1 = (yNext - y0) / (xNext - x0);
|
|
1128
|
+
}
|
|
1129
|
+
// Cubic Hermite Spline
|
|
1130
|
+
const t2 = t * t;
|
|
1131
|
+
const t3 = t2 * t;
|
|
1132
|
+
const h00 = 2 * t3 - 3 * t2 + 1;
|
|
1133
|
+
const h10 = t3 - 2 * t2 + t;
|
|
1134
|
+
const h01 = -2 * t3 + 3 * t2;
|
|
1135
|
+
const h11 = t3 - t2;
|
|
1136
|
+
const y = h00 * y0 + h01 * y1 + h * (h10 * m0 + h11 * m1);
|
|
1137
|
+
return Math.exp(y);
|
|
1138
|
+
}
|
|
1139
|
+
setPortamentoVolumeEnvelope(channel, note, scheduleTime) {
|
|
1116
1140
|
const { voiceParams, startTime } = note;
|
|
1117
|
-
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation)
|
|
1141
|
+
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
|
|
1142
|
+
(1 + this.getAmplitudeControl(channel));
|
|
1118
1143
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
1119
1144
|
const volDelay = startTime + voiceParams.volDelay;
|
|
1120
|
-
const
|
|
1145
|
+
const volAttack = volDelay + voiceParams.volAttack;
|
|
1146
|
+
const volHold = volAttack + voiceParams.volHold;
|
|
1121
1147
|
note.volumeEnvelopeNode.gain
|
|
1122
1148
|
.cancelScheduledValues(scheduleTime)
|
|
1123
|
-
.setValueAtTime(
|
|
1124
|
-
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
1149
|
+
.setValueAtTime(sustainVolume, volHold);
|
|
1125
1150
|
}
|
|
1126
1151
|
setVolumeEnvelope(channel, note, scheduleTime) {
|
|
1127
1152
|
const { voiceParams, startTime } = note;
|
|
@@ -1140,6 +1165,12 @@ export class MidyGM2 {
|
|
|
1140
1165
|
.setValueAtTime(attackVolume, volHold)
|
|
1141
1166
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
1142
1167
|
}
|
|
1168
|
+
setPortamentoPitchEnvelope(note, scheduleTime) {
|
|
1169
|
+
const baseRate = note.voiceParams.playbackRate;
|
|
1170
|
+
note.bufferSource.playbackRate
|
|
1171
|
+
.cancelScheduledValues(scheduleTime)
|
|
1172
|
+
.setValueAtTime(baseRate, scheduleTime);
|
|
1173
|
+
}
|
|
1143
1174
|
setPitchEnvelope(note, scheduleTime) {
|
|
1144
1175
|
const { voiceParams } = note;
|
|
1145
1176
|
const baseRate = voiceParams.playbackRate;
|
|
@@ -1167,19 +1198,18 @@ export class MidyGM2 {
|
|
|
1167
1198
|
const maxFrequency = 20000; // max Hz of initialFilterFc
|
|
1168
1199
|
return Math.max(minFrequency, Math.min(frequency, maxFrequency));
|
|
1169
1200
|
}
|
|
1170
|
-
|
|
1171
|
-
const
|
|
1172
|
-
const
|
|
1173
|
-
const
|
|
1174
|
-
|
|
1175
|
-
const baseFreq = this.centToHz(
|
|
1176
|
-
softPedalFactor;
|
|
1201
|
+
setPortamentoFilterEnvelope(channel, note, scheduleTime) {
|
|
1202
|
+
const { voiceParams, startTime } = note;
|
|
1203
|
+
const softPedalFactor = this.getSoftPedalFactor(channel, note);
|
|
1204
|
+
const baseCent = voiceParams.initialFilterFc +
|
|
1205
|
+
this.getFilterCutoffControl(channel);
|
|
1206
|
+
const baseFreq = this.centToHz(baseCent) * softPedalFactor;
|
|
1177
1207
|
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor;
|
|
1178
1208
|
const sustainFreq = baseFreq +
|
|
1179
1209
|
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
1180
1210
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
1181
1211
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
1182
|
-
const portamentoTime = startTime + this.getPortamentoTime(channel);
|
|
1212
|
+
const portamentoTime = startTime + this.getPortamentoTime(channel, note);
|
|
1183
1213
|
const modDelay = startTime + voiceParams.modDelay;
|
|
1184
1214
|
note.filterNode.frequency
|
|
1185
1215
|
.cancelScheduledValues(scheduleTime)
|
|
@@ -1188,10 +1218,8 @@ export class MidyGM2 {
|
|
|
1188
1218
|
.linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
|
|
1189
1219
|
}
|
|
1190
1220
|
setFilterEnvelope(channel, note, scheduleTime) {
|
|
1191
|
-
const
|
|
1192
|
-
const
|
|
1193
|
-
const softPedalFactor = 1 -
|
|
1194
|
-
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1221
|
+
const { voiceParams, startTime } = note;
|
|
1222
|
+
const softPedalFactor = this.getSoftPedalFactor(channel, note);
|
|
1195
1223
|
const baseCent = voiceParams.initialFilterFc +
|
|
1196
1224
|
this.getFilterCutoffControl(channel);
|
|
1197
1225
|
const baseFreq = this.centToHz(baseCent) * softPedalFactor;
|
|
@@ -1264,14 +1292,14 @@ export class MidyGM2 {
|
|
|
1264
1292
|
return audioBuffer;
|
|
1265
1293
|
}
|
|
1266
1294
|
}
|
|
1267
|
-
async createNote(channel, voice, noteNumber, velocity, startTime,
|
|
1295
|
+
async createNote(channel, voice, noteNumber, velocity, startTime, isSF3) {
|
|
1268
1296
|
const now = this.audioContext.currentTime;
|
|
1269
1297
|
const state = channel.state;
|
|
1270
1298
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1271
1299
|
const voiceParams = voice.getAllParams(controllerState);
|
|
1272
1300
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1273
1301
|
const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
|
|
1274
|
-
note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
|
|
1302
|
+
note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
|
|
1275
1303
|
note.volumeNode = new GainNode(this.audioContext);
|
|
1276
1304
|
note.gainL = new GainNode(this.audioContext);
|
|
1277
1305
|
note.gainR = new GainNode(this.audioContext);
|
|
@@ -1280,20 +1308,24 @@ export class MidyGM2 {
|
|
|
1280
1308
|
type: "lowpass",
|
|
1281
1309
|
Q: voiceParams.initialFilterQ / 10, // dB
|
|
1282
1310
|
});
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1311
|
+
const prevNote = channel.scheduledNotes.at(-1);
|
|
1312
|
+
if (prevNote && prevNote.noteNumber !== noteNumber) {
|
|
1313
|
+
note.portamentoNoteNumber = prevNote.noteNumber;
|
|
1314
|
+
}
|
|
1315
|
+
if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
1316
|
+
this.setPortamentoVolumeEnvelope(channel, note, now);
|
|
1317
|
+
this.setPortamentoFilterEnvelope(channel, note, now);
|
|
1318
|
+
this.setPortamentoPitchEnvelope(note, now);
|
|
1287
1319
|
}
|
|
1288
1320
|
else {
|
|
1289
|
-
note.portamento = false;
|
|
1290
1321
|
this.setVolumeEnvelope(channel, note, now);
|
|
1291
1322
|
this.setFilterEnvelope(channel, note, now);
|
|
1323
|
+
this.setPitchEnvelope(note, now);
|
|
1292
1324
|
}
|
|
1325
|
+
this.updateDetune(channel, note, now);
|
|
1293
1326
|
if (0 < state.vibratoDepth) {
|
|
1294
1327
|
this.startVibrato(channel, note, now);
|
|
1295
1328
|
}
|
|
1296
|
-
this.setPitchEnvelope(note, now);
|
|
1297
1329
|
if (0 < state.modulationDepth) {
|
|
1298
1330
|
this.startModulation(channel, note, now);
|
|
1299
1331
|
}
|
|
@@ -1306,10 +1338,10 @@ export class MidyGM2 {
|
|
|
1306
1338
|
note.volumeEnvelopeNode.connect(note.volumeNode);
|
|
1307
1339
|
note.volumeNode.connect(note.gainL);
|
|
1308
1340
|
note.volumeNode.connect(note.gainR);
|
|
1309
|
-
if (0 <
|
|
1341
|
+
if (0 < state.chorusSendLevel) {
|
|
1310
1342
|
this.setChorusEffectsSend(channel, note, 0, now);
|
|
1311
1343
|
}
|
|
1312
|
-
if (0 <
|
|
1344
|
+
if (0 < state.reverbSendLevel) {
|
|
1313
1345
|
this.setReverbEffectsSend(channel, note, 0, now);
|
|
1314
1346
|
}
|
|
1315
1347
|
note.bufferSource.start(startTime);
|
|
@@ -1340,8 +1372,7 @@ export class MidyGM2 {
|
|
|
1340
1372
|
const [prevNote, prevChannelNumber] = prev;
|
|
1341
1373
|
if (prevNote && !prevNote.ending) {
|
|
1342
1374
|
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1343
|
-
startTime, true
|
|
1344
|
-
undefined);
|
|
1375
|
+
startTime, true);
|
|
1345
1376
|
}
|
|
1346
1377
|
}
|
|
1347
1378
|
this.exclusiveClassNotes[exclusiveClass] = [note, channelNumber];
|
|
@@ -1361,8 +1392,7 @@ export class MidyGM2 {
|
|
|
1361
1392
|
const prevNote = this.drumExclusiveClassNotes[index];
|
|
1362
1393
|
if (prevNote && !prevNote.ending) {
|
|
1363
1394
|
this.scheduleNoteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1364
|
-
startTime, true
|
|
1365
|
-
undefined);
|
|
1395
|
+
startTime, true);
|
|
1366
1396
|
}
|
|
1367
1397
|
this.drumExclusiveClassNotes[index] = note;
|
|
1368
1398
|
}
|
|
@@ -1373,7 +1403,7 @@ export class MidyGM2 {
|
|
|
1373
1403
|
return !((programNumber === 48 && noteNumber === 88) ||
|
|
1374
1404
|
(programNumber === 56 && 47 <= noteNumber && noteNumber <= 84));
|
|
1375
1405
|
}
|
|
1376
|
-
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime,
|
|
1406
|
+
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, noteOffEvent) {
|
|
1377
1407
|
const channel = this.channels[channelNumber];
|
|
1378
1408
|
const bankNumber = this.calcBank(channel, channelNumber);
|
|
1379
1409
|
const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
|
|
@@ -1384,7 +1414,8 @@ export class MidyGM2 {
|
|
|
1384
1414
|
if (!voice)
|
|
1385
1415
|
return;
|
|
1386
1416
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
1387
|
-
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime,
|
|
1417
|
+
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
1418
|
+
note.noteOffEvent = noteOffEvent;
|
|
1388
1419
|
note.gainL.connect(channel.gainL);
|
|
1389
1420
|
note.gainR.connect(channel.gainR);
|
|
1390
1421
|
if (0.5 <= channel.state.sustainPedal) {
|
|
@@ -1393,31 +1424,12 @@ export class MidyGM2 {
|
|
|
1393
1424
|
this.handleExclusiveClass(note, channelNumber, startTime);
|
|
1394
1425
|
this.handleDrumExclusiveClass(note, channelNumber, startTime);
|
|
1395
1426
|
const scheduledNotes = channel.scheduledNotes;
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
noteList.push(note);
|
|
1399
|
-
}
|
|
1400
|
-
else {
|
|
1401
|
-
noteList = [note];
|
|
1402
|
-
scheduledNotes.set(noteNumber, noteList);
|
|
1403
|
-
}
|
|
1404
|
-
if (this.isDrumNoteOffException(channel, noteNumber)) {
|
|
1405
|
-
const stopTime = startTime + note.bufferSource.buffer.duration;
|
|
1406
|
-
const index = noteList.length - 1;
|
|
1407
|
-
const promise = new Promise((resolve) => {
|
|
1408
|
-
note.bufferSource.onended = () => {
|
|
1409
|
-
noteList[index] = undefined;
|
|
1410
|
-
this.disconnectNote(note);
|
|
1411
|
-
resolve();
|
|
1412
|
-
};
|
|
1413
|
-
note.bufferSource.stop(stopTime);
|
|
1414
|
-
});
|
|
1415
|
-
this.notePromises.push(promise);
|
|
1416
|
-
}
|
|
1427
|
+
note.index = scheduledNotes.length;
|
|
1428
|
+
scheduledNotes.push(note);
|
|
1417
1429
|
}
|
|
1418
1430
|
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1419
1431
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1420
|
-
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime,
|
|
1432
|
+
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, undefined);
|
|
1421
1433
|
}
|
|
1422
1434
|
disconnectNote(note) {
|
|
1423
1435
|
note.bufferSource.disconnect();
|
|
@@ -1442,84 +1454,71 @@ export class MidyGM2 {
|
|
|
1442
1454
|
note.chorusEffectsSend.disconnect();
|
|
1443
1455
|
}
|
|
1444
1456
|
}
|
|
1445
|
-
|
|
1446
|
-
const
|
|
1457
|
+
releaseNote(channel, note, endTime) {
|
|
1458
|
+
const volRelease = endTime + note.voiceParams.volRelease;
|
|
1459
|
+
const modRelease = endTime + note.voiceParams.modRelease;
|
|
1460
|
+
const stopTime = Math.min(volRelease, modRelease);
|
|
1461
|
+
note.filterNode.frequency
|
|
1462
|
+
.cancelScheduledValues(endTime)
|
|
1463
|
+
.linearRampToValueAtTime(0, modRelease);
|
|
1447
1464
|
note.volumeEnvelopeNode.gain
|
|
1448
1465
|
.cancelScheduledValues(endTime)
|
|
1449
|
-
.linearRampToValueAtTime(0,
|
|
1450
|
-
note.ending = true;
|
|
1451
|
-
this.scheduleTask(() => {
|
|
1452
|
-
note.bufferSource.loop = false;
|
|
1453
|
-
}, stopTime);
|
|
1466
|
+
.linearRampToValueAtTime(0, volRelease);
|
|
1454
1467
|
return new Promise((resolve) => {
|
|
1455
|
-
|
|
1456
|
-
|
|
1468
|
+
this.scheduleTask(() => {
|
|
1469
|
+
const bufferSource = note.bufferSource;
|
|
1470
|
+
bufferSource.loop = false;
|
|
1471
|
+
bufferSource.stop(stopTime);
|
|
1457
1472
|
this.disconnectNote(note);
|
|
1473
|
+
channel.scheduledNotes[note.index] = undefined;
|
|
1458
1474
|
resolve();
|
|
1459
|
-
};
|
|
1460
|
-
note.bufferSource.stop(stopTime);
|
|
1475
|
+
}, stopTime);
|
|
1461
1476
|
});
|
|
1462
1477
|
}
|
|
1463
|
-
|
|
1464
|
-
for (let i = 0; i < noteList.length; i++) {
|
|
1465
|
-
const note = noteList[i];
|
|
1466
|
-
if (!note)
|
|
1467
|
-
continue;
|
|
1468
|
-
if (note.ending)
|
|
1469
|
-
continue;
|
|
1470
|
-
return [note, i];
|
|
1471
|
-
}
|
|
1472
|
-
}
|
|
1473
|
-
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force, portamentoNoteNumber) {
|
|
1478
|
+
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
|
|
1474
1479
|
const channel = this.channels[channelNumber];
|
|
1475
|
-
if (this.isDrumNoteOffException(channel, noteNumber))
|
|
1476
|
-
return;
|
|
1477
1480
|
const state = channel.state;
|
|
1478
1481
|
if (!force) {
|
|
1479
|
-
if (
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
return;
|
|
1490
|
-
const [note, i] = noteOffTarget;
|
|
1491
|
-
if (0.5 <= state.portamento && portamentoNoteNumber !== undefined) {
|
|
1492
|
-
const portamentoTime = endTime + this.getPortamentoTime(channel);
|
|
1493
|
-
const deltaNote = portamentoNoteNumber - noteNumber;
|
|
1494
|
-
const baseRate = note.voiceParams.playbackRate;
|
|
1495
|
-
const targetRate = baseRate * Math.pow(2, deltaNote / 12);
|
|
1496
|
-
note.bufferSource.playbackRate
|
|
1497
|
-
.cancelScheduledValues(endTime)
|
|
1498
|
-
.linearRampToValueAtTime(targetRate, portamentoTime);
|
|
1499
|
-
return this.stopNote(endTime, portamentoTime, noteList, i);
|
|
1482
|
+
if (channel.isDrum) {
|
|
1483
|
+
if (!this.isLoopDrum(channel, noteNumber))
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
else {
|
|
1487
|
+
if (0.5 <= state.sustainPedal)
|
|
1488
|
+
return;
|
|
1489
|
+
if (0.5 <= state.sostenutoPedal)
|
|
1490
|
+
return;
|
|
1491
|
+
}
|
|
1500
1492
|
}
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1493
|
+
const note = this.findNoteOffTarget(channel, noteNumber);
|
|
1494
|
+
if (!note)
|
|
1495
|
+
return;
|
|
1496
|
+
note.ending = true;
|
|
1497
|
+
this.releaseNote(channel, note, endTime);
|
|
1498
|
+
}
|
|
1499
|
+
findNoteOffTarget(channel, noteNumber) {
|
|
1500
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
1501
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
1502
|
+
const note = scheduledNotes[i];
|
|
1503
|
+
if (!note)
|
|
1504
|
+
continue;
|
|
1505
|
+
if (note.ending)
|
|
1506
|
+
continue;
|
|
1507
|
+
if (note.noteNumber !== noteNumber)
|
|
1508
|
+
continue;
|
|
1509
|
+
return note;
|
|
1510
1510
|
}
|
|
1511
1511
|
}
|
|
1512
1512
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1513
1513
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1514
|
-
return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false
|
|
1515
|
-
undefined);
|
|
1514
|
+
return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false);
|
|
1516
1515
|
}
|
|
1517
1516
|
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
1518
1517
|
const velocity = halfVelocity * 2;
|
|
1519
1518
|
const channel = this.channels[channelNumber];
|
|
1520
1519
|
const promises = [];
|
|
1521
1520
|
for (let i = 0; i < channel.sustainNotes.length; i++) {
|
|
1522
|
-
const promise = this.
|
|
1521
|
+
const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
|
|
1523
1522
|
promises.push(promise);
|
|
1524
1523
|
}
|
|
1525
1524
|
channel.sustainNotes = [];
|
|
@@ -1529,12 +1528,14 @@ export class MidyGM2 {
|
|
|
1529
1528
|
const velocity = halfVelocity * 2;
|
|
1530
1529
|
const channel = this.channels[channelNumber];
|
|
1531
1530
|
const promises = [];
|
|
1531
|
+
const sostenutoNotes = channel.sostenutoNotes;
|
|
1532
1532
|
channel.state.sostenutoPedal = 0;
|
|
1533
|
-
|
|
1534
|
-
const
|
|
1533
|
+
for (let i = 0; i < sostenutoNotes.length; i++) {
|
|
1534
|
+
const note = sostenutoNotes[i];
|
|
1535
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime);
|
|
1535
1536
|
promises.push(promise);
|
|
1536
|
-
}
|
|
1537
|
-
channel.sostenutoNotes
|
|
1537
|
+
}
|
|
1538
|
+
channel.sostenutoNotes = [];
|
|
1538
1539
|
return promises;
|
|
1539
1540
|
}
|
|
1540
1541
|
handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
|
|
@@ -1584,7 +1585,7 @@ export class MidyGM2 {
|
|
|
1584
1585
|
channel.detune += pressureDepth * (next - prev);
|
|
1585
1586
|
}
|
|
1586
1587
|
const table = channel.channelPressureTable;
|
|
1587
|
-
this.
|
|
1588
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
1588
1589
|
this.setControllerParameters(channel, note, table);
|
|
1589
1590
|
});
|
|
1590
1591
|
this.applyVoiceParams(channel, 13);
|
|
@@ -1641,10 +1642,13 @@ export class MidyGM2 {
|
|
|
1641
1642
|
.setValueAtTime(volumeDepth, scheduleTime);
|
|
1642
1643
|
}
|
|
1643
1644
|
setReverbEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1645
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
|
|
1646
|
+
let value = note.voiceParams.reverbEffectsSend;
|
|
1647
|
+
if (0 <= keyBasedValue) {
|
|
1648
|
+
value *= keyBasedValue / 127 / channel.state.reverbSendLevel;
|
|
1649
|
+
}
|
|
1644
1650
|
if (0 < prevValue) {
|
|
1645
|
-
if (0 <
|
|
1646
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
|
|
1647
|
-
const value = note.voiceParams.reverbEffectsSend + keyBasedValue;
|
|
1651
|
+
if (0 < value) {
|
|
1648
1652
|
note.reverbEffectsSend.gain
|
|
1649
1653
|
.cancelScheduledValues(scheduleTime)
|
|
1650
1654
|
.setValueAtTime(value, scheduleTime);
|
|
@@ -1654,10 +1658,10 @@ export class MidyGM2 {
|
|
|
1654
1658
|
}
|
|
1655
1659
|
}
|
|
1656
1660
|
else {
|
|
1657
|
-
if (0 <
|
|
1661
|
+
if (0 < value) {
|
|
1658
1662
|
if (!note.reverbEffectsSend) {
|
|
1659
1663
|
note.reverbEffectsSend = new GainNode(this.audioContext, {
|
|
1660
|
-
gain:
|
|
1664
|
+
gain: value,
|
|
1661
1665
|
});
|
|
1662
1666
|
note.volumeNode.connect(note.reverbEffectsSend);
|
|
1663
1667
|
}
|
|
@@ -1666,10 +1670,13 @@ export class MidyGM2 {
|
|
|
1666
1670
|
}
|
|
1667
1671
|
}
|
|
1668
1672
|
setChorusEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1673
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
|
|
1674
|
+
let value = note.voiceParams.chorusEffectsSend;
|
|
1675
|
+
if (0 <= keyBasedValue) {
|
|
1676
|
+
value *= keyBasedValue / 127 / channel.state.chorusSendLevel;
|
|
1677
|
+
}
|
|
1669
1678
|
if (0 < prevValue) {
|
|
1670
|
-
if (0 <
|
|
1671
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
|
|
1672
|
-
const value = note.voiceParams.chorusEffectsSend + keyBasedValue;
|
|
1679
|
+
if (0 < vaule) {
|
|
1673
1680
|
note.chorusEffectsSend.gain
|
|
1674
1681
|
.cancelScheduledValues(scheduleTime)
|
|
1675
1682
|
.setValueAtTime(value, scheduleTime);
|
|
@@ -1679,10 +1686,10 @@ export class MidyGM2 {
|
|
|
1679
1686
|
}
|
|
1680
1687
|
}
|
|
1681
1688
|
else {
|
|
1682
|
-
if (0 <
|
|
1689
|
+
if (0 < value) {
|
|
1683
1690
|
if (!note.chorusEffectsSend) {
|
|
1684
1691
|
note.chorusEffectsSend = new GainNode(this.audioContext, {
|
|
1685
|
-
gain:
|
|
1692
|
+
gain: value,
|
|
1686
1693
|
});
|
|
1687
1694
|
note.volumeNode.connect(note.chorusEffectsSend);
|
|
1688
1695
|
}
|
|
@@ -1791,8 +1798,8 @@ export class MidyGM2 {
|
|
|
1791
1798
|
if (key in voiceParams)
|
|
1792
1799
|
noteVoiceParams[key] = voiceParams[key];
|
|
1793
1800
|
}
|
|
1794
|
-
if (0.5 <= channel.state.portamento && note.
|
|
1795
|
-
this.
|
|
1801
|
+
if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
1802
|
+
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
1796
1803
|
}
|
|
1797
1804
|
else {
|
|
1798
1805
|
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
@@ -1815,32 +1822,32 @@ export class MidyGM2 {
|
|
|
1815
1822
|
});
|
|
1816
1823
|
}
|
|
1817
1824
|
createControlChangeHandlers() {
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1825
|
+
const handlers = new Array(128);
|
|
1826
|
+
handlers[0] = this.setBankMSB;
|
|
1827
|
+
handlers[1] = this.setModulationDepth;
|
|
1828
|
+
handlers[5] = this.setPortamentoTime;
|
|
1829
|
+
handlers[6] = this.dataEntryMSB;
|
|
1830
|
+
handlers[7] = this.setVolume;
|
|
1831
|
+
handlers[10] = this.setPan;
|
|
1832
|
+
handlers[11] = this.setExpression;
|
|
1833
|
+
handlers[32] = this.setBankLSB;
|
|
1834
|
+
handlers[38] = this.dataEntryLSB;
|
|
1835
|
+
handlers[64] = this.setSustainPedal;
|
|
1836
|
+
handlers[65] = this.setPortamento;
|
|
1837
|
+
handlers[66] = this.setSostenutoPedal;
|
|
1838
|
+
handlers[67] = this.setSoftPedal;
|
|
1839
|
+
handlers[91] = this.setReverbSendLevel;
|
|
1840
|
+
handlers[93] = this.setChorusSendLevel;
|
|
1841
|
+
handlers[100] = this.setRPNLSB;
|
|
1842
|
+
handlers[101] = this.setRPNMSB;
|
|
1843
|
+
handlers[120] = this.allSoundOff;
|
|
1844
|
+
handlers[121] = this.resetAllControllers;
|
|
1845
|
+
handlers[123] = this.allNotesOff;
|
|
1846
|
+
handlers[124] = this.omniOff;
|
|
1847
|
+
handlers[125] = this.omniOn;
|
|
1848
|
+
handlers[126] = this.monoOn;
|
|
1849
|
+
handlers[127] = this.polyOn;
|
|
1850
|
+
return handlers;
|
|
1844
1851
|
}
|
|
1845
1852
|
handleControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1846
1853
|
const handler = this.controlChangeHandlers[controllerType];
|
|
@@ -1877,17 +1884,41 @@ export class MidyGM2 {
|
|
|
1877
1884
|
channel.state.modulationDepth = modulation / 127;
|
|
1878
1885
|
this.updateModulation(channel, scheduleTime);
|
|
1879
1886
|
}
|
|
1880
|
-
|
|
1887
|
+
updatePortamento(channel, scheduleTime) {
|
|
1888
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1889
|
+
if (0.5 <= channel.state.portamento) {
|
|
1890
|
+
if (0 <= note.portamentoNoteNumber) {
|
|
1891
|
+
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
1892
|
+
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
1893
|
+
this.setPortamentoPitchEnvelope(note, scheduleTime);
|
|
1894
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
else {
|
|
1898
|
+
if (0 <= note.portamentoNoteNumber) {
|
|
1899
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1900
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
1901
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1902
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
});
|
|
1906
|
+
}
|
|
1907
|
+
setPortamentoTime(channelNumber, portamentoTime, scheduleTime) {
|
|
1881
1908
|
const channel = this.channels[channelNumber];
|
|
1909
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1882
1910
|
channel.state.portamentoTime = portamentoTime / 127;
|
|
1911
|
+
if (channel.isDrum)
|
|
1912
|
+
return;
|
|
1913
|
+
this.updatePortamento(channel, scheduleTime);
|
|
1883
1914
|
}
|
|
1884
1915
|
setKeyBasedVolume(channel, scheduleTime) {
|
|
1885
1916
|
this.processScheduledNotes(channel, (note) => {
|
|
1886
1917
|
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1887
|
-
if (
|
|
1918
|
+
if (0 <= keyBasedValue) {
|
|
1888
1919
|
note.volumeNode.gain
|
|
1889
1920
|
.cancelScheduledValues(scheduleTime)
|
|
1890
|
-
.setValueAtTime(
|
|
1921
|
+
.setValueAtTime(keyBasedValue / 127, scheduleTime);
|
|
1891
1922
|
}
|
|
1892
1923
|
});
|
|
1893
1924
|
}
|
|
@@ -1908,8 +1939,8 @@ export class MidyGM2 {
|
|
|
1908
1939
|
setKeyBasedPan(channel, scheduleTime) {
|
|
1909
1940
|
this.processScheduledNotes(channel, (note) => {
|
|
1910
1941
|
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1911
|
-
if (
|
|
1912
|
-
const { gainLeft, gainRight } = this.panToGain(
|
|
1942
|
+
if (0 <= keyBasedValue) {
|
|
1943
|
+
const { gainLeft, gainRight } = this.panToGain(keyBasedValue / 127);
|
|
1913
1944
|
note.gainL.gain
|
|
1914
1945
|
.cancelScheduledValues(scheduleTime)
|
|
1915
1946
|
.setValueAtTime(gainLeft, scheduleTime);
|
|
@@ -1965,11 +1996,13 @@ export class MidyGM2 {
|
|
|
1965
1996
|
this.releaseSustainPedal(channelNumber, value, scheduleTime);
|
|
1966
1997
|
}
|
|
1967
1998
|
}
|
|
1968
|
-
setPortamento(channelNumber, value) {
|
|
1999
|
+
setPortamento(channelNumber, value, scheduleTime) {
|
|
1969
2000
|
const channel = this.channels[channelNumber];
|
|
1970
2001
|
if (channel.isDrum)
|
|
1971
2002
|
return;
|
|
2003
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1972
2004
|
channel.state.portamento = value / 127;
|
|
2005
|
+
this.updatePortamento(channel, scheduleTime);
|
|
1973
2006
|
}
|
|
1974
2007
|
setSostenutoPedal(channelNumber, value, scheduleTime) {
|
|
1975
2008
|
const channel = this.channels[channelNumber];
|
|
@@ -1978,12 +2011,19 @@ export class MidyGM2 {
|
|
|
1978
2011
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1979
2012
|
channel.state.sostenutoPedal = value / 127;
|
|
1980
2013
|
if (64 <= value) {
|
|
1981
|
-
|
|
2014
|
+
const sostenutoNotes = [];
|
|
2015
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
2016
|
+
sostenutoNotes.push(note);
|
|
2017
|
+
});
|
|
2018
|
+
channel.sostenutoNotes = sostenutoNotes;
|
|
1982
2019
|
}
|
|
1983
2020
|
else {
|
|
1984
2021
|
this.releaseSostenutoPedal(channelNumber, value, scheduleTime);
|
|
1985
2022
|
}
|
|
1986
2023
|
}
|
|
2024
|
+
getSoftPedalFactor(channel, note) {
|
|
2025
|
+
return 1 - (0.1 + (note.noteNumber / 127) * 0.2) * channel.state.softPedal;
|
|
2026
|
+
}
|
|
1987
2027
|
setSoftPedal(channelNumber, softPedal, scheduleTime) {
|
|
1988
2028
|
const channel = this.channels[channelNumber];
|
|
1989
2029
|
if (channel.isDrum)
|
|
@@ -1992,9 +2032,9 @@ export class MidyGM2 {
|
|
|
1992
2032
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1993
2033
|
state.softPedal = softPedal / 127;
|
|
1994
2034
|
this.processScheduledNotes(channel, (note) => {
|
|
1995
|
-
if (0.5 <= state.portamento && note.
|
|
1996
|
-
this.
|
|
1997
|
-
this.
|
|
2035
|
+
if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
2036
|
+
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2037
|
+
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
1998
2038
|
}
|
|
1999
2039
|
else {
|
|
2000
2040
|
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
@@ -2018,7 +2058,8 @@ export class MidyGM2 {
|
|
|
2018
2058
|
this.processScheduledNotes(channel, (note) => {
|
|
2019
2059
|
if (note.voiceParams.reverbEffectsSend <= 0)
|
|
2020
2060
|
return false;
|
|
2021
|
-
note.reverbEffectsSend
|
|
2061
|
+
if (note.reverbEffectsSend)
|
|
2062
|
+
note.reverbEffectsSend.disconnect();
|
|
2022
2063
|
});
|
|
2023
2064
|
}
|
|
2024
2065
|
}
|
|
@@ -2050,7 +2091,8 @@ export class MidyGM2 {
|
|
|
2050
2091
|
this.processScheduledNotes(channel, (note) => {
|
|
2051
2092
|
if (note.voiceParams.chorusEffectsSend <= 0)
|
|
2052
2093
|
return false;
|
|
2053
|
-
note.chorusEffectsSend
|
|
2094
|
+
if (note.chorusEffectsSend)
|
|
2095
|
+
note.chorusEffectsSend.disconnect();
|
|
2054
2096
|
});
|
|
2055
2097
|
}
|
|
2056
2098
|
}
|
|
@@ -2194,21 +2236,29 @@ export class MidyGM2 {
|
|
|
2194
2236
|
return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
|
|
2195
2237
|
}
|
|
2196
2238
|
resetAllStates(channelNumber) {
|
|
2239
|
+
const scheduleTime = this.audioContext.currentTime;
|
|
2197
2240
|
const channel = this.channels[channelNumber];
|
|
2198
2241
|
const state = channel.state;
|
|
2199
|
-
|
|
2200
|
-
|
|
2242
|
+
const entries = Object.entries(defaultControllerState);
|
|
2243
|
+
for (const [key, { type, defaultValue }] of entries) {
|
|
2244
|
+
if (128 <= type) {
|
|
2245
|
+
this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
2246
|
+
}
|
|
2247
|
+
else {
|
|
2248
|
+
state[key] = defaultValue;
|
|
2249
|
+
}
|
|
2201
2250
|
}
|
|
2202
|
-
for (const
|
|
2203
|
-
channel[
|
|
2251
|
+
for (const key of Object.keys(this.constructor.channelSettings)) {
|
|
2252
|
+
channel[key] = this.constructor.channelSettings[key];
|
|
2204
2253
|
}
|
|
2254
|
+
this.resetChannelTable(channel);
|
|
2205
2255
|
this.mode = "GM2";
|
|
2206
2256
|
this.masterFineTuning = 0; // cb
|
|
2207
2257
|
this.masterCoarseTuning = 0; // cb
|
|
2208
2258
|
}
|
|
2209
2259
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
|
|
2210
|
-
resetAllControllers(channelNumber) {
|
|
2211
|
-
const
|
|
2260
|
+
resetAllControllers(channelNumber, _value, scheduleTime) {
|
|
2261
|
+
const keys = [
|
|
2212
2262
|
"channelPressure",
|
|
2213
2263
|
"pitchWheel",
|
|
2214
2264
|
"expression",
|
|
@@ -2220,10 +2270,17 @@ export class MidyGM2 {
|
|
|
2220
2270
|
];
|
|
2221
2271
|
const channel = this.channels[channelNumber];
|
|
2222
2272
|
const state = channel.state;
|
|
2223
|
-
for (let i = 0; i <
|
|
2224
|
-
const
|
|
2225
|
-
|
|
2273
|
+
for (let i = 0; i < keys.length; i++) {
|
|
2274
|
+
const key = keys[i];
|
|
2275
|
+
const { type, defaultValue } = defaultControllerState[key];
|
|
2276
|
+
if (128 <= type) {
|
|
2277
|
+
this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
2278
|
+
}
|
|
2279
|
+
else {
|
|
2280
|
+
state[key] = defaultValue;
|
|
2281
|
+
}
|
|
2226
2282
|
}
|
|
2283
|
+
this.setPitchBend(channelNumber, 8192, scheduleTime);
|
|
2227
2284
|
const settingTypes = [
|
|
2228
2285
|
"rpnMSB",
|
|
2229
2286
|
"rpnLSB",
|
|
@@ -2452,7 +2509,7 @@ export class MidyGM2 {
|
|
|
2452
2509
|
this.reverbEffect = options.reverbAlgorithm(audioContext);
|
|
2453
2510
|
}
|
|
2454
2511
|
getReverbTime(value) {
|
|
2455
|
-
return Math.
|
|
2512
|
+
return Math.exp((value - 40) * 0.025);
|
|
2456
2513
|
}
|
|
2457
2514
|
// mean free path equation
|
|
2458
2515
|
// https://repository.dl.itc.u-tokyo.ac.jp/record/8550/files/A31912.pdf
|
|
@@ -2609,6 +2666,8 @@ export class MidyGM2 {
|
|
|
2609
2666
|
if (!channelBitmap[i])
|
|
2610
2667
|
continue;
|
|
2611
2668
|
const channel = this.channels[i];
|
|
2669
|
+
if (channel.isDrum)
|
|
2670
|
+
continue;
|
|
2612
2671
|
for (let j = 0; j < 12; j++) {
|
|
2613
2672
|
const centValue = data[j + 7] - 64;
|
|
2614
2673
|
channel.scaleOctaveTuningTable[j] = centValue;
|
|
@@ -2645,7 +2704,13 @@ export class MidyGM2 {
|
|
|
2645
2704
|
setControllerParameters(channel, note, table) {
|
|
2646
2705
|
if (table[0] !== 64)
|
|
2647
2706
|
this.updateDetune(channel, note);
|
|
2648
|
-
if (
|
|
2707
|
+
if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
|
|
2708
|
+
if (table[1] !== 64)
|
|
2709
|
+
this.setPortamentoFilterEnvelope(channel, note);
|
|
2710
|
+
if (table[2] !== 64)
|
|
2711
|
+
this.setPortamentoVolumeEnvelope(channel, note);
|
|
2712
|
+
}
|
|
2713
|
+
else {
|
|
2649
2714
|
if (table[1] !== 64)
|
|
2650
2715
|
this.setFilterEnvelope(channel, note);
|
|
2651
2716
|
if (table[2] !== 64)
|
|
@@ -2660,7 +2725,10 @@ export class MidyGM2 {
|
|
|
2660
2725
|
}
|
|
2661
2726
|
handlePressureSysEx(data, tableName) {
|
|
2662
2727
|
const channelNumber = data[4];
|
|
2663
|
-
const
|
|
2728
|
+
const channel = this.channels[channelNumber];
|
|
2729
|
+
if (channel.isDrum)
|
|
2730
|
+
return;
|
|
2731
|
+
const table = channel[tableName];
|
|
2664
2732
|
for (let i = 5; i < data.length - 1; i += 2) {
|
|
2665
2733
|
const pp = data[i];
|
|
2666
2734
|
const rr = data[i + 1];
|
|
@@ -2670,8 +2738,13 @@ export class MidyGM2 {
|
|
|
2670
2738
|
initControlTable() {
|
|
2671
2739
|
const channelCount = 128;
|
|
2672
2740
|
const slotSize = 6;
|
|
2673
|
-
const defaultValues = [64, 64, 64, 0, 0, 0];
|
|
2674
2741
|
const table = new Uint8Array(channelCount * slotSize);
|
|
2742
|
+
return this.resetControlTable(table);
|
|
2743
|
+
}
|
|
2744
|
+
resetControlTable(table) {
|
|
2745
|
+
const channelCount = 128;
|
|
2746
|
+
const slotSize = 6;
|
|
2747
|
+
const defaultValues = [64, 64, 64, 0, 0, 0];
|
|
2675
2748
|
for (let ch = 0; ch < channelCount; ch++) {
|
|
2676
2749
|
const offset = ch * slotSize;
|
|
2677
2750
|
table.set(defaultValues, offset);
|
|
@@ -2688,8 +2761,11 @@ export class MidyGM2 {
|
|
|
2688
2761
|
}
|
|
2689
2762
|
handleControlChangeSysEx(data) {
|
|
2690
2763
|
const channelNumber = data[4];
|
|
2764
|
+
const channel = this.channels[channelNumber];
|
|
2765
|
+
if (channel.isDrum)
|
|
2766
|
+
return;
|
|
2691
2767
|
const controllerType = data[5];
|
|
2692
|
-
const table =
|
|
2768
|
+
const table = channel.controlTable[controllerType];
|
|
2693
2769
|
for (let i = 6; i < data.length - 1; i += 2) {
|
|
2694
2770
|
const pp = data[i];
|
|
2695
2771
|
const rr = data[i + 1];
|
|
@@ -2699,17 +2775,20 @@ export class MidyGM2 {
|
|
|
2699
2775
|
getKeyBasedInstrumentControlValue(channel, keyNumber, controllerType) {
|
|
2700
2776
|
const index = keyNumber * 128 + controllerType;
|
|
2701
2777
|
const controlValue = channel.keyBasedInstrumentControlTable[index];
|
|
2702
|
-
return
|
|
2778
|
+
return controlValue;
|
|
2703
2779
|
}
|
|
2704
2780
|
handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
|
|
2705
2781
|
const channelNumber = data[4];
|
|
2782
|
+
const channel = this.channels[channelNumber];
|
|
2783
|
+
if (channel.isDrum)
|
|
2784
|
+
return;
|
|
2706
2785
|
const keyNumber = data[5];
|
|
2707
|
-
const table =
|
|
2786
|
+
const table = channel.keyBasedInstrumentControlTable;
|
|
2708
2787
|
for (let i = 6; i < data.length - 1; i += 2) {
|
|
2709
2788
|
const controllerType = data[i];
|
|
2710
2789
|
const value = data[i + 1];
|
|
2711
2790
|
const index = keyNumber * 128 + controllerType;
|
|
2712
|
-
table[index] = value
|
|
2791
|
+
table[index] = value;
|
|
2713
2792
|
}
|
|
2714
2793
|
this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
|
|
2715
2794
|
}
|