@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/esm/midy.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, "noteOffEvent", {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
configurable: true,
|
|
14
|
+
writable: true,
|
|
15
|
+
value: void 0
|
|
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
|
Object.defineProperty(this, "pressure", {
|
|
148
108
|
enumerable: true,
|
|
@@ -204,7 +164,7 @@ const defaultControllerState = {
|
|
|
204
164
|
portamentoTime: { type: 128 + 5, defaultValue: 0 },
|
|
205
165
|
// dataMSB: { type: 128 + 6, defaultValue: 0, },
|
|
206
166
|
volume: { type: 128 + 7, defaultValue: 100 / 127 },
|
|
207
|
-
pan: { type: 128 + 10, defaultValue:
|
|
167
|
+
pan: { type: 128 + 10, defaultValue: 64 / 127 },
|
|
208
168
|
expression: { type: 128 + 11, defaultValue: 1 },
|
|
209
169
|
// bankLSB: { type: 128 + 32, defaultValue: 0, },
|
|
210
170
|
// dataLSB: { type: 128 + 38, defaultValue: 0, },
|
|
@@ -212,14 +172,14 @@ const defaultControllerState = {
|
|
|
212
172
|
portamento: { type: 128 + 65, defaultValue: 0 },
|
|
213
173
|
sostenutoPedal: { type: 128 + 66, defaultValue: 0 },
|
|
214
174
|
softPedal: { type: 128 + 67, defaultValue: 0 },
|
|
215
|
-
filterResonance: { type: 128 + 71, defaultValue:
|
|
216
|
-
releaseTime: { type: 128 + 72, defaultValue:
|
|
217
|
-
attackTime: { type: 128 + 73, defaultValue:
|
|
218
|
-
brightness: { type: 128 + 74, defaultValue:
|
|
219
|
-
decayTime: { type: 128 + 75, defaultValue:
|
|
220
|
-
vibratoRate: { type: 128 + 76, defaultValue:
|
|
221
|
-
vibratoDepth: { type: 128 + 77, defaultValue:
|
|
222
|
-
vibratoDelay: { type: 128 + 78, defaultValue:
|
|
175
|
+
filterResonance: { type: 128 + 71, defaultValue: 64 / 127 },
|
|
176
|
+
releaseTime: { type: 128 + 72, defaultValue: 64 / 127 },
|
|
177
|
+
attackTime: { type: 128 + 73, defaultValue: 64 / 127 },
|
|
178
|
+
brightness: { type: 128 + 74, defaultValue: 64 / 127 },
|
|
179
|
+
decayTime: { type: 128 + 75, defaultValue: 64 / 127 },
|
|
180
|
+
vibratoRate: { type: 128 + 76, defaultValue: 64 / 127 },
|
|
181
|
+
vibratoDepth: { type: 128 + 77, defaultValue: 64 / 127 },
|
|
182
|
+
vibratoDelay: { type: 128 + 78, defaultValue: 64 / 127 },
|
|
223
183
|
reverbSendLevel: { type: 128 + 91, defaultValue: 0 },
|
|
224
184
|
chorusSendLevel: { type: 128 + 93, defaultValue: 0 },
|
|
225
185
|
// dataIncrement: { type: 128 + 96, defaultValue: 0 },
|
|
@@ -494,7 +454,7 @@ export class Midy {
|
|
|
494
454
|
initSoundFontTable() {
|
|
495
455
|
const table = new Array(128);
|
|
496
456
|
for (let i = 0; i < 128; i++) {
|
|
497
|
-
table[i] = new
|
|
457
|
+
table[i] = new Map();
|
|
498
458
|
}
|
|
499
459
|
return table;
|
|
500
460
|
}
|
|
@@ -541,18 +501,25 @@ export class Midy {
|
|
|
541
501
|
merger,
|
|
542
502
|
};
|
|
543
503
|
}
|
|
504
|
+
resetChannelTable(channel) {
|
|
505
|
+
this.resetControlTable(channel.controlTable);
|
|
506
|
+
channel.scaleOctaveTuningTable.fill(0); // [-100, 100] cent
|
|
507
|
+
channel.channelPressureTable.set([64, 64, 64, 0, 0, 0]);
|
|
508
|
+
channel.polyphonicKeyPressureTable.set([64, 64, 64, 0, 0, 0]);
|
|
509
|
+
channel.keyBasedInstrumentControlTable.fill(0); // [-64, 63]
|
|
510
|
+
}
|
|
544
511
|
createChannels(audioContext) {
|
|
545
512
|
const channels = Array.from({ length: this.numChannels }, () => {
|
|
546
513
|
return {
|
|
547
514
|
currentBufferSource: null,
|
|
548
515
|
isDrum: false,
|
|
549
|
-
...this.constructor.channelSettings,
|
|
550
516
|
state: new ControllerState(),
|
|
551
|
-
|
|
517
|
+
...this.constructor.channelSettings,
|
|
552
518
|
...this.setChannelAudioNodes(audioContext),
|
|
553
|
-
scheduledNotes:
|
|
519
|
+
scheduledNotes: [],
|
|
554
520
|
sustainNotes: [],
|
|
555
|
-
sostenutoNotes:
|
|
521
|
+
sostenutoNotes: [],
|
|
522
|
+
controlTable: this.initControlTable(),
|
|
556
523
|
scaleOctaveTuningTable: new Float32Array(12), // [-100, 100] cent
|
|
557
524
|
channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
558
525
|
polyphonicKeyPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
@@ -600,46 +567,20 @@ export class Midy {
|
|
|
600
567
|
}
|
|
601
568
|
return bufferSource;
|
|
602
569
|
}
|
|
603
|
-
|
|
604
|
-
const endEvent = this.timeline[queueIndex];
|
|
605
|
-
if (!this.channels[endEvent.channel].portamento)
|
|
606
|
-
return;
|
|
607
|
-
const endTime = endEvent.startTime;
|
|
608
|
-
let target;
|
|
609
|
-
while (++queueIndex < this.timeline.length) {
|
|
610
|
-
const event = this.timeline[queueIndex];
|
|
611
|
-
if (endTime !== event.startTime)
|
|
612
|
-
break;
|
|
613
|
-
if (event.type !== "noteOn")
|
|
614
|
-
continue;
|
|
615
|
-
if (!target || event.noteNumber < target.noteNumber) {
|
|
616
|
-
target = event;
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
return target;
|
|
620
|
-
}
|
|
621
|
-
async scheduleTimelineEvents(t, offset, queueIndex) {
|
|
570
|
+
async scheduleTimelineEvents(t, resumeTime, queueIndex) {
|
|
622
571
|
while (queueIndex < this.timeline.length) {
|
|
623
572
|
const event = this.timeline[queueIndex];
|
|
624
573
|
if (event.startTime > t + this.lookAhead)
|
|
625
574
|
break;
|
|
626
|
-
const
|
|
575
|
+
const delay = this.startDelay - resumeTime;
|
|
576
|
+
const startTime = event.startTime + delay;
|
|
627
577
|
switch (event.type) {
|
|
628
|
-
case "noteOn":
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
case "noteOff": {
|
|
635
|
-
const portamentoTarget = this.findPortamentoTarget(queueIndex);
|
|
636
|
-
if (portamentoTarget)
|
|
637
|
-
portamentoTarget.portamento = true;
|
|
638
|
-
const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false, // force
|
|
639
|
-
portamentoTarget?.noteNumber);
|
|
640
|
-
if (notePromise) {
|
|
641
|
-
this.notePromises.push(notePromise);
|
|
642
|
-
}
|
|
578
|
+
case "noteOn": {
|
|
579
|
+
const noteOffEvent = {
|
|
580
|
+
...event.noteOffEvent,
|
|
581
|
+
startTime: event.noteOffEvent.startTime + delay,
|
|
582
|
+
};
|
|
583
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, noteOffEvent);
|
|
643
584
|
break;
|
|
644
585
|
}
|
|
645
586
|
case "noteAftertouch":
|
|
@@ -678,7 +619,7 @@ export class Midy {
|
|
|
678
619
|
this.isPaused = false;
|
|
679
620
|
this.startTime = this.audioContext.currentTime;
|
|
680
621
|
let queueIndex = this.getQueueIndex(this.resumeTime);
|
|
681
|
-
let
|
|
622
|
+
let resumeTime = this.resumeTime - this.startTime;
|
|
682
623
|
this.notePromises = [];
|
|
683
624
|
const schedulePlayback = async () => {
|
|
684
625
|
if (queueIndex >= this.timeline.length) {
|
|
@@ -687,18 +628,21 @@ export class Midy {
|
|
|
687
628
|
this.exclusiveClassNotes.fill(undefined);
|
|
688
629
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
689
630
|
this.audioBufferCache.clear();
|
|
631
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
632
|
+
this.resetAllStates(i);
|
|
633
|
+
}
|
|
690
634
|
resolve();
|
|
691
635
|
return;
|
|
692
636
|
}
|
|
693
637
|
const now = this.audioContext.currentTime;
|
|
694
|
-
const t = now +
|
|
695
|
-
queueIndex = await this.scheduleTimelineEvents(t,
|
|
638
|
+
const t = now + resumeTime;
|
|
639
|
+
queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
|
|
696
640
|
if (this.isPausing) {
|
|
697
641
|
await this.stopNotes(0, true, now);
|
|
698
642
|
this.notePromises = [];
|
|
699
|
-
resolve();
|
|
700
643
|
this.isPausing = false;
|
|
701
644
|
this.isPaused = true;
|
|
645
|
+
resolve();
|
|
702
646
|
return;
|
|
703
647
|
}
|
|
704
648
|
else if (this.isStopping) {
|
|
@@ -707,9 +651,12 @@ export class Midy {
|
|
|
707
651
|
this.exclusiveClassNotes.fill(undefined);
|
|
708
652
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
709
653
|
this.audioBufferCache.clear();
|
|
710
|
-
|
|
654
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
655
|
+
this.resetAllStates(i);
|
|
656
|
+
}
|
|
711
657
|
this.isStopping = false;
|
|
712
658
|
this.isPaused = false;
|
|
659
|
+
resolve();
|
|
713
660
|
return;
|
|
714
661
|
}
|
|
715
662
|
else if (this.isSeeking) {
|
|
@@ -718,7 +665,7 @@ export class Midy {
|
|
|
718
665
|
this.drumExclusiveClassNotes.fill(undefined);
|
|
719
666
|
this.startTime = this.audioContext.currentTime;
|
|
720
667
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
721
|
-
|
|
668
|
+
resumeTime = this.resumeTime - this.startTime;
|
|
722
669
|
this.isSeeking = false;
|
|
723
670
|
await schedulePlayback();
|
|
724
671
|
}
|
|
@@ -840,17 +787,52 @@ export class Midy {
|
|
|
840
787
|
prevTempoTicks = event.ticks;
|
|
841
788
|
}
|
|
842
789
|
}
|
|
790
|
+
const activeNotes = new Array(this.channels.length * 128);
|
|
791
|
+
for (let i = 0; i < activeNotes.length; i++) {
|
|
792
|
+
activeNotes[i] = [];
|
|
793
|
+
}
|
|
794
|
+
for (let i = 0; i < timeline.length; i++) {
|
|
795
|
+
const event = timeline[i];
|
|
796
|
+
switch (event.type) {
|
|
797
|
+
case "noteOn": {
|
|
798
|
+
const index = event.channel * 128 + event.noteNumber;
|
|
799
|
+
activeNotes[index].push(event);
|
|
800
|
+
break;
|
|
801
|
+
}
|
|
802
|
+
case "noteOff": {
|
|
803
|
+
const index = event.channel * 128 + event.noteNumber;
|
|
804
|
+
const noteOn = activeNotes[index].pop();
|
|
805
|
+
if (noteOn) {
|
|
806
|
+
noteOn.noteOffEvent = event;
|
|
807
|
+
}
|
|
808
|
+
else {
|
|
809
|
+
const eventString = JSON.stringify(event, null, 2);
|
|
810
|
+
console.warn(`noteOff without matching noteOn: ${eventString}`);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
843
815
|
return { instruments, timeline };
|
|
844
816
|
}
|
|
817
|
+
stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
|
|
818
|
+
const channel = this.channels[channelNumber];
|
|
819
|
+
const promises = [];
|
|
820
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
821
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
|
|
822
|
+
this.notePromises.push(promise);
|
|
823
|
+
promises.push(promise);
|
|
824
|
+
});
|
|
825
|
+
return Promise.all(promises);
|
|
826
|
+
}
|
|
845
827
|
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
846
828
|
const channel = this.channels[channelNumber];
|
|
847
829
|
const promises = [];
|
|
848
830
|
this.processScheduledNotes(channel, (note) => {
|
|
849
|
-
const promise = this.scheduleNoteOff(channelNumber, note
|
|
831
|
+
const promise = this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, force);
|
|
850
832
|
this.notePromises.push(promise);
|
|
851
833
|
promises.push(promise);
|
|
852
834
|
});
|
|
853
|
-
channel.scheduledNotes
|
|
835
|
+
channel.scheduledNotes = [];
|
|
854
836
|
return Promise.all(promises);
|
|
855
837
|
}
|
|
856
838
|
stopNotes(velocity, force, scheduleTime) {
|
|
@@ -871,9 +853,6 @@ export class Midy {
|
|
|
871
853
|
if (!this.isPlaying)
|
|
872
854
|
return;
|
|
873
855
|
this.isStopping = true;
|
|
874
|
-
for (let i = 0; i < this.channels.length; i++) {
|
|
875
|
-
this.resetAllStates(i);
|
|
876
|
-
}
|
|
877
856
|
}
|
|
878
857
|
pause() {
|
|
879
858
|
if (!this.isPlaying || this.isPaused)
|
|
@@ -908,35 +887,31 @@ export class Midy {
|
|
|
908
887
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
909
888
|
}
|
|
910
889
|
processScheduledNotes(channel, callback) {
|
|
911
|
-
channel.scheduledNotes
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
getActiveNotes(channel, scheduleTime) {
|
|
921
|
-
const activeNotes = new SparseMap(128);
|
|
922
|
-
channel.scheduledNotes.forEach((noteList) => {
|
|
923
|
-
const activeNote = this.getActiveNote(noteList, scheduleTime);
|
|
924
|
-
if (activeNote) {
|
|
925
|
-
activeNotes.set(activeNote.noteNumber, activeNote);
|
|
926
|
-
}
|
|
927
|
-
});
|
|
928
|
-
return activeNotes;
|
|
890
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
891
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
892
|
+
const note = scheduledNotes[i];
|
|
893
|
+
if (!note)
|
|
894
|
+
continue;
|
|
895
|
+
if (note.ending)
|
|
896
|
+
continue;
|
|
897
|
+
callback(note);
|
|
898
|
+
}
|
|
929
899
|
}
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
900
|
+
processActiveNotes(channel, scheduleTime, callback) {
|
|
901
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
902
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
903
|
+
const note = scheduledNotes[i];
|
|
933
904
|
if (!note)
|
|
934
|
-
|
|
905
|
+
continue;
|
|
906
|
+
if (note.ending)
|
|
907
|
+
continue;
|
|
908
|
+
const noteOffEvent = note.noteOffEvent;
|
|
909
|
+
if (noteOffEvent && noteOffEvent.startTime < scheduleTime)
|
|
910
|
+
continue;
|
|
935
911
|
if (scheduleTime < note.startTime)
|
|
936
912
|
continue;
|
|
937
|
-
|
|
913
|
+
callback(note);
|
|
938
914
|
}
|
|
939
|
-
return noteList[0];
|
|
940
915
|
}
|
|
941
916
|
createConvolutionReverbImpulse(audioContext, decay, preDecay) {
|
|
942
917
|
const sampleRate = audioContext.sampleRate;
|
|
@@ -1103,24 +1078,94 @@ export class Midy {
|
|
|
1103
1078
|
const noteDetune = this.calcNoteDetune(channel, note);
|
|
1104
1079
|
const pitchControl = this.getPitchControl(channel, note);
|
|
1105
1080
|
const detune = channel.detune + noteDetune + pitchControl;
|
|
1106
|
-
note.
|
|
1107
|
-
.
|
|
1108
|
-
.
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1081
|
+
if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
1082
|
+
const startTime = note.startTime;
|
|
1083
|
+
const deltaCent = (note.noteNumber - note.portamentoNoteNumber) * 100;
|
|
1084
|
+
const portamentoTime = startTime + this.getPortamentoTime(channel, note);
|
|
1085
|
+
note.bufferSource.detune
|
|
1086
|
+
.cancelScheduledValues(scheduleTime)
|
|
1087
|
+
.setValueAtTime(detune - deltaCent, scheduleTime)
|
|
1088
|
+
.linearRampToValueAtTime(detune, portamentoTime);
|
|
1089
|
+
}
|
|
1090
|
+
else {
|
|
1091
|
+
note.bufferSource.detune
|
|
1092
|
+
.cancelScheduledValues(scheduleTime)
|
|
1093
|
+
.setValueAtTime(detune, scheduleTime);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
getPortamentoTime(channel, note) {
|
|
1097
|
+
const deltaSemitone = Math.abs(note.noteNumber - note.portamentoNoteNumber);
|
|
1098
|
+
const value = Math.ceil(channel.state.portamentoTime * 127);
|
|
1099
|
+
return deltaSemitone / this.getPitchIncrementSpeed(value) / 10;
|
|
1100
|
+
}
|
|
1101
|
+
getPitchIncrementSpeed(value) {
|
|
1102
|
+
const points = [
|
|
1103
|
+
[0, 1000],
|
|
1104
|
+
[6, 100],
|
|
1105
|
+
[16, 20],
|
|
1106
|
+
[32, 10],
|
|
1107
|
+
[48, 5],
|
|
1108
|
+
[64, 2.5],
|
|
1109
|
+
[80, 1],
|
|
1110
|
+
[96, 0.4],
|
|
1111
|
+
[112, 0.15],
|
|
1112
|
+
[127, 0.01],
|
|
1113
|
+
];
|
|
1114
|
+
const logPoints = new Array(points.length);
|
|
1115
|
+
for (let i = 0; i < points.length; i++) {
|
|
1116
|
+
const [x, y] = points[i];
|
|
1117
|
+
if (value === x)
|
|
1118
|
+
return y;
|
|
1119
|
+
logPoints[i] = [x, Math.log(y)];
|
|
1120
|
+
}
|
|
1121
|
+
let startIndex = 0;
|
|
1122
|
+
for (let i = 1; i < logPoints.length; i++) {
|
|
1123
|
+
if (value <= logPoints[i][0]) {
|
|
1124
|
+
startIndex = i - 1;
|
|
1125
|
+
break;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
const [x0, y0] = logPoints[startIndex];
|
|
1129
|
+
const [x1, y1] = logPoints[startIndex + 1];
|
|
1130
|
+
const h = x1 - x0;
|
|
1131
|
+
const t = (value - x0) / h;
|
|
1132
|
+
let m0, m1;
|
|
1133
|
+
if (startIndex === 0) {
|
|
1134
|
+
m0 = (y1 - y0) / h;
|
|
1135
|
+
}
|
|
1136
|
+
else {
|
|
1137
|
+
const [xPrev, yPrev] = logPoints[startIndex - 1];
|
|
1138
|
+
m0 = (y1 - yPrev) / (x1 - xPrev);
|
|
1139
|
+
}
|
|
1140
|
+
if (startIndex === logPoints.length - 2) {
|
|
1141
|
+
m1 = (y1 - y0) / h;
|
|
1142
|
+
}
|
|
1143
|
+
else {
|
|
1144
|
+
const [xNext, yNext] = logPoints[startIndex + 2];
|
|
1145
|
+
m1 = (yNext - y0) / (xNext - x0);
|
|
1146
|
+
}
|
|
1147
|
+
// Cubic Hermite Spline
|
|
1148
|
+
const t2 = t * t;
|
|
1149
|
+
const t3 = t2 * t;
|
|
1150
|
+
const h00 = 2 * t3 - 3 * t2 + 1;
|
|
1151
|
+
const h10 = t3 - 2 * t2 + t;
|
|
1152
|
+
const h01 = -2 * t3 + 3 * t2;
|
|
1153
|
+
const h11 = t3 - t2;
|
|
1154
|
+
const y = h00 * y0 + h01 * y1 + h * (h10 * m0 + h11 * m1);
|
|
1155
|
+
return Math.exp(y);
|
|
1156
|
+
}
|
|
1157
|
+
setPortamentoVolumeEnvelope(channel, note, scheduleTime) {
|
|
1158
|
+
const state = channel.state;
|
|
1115
1159
|
const { voiceParams, startTime } = note;
|
|
1116
|
-
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation)
|
|
1160
|
+
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
|
|
1161
|
+
(1 + this.getAmplitudeControl(channel, note));
|
|
1117
1162
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
1118
1163
|
const volDelay = startTime + voiceParams.volDelay;
|
|
1119
|
-
const
|
|
1164
|
+
const volAttack = volDelay + voiceParams.volAttack * state.attackTime * 2;
|
|
1165
|
+
const volHold = volAttack + voiceParams.volHold;
|
|
1120
1166
|
note.volumeEnvelopeNode.gain
|
|
1121
1167
|
.cancelScheduledValues(scheduleTime)
|
|
1122
|
-
.setValueAtTime(
|
|
1123
|
-
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
1168
|
+
.setValueAtTime(sustainVolume, volHold);
|
|
1124
1169
|
}
|
|
1125
1170
|
setVolumeEnvelope(channel, note, scheduleTime) {
|
|
1126
1171
|
const state = channel.state;
|
|
@@ -1140,6 +1185,12 @@ export class Midy {
|
|
|
1140
1185
|
.setValueAtTime(attackVolume, volHold)
|
|
1141
1186
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
1142
1187
|
}
|
|
1188
|
+
setPortamentoPitchEnvelope(note, scheduleTime) {
|
|
1189
|
+
const baseRate = note.voiceParams.playbackRate;
|
|
1190
|
+
note.bufferSource.playbackRate
|
|
1191
|
+
.cancelScheduledValues(scheduleTime)
|
|
1192
|
+
.setValueAtTime(baseRate, scheduleTime);
|
|
1193
|
+
}
|
|
1143
1194
|
setPitchEnvelope(note, scheduleTime) {
|
|
1144
1195
|
const { voiceParams } = note;
|
|
1145
1196
|
const baseRate = voiceParams.playbackRate;
|
|
@@ -1167,20 +1218,21 @@ export class Midy {
|
|
|
1167
1218
|
const maxFrequency = 20000; // max Hz of initialFilterFc
|
|
1168
1219
|
return Math.max(minFrequency, Math.min(frequency, maxFrequency));
|
|
1169
1220
|
}
|
|
1170
|
-
|
|
1221
|
+
setPortamentoFilterEnvelope(channel, note, scheduleTime) {
|
|
1171
1222
|
const state = channel.state;
|
|
1172
1223
|
const { voiceParams, noteNumber, startTime } = note;
|
|
1173
1224
|
const softPedalFactor = 1 -
|
|
1174
1225
|
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1175
|
-
const
|
|
1176
|
-
|
|
1226
|
+
const baseCent = voiceParams.initialFilterFc +
|
|
1227
|
+
this.getFilterCutoffControl(channel, note);
|
|
1228
|
+
const baseFreq = this.centToHz(baseCent) * softPedalFactor *
|
|
1177
1229
|
state.brightness * 2;
|
|
1178
1230
|
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor * state.brightness * 2;
|
|
1179
1231
|
const sustainFreq = baseFreq +
|
|
1180
1232
|
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
1181
1233
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
1182
1234
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
1183
|
-
const portamentoTime = startTime + this.getPortamentoTime(channel);
|
|
1235
|
+
const portamentoTime = startTime + this.getPortamentoTime(channel, note);
|
|
1184
1236
|
const modDelay = startTime + voiceParams.modDelay;
|
|
1185
1237
|
note.filterNode.frequency
|
|
1186
1238
|
.cancelScheduledValues(scheduleTime)
|
|
@@ -1266,7 +1318,7 @@ export class Midy {
|
|
|
1266
1318
|
return audioBuffer;
|
|
1267
1319
|
}
|
|
1268
1320
|
}
|
|
1269
|
-
async createNote(channel, voice, noteNumber, velocity, startTime,
|
|
1321
|
+
async createNote(channel, voice, noteNumber, velocity, startTime, isSF3) {
|
|
1270
1322
|
const now = this.audioContext.currentTime;
|
|
1271
1323
|
const state = channel.state;
|
|
1272
1324
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
@@ -1282,20 +1334,24 @@ export class Midy {
|
|
|
1282
1334
|
type: "lowpass",
|
|
1283
1335
|
Q: voiceParams.initialFilterQ / 5 * state.filterResonance, // dB
|
|
1284
1336
|
});
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1337
|
+
const prevNote = channel.scheduledNotes.at(-1);
|
|
1338
|
+
if (prevNote && prevNote.noteNumber !== noteNumber) {
|
|
1339
|
+
note.portamentoNoteNumber = prevNote.noteNumber;
|
|
1340
|
+
}
|
|
1341
|
+
if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
1342
|
+
this.setPortamentoVolumeEnvelope(channel, note, now);
|
|
1343
|
+
this.setPortamentoFilterEnvelope(channel, note, now);
|
|
1344
|
+
this.setPortamentoPitchEnvelope(note, now);
|
|
1289
1345
|
}
|
|
1290
1346
|
else {
|
|
1291
|
-
note.portamento = false;
|
|
1292
1347
|
this.setVolumeEnvelope(channel, note, now);
|
|
1293
1348
|
this.setFilterEnvelope(channel, note, now);
|
|
1349
|
+
this.setPitchEnvelope(note, now);
|
|
1294
1350
|
}
|
|
1351
|
+
this.updateDetune(channel, note, now);
|
|
1295
1352
|
if (0 < state.vibratoDepth) {
|
|
1296
1353
|
this.startVibrato(channel, note, now);
|
|
1297
1354
|
}
|
|
1298
|
-
this.setPitchEnvelope(note, now);
|
|
1299
1355
|
if (0 < state.modulationDepth) {
|
|
1300
1356
|
this.startModulation(channel, note, now);
|
|
1301
1357
|
}
|
|
@@ -1341,9 +1397,8 @@ export class Midy {
|
|
|
1341
1397
|
if (prev) {
|
|
1342
1398
|
const [prevNote, prevChannelNumber] = prev;
|
|
1343
1399
|
if (prevNote && !prevNote.ending) {
|
|
1344
|
-
this.scheduleNoteOff(prevChannelNumber, prevNote
|
|
1345
|
-
startTime, true
|
|
1346
|
-
undefined);
|
|
1400
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote, 0, // velocity,
|
|
1401
|
+
startTime, true);
|
|
1347
1402
|
}
|
|
1348
1403
|
}
|
|
1349
1404
|
this.exclusiveClassNotes[exclusiveClass] = [note, channelNumber];
|
|
@@ -1362,9 +1417,8 @@ export class Midy {
|
|
|
1362
1417
|
channelNumber;
|
|
1363
1418
|
const prevNote = this.drumExclusiveClassNotes[index];
|
|
1364
1419
|
if (prevNote && !prevNote.ending) {
|
|
1365
|
-
this.scheduleNoteOff(channelNumber, prevNote
|
|
1366
|
-
startTime, true
|
|
1367
|
-
undefined);
|
|
1420
|
+
this.scheduleNoteOff(channelNumber, prevNote, 0, // velocity,
|
|
1421
|
+
startTime, true);
|
|
1368
1422
|
}
|
|
1369
1423
|
this.drumExclusiveClassNotes[index] = note;
|
|
1370
1424
|
}
|
|
@@ -1372,10 +1426,10 @@ export class Midy {
|
|
|
1372
1426
|
if (!channel.isDrum)
|
|
1373
1427
|
return false;
|
|
1374
1428
|
const programNumber = channel.programNumber;
|
|
1375
|
-
return (programNumber === 48 && noteNumber === 88) ||
|
|
1376
|
-
(programNumber === 56 && 47 <= noteNumber && noteNumber <= 84);
|
|
1429
|
+
return !((programNumber === 48 && noteNumber === 88) ||
|
|
1430
|
+
(programNumber === 56 && 47 <= noteNumber && noteNumber <= 84));
|
|
1377
1431
|
}
|
|
1378
|
-
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime,
|
|
1432
|
+
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, noteOffEvent) {
|
|
1379
1433
|
const channel = this.channels[channelNumber];
|
|
1380
1434
|
const bankNumber = this.calcBank(channel, channelNumber);
|
|
1381
1435
|
const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
|
|
@@ -1386,7 +1440,8 @@ export class Midy {
|
|
|
1386
1440
|
if (!voice)
|
|
1387
1441
|
return;
|
|
1388
1442
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
1389
|
-
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime,
|
|
1443
|
+
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
1444
|
+
note.noteOffEvent = noteOffEvent;
|
|
1390
1445
|
note.gainL.connect(channel.gainL);
|
|
1391
1446
|
note.gainR.connect(channel.gainR);
|
|
1392
1447
|
if (0.5 <= channel.state.sustainPedal) {
|
|
@@ -1395,33 +1450,39 @@ export class Midy {
|
|
|
1395
1450
|
this.handleExclusiveClass(note, channelNumber, startTime);
|
|
1396
1451
|
this.handleDrumExclusiveClass(note, channelNumber, startTime);
|
|
1397
1452
|
const scheduledNotes = channel.scheduledNotes;
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
notes.push(note);
|
|
1401
|
-
}
|
|
1402
|
-
else {
|
|
1403
|
-
notes = [note];
|
|
1404
|
-
scheduledNotes.set(noteNumber, notes);
|
|
1405
|
-
}
|
|
1453
|
+
note.index = scheduledNotes.length;
|
|
1454
|
+
scheduledNotes.push(note);
|
|
1406
1455
|
if (this.isDrumNoteOffException(channel, noteNumber)) {
|
|
1407
1456
|
const stopTime = startTime + note.bufferSource.buffer.duration;
|
|
1408
|
-
const index = notes.length - 1;
|
|
1409
1457
|
const promise = new Promise((resolve) => {
|
|
1410
1458
|
note.bufferSource.onended = () => {
|
|
1411
|
-
|
|
1459
|
+
scheduledNotes[note.index] = undefined;
|
|
1460
|
+
this.disconnectNote(note);
|
|
1412
1461
|
resolve();
|
|
1413
1462
|
};
|
|
1414
1463
|
note.bufferSource.stop(stopTime);
|
|
1415
1464
|
});
|
|
1416
1465
|
this.notePromises.push(promise);
|
|
1417
1466
|
}
|
|
1467
|
+
else if (noteOffEvent) {
|
|
1468
|
+
if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
1469
|
+
const portamentoTime = this.getPortamentoTime(channel, note);
|
|
1470
|
+
const portamentoEndTime = startTime + portamentoTime;
|
|
1471
|
+
const notePromise = this.scheduleNoteOff(channelNumber, note, 0, // velocity
|
|
1472
|
+
Math.max(noteOffEvent.startTime, portamentoEndTime), false);
|
|
1473
|
+
this.notePromises.push(notePromise);
|
|
1474
|
+
}
|
|
1475
|
+
else {
|
|
1476
|
+
const notePromise = this.scheduleNoteOff(channelNumber, note, noteOffEvent.velocity, noteOffEvent.startTime, false);
|
|
1477
|
+
this.notePromises.push(notePromise);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1418
1480
|
}
|
|
1419
1481
|
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1420
1482
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1421
|
-
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime,
|
|
1483
|
+
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, undefined);
|
|
1422
1484
|
}
|
|
1423
|
-
disconnectNote(note
|
|
1424
|
-
scheduledNotes[index] = null;
|
|
1485
|
+
disconnectNote(note) {
|
|
1425
1486
|
note.bufferSource.disconnect();
|
|
1426
1487
|
note.filterNode.disconnect();
|
|
1427
1488
|
note.volumeEnvelopeNode.disconnect();
|
|
@@ -1444,8 +1505,7 @@ export class Midy {
|
|
|
1444
1505
|
note.chorusEffectsSend.disconnect();
|
|
1445
1506
|
}
|
|
1446
1507
|
}
|
|
1447
|
-
stopNote(
|
|
1448
|
-
const note = scheduledNotes[index];
|
|
1508
|
+
stopNote(channel, note, endTime, stopTime) {
|
|
1449
1509
|
note.volumeEnvelopeNode.gain
|
|
1450
1510
|
.cancelScheduledValues(endTime)
|
|
1451
1511
|
.linearRampToValueAtTime(0, stopTime);
|
|
@@ -1455,65 +1515,58 @@ export class Midy {
|
|
|
1455
1515
|
}, stopTime);
|
|
1456
1516
|
return new Promise((resolve) => {
|
|
1457
1517
|
note.bufferSource.onended = () => {
|
|
1458
|
-
|
|
1518
|
+
channel.scheduledNotes[note.index] = undefined;
|
|
1519
|
+
this.disconnectNote(note);
|
|
1459
1520
|
resolve();
|
|
1460
1521
|
};
|
|
1461
1522
|
note.bufferSource.stop(stopTime);
|
|
1462
1523
|
});
|
|
1463
1524
|
}
|
|
1464
|
-
scheduleNoteOff(channelNumber,
|
|
1525
|
+
scheduleNoteOff(channelNumber, note, _velocity, endTime, force) {
|
|
1465
1526
|
const channel = this.channels[channelNumber];
|
|
1466
|
-
if (this.isDrumNoteOffException(channel, noteNumber))
|
|
1527
|
+
if (this.isDrumNoteOffException(channel, note.noteNumber))
|
|
1467
1528
|
return;
|
|
1468
1529
|
const state = channel.state;
|
|
1469
1530
|
if (!force) {
|
|
1470
1531
|
if (0.5 <= state.sustainPedal)
|
|
1471
1532
|
return;
|
|
1472
|
-
if (channel.
|
|
1533
|
+
if (0.5 <= channel.state.sostenutoPedal)
|
|
1473
1534
|
return;
|
|
1474
1535
|
}
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
const
|
|
1536
|
+
const volRelease = endTime +
|
|
1537
|
+
note.voiceParams.volRelease * channel.state.releaseTime * 2;
|
|
1538
|
+
const modRelease = endTime + note.voiceParams.modRelease;
|
|
1539
|
+
note.filterNode.frequency
|
|
1540
|
+
.cancelScheduledValues(endTime)
|
|
1541
|
+
.linearRampToValueAtTime(0, modRelease);
|
|
1542
|
+
const stopTime = Math.min(volRelease, modRelease);
|
|
1543
|
+
return this.stopNote(channel, note, endTime, stopTime);
|
|
1544
|
+
}
|
|
1545
|
+
findNoteOffTarget(channel, noteNumber) {
|
|
1546
|
+
const scheduledNotes = channel.scheduledNotes;
|
|
1478
1547
|
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
1479
1548
|
const note = scheduledNotes[i];
|
|
1480
1549
|
if (!note)
|
|
1481
1550
|
continue;
|
|
1482
1551
|
if (note.ending)
|
|
1483
1552
|
continue;
|
|
1484
|
-
if (
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
const modRelease = endTime + note.voiceParams.modRelease;
|
|
1488
|
-
note.filterNode.frequency
|
|
1489
|
-
.cancelScheduledValues(endTime)
|
|
1490
|
-
.linearRampToValueAtTime(0, modRelease);
|
|
1491
|
-
const stopTime = Math.min(volRelease, modRelease);
|
|
1492
|
-
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
1493
|
-
}
|
|
1494
|
-
else {
|
|
1495
|
-
const portamentoTime = endTime + this.getPortamentoTime(channel);
|
|
1496
|
-
const deltaNote = portamentoNoteNumber - noteNumber;
|
|
1497
|
-
const baseRate = note.voiceParams.playbackRate;
|
|
1498
|
-
const targetRate = baseRate * Math.pow(2, deltaNote / 12);
|
|
1499
|
-
note.bufferSource.playbackRate
|
|
1500
|
-
.cancelScheduledValues(endTime)
|
|
1501
|
-
.linearRampToValueAtTime(targetRate, portamentoTime);
|
|
1502
|
-
return this.stopNote(endTime, portamentoTime, scheduledNotes, i);
|
|
1503
|
-
}
|
|
1553
|
+
if (note.noteNumber !== noteNumber)
|
|
1554
|
+
continue;
|
|
1555
|
+
return note;
|
|
1504
1556
|
}
|
|
1505
1557
|
}
|
|
1506
1558
|
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1507
1559
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1508
|
-
|
|
1509
|
-
|
|
1560
|
+
const channel = this.channels[channelNumber];
|
|
1561
|
+
const note = this.findNoteOffTarget(channel, noteNumber);
|
|
1562
|
+
return this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, false);
|
|
1510
1563
|
}
|
|
1511
1564
|
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
1512
1565
|
const velocity = halfVelocity * 2;
|
|
1513
1566
|
const channel = this.channels[channelNumber];
|
|
1514
1567
|
const promises = [];
|
|
1515
1568
|
for (let i = 0; i < channel.sustainNotes.length; i++) {
|
|
1516
|
-
const promise = this.
|
|
1569
|
+
const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i], velocity, scheduleTime);
|
|
1517
1570
|
promises.push(promise);
|
|
1518
1571
|
}
|
|
1519
1572
|
channel.sustainNotes = [];
|
|
@@ -1523,12 +1576,14 @@ export class Midy {
|
|
|
1523
1576
|
const velocity = halfVelocity * 2;
|
|
1524
1577
|
const channel = this.channels[channelNumber];
|
|
1525
1578
|
const promises = [];
|
|
1579
|
+
const sostenutoNotes = channel.sostenutoNotes;
|
|
1526
1580
|
channel.state.sostenutoPedal = 0;
|
|
1527
|
-
|
|
1528
|
-
const
|
|
1581
|
+
for (let i = 0; i < sostenutoNotes.length; i++) {
|
|
1582
|
+
const note = sostenutoNotes[i];
|
|
1583
|
+
const promise = this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime);
|
|
1529
1584
|
promises.push(promise);
|
|
1530
|
-
}
|
|
1531
|
-
channel.sostenutoNotes
|
|
1585
|
+
}
|
|
1586
|
+
channel.sostenutoNotes = [];
|
|
1532
1587
|
return promises;
|
|
1533
1588
|
}
|
|
1534
1589
|
handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
|
|
@@ -1557,12 +1612,12 @@ export class Midy {
|
|
|
1557
1612
|
const channel = this.channels[channelNumber];
|
|
1558
1613
|
channel.state.polyphonicKeyPressure = pressure / 127;
|
|
1559
1614
|
const table = channel.polyphonicKeyPressureTable;
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
}
|
|
1565
|
-
|
|
1615
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
1616
|
+
if (note.noteNumber === noteNumber) {
|
|
1617
|
+
this.setControllerParameters(channel, note, table);
|
|
1618
|
+
}
|
|
1619
|
+
});
|
|
1620
|
+
this.applyVoiceParams(channel, 10);
|
|
1566
1621
|
}
|
|
1567
1622
|
handleProgramChange(channelNumber, programNumber, _scheduleTime) {
|
|
1568
1623
|
const channel = this.channels[channelNumber];
|
|
@@ -1591,10 +1646,10 @@ export class Midy {
|
|
|
1591
1646
|
channel.detune += pressureDepth * (next - prev);
|
|
1592
1647
|
}
|
|
1593
1648
|
const table = channel.channelPressureTable;
|
|
1594
|
-
this.
|
|
1649
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
1595
1650
|
this.setControllerParameters(channel, note, table);
|
|
1596
1651
|
});
|
|
1597
|
-
|
|
1652
|
+
this.applyVoiceParams(channel, 13);
|
|
1598
1653
|
}
|
|
1599
1654
|
handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
|
|
1600
1655
|
const pitchBend = msb * 128 + lsb;
|
|
@@ -1771,6 +1826,8 @@ export class Midy {
|
|
|
1771
1826
|
state.set(channel.state.array);
|
|
1772
1827
|
state[2] = velocity / 127;
|
|
1773
1828
|
state[3] = noteNumber / 127;
|
|
1829
|
+
state[10] = state.polyphonicKeyPressure / 127;
|
|
1830
|
+
state[13] = state.channelPressure / 127;
|
|
1774
1831
|
return state;
|
|
1775
1832
|
}
|
|
1776
1833
|
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
@@ -1797,8 +1854,8 @@ export class Midy {
|
|
|
1797
1854
|
if (key in voiceParams)
|
|
1798
1855
|
noteVoiceParams[key] = voiceParams[key];
|
|
1799
1856
|
}
|
|
1800
|
-
if (note.
|
|
1801
|
-
this.
|
|
1857
|
+
if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
1858
|
+
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
1802
1859
|
}
|
|
1803
1860
|
else {
|
|
1804
1861
|
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
@@ -1821,42 +1878,42 @@ export class Midy {
|
|
|
1821
1878
|
});
|
|
1822
1879
|
}
|
|
1823
1880
|
createControlChangeHandlers() {
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
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
|
-
|
|
1881
|
+
const handlers = new Array(128);
|
|
1882
|
+
handlers[0] = this.setBankMSB;
|
|
1883
|
+
handlers[1] = this.setModulationDepth;
|
|
1884
|
+
handlers[5] = this.setPortamentoTime;
|
|
1885
|
+
handlers[6] = this.dataEntryMSB;
|
|
1886
|
+
handlers[7] = this.setVolume;
|
|
1887
|
+
handlers[10] = this.setPan;
|
|
1888
|
+
handlers[11] = this.setExpression;
|
|
1889
|
+
handlers[32] = this.setBankLSB;
|
|
1890
|
+
handlers[38] = this.dataEntryLSB;
|
|
1891
|
+
handlers[64] = this.setSustainPedal;
|
|
1892
|
+
handlers[65] = this.setPortamento;
|
|
1893
|
+
handlers[66] = this.setSostenutoPedal;
|
|
1894
|
+
handlers[67] = this.setSoftPedal;
|
|
1895
|
+
handlers[71] = this.setFilterResonance;
|
|
1896
|
+
handlers[72] = this.setReleaseTime;
|
|
1897
|
+
handlers[73] = this.setAttackTime;
|
|
1898
|
+
handlers[74] = this.setBrightness;
|
|
1899
|
+
handlers[75] = this.setDecayTime;
|
|
1900
|
+
handlers[76] = this.setVibratoRate;
|
|
1901
|
+
handlers[77] = this.setVibratoDepth;
|
|
1902
|
+
handlers[78] = this.setVibratoDelay;
|
|
1903
|
+
handlers[91] = this.setReverbSendLevel;
|
|
1904
|
+
handlers[93] = this.setChorusSendLevel;
|
|
1905
|
+
handlers[96] = this.dataIncrement;
|
|
1906
|
+
handlers[97] = this.dataDecrement;
|
|
1907
|
+
handlers[100] = this.setRPNLSB;
|
|
1908
|
+
handlers[101] = this.setRPNMSB;
|
|
1909
|
+
handlers[120] = this.allSoundOff;
|
|
1910
|
+
handlers[121] = this.resetAllControllers;
|
|
1911
|
+
handlers[123] = this.allNotesOff;
|
|
1912
|
+
handlers[124] = this.omniOff;
|
|
1913
|
+
handlers[125] = this.omniOn;
|
|
1914
|
+
handlers[126] = this.monoOn;
|
|
1915
|
+
handlers[127] = this.polyOn;
|
|
1916
|
+
return handlers;
|
|
1860
1917
|
}
|
|
1861
1918
|
handleControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1862
1919
|
const handler = this.controlChangeHandlers[controllerType];
|
|
@@ -1893,9 +1950,33 @@ export class Midy {
|
|
|
1893
1950
|
channel.state.modulationDepth = modulation / 127;
|
|
1894
1951
|
this.updateModulation(channel, scheduleTime);
|
|
1895
1952
|
}
|
|
1896
|
-
|
|
1953
|
+
updatePortamento(channel, scheduleTime) {
|
|
1954
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1955
|
+
if (0.5 <= channel.state.portamento) {
|
|
1956
|
+
if (0 <= note.portamentoNoteNumber) {
|
|
1957
|
+
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
1958
|
+
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
1959
|
+
this.setPortamentoPitchEnvelope(note, scheduleTime);
|
|
1960
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
else {
|
|
1964
|
+
if (0 <= note.portamentoNoteNumber) {
|
|
1965
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1966
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
1967
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1968
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
});
|
|
1972
|
+
}
|
|
1973
|
+
setPortamentoTime(channelNumber, portamentoTime, scheduleTime) {
|
|
1897
1974
|
const channel = this.channels[channelNumber];
|
|
1975
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1898
1976
|
channel.state.portamentoTime = portamentoTime / 127;
|
|
1977
|
+
if (channel.isDrum)
|
|
1978
|
+
return;
|
|
1979
|
+
this.updatePortamento(channel, scheduleTime);
|
|
1899
1980
|
}
|
|
1900
1981
|
setKeyBasedVolume(channel, scheduleTime) {
|
|
1901
1982
|
this.processScheduledNotes(channel, (note) => {
|
|
@@ -1953,7 +2034,7 @@ export class Midy {
|
|
|
1953
2034
|
}
|
|
1954
2035
|
dataEntryLSB(channelNumber, value, scheduleTime) {
|
|
1955
2036
|
this.channels[channelNumber].dataLSB = value;
|
|
1956
|
-
this.handleRPN(channelNumber, scheduleTime);
|
|
2037
|
+
this.handleRPN(channelNumber, 0, scheduleTime);
|
|
1957
2038
|
}
|
|
1958
2039
|
updateChannelVolume(channel, scheduleTime) {
|
|
1959
2040
|
const state = channel.state;
|
|
@@ -1981,11 +2062,13 @@ export class Midy {
|
|
|
1981
2062
|
this.releaseSustainPedal(channelNumber, value, scheduleTime);
|
|
1982
2063
|
}
|
|
1983
2064
|
}
|
|
1984
|
-
setPortamento(channelNumber, value) {
|
|
2065
|
+
setPortamento(channelNumber, value, scheduleTime) {
|
|
1985
2066
|
const channel = this.channels[channelNumber];
|
|
1986
2067
|
if (channel.isDrum)
|
|
1987
2068
|
return;
|
|
2069
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1988
2070
|
channel.state.portamento = value / 127;
|
|
2071
|
+
this.updatePortamento(channel, scheduleTime);
|
|
1989
2072
|
}
|
|
1990
2073
|
setSostenutoPedal(channelNumber, value, scheduleTime) {
|
|
1991
2074
|
const channel = this.channels[channelNumber];
|
|
@@ -1994,7 +2077,11 @@ export class Midy {
|
|
|
1994
2077
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1995
2078
|
channel.state.sostenutoPedal = value / 127;
|
|
1996
2079
|
if (64 <= value) {
|
|
1997
|
-
|
|
2080
|
+
const sostenutoNotes = [];
|
|
2081
|
+
this.processActiveNotes(channel, scheduleTime, (note) => {
|
|
2082
|
+
sostenutoNotes.push(note);
|
|
2083
|
+
});
|
|
2084
|
+
channel.sostenutoNotes = sostenutoNotes;
|
|
1998
2085
|
}
|
|
1999
2086
|
else {
|
|
2000
2087
|
this.releaseSostenutoPedal(channelNumber, value, scheduleTime);
|
|
@@ -2004,12 +2091,13 @@ export class Midy {
|
|
|
2004
2091
|
const channel = this.channels[channelNumber];
|
|
2005
2092
|
if (channel.isDrum)
|
|
2006
2093
|
return;
|
|
2094
|
+
const state = channel.state;
|
|
2007
2095
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2008
|
-
|
|
2096
|
+
state.softPedal = softPedal / 127;
|
|
2009
2097
|
this.processScheduledNotes(channel, (note) => {
|
|
2010
|
-
if (note.
|
|
2011
|
-
this.
|
|
2012
|
-
this.
|
|
2098
|
+
if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
2099
|
+
this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
|
|
2100
|
+
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2013
2101
|
}
|
|
2014
2102
|
else {
|
|
2015
2103
|
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
@@ -2023,25 +2111,25 @@ export class Midy {
|
|
|
2023
2111
|
return;
|
|
2024
2112
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2025
2113
|
const state = channel.state;
|
|
2026
|
-
state.filterResonance = filterResonance /
|
|
2114
|
+
state.filterResonance = filterResonance / 127;
|
|
2027
2115
|
this.processScheduledNotes(channel, (note) => {
|
|
2028
2116
|
const Q = note.voiceParams.initialFilterQ / 5 * state.filterResonance;
|
|
2029
2117
|
note.filterNode.Q.setValueAtTime(Q, scheduleTime);
|
|
2030
2118
|
});
|
|
2031
2119
|
}
|
|
2032
|
-
setReleaseTime(channelNumber, releaseTime,
|
|
2120
|
+
setReleaseTime(channelNumber, releaseTime, scheduleTime) {
|
|
2033
2121
|
const channel = this.channels[channelNumber];
|
|
2034
2122
|
if (channel.isDrum)
|
|
2035
2123
|
return;
|
|
2036
2124
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2037
|
-
channel.state.releaseTime = releaseTime /
|
|
2125
|
+
channel.state.releaseTime = releaseTime / 127;
|
|
2038
2126
|
}
|
|
2039
2127
|
setAttackTime(channelNumber, attackTime, scheduleTime) {
|
|
2040
2128
|
const channel = this.channels[channelNumber];
|
|
2041
2129
|
if (channel.isDrum)
|
|
2042
2130
|
return;
|
|
2043
2131
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2044
|
-
channel.state.attackTime = attackTime /
|
|
2132
|
+
channel.state.attackTime = attackTime / 127;
|
|
2045
2133
|
this.processScheduledNotes(channel, (note) => {
|
|
2046
2134
|
if (note.startTime < scheduleTime)
|
|
2047
2135
|
return false;
|
|
@@ -2052,11 +2140,12 @@ export class Midy {
|
|
|
2052
2140
|
const channel = this.channels[channelNumber];
|
|
2053
2141
|
if (channel.isDrum)
|
|
2054
2142
|
return;
|
|
2143
|
+
const state = channel.state;
|
|
2055
2144
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2056
|
-
|
|
2145
|
+
state.brightness = brightness / 127;
|
|
2057
2146
|
this.processScheduledNotes(channel, (note) => {
|
|
2058
|
-
if (note.
|
|
2059
|
-
this.
|
|
2147
|
+
if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
|
|
2148
|
+
this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
|
|
2060
2149
|
}
|
|
2061
2150
|
else {
|
|
2062
2151
|
this.setFilterEnvelope(channel, note);
|
|
@@ -2068,7 +2157,7 @@ export class Midy {
|
|
|
2068
2157
|
if (channel.isDrum)
|
|
2069
2158
|
return;
|
|
2070
2159
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2071
|
-
channel.state.decayTime = dacayTime /
|
|
2160
|
+
channel.state.decayTime = dacayTime / 127;
|
|
2072
2161
|
this.processScheduledNotes(channel, (note) => {
|
|
2073
2162
|
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
2074
2163
|
});
|
|
@@ -2078,7 +2167,7 @@ export class Midy {
|
|
|
2078
2167
|
if (channel.isDrum)
|
|
2079
2168
|
return;
|
|
2080
2169
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2081
|
-
channel.state.vibratoRate = vibratoRate /
|
|
2170
|
+
channel.state.vibratoRate = vibratoRate / 127;
|
|
2082
2171
|
if (channel.vibratoDepth <= 0)
|
|
2083
2172
|
return;
|
|
2084
2173
|
this.processScheduledNotes(channel, (note) => {
|
|
@@ -2091,7 +2180,7 @@ export class Midy {
|
|
|
2091
2180
|
return;
|
|
2092
2181
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2093
2182
|
const prev = channel.state.vibratoDepth;
|
|
2094
|
-
channel.state.vibratoDepth = vibratoDepth /
|
|
2183
|
+
channel.state.vibratoDepth = vibratoDepth / 127;
|
|
2095
2184
|
if (0 < prev) {
|
|
2096
2185
|
this.processScheduledNotes(channel, (note) => {
|
|
2097
2186
|
this.setFreqVibLFO(channel, note, scheduleTime);
|
|
@@ -2103,12 +2192,12 @@ export class Midy {
|
|
|
2103
2192
|
});
|
|
2104
2193
|
}
|
|
2105
2194
|
}
|
|
2106
|
-
setVibratoDelay(channelNumber, vibratoDelay) {
|
|
2195
|
+
setVibratoDelay(channelNumber, vibratoDelay, scheduleTime) {
|
|
2107
2196
|
const channel = this.channels[channelNumber];
|
|
2108
2197
|
if (channel.isDrum)
|
|
2109
2198
|
return;
|
|
2110
2199
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2111
|
-
channel.state.vibratoDelay = vibratoDelay /
|
|
2200
|
+
channel.state.vibratoDelay = vibratoDelay / 127;
|
|
2112
2201
|
if (0 < channel.state.vibratoDepth) {
|
|
2113
2202
|
this.processScheduledNotes(channel, (note) => {
|
|
2114
2203
|
this.startVibrato(channel, note, scheduleTime);
|
|
@@ -2230,12 +2319,14 @@ export class Midy {
|
|
|
2230
2319
|
}
|
|
2231
2320
|
}
|
|
2232
2321
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp18.pdf
|
|
2233
|
-
dataIncrement(channelNumber) {
|
|
2234
|
-
this.
|
|
2322
|
+
dataIncrement(channelNumber, scheduleTime) {
|
|
2323
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2324
|
+
this.handleRPN(channelNumber, 1, scheduleTime);
|
|
2235
2325
|
}
|
|
2236
2326
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp18.pdf
|
|
2237
|
-
dataDecrement(channelNumber) {
|
|
2238
|
-
this.
|
|
2327
|
+
dataDecrement(channelNumber, scheduleTime) {
|
|
2328
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2329
|
+
this.handleRPN(channelNumber, -1, scheduleTime);
|
|
2239
2330
|
}
|
|
2240
2331
|
setRPNMSB(channelNumber, value) {
|
|
2241
2332
|
this.channels[channelNumber].rpnMSB = value;
|
|
@@ -2245,7 +2336,7 @@ export class Midy {
|
|
|
2245
2336
|
}
|
|
2246
2337
|
dataEntryMSB(channelNumber, value, scheduleTime) {
|
|
2247
2338
|
this.channels[channelNumber].dataMSB = value;
|
|
2248
|
-
this.handleRPN(channelNumber, scheduleTime);
|
|
2339
|
+
this.handleRPN(channelNumber, 0, scheduleTime);
|
|
2249
2340
|
}
|
|
2250
2341
|
handlePitchBendRangeRPN(channelNumber, scheduleTime) {
|
|
2251
2342
|
const channel = this.channels[channelNumber];
|
|
@@ -2316,24 +2407,32 @@ export class Midy {
|
|
|
2316
2407
|
}
|
|
2317
2408
|
allSoundOff(channelNumber, _value, scheduleTime) {
|
|
2318
2409
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2319
|
-
return this.
|
|
2410
|
+
return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
|
|
2320
2411
|
}
|
|
2321
2412
|
resetAllStates(channelNumber) {
|
|
2413
|
+
const scheduleTime = this.audioContext.currentTime;
|
|
2322
2414
|
const channel = this.channels[channelNumber];
|
|
2323
2415
|
const state = channel.state;
|
|
2324
|
-
|
|
2325
|
-
|
|
2416
|
+
const entries = Object.entries(defaultControllerState);
|
|
2417
|
+
for (const [key, { type, defaultValue }] of entries) {
|
|
2418
|
+
if (128 <= type) {
|
|
2419
|
+
this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
2420
|
+
}
|
|
2421
|
+
else {
|
|
2422
|
+
state[key] = defaultValue;
|
|
2423
|
+
}
|
|
2326
2424
|
}
|
|
2327
|
-
for (const
|
|
2328
|
-
channel[
|
|
2425
|
+
for (const key of Object.keys(this.constructor.channelSettings)) {
|
|
2426
|
+
channel[key] = this.constructor.channelSettings[key];
|
|
2329
2427
|
}
|
|
2428
|
+
this.resetChannelTable(channel);
|
|
2330
2429
|
this.mode = "GM2";
|
|
2331
2430
|
this.masterFineTuning = 0; // cb
|
|
2332
2431
|
this.masterCoarseTuning = 0; // cb
|
|
2333
2432
|
}
|
|
2334
2433
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
|
|
2335
|
-
resetAllControllers(channelNumber) {
|
|
2336
|
-
const
|
|
2434
|
+
resetAllControllers(channelNumber, _value, scheduleTime) {
|
|
2435
|
+
const keys = [
|
|
2337
2436
|
"polyphonicKeyPressure",
|
|
2338
2437
|
"channelPressure",
|
|
2339
2438
|
"pitchWheel",
|
|
@@ -2346,10 +2445,17 @@ export class Midy {
|
|
|
2346
2445
|
];
|
|
2347
2446
|
const channel = this.channels[channelNumber];
|
|
2348
2447
|
const state = channel.state;
|
|
2349
|
-
for (let i = 0; i <
|
|
2350
|
-
const
|
|
2351
|
-
|
|
2448
|
+
for (let i = 0; i < keys.length; i++) {
|
|
2449
|
+
const key = keys[i];
|
|
2450
|
+
const { type, defaultValue } = defaultControllerState[key];
|
|
2451
|
+
if (128 <= type) {
|
|
2452
|
+
this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
|
|
2453
|
+
}
|
|
2454
|
+
else {
|
|
2455
|
+
state[key] = defaultValue;
|
|
2456
|
+
}
|
|
2352
2457
|
}
|
|
2458
|
+
this.setPitchBend(channelNumber, 8192, scheduleTime);
|
|
2353
2459
|
const settingTypes = [
|
|
2354
2460
|
"rpnMSB",
|
|
2355
2461
|
"rpnLSB",
|
|
@@ -2361,7 +2467,7 @@ export class Midy {
|
|
|
2361
2467
|
}
|
|
2362
2468
|
allNotesOff(channelNumber, _value, scheduleTime) {
|
|
2363
2469
|
scheduleTime ??= this.audioContext.currentTime;
|
|
2364
|
-
return this.
|
|
2470
|
+
return this.stopActiveNotes(channelNumber, 0, false, scheduleTime);
|
|
2365
2471
|
}
|
|
2366
2472
|
omniOff(channelNumber, value, scheduleTime) {
|
|
2367
2473
|
this.allNotesOff(channelNumber, value, scheduleTime);
|
|
@@ -2594,7 +2700,7 @@ export class Midy {
|
|
|
2594
2700
|
this.reverbEffect = options.reverbAlgorithm(audioContext);
|
|
2595
2701
|
}
|
|
2596
2702
|
getReverbTime(value) {
|
|
2597
|
-
return Math.
|
|
2703
|
+
return Math.exp((value - 40) * 0.025);
|
|
2598
2704
|
}
|
|
2599
2705
|
// mean free path equation
|
|
2600
2706
|
// https://repository.dl.itc.u-tokyo.ac.jp/record/8550/files/A31912.pdf
|
|
@@ -2828,7 +2934,13 @@ export class Midy {
|
|
|
2828
2934
|
setControllerParameters(channel, note, table) {
|
|
2829
2935
|
if (table[0] !== 64)
|
|
2830
2936
|
this.updateDetune(channel, note);
|
|
2831
|
-
if (
|
|
2937
|
+
if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
|
|
2938
|
+
if (table[1] !== 64)
|
|
2939
|
+
this.setPortamentoFilterEnvelope(channel, note);
|
|
2940
|
+
if (table[2] !== 64)
|
|
2941
|
+
this.setPortamentoVolumeEnvelope(channel, note);
|
|
2942
|
+
}
|
|
2943
|
+
else {
|
|
2832
2944
|
if (table[1] !== 64)
|
|
2833
2945
|
this.setFilterEnvelope(channel, note);
|
|
2834
2946
|
if (table[2] !== 64)
|
|
@@ -2856,8 +2968,13 @@ export class Midy {
|
|
|
2856
2968
|
initControlTable() {
|
|
2857
2969
|
const channelCount = 128;
|
|
2858
2970
|
const slotSize = 6;
|
|
2859
|
-
const defaultValues = [64, 64, 64, 0, 0, 0];
|
|
2860
2971
|
const table = new Uint8Array(channelCount * slotSize);
|
|
2972
|
+
return this.resetControlTable(table);
|
|
2973
|
+
}
|
|
2974
|
+
resetControlTable(table) {
|
|
2975
|
+
const channelCount = 128;
|
|
2976
|
+
const slotSize = 6;
|
|
2977
|
+
const defaultValues = [64, 64, 64, 0, 0, 0];
|
|
2861
2978
|
for (let ch = 0; ch < channelCount; ch++) {
|
|
2862
2979
|
const offset = ch * slotSize;
|
|
2863
2980
|
table.set(defaultValues, offset);
|