@marmooo/midy 0.3.0 → 0.3.2
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 +2 -1
- package/esm/midy-GM1.d.ts +13 -50
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +164 -160
- package/esm/midy-GM2.d.ts +27 -83
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +390 -275
- package/esm/midy-GMLite.d.ts +12 -49
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +170 -163
- package/esm/midy.d.ts +31 -107
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +423 -306
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +13 -50
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +164 -160
- package/script/midy-GM2.d.ts +27 -83
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +390 -275
- package/script/midy-GMLite.d.ts +12 -49
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +170 -163
- package/script/midy.d.ts +31 -107
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +423 -306
package/script/midy.js
CHANGED
|
@@ -3,60 +3,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Midy = void 0;
|
|
4
4
|
const midi_file_1 = require("midi-file");
|
|
5
5
|
const soundfont_parser_1 = require("@marmooo/soundfont-parser");
|
|
6
|
-
// 2-3 times faster than Map
|
|
7
|
-
class SparseMap {
|
|
8
|
-
constructor(size) {
|
|
9
|
-
this.data = new Array(size);
|
|
10
|
-
this.activeIndices = [];
|
|
11
|
-
}
|
|
12
|
-
set(key, value) {
|
|
13
|
-
if (this.data[key] === undefined) {
|
|
14
|
-
this.activeIndices.push(key);
|
|
15
|
-
}
|
|
16
|
-
this.data[key] = value;
|
|
17
|
-
}
|
|
18
|
-
get(key) {
|
|
19
|
-
return this.data[key];
|
|
20
|
-
}
|
|
21
|
-
delete(key) {
|
|
22
|
-
if (this.data[key] !== undefined) {
|
|
23
|
-
this.data[key] = undefined;
|
|
24
|
-
const index = this.activeIndices.indexOf(key);
|
|
25
|
-
if (index !== -1) {
|
|
26
|
-
this.activeIndices.splice(index, 1);
|
|
27
|
-
}
|
|
28
|
-
return true;
|
|
29
|
-
}
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
has(key) {
|
|
33
|
-
return this.data[key] !== undefined;
|
|
34
|
-
}
|
|
35
|
-
get size() {
|
|
36
|
-
return this.activeIndices.length;
|
|
37
|
-
}
|
|
38
|
-
clear() {
|
|
39
|
-
for (let i = 0; i < this.activeIndices.length; i++) {
|
|
40
|
-
const key = this.activeIndices[i];
|
|
41
|
-
this.data[key] = undefined;
|
|
42
|
-
}
|
|
43
|
-
this.activeIndices = [];
|
|
44
|
-
}
|
|
45
|
-
*[Symbol.iterator]() {
|
|
46
|
-
for (let i = 0; i < this.activeIndices.length; i++) {
|
|
47
|
-
const key = this.activeIndices[i];
|
|
48
|
-
yield [key, this.data[key]];
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
forEach(callback) {
|
|
52
|
-
for (let i = 0; i < this.activeIndices.length; i++) {
|
|
53
|
-
const key = this.activeIndices[i];
|
|
54
|
-
callback(this.data[key], key, this);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
6
|
class Note {
|
|
59
7
|
constructor(noteNumber, velocity, startTime, voice, voiceParams) {
|
|
8
|
+
Object.defineProperty(this, "index", {
|
|
9
|
+
enumerable: true,
|
|
10
|
+
configurable: true,
|
|
11
|
+
writable: true,
|
|
12
|
+
value: -1
|
|
13
|
+
});
|
|
14
|
+
Object.defineProperty(this, "noteOffEvent", {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
configurable: true,
|
|
17
|
+
writable: true,
|
|
18
|
+
value: void 0
|
|
19
|
+
});
|
|
60
20
|
Object.defineProperty(this, "bufferSource", {
|
|
61
21
|
enumerable: true,
|
|
62
22
|
configurable: true,
|
|
@@ -141,11 +101,11 @@ class Note {
|
|
|
141
101
|
writable: true,
|
|
142
102
|
value: void 0
|
|
143
103
|
});
|
|
144
|
-
Object.defineProperty(this, "
|
|
104
|
+
Object.defineProperty(this, "portamentoNoteNumber", {
|
|
145
105
|
enumerable: true,
|
|
146
106
|
configurable: true,
|
|
147
107
|
writable: true,
|
|
148
|
-
value:
|
|
108
|
+
value: -1
|
|
149
109
|
});
|
|
150
110
|
Object.defineProperty(this, "pressure", {
|
|
151
111
|
enumerable: true,
|
|
@@ -207,7 +167,7 @@ const defaultControllerState = {
|
|
|
207
167
|
portamentoTime: { type: 128 + 5, defaultValue: 0 },
|
|
208
168
|
// dataMSB: { type: 128 + 6, defaultValue: 0, },
|
|
209
169
|
volume: { type: 128 + 7, defaultValue: 100 / 127 },
|
|
210
|
-
pan: { type: 128 + 10, defaultValue:
|
|
170
|
+
pan: { type: 128 + 10, defaultValue: 64 / 127 },
|
|
211
171
|
expression: { type: 128 + 11, defaultValue: 1 },
|
|
212
172
|
// bankLSB: { type: 128 + 32, defaultValue: 0, },
|
|
213
173
|
// dataLSB: { type: 128 + 38, defaultValue: 0, },
|
|
@@ -215,14 +175,14 @@ const defaultControllerState = {
|
|
|
215
175
|
portamento: { type: 128 + 65, defaultValue: 0 },
|
|
216
176
|
sostenutoPedal: { type: 128 + 66, defaultValue: 0 },
|
|
217
177
|
softPedal: { type: 128 + 67, defaultValue: 0 },
|
|
218
|
-
filterResonance: { type: 128 + 71, defaultValue:
|
|
219
|
-
releaseTime: { type: 128 + 72, defaultValue:
|
|
220
|
-
attackTime: { type: 128 + 73, defaultValue:
|
|
221
|
-
brightness: { type: 128 + 74, defaultValue:
|
|
222
|
-
decayTime: { type: 128 + 75, defaultValue:
|
|
223
|
-
vibratoRate: { type: 128 + 76, defaultValue:
|
|
224
|
-
vibratoDepth: { type: 128 + 77, defaultValue:
|
|
225
|
-
vibratoDelay: { type: 128 + 78, defaultValue:
|
|
178
|
+
filterResonance: { type: 128 + 71, defaultValue: 64 / 127 },
|
|
179
|
+
releaseTime: { type: 128 + 72, defaultValue: 64 / 127 },
|
|
180
|
+
attackTime: { type: 128 + 73, defaultValue: 64 / 127 },
|
|
181
|
+
brightness: { type: 128 + 74, defaultValue: 64 / 127 },
|
|
182
|
+
decayTime: { type: 128 + 75, defaultValue: 64 / 127 },
|
|
183
|
+
vibratoRate: { type: 128 + 76, defaultValue: 64 / 127 },
|
|
184
|
+
vibratoDepth: { type: 128 + 77, defaultValue: 64 / 127 },
|
|
185
|
+
vibratoDelay: { type: 128 + 78, defaultValue: 64 / 127 },
|
|
226
186
|
reverbSendLevel: { type: 128 + 91, defaultValue: 0 },
|
|
227
187
|
chorusSendLevel: { type: 128 + 93, defaultValue: 0 },
|
|
228
188
|
// dataIncrement: { type: 128 + 96, defaultValue: 0 },
|
|
@@ -497,7 +457,7 @@ class Midy {
|
|
|
497
457
|
initSoundFontTable() {
|
|
498
458
|
const table = new Array(128);
|
|
499
459
|
for (let i = 0; i < 128; i++) {
|
|
500
|
-
table[i] = new
|
|
460
|
+
table[i] = new Map();
|
|
501
461
|
}
|
|
502
462
|
return table;
|
|
503
463
|
}
|
|
@@ -544,18 +504,25 @@ class Midy {
|
|
|
544
504
|
merger,
|
|
545
505
|
};
|
|
546
506
|
}
|
|
507
|
+
resetChannelTable(channel) {
|
|
508
|
+
this.resetControlTable(channel.controlTable);
|
|
509
|
+
channel.scaleOctaveTuningTable.fill(0); // [-100, 100] cent
|
|
510
|
+
channel.channelPressureTable.set([64, 64, 64, 0, 0, 0]);
|
|
511
|
+
channel.polyphonicKeyPressureTable.set([64, 64, 64, 0, 0, 0]);
|
|
512
|
+
channel.keyBasedInstrumentControlTable.fill(0); // [-64, 63]
|
|
513
|
+
}
|
|
547
514
|
createChannels(audioContext) {
|
|
548
515
|
const channels = Array.from({ length: this.numChannels }, () => {
|
|
549
516
|
return {
|
|
550
517
|
currentBufferSource: null,
|
|
551
518
|
isDrum: false,
|
|
552
|
-
...this.constructor.channelSettings,
|
|
553
519
|
state: new ControllerState(),
|
|
554
|
-
|
|
520
|
+
...this.constructor.channelSettings,
|
|
555
521
|
...this.setChannelAudioNodes(audioContext),
|
|
556
|
-
scheduledNotes:
|
|
522
|
+
scheduledNotes: [],
|
|
557
523
|
sustainNotes: [],
|
|
558
|
-
sostenutoNotes:
|
|
524
|
+
sostenutoNotes: [],
|
|
525
|
+
controlTable: this.initControlTable(),
|
|
559
526
|
scaleOctaveTuningTable: new Float32Array(12), // [-100, 100] cent
|
|
560
527
|
channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
561
528
|
polyphonicKeyPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
@@ -603,46 +570,20 @@ class Midy {
|
|
|
603
570
|
}
|
|
604
571
|
return bufferSource;
|
|
605
572
|
}
|
|
606
|
-
|
|
607
|
-
const endEvent = this.timeline[queueIndex];
|
|
608
|
-
if (!this.channels[endEvent.channel].portamento)
|
|
609
|
-
return;
|
|
610
|
-
const endTime = endEvent.startTime;
|
|
611
|
-
let target;
|
|
612
|
-
while (++queueIndex < this.timeline.length) {
|
|
613
|
-
const event = this.timeline[queueIndex];
|
|
614
|
-
if (endTime !== event.startTime)
|
|
615
|
-
break;
|
|
616
|
-
if (event.type !== "noteOn")
|
|
617
|
-
continue;
|
|
618
|
-
if (!target || event.noteNumber < target.noteNumber) {
|
|
619
|
-
target = event;
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
return target;
|
|
623
|
-
}
|
|
624
|
-
async scheduleTimelineEvents(t, offset, queueIndex) {
|
|
573
|
+
async scheduleTimelineEvents(t, resumeTime, queueIndex) {
|
|
625
574
|
while (queueIndex < this.timeline.length) {
|
|
626
575
|
const event = this.timeline[queueIndex];
|
|
627
576
|
if (event.startTime > t + this.lookAhead)
|
|
628
577
|
break;
|
|
629
|
-
const
|
|
578
|
+
const delay = this.startDelay - resumeTime;
|
|
579
|
+
const startTime = event.startTime + delay;
|
|
630
580
|
switch (event.type) {
|
|
631
|
-
case "noteOn":
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
case "noteOff": {
|
|
638
|
-
const portamentoTarget = this.findPortamentoTarget(queueIndex);
|
|
639
|
-
if (portamentoTarget)
|
|
640
|
-
portamentoTarget.portamento = true;
|
|
641
|
-
const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false, // force
|
|
642
|
-
portamentoTarget?.noteNumber);
|
|
643
|
-
if (notePromise) {
|
|
644
|
-
this.notePromises.push(notePromise);
|
|
645
|
-
}
|
|
581
|
+
case "noteOn": {
|
|
582
|
+
const noteOffEvent = {
|
|
583
|
+
...event.noteOffEvent,
|
|
584
|
+
startTime: event.noteOffEvent.startTime + delay,
|
|
585
|
+
};
|
|
586
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, noteOffEvent);
|
|
646
587
|
break;
|
|
647
588
|
}
|
|
648
589
|
case "noteAftertouch":
|
|
@@ -681,7 +622,7 @@ class Midy {
|
|
|
681
622
|
this.isPaused = false;
|
|
682
623
|
this.startTime = this.audioContext.currentTime;
|
|
683
624
|
let queueIndex = this.getQueueIndex(this.resumeTime);
|
|
684
|
-
let
|
|
625
|
+
let resumeTime = this.resumeTime - this.startTime;
|
|
685
626
|
this.notePromises = [];
|
|
686
627
|
const schedulePlayback = async () => {
|
|
687
628
|
if (queueIndex >= this.timeline.length) {
|
|
@@ -690,18 +631,21 @@ class Midy {
|
|
|
690
631
|
this.exclusiveClassNotes.fill(undefined);
|
|
691
632
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
692
633
|
this.audioBufferCache.clear();
|
|
634
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
635
|
+
this.resetAllStates(i);
|
|
636
|
+
}
|
|
693
637
|
resolve();
|
|
694
638
|
return;
|
|
695
639
|
}
|
|
696
640
|
const now = this.audioContext.currentTime;
|
|
697
|
-
const t = now +
|
|
698
|
-
queueIndex = await this.scheduleTimelineEvents(t,
|
|
641
|
+
const t = now + resumeTime;
|
|
642
|
+
queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
|
|
699
643
|
if (this.isPausing) {
|
|
700
644
|
await this.stopNotes(0, true, now);
|
|
701
645
|
this.notePromises = [];
|
|
702
|
-
resolve();
|
|
703
646
|
this.isPausing = false;
|
|
704
647
|
this.isPaused = true;
|
|
648
|
+
resolve();
|
|
705
649
|
return;
|
|
706
650
|
}
|
|
707
651
|
else if (this.isStopping) {
|
|
@@ -710,9 +654,12 @@ class Midy {
|
|
|
710
654
|
this.exclusiveClassNotes.fill(undefined);
|
|
711
655
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
712
656
|
this.audioBufferCache.clear();
|
|
713
|
-
|
|
657
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
658
|
+
this.resetAllStates(i);
|
|
659
|
+
}
|
|
714
660
|
this.isStopping = false;
|
|
715
661
|
this.isPaused = false;
|
|
662
|
+
resolve();
|
|
716
663
|
return;
|
|
717
664
|
}
|
|
718
665
|
else if (this.isSeeking) {
|
|
@@ -721,7 +668,7 @@ class Midy {
|
|
|
721
668
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
722
669
|
this.startTime = this.audioContext.currentTime;
|
|
723
670
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
724
|
-
|
|
671
|
+
resumeTime = this.resumeTime - this.startTime;
|
|
725
672
|
this.isSeeking = false;
|
|
726
673
|
await schedulePlayback();
|
|
727
674
|
}
|
|
@@ -843,17 +790,52 @@ class Midy {
|
|
|
843
790
|
prevTempoTicks = event.ticks;
|
|
844
791
|
}
|
|
845
792
|
}
|
|
793
|
+
const activeNotes = new Array(this.channels.length * 128);
|
|
794
|
+
for (let i = 0; i < activeNotes.length; i++) {
|
|
795
|
+
activeNotes[i] = [];
|
|
796
|
+
}
|
|
797
|
+
for (let i = 0; i < timeline.length; i++) {
|
|
798
|
+
const event = timeline[i];
|
|
799
|
+
switch (event.type) {
|
|
800
|
+
case "noteOn": {
|
|
801
|
+
const index = event.channel * 128 + event.noteNumber;
|
|
802
|
+
activeNotes[index].push(event);
|
|
803
|
+
break;
|
|
804
|
+
}
|
|
805
|
+
case "noteOff": {
|
|
806
|
+
const index = event.channel * 128 + event.noteNumber;
|
|
807
|
+
const noteOn = activeNotes[index].pop();
|
|
808
|
+
if (noteOn) {
|
|
809
|
+
noteOn.noteOffEvent = event;
|
|
810
|
+
}
|
|
811
|
+
else {
|
|
812
|
+
const eventString = JSON.stringify(event, null, 2);
|
|
813
|
+
console.warn(`noteOff without matching noteOn: ${eventString}`);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
846
818
|
return { instruments, timeline };
|
|
847
819
|
}
|
|
820
|
+
stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
|
|
821
|
+
const channel = this.channels[channelNumber];
|
|
822
|
+
const promises = [];
|
|
823
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
824
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
|
|
825
|
+
this.notePromises.push(promise);
|
|
826
|
+
promises.push(promise);
|
|
827
|
+
});
|
|
828
|
+
return Promise.all(promises);
|
|
829
|
+
}
|
|
848
830
|
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
849
831
|
const channel = this.channels[channelNumber];
|
|
850
832
|
const promises = [];
|
|
851
833
|
this.processScheduledNotes(channel, (note) => {
|
|
852
|
-
const promise = this.scheduleNoteOff(channelNumber, note
|
|
834
|
+
const promise = this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, force);
|
|
853
835
|
this.notePromises.push(promise);
|
|
854
836
|
promises.push(promise);
|
|
855
837
|
});
|
|
856
|
-
channel.scheduledNotes
|
|
838
|
+
channel.scheduledNotes = [];
|
|
857
839
|
return Promise.all(promises);
|
|
858
840
|
}
|
|
859
841
|
stopNotes(velocity, force, scheduleTime) {
|
|
@@ -874,9 +856,6 @@ class Midy {
|
|
|
874
856
|
if (!this.isPlaying)
|
|
875
857
|
return;
|
|
876
858
|
this.isStopping = true;
|
|
877
|
-
for (let i = 0; i < this.channels.length; i++) {
|
|
878
|
-
this.resetAllStates(i);
|
|
879
|
-
}
|
|
880
859
|
}
|
|
881
860
|
pause() {
|
|
882
861
|
if (!this.isPlaying || this.isPaused)
|
|
@@ -911,35 +890,31 @@ class Midy {
|
|
|
911
890
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
912
891
|
}
|
|
913
892
|
processScheduledNotes(channel, callback) {
|
|
914
|
-
channel.scheduledNotes
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
getActiveNotes(channel, scheduleTime) {
|
|
924
|
-
const activeNotes = new SparseMap(128);
|
|
925
|
-
channel.scheduledNotes.forEach((noteList) => {
|
|
926
|
-
const activeNote = this.getActiveNote(noteList, scheduleTime);
|
|
927
|
-
if (activeNote) {
|
|
928
|
-
activeNotes.set(activeNote.noteNumber, activeNote);
|
|
929
|
-
}
|
|
930
|
-
});
|
|
931
|
-
return activeNotes;
|
|
893
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
894
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
895
|
+
const note = scheduledNotes[i];
|
|
896
|
+
if (!note)
|
|
897
|
+
continue;
|
|
898
|
+
if (note.ending)
|
|
899
|
+
continue;
|
|
900
|
+
callback(note);
|
|
901
|
+
}
|
|
932
902
|
}
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
903
|
+
processActiveNotes(channel, scheduleTime, callback) {
|
|
904
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
905
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
906
|
+
const note = scheduledNotes[i];
|
|
936
907
|
if (!note)
|
|
937
|
-
|
|
908
|
+
continue;
|
|
909
|
+
if (note.ending)
|
|
910
|
+
continue;
|
|
911
|
+
const noteOffEvent = note.noteOffEvent;
|
|
912
|
+
if (noteOffEvent && noteOffEvent.startTime < scheduleTime)
|
|
913
|
+
continue;
|
|
938
914
|
if (scheduleTime < note.startTime)
|
|
939
915
|
continue;
|
|
940
|
-
|
|
916
|
+
callback(note);
|
|
941
917
|
}
|
|
942
|
-
return noteList[0];
|
|
943
918
|
}
|
|
944
919
|
createConvolutionReverbImpulse(audioContext, decay, preDecay) {
|
|
945
920
|
const sampleRate = audioContext.sampleRate;
|
|
@@ -1106,24 +1081,94 @@ class Midy {
|
|
|
1106
1081
|
const noteDetune = this.calcNoteDetune(channel, note);
|
|
1107
1082
|
const pitchControl = this.getPitchControl(channel, note);
|
|
1108
1083
|
const detune = channel.detune + noteDetune + pitchControl;
|
|
1109
|
-
note.
|
|
1110
|
-
.
|
|
1111
|
-
.
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1084
|
+
if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
1085
|
+
const startTime = note.startTime;
|
|
1086
|
+
const deltaCent = (note.noteNumber - note.portamentoNoteNumber) * 100;
|
|
1087
|
+
const portamentoTime = startTime + this.getPortamentoTime(channel, note);
|
|
1088
|
+
note.bufferSource.detune
|
|
1089
|
+
.cancelScheduledValues(scheduleTime)
|
|
1090
|
+
.setValueAtTime(detune - deltaCent, scheduleTime)
|
|
1091
|
+
.linearRampToValueAtTime(detune, portamentoTime);
|
|
1092
|
+
}
|
|
1093
|
+
else {
|
|
1094
|
+
note.bufferSource.detune
|
|
1095
|
+
.cancelScheduledValues(scheduleTime)
|
|
1096
|
+
.setValueAtTime(detune, scheduleTime);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
getPortamentoTime(channel, note) {
|
|
1100
|
+
const deltaSemitone = Math.abs(note.noteNumber - note.portamentoNoteNumber);
|
|
1101
|
+
const value = Math.ceil(channel.state.portamentoTime * 127);
|
|
1102
|
+
return deltaSemitone / this.getPitchIncrementSpeed(value) / 10;
|
|
1103
|
+
}
|
|
1104
|
+
getPitchIncrementSpeed(value) {
|
|
1105
|
+
const points = [
|
|
1106
|
+
[0, 1000],
|
|
1107
|
+
[6, 100],
|
|
1108
|
+
[16, 20],
|
|
1109
|
+
[32, 10],
|
|
1110
|
+
[48, 5],
|
|
1111
|
+
[64, 2.5],
|
|
1112
|
+
[80, 1],
|
|
1113
|
+
[96, 0.4],
|
|
1114
|
+
[112, 0.15],
|
|
1115
|
+
[127, 0.01],
|
|
1116
|
+
];
|
|
1117
|
+
const logPoints = new Array(points.length);
|
|
1118
|
+
for (let i = 0; i < points.length; i++) {
|
|
1119
|
+
const [x, y] = points[i];
|
|
1120
|
+
if (value === x)
|
|
1121
|
+
return y;
|
|
1122
|
+
logPoints[i] = [x, Math.log(y)];
|
|
1123
|
+
}
|
|
1124
|
+
let startIndex = 0;
|
|
1125
|
+
for (let i = 1; i < logPoints.length; i++) {
|
|
1126
|
+
if (value <= logPoints[i][0]) {
|
|
1127
|
+
startIndex = i - 1;
|
|
1128
|
+
break;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
const [x0, y0] = logPoints[startIndex];
|
|
1132
|
+
const [x1, y1] = logPoints[startIndex + 1];
|
|
1133
|
+
const h = x1 - x0;
|
|
1134
|
+
const t = (value - x0) / h;
|
|
1135
|
+
let m0, m1;
|
|
1136
|
+
if (startIndex === 0) {
|
|
1137
|
+
m0 = (y1 - y0) / h;
|
|
1138
|
+
}
|
|
1139
|
+
else {
|
|
1140
|
+
const [xPrev, yPrev] = logPoints[startIndex - 1];
|
|
1141
|
+
m0 = (y1 - yPrev) / (x1 - xPrev);
|
|
1142
|
+
}
|
|
1143
|
+
if (startIndex === logPoints.length - 2) {
|
|
1144
|
+
m1 = (y1 - y0) / h;
|
|
1145
|
+
}
|
|
1146
|
+
else {
|
|
1147
|
+
const [xNext, yNext] = logPoints[startIndex + 2];
|
|
1148
|
+
m1 = (yNext - y0) / (xNext - x0);
|
|
1149
|
+
}
|
|
1150
|
+
// Cubic Hermite Spline
|
|
1151
|
+
const t2 = t * t;
|
|
1152
|
+
const t3 = t2 * t;
|
|
1153
|
+
const h00 = 2 * t3 - 3 * t2 + 1;
|
|
1154
|
+
const h10 = t3 - 2 * t2 + t;
|
|
1155
|
+
const h01 = -2 * t3 + 3 * t2;
|
|
1156
|
+
const h11 = t3 - t2;
|
|
1157
|
+
const y = h00 * y0 + h01 * y1 + h * (h10 * m0 + h11 * m1);
|
|
1158
|
+
return Math.exp(y);
|
|
1159
|
+
}
|
|
1160
|
+
setPortamentoVolumeEnvelope(channel, note, scheduleTime) {
|
|
1161
|
+
const state = channel.state;
|
|
1118
1162
|
const { voiceParams, startTime } = note;
|
|
1119
|
-
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation)
|
|
1163
|
+
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
|
|
1164
|
+
(1 + this.getAmplitudeControl(channel, note));
|
|
1120
1165
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
1121
1166
|
const volDelay = startTime + voiceParams.volDelay;
|
|
1122
|
-
const
|
|
1167
|
+
const volAttack = volDelay + voiceParams.volAttack * state.attackTime * 2;
|
|
1168
|
+
const volHold = volAttack + voiceParams.volHold;
|
|
1123
1169
|
note.volumeEnvelopeNode.gain
|
|
1124
1170
|
.cancelScheduledValues(scheduleTime)
|
|
1125
|
-
.setValueAtTime(
|
|
1126
|
-
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
1171
|
+
.setValueAtTime(sustainVolume, volHold);
|
|
1127
1172
|
}
|
|
1128
1173
|
setVolumeEnvelope(channel, note, scheduleTime) {
|
|
1129
1174
|
const state = channel.state;
|
|
@@ -1143,6 +1188,12 @@ class Midy {
|
|
|
1143
1188
|
.setValueAtTime(attackVolume, volHold)
|
|
1144
1189
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
1145
1190
|
}
|
|
1191
|
+
setPortamentoPitchEnvelope(note, scheduleTime) {
|
|
1192
|
+
const baseRate = note.voiceParams.playbackRate;
|
|
1193
|
+
note.bufferSource.playbackRate
|
|
1194
|
+
.cancelScheduledValues(scheduleTime)
|
|
1195
|
+
.setValueAtTime(baseRate, scheduleTime);
|
|
1196
|
+
}
|
|
1146
1197
|
setPitchEnvelope(note, scheduleTime) {
|
|
1147
1198
|
const { voiceParams } = note;
|
|
1148
1199
|
const baseRate = voiceParams.playbackRate;
|
|
@@ -1170,20 +1221,21 @@ class Midy {
|
|
|
1170
1221
|
const maxFrequency = 20000; // max Hz of initialFilterFc
|
|
1171
1222
|
return Math.max(minFrequency, Math.min(frequency, maxFrequency));
|
|
1172
1223
|
}
|
|
1173
|
-
|
|
1224
|
+
setPortamentoFilterEnvelope(channel, note, scheduleTime) {
|
|
1174
1225
|
const state = channel.state;
|
|
1175
1226
|
const { voiceParams, noteNumber, startTime } = note;
|
|
1176
1227
|
const softPedalFactor = 1 -
|
|
1177
1228
|
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1178
|
-
const
|
|
1179
|
-
|
|
1229
|
+
const baseCent = voiceParams.initialFilterFc +
|
|
1230
|
+
this.getFilterCutoffControl(channel, note);
|
|
1231
|
+
const baseFreq = this.centToHz(baseCent) * softPedalFactor *
|
|
1180
1232
|
state.brightness * 2;
|
|
1181
1233
|
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor * state.brightness * 2;
|
|
1182
1234
|
const sustainFreq = baseFreq +
|
|
1183
1235
|
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
1184
1236
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
1185
1237
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
1186
|
-
const portamentoTime = startTime + this.getPortamentoTime(channel);
|
|
1238
|
+
const portamentoTime = startTime + this.getPortamentoTime(channel, note);
|
|
1187
1239
|
const modDelay = startTime + voiceParams.modDelay;
|
|
1188
1240
|
note.filterNode.frequency
|
|
1189
1241
|
.cancelScheduledValues(scheduleTime)
|
|
@@ -1269,7 +1321,7 @@ class Midy {
|
|
|
1269
1321
|
return audioBuffer;
|
|
1270
1322
|
}
|
|
1271
1323
|
}
|
|
1272
|
-
async createNote(channel, voice, noteNumber, velocity, startTime,
|
|
1324
|
+
async createNote(channel, voice, noteNumber, velocity, startTime, isSF3) {
|
|
1273
1325
|
const now = this.audioContext.currentTime;
|
|
1274
1326
|
const state = channel.state;
|
|
1275
1327
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
@@ -1285,20 +1337,24 @@ class Midy {
|
|
|
1285
1337
|
type: "lowpass",
|
|
1286
1338
|
Q: voiceParams.initialFilterQ / 5 * state.filterResonance, // dB
|
|
1287
1339
|
});
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1340
|
+
const prevNote = channel.scheduledNotes.at(-1);
|
|
1341
|
+
if (prevNote && prevNote.noteNumber !== noteNumber) {
|
|
1342
|
+
note.portamentoNoteNumber = prevNote.noteNumber;
|
|
1343
|
+
}
|
|
1344
|
+
if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
1345
|
+
this.setPortamentoVolumeEnvelope(channel, note, now);
|
|
1346
|
+
this.setPortamentoFilterEnvelope(channel, note, now);
|
|
1347
|
+
this.setPortamentoPitchEnvelope(note, now);
|
|
1292
1348
|
}
|
|
1293
1349
|
else {
|
|
1294
|
-
note.portamento = false;
|
|
1295
1350
|
this.setVolumeEnvelope(channel, note, now);
|
|
1296
1351
|
this.setFilterEnvelope(channel, note, now);
|
|
1352
|
+
this.setPitchEnvelope(note, now);
|
|
1297
1353
|
}
|
|
1354
|
+
this.updateDetune(channel, note, now);
|
|
1298
1355
|
if (0 < state.vibratoDepth) {
|
|
1299
1356
|
this.startVibrato(channel, note, now);
|
|
1300
1357
|
}
|
|
1301
|
-
this.setPitchEnvelope(note, now);
|
|
1302
1358
|
if (0 < state.modulationDepth) {
|
|
1303
1359
|
this.startModulation(channel, note, now);
|
|
1304
1360
|
}
|
|
@@ -1344,9 +1400,8 @@ class Midy {
|
|
|
1344
1400
|
if (prev) {
|
|
1345
1401
|
const [prevNote, prevChannelNumber] = prev;
|
|
1346
1402
|
if (prevNote && !prevNote.ending) {
|
|
1347
|
-
this.scheduleNoteOff(prevChannelNumber, prevNote
|
|
1348
|
-
startTime, true
|
|
1349
|
-
undefined);
|
|
1403
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote, 0, // velocity,
|
|
1404
|
+
startTime, true);
|
|
1350
1405
|
}
|
|
1351
1406
|
}
|
|
1352
1407
|
this.exclusiveClassNotes[exclusiveClass] = [note, channelNumber];
|
|
@@ -1365,9 +1420,8 @@ class Midy {
|
|
|
1365
1420
|
channelNumber;
|
|
1366
1421
|
const prevNote = this.drumExclusiveClassNotes[index];
|
|
1367
1422
|
if (prevNote && !prevNote.ending) {
|
|
1368
|
-
this.scheduleNoteOff(channelNumber, prevNote
|
|
1369
|
-
startTime, true
|
|
1370
|
-
undefined);
|
|
1423
|
+
this.scheduleNoteOff(channelNumber, prevNote, 0, // velocity,
|
|
1424
|
+
startTime, true);
|
|
1371
1425
|
}
|
|
1372
1426
|
this.drumExclusiveClassNotes[index] = note;
|
|
1373
1427
|
}
|
|
@@ -1375,10 +1429,10 @@ class Midy {
|
|
|
1375
1429
|
if (!channel.isDrum)
|
|
1376
1430
|
return false;
|
|
1377
1431
|
const programNumber = channel.programNumber;
|
|
1378
|
-
return (programNumber === 48 && noteNumber === 88) ||
|
|
1379
|
-
(programNumber === 56 && 47 <= noteNumber && noteNumber <= 84);
|
|
1432
|
+
return !((programNumber === 48 && noteNumber === 88) ||
|
|
1433
|
+
(programNumber === 56 && 47 <= noteNumber && noteNumber <= 84));
|
|
1380
1434
|
}
|
|
1381
|
-
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime,
|
|
1435
|
+
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, noteOffEvent) {
|
|
1382
1436
|
const channel = this.channels[channelNumber];
|
|
1383
1437
|
const bankNumber = this.calcBank(channel, channelNumber);
|
|
1384
1438
|
const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
|
|
@@ -1389,7 +1443,8 @@ class Midy {
|
|
|
1389
1443
|
if (!voice)
|
|
1390
1444
|
return;
|
|
1391
1445
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
1392
|
-
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime,
|
|
1446
|
+
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
1447
|
+
note.noteOffEvent = noteOffEvent;
|
|
1393
1448
|
note.gainL.connect(channel.gainL);
|
|
1394
1449
|
note.gainR.connect(channel.gainR);
|
|
1395
1450
|
if (0.5 <= channel.state.sustainPedal) {
|
|
@@ -1398,33 +1453,39 @@ class Midy {
|
|
|
1398
1453
|
this.handleExclusiveClass(note, channelNumber, startTime);
|
|
1399
1454
|
this.handleDrumExclusiveClass(note, channelNumber, startTime);
|
|
1400
1455
|
const scheduledNotes = channel.scheduledNotes;
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
notes.push(note);
|
|
1404
|
-
}
|
|
1405
|
-
else {
|
|
1406
|
-
notes = [note];
|
|
1407
|
-
scheduledNotes.set(noteNumber, notes);
|
|
1408
|
-
}
|
|
1456
|
+
note.index = scheduledNotes.length;
|
|
1457
|
+
scheduledNotes.push(note);
|
|
1409
1458
|
if (this.isDrumNoteOffException(channel, noteNumber)) {
|
|
1410
1459
|
const stopTime = startTime + note.bufferSource.buffer.duration;
|
|
1411
|
-
const index = notes.length - 1;
|
|
1412
1460
|
const promise = new Promise((resolve) => {
|
|
1413
1461
|
note.bufferSource.onended = () => {
|
|
1414
|
-
|
|
1462
|
+
scheduledNotes[note.index] = undefined;
|
|
1463
|
+
this.disconnectNote(note);
|
|
1415
1464
|
resolve();
|
|
1416
1465
|
};
|
|
1417
1466
|
note.bufferSource.stop(stopTime);
|
|
1418
1467
|
});
|
|
1419
1468
|
this.notePromises.push(promise);
|
|
1420
1469
|
}
|
|
1470
|
+
else if (noteOffEvent) {
|
|
1471
|
+
if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
1472
|
+
const portamentoTime = this.getPortamentoTime(channel, note);
|
|
1473
|
+
const portamentoEndTime = startTime + portamentoTime;
|
|
1474
|
+
const notePromise = this.scheduleNoteOff(channelNumber, note, 0, // velocity
|
|
1475
|
+
Math.max(noteOffEvent.startTime, portamentoEndTime), false);
|
|
1476
|
+
this.notePromises.push(notePromise);
|
|
1477
|
+
}
|
|
1478
|
+
else {
|
|
1479
|
+
const notePromise = this.scheduleNoteOff(channelNumber, note, noteOffEvent.velocity, noteOffEvent.startTime, false);
|
|
1480
|
+
this.notePromises.push(notePromise);
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1421
1483
|
}
|
|
1422
1484
|
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1423
1485
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1424
|
-
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime,
|
|
1486
|
+
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, undefined);
|
|
1425
1487
|
}
|
|
1426
|
-
disconnectNote(note
|
|
1427
|
-
scheduledNotes[index] = null;
|
|
1488
|
+
disconnectNote(note) {
|
|
1428
1489
|
note.bufferSource.disconnect();
|
|
1429
1490
|
note.filterNode.disconnect();
|
|
1430
1491
|
note.volumeEnvelopeNode.disconnect();
|
|
@@ -1447,8 +1508,7 @@ class Midy {
|
|
|
1447
1508
|
note.chorusEffectsSend.disconnect();
|
|
1448
1509
|
}
|
|
1449
1510
|
}
|
|
1450
|
-
stopNote(
|
|
1451
|
-
const note = scheduledNotes[index];
|
|
1511
|
+
stopNote(channel, note, endTime, stopTime) {
|
|
1452
1512
|
note.volumeEnvelopeNode.gain
|
|
1453
1513
|
.cancelScheduledValues(endTime)
|
|
1454
1514
|
.linearRampToValueAtTime(0, stopTime);
|
|
@@ -1458,65 +1518,58 @@ class Midy {
|
|
|
1458
1518
|
}, stopTime);
|
|
1459
1519
|
return new Promise((resolve) => {
|
|
1460
1520
|
note.bufferSource.onended = () => {
|
|
1461
|
-
|
|
1521
|
+
channel.scheduledNotes[note.index] = undefined;
|
|
1522
|
+
this.disconnectNote(note);
|
|
1462
1523
|
resolve();
|
|
1463
1524
|
};
|
|
1464
1525
|
note.bufferSource.stop(stopTime);
|
|
1465
1526
|
});
|
|
1466
1527
|
}
|
|
1467
|
-
scheduleNoteOff(channelNumber,
|
|
1528
|
+
scheduleNoteOff(channelNumber, note, _velocity, endTime, force) {
|
|
1468
1529
|
const channel = this.channels[channelNumber];
|
|
1469
|
-
if (this.isDrumNoteOffException(channel, noteNumber))
|
|
1530
|
+
if (this.isDrumNoteOffException(channel, note.noteNumber))
|
|
1470
1531
|
return;
|
|
1471
1532
|
const state = channel.state;
|
|
1472
1533
|
if (!force) {
|
|
1473
1534
|
if (0.5 <= state.sustainPedal)
|
|
1474
1535
|
return;
|
|
1475
|
-
if (channel.
|
|
1536
|
+
if (0.5 <= channel.state.sostenutoPedal)
|
|
1476
1537
|
return;
|
|
1477
1538
|
}
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
const
|
|
1539
|
+
const volRelease = endTime +
|
|
1540
|
+
note.voiceParams.volRelease * channel.state.releaseTime * 2;
|
|
1541
|
+
const modRelease = endTime + note.voiceParams.modRelease;
|
|
1542
|
+
note.filterNode.frequency
|
|
1543
|
+
.cancelScheduledValues(endTime)
|
|
1544
|
+
.linearRampToValueAtTime(0, modRelease);
|
|
1545
|
+
const stopTime = Math.min(volRelease, modRelease);
|
|
1546
|
+
return this.stopNote(channel, note, endTime, stopTime);
|
|
1547
|
+
}
|
|
1548
|
+
findNoteOffTarget(channel, noteNumber) {
|
|
1549
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
1481
1550
|
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
1482
1551
|
const note = scheduledNotes[i];
|
|
1483
1552
|
if (!note)
|
|
1484
1553
|
continue;
|
|
1485
1554
|
if (note.ending)
|
|
1486
1555
|
continue;
|
|
1487
|
-
if (
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
const modRelease = endTime + note.voiceParams.modRelease;
|
|
1491
|
-
note.filterNode.frequency
|
|
1492
|
-
.cancelScheduledValues(endTime)
|
|
1493
|
-
.linearRampToValueAtTime(0, modRelease);
|
|
1494
|
-
const stopTime = Math.min(volRelease, modRelease);
|
|
1495
|
-
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
1496
|
-
}
|
|
1497
|
-
else {
|
|
1498
|
-
const portamentoTime = endTime + this.getPortamentoTime(channel);
|
|
1499
|
-
const deltaNote = portamentoNoteNumber - noteNumber;
|
|
1500
|
-
const baseRate = note.voiceParams.playbackRate;
|
|
1501
|
-
const targetRate = baseRate * Math.pow(2, deltaNote / 12);
|
|
1502
|
-
note.bufferSource.playbackRate
|
|
1503
|
-
.cancelScheduledValues(endTime)
|
|
1504
|
-
.linearRampToValueAtTime(targetRate, portamentoTime);
|
|
1505
|
-
return this.stopNote(endTime, portamentoTime, scheduledNotes, i);
|
|
1506
|
-
}
|
|
1556
|
+
if (note.noteNumber !== noteNumber)
|
|
1557
|
+
continue;
|
|
1558
|
+
return note;
|
|
1507
1559
|
}
|
|
1508
1560
|
}
|
|
1509
1561
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1510
1562
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1511
|
-
|
|
1512
|
-
|
|
1563
|
+
const channel = this.channels[channelNumber];
|
|
1564
|
+
const note = this.findNoteOffTarget(channel, noteNumber);
|
|
1565
|
+
return this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, false);
|
|
1513
1566
|
}
|
|
1514
1567
|
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
1515
1568
|
const velocity = halfVelocity * 2;
|
|
1516
1569
|
const channel = this.channels[channelNumber];
|
|
1517
1570
|
const promises = [];
|
|
1518
1571
|
for (let i = 0; i < channel.sustainNotes.length; i++) {
|
|
1519
|
-
const promise = this.
|
|
1572
|
+
const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i], velocity, scheduleTime);
|
|
1520
1573
|
promises.push(promise);
|
|
1521
1574
|
}
|
|
1522
1575
|
channel.sustainNotes = [];
|
|
@@ -1526,12 +1579,14 @@ class Midy {
|
|
|
1526
1579
|
const velocity = halfVelocity * 2;
|
|
1527
1580
|
const channel = this.channels[channelNumber];
|
|
1528
1581
|
const promises = [];
|
|
1582
|
+
const sostenutoNotes = channel.sostenutoNotes;
|
|
1529
1583
|
channel.state.sostenutoPedal = 0;
|
|
1530
|
-
|
|
1531
|
-
const
|
|
1584
|
+
for (let i = 0; i < sostenutoNotes.length; i++) {
|
|
1585
|
+
const note = sostenutoNotes[i];
|
|
1586
|
+
const promise = this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime);
|
|
1532
1587
|
promises.push(promise);
|
|
1533
|
-
}
|
|
1534
|
-
channel.sostenutoNotes
|
|
1588
|
+
}
|
|
1589
|
+
channel.sostenutoNotes = [];
|
|
1535
1590
|
return promises;
|
|
1536
1591
|
}
|
|
1537
1592
|
handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
|
|
@@ -1560,12 +1615,12 @@ class Midy {
|
|
|
1560
1615
|
const channel = this.channels[channelNumber];
|
|
1561
1616
|
channel.state.polyphonicKeyPressure = pressure / 127;
|
|
1562
1617
|
const table = channel.polyphonicKeyPressureTable;
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
}
|
|
1568
|
-
|
|
1618
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
1619
|
+
if (note.noteNumber === noteNumber) {
|
|
1620
|
+
this.setControllerParameters(channel, note, table);
|
|
1621
|
+
}
|
|
1622
|
+
});
|
|
1623
|
+
this.applyVoiceParams(channel, 10);
|
|
1569
1624
|
}
|
|
1570
1625
|
handleProgramChange(channelNumber, programNumber, _scheduleTime) {
|
|
1571
1626
|
const channel = this.channels[channelNumber];
|
|
@@ -1594,10 +1649,10 @@ class Midy {
|
|
|
1594
1649
|
channel.detune += pressureDepth * (next - prev);
|
|
1595
1650
|
}
|
|
1596
1651
|
const table = channel.channelPressureTable;
|
|
1597
|
-
this.
|
|
1652
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
1598
1653
|
this.setControllerParameters(channel, note, table);
|
|
1599
1654
|
});
|
|
1600
|
-
|
|
1655
|
+
this.applyVoiceParams(channel, 13);
|
|
1601
1656
|
}
|
|
1602
1657
|
handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
|
|
1603
1658
|
const pitchBend = msb * 128 + lsb;
|
|
@@ -1774,6 +1829,8 @@ class Midy {
|
|
|
1774
1829
|
state.set(channel.state.array);
|
|
1775
1830
|
state[2] = velocity / 127;
|
|
1776
1831
|
state[3] = noteNumber / 127;
|
|
1832
|
+
state[10] = state.polyphonicKeyPressure / 127;
|
|
1833
|
+
state[13] = state.channelPressure / 127;
|
|
1777
1834
|
return state;
|
|
1778
1835
|
}
|
|
1779
1836
|
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
@@ -1800,8 +1857,8 @@ class Midy {
|
|
|
1800
1857
|
if (key in voiceParams)
|
|
1801
1858
|
noteVoiceParams[key] = voiceParams[key];
|
|
1802
1859
|
}
|
|
1803
|
-
if (note.
|
|
1804
|
-
this.
|
|
1860
|
+
if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
1861
|
+
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
1805
1862
|
}
|
|
1806
1863
|
else {
|
|
1807
1864
|
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
@@ -1824,42 +1881,42 @@ class Midy {
|
|
|
1824
1881
|
});
|
|
1825
1882
|
}
|
|
1826
1883
|
createControlChangeHandlers() {
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1884
|
+
const handlers = new Array(128);
|
|
1885
|
+
handlers[0] = this.setBankMSB;
|
|
1886
|
+
handlers[1] = this.setModulationDepth;
|
|
1887
|
+
handlers[5] = this.setPortamentoTime;
|
|
1888
|
+
handlers[6] = this.dataEntryMSB;
|
|
1889
|
+
handlers[7] = this.setVolume;
|
|
1890
|
+
handlers[10] = this.setPan;
|
|
1891
|
+
handlers[11] = this.setExpression;
|
|
1892
|
+
handlers[32] = this.setBankLSB;
|
|
1893
|
+
handlers[38] = this.dataEntryLSB;
|
|
1894
|
+
handlers[64] = this.setSustainPedal;
|
|
1895
|
+
handlers[65] = this.setPortamento;
|
|
1896
|
+
handlers[66] = this.setSostenutoPedal;
|
|
1897
|
+
handlers[67] = this.setSoftPedal;
|
|
1898
|
+
handlers[71] = this.setFilterResonance;
|
|
1899
|
+
handlers[72] = this.setReleaseTime;
|
|
1900
|
+
handlers[73] = this.setAttackTime;
|
|
1901
|
+
handlers[74] = this.setBrightness;
|
|
1902
|
+
handlers[75] = this.setDecayTime;
|
|
1903
|
+
handlers[76] = this.setVibratoRate;
|
|
1904
|
+
handlers[77] = this.setVibratoDepth;
|
|
1905
|
+
handlers[78] = this.setVibratoDelay;
|
|
1906
|
+
handlers[91] = this.setReverbSendLevel;
|
|
1907
|
+
handlers[93] = this.setChorusSendLevel;
|
|
1908
|
+
handlers[96] = this.dataIncrement;
|
|
1909
|
+
handlers[97] = this.dataDecrement;
|
|
1910
|
+
handlers[100] = this.setRPNLSB;
|
|
1911
|
+
handlers[101] = this.setRPNMSB;
|
|
1912
|
+
handlers[120] = this.allSoundOff;
|
|
1913
|
+
handlers[121] = this.resetAllControllers;
|
|
1914
|
+
handlers[123] = this.allNotesOff;
|
|
1915
|
+
handlers[124] = this.omniOff;
|
|
1916
|
+
handlers[125] = this.omniOn;
|
|
1917
|
+
handlers[126] = this.monoOn;
|
|
1918
|
+
handlers[127] = this.polyOn;
|
|
1919
|
+
return handlers;
|
|
1863
1920
|
}
|
|
1864
1921
|
handleControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1865
1922
|
const handler = this.controlChangeHandlers[controllerType];
|
|
@@ -1896,9 +1953,33 @@ class Midy {
|
|
|
1896
1953
|
channel.state.modulationDepth = modulation / 127;
|
|
1897
1954
|
this.updateModulation(channel, scheduleTime);
|
|
1898
1955
|
}
|
|
1899
|
-
|
|
1956
|
+
updatePortamento(channel, scheduleTime) {
|
|
1957
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1958
|
+
if (0.5 <= channel.state.portamento) {
|
|
1959
|
+
if (0 <= note.portamentoNoteNumber) {
|
|
1960
|
+
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
1961
|
+
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
1962
|
+
this.setPortamentoPitchEnvelope(note, scheduleTime);
|
|
1963
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
else {
|
|
1967
|
+
if (0 <= note.portamentoNoteNumber) {
|
|
1968
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1969
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
1970
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1971
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
});
|
|
1975
|
+
}
|
|
1976
|
+
setPortamentoTime(channelNumber, portamentoTime, scheduleTime) {
|
|
1900
1977
|
const channel = this.channels[channelNumber];
|
|
1978
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1901
1979
|
channel.state.portamentoTime = portamentoTime / 127;
|
|
1980
|
+
if (channel.isDrum)
|
|
1981
|
+
return;
|
|
1982
|
+
this.updatePortamento(channel, scheduleTime);
|
|
1902
1983
|
}
|
|
1903
1984
|
setKeyBasedVolume(channel, scheduleTime) {
|
|
1904
1985
|
this.processScheduledNotes(channel, (note) => {
|
|
@@ -1956,7 +2037,7 @@ class Midy {
|
|
|
1956
2037
|
}
|
|
1957
2038
|
dataEntryLSB(channelNumber, value, scheduleTime) {
|
|
1958
2039
|
this.channels[channelNumber].dataLSB = value;
|
|
1959
|
-
this.handleRPN(channelNumber, scheduleTime);
|
|
2040
|
+
this.handleRPN(channelNumber, 0, scheduleTime);
|
|
1960
2041
|
}
|
|
1961
2042
|
updateChannelVolume(channel, scheduleTime) {
|
|
1962
2043
|
const state = channel.state;
|
|
@@ -1984,11 +2065,13 @@ class Midy {
|
|
|
1984
2065
|
this.releaseSustainPedal(channelNumber, value, scheduleTime);
|
|
1985
2066
|
}
|
|
1986
2067
|
}
|
|
1987
|
-
setPortamento(channelNumber, value) {
|
|
2068
|
+
setPortamento(channelNumber, value, scheduleTime) {
|
|
1988
2069
|
const channel = this.channels[channelNumber];
|
|
1989
2070
|
if (channel.isDrum)
|
|
1990
2071
|
return;
|
|
2072
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1991
2073
|
channel.state.portamento = value / 127;
|
|
2074
|
+
this.updatePortamento(channel, scheduleTime);
|
|
1992
2075
|
}
|
|
1993
2076
|
setSostenutoPedal(channelNumber, value, scheduleTime) {
|
|
1994
2077
|
const channel = this.channels[channelNumber];
|
|
@@ -1997,7 +2080,11 @@ class Midy {
|
|
|
1997
2080
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1998
2081
|
channel.state.sostenutoPedal = value / 127;
|
|
1999
2082
|
if (64 <= value) {
|
|
2000
|
-
|
|
2083
|
+
const sostenutoNotes = [];
|
|
2084
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
2085
|
+
sostenutoNotes.push(note);
|
|
2086
|
+
});
|
|
2087
|
+
channel.sostenutoNotes = sostenutoNotes;
|
|
2001
2088
|
}
|
|
2002
2089
|
else {
|
|
2003
2090
|
this.releaseSostenutoPedal(channelNumber, value, scheduleTime);
|
|
@@ -2007,12 +2094,13 @@ class Midy {
|
|
|
2007
2094
|
const channel = this.channels[channelNumber];
|
|
2008
2095
|
if (channel.isDrum)
|
|
2009
2096
|
return;
|
|
2097
|
+
const state = channel.state;
|
|
2010
2098
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2011
|
-
|
|
2099
|
+
state.softPedal = softPedal / 127;
|
|
2012
2100
|
this.processScheduledNotes(channel, (note) => {
|
|
2013
|
-
if (note.
|
|
2014
|
-
this.
|
|
2015
|
-
this.
|
|
2101
|
+
if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
2102
|
+
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2103
|
+
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2016
2104
|
}
|
|
2017
2105
|
else {
|
|
2018
2106
|
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
@@ -2026,25 +2114,25 @@ class Midy {
|
|
|
2026
2114
|
return;
|
|
2027
2115
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2028
2116
|
const state = channel.state;
|
|
2029
|
-
state.filterResonance = filterResonance /
|
|
2117
|
+
state.filterResonance = filterResonance / 127;
|
|
2030
2118
|
this.processScheduledNotes(channel, (note) => {
|
|
2031
2119
|
const Q = note.voiceParams.initialFilterQ / 5 * state.filterResonance;
|
|
2032
2120
|
note.filterNode.Q.setValueAtTime(Q, scheduleTime);
|
|
2033
2121
|
});
|
|
2034
2122
|
}
|
|
2035
|
-
setReleaseTime(channelNumber, releaseTime,
|
|
2123
|
+
setReleaseTime(channelNumber, releaseTime, scheduleTime) {
|
|
2036
2124
|
const channel = this.channels[channelNumber];
|
|
2037
2125
|
if (channel.isDrum)
|
|
2038
2126
|
return;
|
|
2039
2127
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2040
|
-
channel.state.releaseTime = releaseTime /
|
|
2128
|
+
channel.state.releaseTime = releaseTime / 127;
|
|
2041
2129
|
}
|
|
2042
2130
|
setAttackTime(channelNumber, attackTime, scheduleTime) {
|
|
2043
2131
|
const channel = this.channels[channelNumber];
|
|
2044
2132
|
if (channel.isDrum)
|
|
2045
2133
|
return;
|
|
2046
2134
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2047
|
-
channel.state.attackTime = attackTime /
|
|
2135
|
+
channel.state.attackTime = attackTime / 127;
|
|
2048
2136
|
this.processScheduledNotes(channel, (note) => {
|
|
2049
2137
|
if (note.startTime < scheduleTime)
|
|
2050
2138
|
return false;
|
|
@@ -2055,11 +2143,12 @@ class Midy {
|
|
|
2055
2143
|
const channel = this.channels[channelNumber];
|
|
2056
2144
|
if (channel.isDrum)
|
|
2057
2145
|
return;
|
|
2146
|
+
const state = channel.state;
|
|
2058
2147
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2059
|
-
|
|
2148
|
+
state.brightness = brightness / 127;
|
|
2060
2149
|
this.processScheduledNotes(channel, (note) => {
|
|
2061
|
-
if (note.
|
|
2062
|
-
this.
|
|
2150
|
+
if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
2151
|
+
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2063
2152
|
}
|
|
2064
2153
|
else {
|
|
2065
2154
|
this.setFilterEnvelope(channel, note);
|
|
@@ -2071,7 +2160,7 @@ class Midy {
|
|
|
2071
2160
|
if (channel.isDrum)
|
|
2072
2161
|
return;
|
|
2073
2162
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2074
|
-
channel.state.decayTime = dacayTime /
|
|
2163
|
+
channel.state.decayTime = dacayTime / 127;
|
|
2075
2164
|
this.processScheduledNotes(channel, (note) => {
|
|
2076
2165
|
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
2077
2166
|
});
|
|
@@ -2081,7 +2170,7 @@ class Midy {
|
|
|
2081
2170
|
if (channel.isDrum)
|
|
2082
2171
|
return;
|
|
2083
2172
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2084
|
-
channel.state.vibratoRate = vibratoRate /
|
|
2173
|
+
channel.state.vibratoRate = vibratoRate / 127;
|
|
2085
2174
|
if (channel.vibratoDepth <= 0)
|
|
2086
2175
|
return;
|
|
2087
2176
|
this.processScheduledNotes(channel, (note) => {
|
|
@@ -2094,7 +2183,7 @@ class Midy {
|
|
|
2094
2183
|
return;
|
|
2095
2184
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2096
2185
|
const prev = channel.state.vibratoDepth;
|
|
2097
|
-
channel.state.vibratoDepth = vibratoDepth /
|
|
2186
|
+
channel.state.vibratoDepth = vibratoDepth / 127;
|
|
2098
2187
|
if (0 < prev) {
|
|
2099
2188
|
this.processScheduledNotes(channel, (note) => {
|
|
2100
2189
|
this.setFreqVibLFO(channel, note, scheduleTime);
|
|
@@ -2106,12 +2195,12 @@ class Midy {
|
|
|
2106
2195
|
});
|
|
2107
2196
|
}
|
|
2108
2197
|
}
|
|
2109
|
-
setVibratoDelay(channelNumber, vibratoDelay) {
|
|
2198
|
+
setVibratoDelay(channelNumber, vibratoDelay, scheduleTime) {
|
|
2110
2199
|
const channel = this.channels[channelNumber];
|
|
2111
2200
|
if (channel.isDrum)
|
|
2112
2201
|
return;
|
|
2113
2202
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2114
|
-
channel.state.vibratoDelay = vibratoDelay /
|
|
2203
|
+
channel.state.vibratoDelay = vibratoDelay / 127;
|
|
2115
2204
|
if (0 < channel.state.vibratoDepth) {
|
|
2116
2205
|
this.processScheduledNotes(channel, (note) => {
|
|
2117
2206
|
this.startVibrato(channel, note, scheduleTime);
|
|
@@ -2233,12 +2322,14 @@ class Midy {
|
|
|
2233
2322
|
}
|
|
2234
2323
|
}
|
|
2235
2324
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp18.pdf
|
|
2236
|
-
dataIncrement(channelNumber) {
|
|
2237
|
-
this.
|
|
2325
|
+
dataIncrement(channelNumber, scheduleTime) {
|
|
2326
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2327
|
+
this.handleRPN(channelNumber, 1, scheduleTime);
|
|
2238
2328
|
}
|
|
2239
2329
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp18.pdf
|
|
2240
|
-
dataDecrement(channelNumber) {
|
|
2241
|
-
this.
|
|
2330
|
+
dataDecrement(channelNumber, scheduleTime) {
|
|
2331
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2332
|
+
this.handleRPN(channelNumber, -1, scheduleTime);
|
|
2242
2333
|
}
|
|
2243
2334
|
setRPNMSB(channelNumber, value) {
|
|
2244
2335
|
this.channels[channelNumber].rpnMSB = value;
|
|
@@ -2248,7 +2339,7 @@ class Midy {
|
|
|
2248
2339
|
}
|
|
2249
2340
|
dataEntryMSB(channelNumber, value, scheduleTime) {
|
|
2250
2341
|
this.channels[channelNumber].dataMSB = value;
|
|
2251
|
-
this.handleRPN(channelNumber, scheduleTime);
|
|
2342
|
+
this.handleRPN(channelNumber, 0, scheduleTime);
|
|
2252
2343
|
}
|
|
2253
2344
|
handlePitchBendRangeRPN(channelNumber, scheduleTime) {
|
|
2254
2345
|
const channel = this.channels[channelNumber];
|
|
@@ -2319,24 +2410,32 @@ class Midy {
|
|
|
2319
2410
|
}
|
|
2320
2411
|
allSoundOff(channelNumber, _value, scheduleTime) {
|
|
2321
2412
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2322
|
-
return this.
|
|
2413
|
+
return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
|
|
2323
2414
|
}
|
|
2324
2415
|
resetAllStates(channelNumber) {
|
|
2416
|
+
const scheduleTime = this.audioContext.currentTime;
|
|
2325
2417
|
const channel = this.channels[channelNumber];
|
|
2326
2418
|
const state = channel.state;
|
|
2327
|
-
|
|
2328
|
-
|
|
2419
|
+
const entries = Object.entries(defaultControllerState);
|
|
2420
|
+
for (const [key, { type, defaultValue }] of entries) {
|
|
2421
|
+
if (128 <= type) {
|
|
2422
|
+
this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
2423
|
+
}
|
|
2424
|
+
else {
|
|
2425
|
+
state[key] = defaultValue;
|
|
2426
|
+
}
|
|
2329
2427
|
}
|
|
2330
|
-
for (const
|
|
2331
|
-
channel[
|
|
2428
|
+
for (const key of Object.keys(this.constructor.channelSettings)) {
|
|
2429
|
+
channel[key] = this.constructor.channelSettings[key];
|
|
2332
2430
|
}
|
|
2431
|
+
this.resetChannelTable(channel);
|
|
2333
2432
|
this.mode = "GM2";
|
|
2334
2433
|
this.masterFineTuning = 0; // cb
|
|
2335
2434
|
this.masterCoarseTuning = 0; // cb
|
|
2336
2435
|
}
|
|
2337
2436
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
|
|
2338
|
-
resetAllControllers(channelNumber) {
|
|
2339
|
-
const
|
|
2437
|
+
resetAllControllers(channelNumber, _value, scheduleTime) {
|
|
2438
|
+
const keys = [
|
|
2340
2439
|
"polyphonicKeyPressure",
|
|
2341
2440
|
"channelPressure",
|
|
2342
2441
|
"pitchWheel",
|
|
@@ -2349,10 +2448,17 @@ class Midy {
|
|
|
2349
2448
|
];
|
|
2350
2449
|
const channel = this.channels[channelNumber];
|
|
2351
2450
|
const state = channel.state;
|
|
2352
|
-
for (let i = 0; i <
|
|
2353
|
-
const
|
|
2354
|
-
|
|
2451
|
+
for (let i = 0; i < keys.length; i++) {
|
|
2452
|
+
const key = keys[i];
|
|
2453
|
+
const { type, defaultValue } = defaultControllerState[key];
|
|
2454
|
+
if (128 <= type) {
|
|
2455
|
+
this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
2456
|
+
}
|
|
2457
|
+
else {
|
|
2458
|
+
state[key] = defaultValue;
|
|
2459
|
+
}
|
|
2355
2460
|
}
|
|
2461
|
+
this.setPitchBend(channelNumber, 8192, scheduleTime);
|
|
2356
2462
|
const settingTypes = [
|
|
2357
2463
|
"rpnMSB",
|
|
2358
2464
|
"rpnLSB",
|
|
@@ -2364,7 +2470,7 @@ class Midy {
|
|
|
2364
2470
|
}
|
|
2365
2471
|
allNotesOff(channelNumber, _value, scheduleTime) {
|
|
2366
2472
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2367
|
-
return this.
|
|
2473
|
+
return this.stopActiveNotes(channelNumber, 0, false, scheduleTime);
|
|
2368
2474
|
}
|
|
2369
2475
|
omniOff(channelNumber, value, scheduleTime) {
|
|
2370
2476
|
this.allNotesOff(channelNumber, value, scheduleTime);
|
|
@@ -2597,7 +2703,7 @@ class Midy {
|
|
|
2597
2703
|
this.reverbEffect = options.reverbAlgorithm(audioContext);
|
|
2598
2704
|
}
|
|
2599
2705
|
getReverbTime(value) {
|
|
2600
|
-
return Math.
|
|
2706
|
+
return Math.exp((value - 40) * 0.025);
|
|
2601
2707
|
}
|
|
2602
2708
|
// mean free path equation
|
|
2603
2709
|
// https://repository.dl.itc.u-tokyo.ac.jp/record/8550/files/A31912.pdf
|
|
@@ -2831,7 +2937,13 @@ class Midy {
|
|
|
2831
2937
|
setControllerParameters(channel, note, table) {
|
|
2832
2938
|
if (table[0] !== 64)
|
|
2833
2939
|
this.updateDetune(channel, note);
|
|
2834
|
-
if (
|
|
2940
|
+
if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
|
|
2941
|
+
if (table[1] !== 64)
|
|
2942
|
+
this.setPortamentoFilterEnvelope(channel, note);
|
|
2943
|
+
if (table[2] !== 64)
|
|
2944
|
+
this.setPortamentoVolumeEnvelope(channel, note);
|
|
2945
|
+
}
|
|
2946
|
+
else {
|
|
2835
2947
|
if (table[1] !== 64)
|
|
2836
2948
|
this.setFilterEnvelope(channel, note);
|
|
2837
2949
|
if (table[2] !== 64)
|
|
@@ -2859,8 +2971,13 @@ class Midy {
|
|
|
2859
2971
|
initControlTable() {
|
|
2860
2972
|
const channelCount = 128;
|
|
2861
2973
|
const slotSize = 6;
|
|
2862
|
-
const defaultValues = [64, 64, 64, 0, 0, 0];
|
|
2863
2974
|
const table = new Uint8Array(channelCount * slotSize);
|
|
2975
|
+
return this.resetControlTable(table);
|
|
2976
|
+
}
|
|
2977
|
+
resetControlTable(table) {
|
|
2978
|
+
const channelCount = 128;
|
|
2979
|
+
const slotSize = 6;
|
|
2980
|
+
const defaultValues = [64, 64, 64, 0, 0, 0];
|
|
2864
2981
|
for (let ch = 0; ch < channelCount; ch++) {
|
|
2865
2982
|
const offset = ch * slotSize;
|
|
2866
2983
|
table.set(defaultValues, offset);
|