@marmooo/midy 0.2.5 → 0.2.7
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 +17 -12
- package/esm/midy-GM1.d.ts +85 -87
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +188 -237
- package/esm/midy-GM2.d.ts +129 -126
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +374 -390
- package/esm/midy-GMLite.d.ts +85 -84
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +191 -190
- package/esm/midy.d.ts +153 -150
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +420 -442
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +85 -87
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +188 -237
- package/script/midy-GM2.d.ts +129 -126
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +374 -390
- package/script/midy-GMLite.d.ts +85 -84
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +191 -190
- package/script/midy.d.ts +153 -150
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +420 -442
package/esm/midy.js
CHANGED
|
@@ -66,31 +66,37 @@ class Note {
|
|
|
66
66
|
writable: true,
|
|
67
67
|
value: void 0
|
|
68
68
|
});
|
|
69
|
+
Object.defineProperty(this, "filterDepth", {
|
|
70
|
+
enumerable: true,
|
|
71
|
+
configurable: true,
|
|
72
|
+
writable: true,
|
|
73
|
+
value: void 0
|
|
74
|
+
});
|
|
69
75
|
Object.defineProperty(this, "volumeEnvelopeNode", {
|
|
70
76
|
enumerable: true,
|
|
71
77
|
configurable: true,
|
|
72
78
|
writable: true,
|
|
73
79
|
value: void 0
|
|
74
80
|
});
|
|
75
|
-
Object.defineProperty(this, "
|
|
81
|
+
Object.defineProperty(this, "volumeDepth", {
|
|
76
82
|
enumerable: true,
|
|
77
83
|
configurable: true,
|
|
78
84
|
writable: true,
|
|
79
85
|
value: void 0
|
|
80
86
|
});
|
|
81
|
-
Object.defineProperty(this, "
|
|
87
|
+
Object.defineProperty(this, "volumeNode", {
|
|
82
88
|
enumerable: true,
|
|
83
89
|
configurable: true,
|
|
84
90
|
writable: true,
|
|
85
91
|
value: void 0
|
|
86
92
|
});
|
|
87
|
-
Object.defineProperty(this, "
|
|
93
|
+
Object.defineProperty(this, "gainL", {
|
|
88
94
|
enumerable: true,
|
|
89
95
|
configurable: true,
|
|
90
96
|
writable: true,
|
|
91
97
|
value: void 0
|
|
92
98
|
});
|
|
93
|
-
Object.defineProperty(this, "
|
|
99
|
+
Object.defineProperty(this, "gainR", {
|
|
94
100
|
enumerable: true,
|
|
95
101
|
configurable: true,
|
|
96
102
|
writable: true,
|
|
@@ -499,6 +505,10 @@ export class Midy {
|
|
|
499
505
|
...this.setChannelAudioNodes(audioContext),
|
|
500
506
|
scheduledNotes: new SparseMap(128),
|
|
501
507
|
sostenutoNotes: new SparseMap(128),
|
|
508
|
+
scaleOctaveTuningTable: new Float32Array(12), // [-100, 100] cent
|
|
509
|
+
channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
510
|
+
polyphonicKeyPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
511
|
+
keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
|
|
502
512
|
};
|
|
503
513
|
});
|
|
504
514
|
return channels;
|
|
@@ -565,10 +575,11 @@ export class Midy {
|
|
|
565
575
|
const event = this.timeline[queueIndex];
|
|
566
576
|
if (event.startTime > t + this.lookAhead)
|
|
567
577
|
break;
|
|
578
|
+
const startTime = event.startTime + this.startDelay - offset;
|
|
568
579
|
switch (event.type) {
|
|
569
580
|
case "noteOn":
|
|
570
581
|
if (event.velocity !== 0) {
|
|
571
|
-
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity,
|
|
582
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, event.portamento);
|
|
572
583
|
break;
|
|
573
584
|
}
|
|
574
585
|
/* falls through */
|
|
@@ -576,29 +587,30 @@ export class Midy {
|
|
|
576
587
|
const portamentoTarget = this.findPortamentoTarget(queueIndex);
|
|
577
588
|
if (portamentoTarget)
|
|
578
589
|
portamentoTarget.portamento = true;
|
|
579
|
-
const notePromise = this.
|
|
590
|
+
const notePromise = this.scheduleNoteOff(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, startTime, false, // force
|
|
591
|
+
portamentoTarget?.noteNumber);
|
|
580
592
|
if (notePromise) {
|
|
581
593
|
this.notePromises.push(notePromise);
|
|
582
594
|
}
|
|
583
595
|
break;
|
|
584
596
|
}
|
|
585
597
|
case "noteAftertouch":
|
|
586
|
-
this.handlePolyphonicKeyPressure(event.channel, event.noteNumber, event.amount);
|
|
598
|
+
this.handlePolyphonicKeyPressure(event.channel, event.noteNumber, event.amount, startTime);
|
|
587
599
|
break;
|
|
588
600
|
case "controller":
|
|
589
|
-
this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
|
|
601
|
+
this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value, startTime);
|
|
590
602
|
break;
|
|
591
603
|
case "programChange":
|
|
592
|
-
this.handleProgramChange(event.channel, event.programNumber);
|
|
604
|
+
this.handleProgramChange(event.channel, event.programNumber, startTime);
|
|
593
605
|
break;
|
|
594
606
|
case "channelAftertouch":
|
|
595
|
-
this.handleChannelPressure(event.channel, event.amount);
|
|
607
|
+
this.handleChannelPressure(event.channel, event.amount, startTime);
|
|
596
608
|
break;
|
|
597
609
|
case "pitchBend":
|
|
598
|
-
this.setPitchBend(event.channel, event.value + 8192);
|
|
610
|
+
this.setPitchBend(event.channel, event.value + 8192, startTime);
|
|
599
611
|
break;
|
|
600
612
|
case "sysEx":
|
|
601
|
-
this.handleSysEx(event.data);
|
|
613
|
+
this.handleSysEx(event.data, startTime);
|
|
602
614
|
}
|
|
603
615
|
queueIndex++;
|
|
604
616
|
}
|
|
@@ -629,10 +641,11 @@ export class Midy {
|
|
|
629
641
|
resolve();
|
|
630
642
|
return;
|
|
631
643
|
}
|
|
632
|
-
const
|
|
644
|
+
const now = this.audioContext.currentTime;
|
|
645
|
+
const t = now + offset;
|
|
633
646
|
queueIndex = await this.scheduleTimelineEvents(t, offset, queueIndex);
|
|
634
647
|
if (this.isPausing) {
|
|
635
|
-
await this.stopNotes(0, true);
|
|
648
|
+
await this.stopNotes(0, true, now);
|
|
636
649
|
this.notePromises = [];
|
|
637
650
|
resolve();
|
|
638
651
|
this.isPausing = false;
|
|
@@ -640,7 +653,7 @@ export class Midy {
|
|
|
640
653
|
return;
|
|
641
654
|
}
|
|
642
655
|
else if (this.isStopping) {
|
|
643
|
-
await this.stopNotes(0, true);
|
|
656
|
+
await this.stopNotes(0, true, now);
|
|
644
657
|
this.notePromises = [];
|
|
645
658
|
this.exclusiveClassMap.clear();
|
|
646
659
|
this.audioBufferCache.clear();
|
|
@@ -650,7 +663,7 @@ export class Midy {
|
|
|
650
663
|
return;
|
|
651
664
|
}
|
|
652
665
|
else if (this.isSeeking) {
|
|
653
|
-
this.stopNotes(0, true);
|
|
666
|
+
this.stopNotes(0, true, now);
|
|
654
667
|
this.exclusiveClassMap.clear();
|
|
655
668
|
this.startTime = this.audioContext.currentTime;
|
|
656
669
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
@@ -659,7 +672,6 @@ export class Midy {
|
|
|
659
672
|
await schedulePlayback();
|
|
660
673
|
}
|
|
661
674
|
else {
|
|
662
|
-
const now = this.audioContext.currentTime;
|
|
663
675
|
const waitTime = now + this.noteCheckInterval;
|
|
664
676
|
await this.scheduleTask(() => { }, waitTime);
|
|
665
677
|
await schedulePlayback();
|
|
@@ -779,25 +791,26 @@ export class Midy {
|
|
|
779
791
|
}
|
|
780
792
|
return { instruments, timeline };
|
|
781
793
|
}
|
|
782
|
-
|
|
783
|
-
const now = this.audioContext.currentTime;
|
|
794
|
+
stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
|
|
784
795
|
const channel = this.channels[channelNumber];
|
|
796
|
+
const promises = [];
|
|
785
797
|
channel.scheduledNotes.forEach((noteList) => {
|
|
786
798
|
for (let i = 0; i < noteList.length; i++) {
|
|
787
799
|
const note = noteList[i];
|
|
788
800
|
if (!note)
|
|
789
801
|
continue;
|
|
790
|
-
const promise = this.
|
|
791
|
-
force);
|
|
802
|
+
const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
|
|
792
803
|
this.notePromises.push(promise);
|
|
804
|
+
promises.push(promise);
|
|
793
805
|
}
|
|
794
806
|
});
|
|
795
807
|
channel.scheduledNotes.clear();
|
|
796
|
-
|
|
808
|
+
return Promise.all(promises);
|
|
797
809
|
}
|
|
798
|
-
stopNotes(velocity, force) {
|
|
810
|
+
stopNotes(velocity, force, scheduleTime) {
|
|
811
|
+
const promises = [];
|
|
799
812
|
for (let i = 0; i < this.channels.length; i++) {
|
|
800
|
-
this.stopChannelNotes(i, velocity, force);
|
|
813
|
+
promises.push(this.stopChannelNotes(i, velocity, force, scheduleTime));
|
|
801
814
|
}
|
|
802
815
|
return Promise.all(this.notePromises);
|
|
803
816
|
}
|
|
@@ -845,22 +858,34 @@ export class Midy {
|
|
|
845
858
|
const now = this.audioContext.currentTime;
|
|
846
859
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
847
860
|
}
|
|
848
|
-
|
|
861
|
+
processScheduledNotes(channel, scheduleTime, callback) {
|
|
862
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
863
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
864
|
+
const note = noteList[i];
|
|
865
|
+
if (!note)
|
|
866
|
+
continue;
|
|
867
|
+
if (scheduleTime < note.startTime)
|
|
868
|
+
continue;
|
|
869
|
+
callback(note);
|
|
870
|
+
}
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
getActiveNotes(channel, scheduleTime) {
|
|
849
874
|
const activeNotes = new SparseMap(128);
|
|
850
875
|
channel.scheduledNotes.forEach((noteList) => {
|
|
851
|
-
const activeNote = this.getActiveNote(noteList,
|
|
876
|
+
const activeNote = this.getActiveNote(noteList, scheduleTime);
|
|
852
877
|
if (activeNote) {
|
|
853
878
|
activeNotes.set(activeNote.noteNumber, activeNote);
|
|
854
879
|
}
|
|
855
880
|
});
|
|
856
881
|
return activeNotes;
|
|
857
882
|
}
|
|
858
|
-
getActiveNote(noteList,
|
|
883
|
+
getActiveNote(noteList, scheduleTime) {
|
|
859
884
|
for (let i = noteList.length - 1; i >= 0; i--) {
|
|
860
885
|
const note = noteList[i];
|
|
861
886
|
if (!note)
|
|
862
887
|
return;
|
|
863
|
-
if (
|
|
888
|
+
if (scheduleTime < note.startTime)
|
|
864
889
|
continue;
|
|
865
890
|
return (note.ending) ? null : note;
|
|
866
891
|
}
|
|
@@ -1020,74 +1045,66 @@ export class Midy {
|
|
|
1020
1045
|
calcNoteDetune(channel, note) {
|
|
1021
1046
|
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
1022
1047
|
}
|
|
1023
|
-
updateChannelDetune(channel) {
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
const note = noteList[i];
|
|
1027
|
-
if (!note)
|
|
1028
|
-
continue;
|
|
1029
|
-
this.updateDetune(channel, note, 0);
|
|
1030
|
-
}
|
|
1048
|
+
updateChannelDetune(channel, scheduleTime) {
|
|
1049
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1050
|
+
this.updateDetune(channel, note, scheduleTime);
|
|
1031
1051
|
});
|
|
1032
1052
|
}
|
|
1033
|
-
updateDetune(channel, note,
|
|
1034
|
-
const now = this.audioContext.currentTime;
|
|
1053
|
+
updateDetune(channel, note, scheduleTime) {
|
|
1035
1054
|
const noteDetune = this.calcNoteDetune(channel, note);
|
|
1036
|
-
const
|
|
1055
|
+
const pitchControl = this.getPitchControl(channel, note);
|
|
1056
|
+
const detune = channel.detune + noteDetune + pitchControl;
|
|
1037
1057
|
note.bufferSource.detune
|
|
1038
|
-
.cancelScheduledValues(
|
|
1039
|
-
.setValueAtTime(detune,
|
|
1058
|
+
.cancelScheduledValues(scheduleTime)
|
|
1059
|
+
.setValueAtTime(detune, scheduleTime);
|
|
1040
1060
|
}
|
|
1041
1061
|
getPortamentoTime(channel) {
|
|
1042
1062
|
const factor = 5 * Math.log(10) / 127;
|
|
1043
1063
|
const time = channel.state.portamentoTime;
|
|
1044
1064
|
return Math.log(time) / factor;
|
|
1045
1065
|
}
|
|
1046
|
-
setPortamentoStartVolumeEnvelope(channel, note) {
|
|
1047
|
-
const now = this.audioContext.currentTime;
|
|
1066
|
+
setPortamentoStartVolumeEnvelope(channel, note, scheduleTime) {
|
|
1048
1067
|
const { voiceParams, startTime } = note;
|
|
1049
1068
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
1050
1069
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
1051
1070
|
const volDelay = startTime + voiceParams.volDelay;
|
|
1052
1071
|
const portamentoTime = volDelay + this.getPortamentoTime(channel);
|
|
1053
1072
|
note.volumeEnvelopeNode.gain
|
|
1054
|
-
.cancelScheduledValues(
|
|
1073
|
+
.cancelScheduledValues(scheduleTime)
|
|
1055
1074
|
.setValueAtTime(0, volDelay)
|
|
1056
1075
|
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
1057
1076
|
}
|
|
1058
|
-
setVolumeEnvelope(channel, note,
|
|
1059
|
-
const now = this.audioContext.currentTime;
|
|
1077
|
+
setVolumeEnvelope(channel, note, scheduleTime) {
|
|
1060
1078
|
const state = channel.state;
|
|
1061
1079
|
const { voiceParams, startTime } = note;
|
|
1062
1080
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
|
|
1063
|
-
(1 +
|
|
1081
|
+
(1 + this.getAmplitudeControl(channel, note));
|
|
1064
1082
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
1065
1083
|
const volDelay = startTime + voiceParams.volDelay;
|
|
1066
1084
|
const volAttack = volDelay + voiceParams.volAttack * state.attackTime * 2;
|
|
1067
1085
|
const volHold = volAttack + voiceParams.volHold;
|
|
1068
1086
|
const volDecay = volHold + voiceParams.volDecay * state.decayTime * 2;
|
|
1069
1087
|
note.volumeEnvelopeNode.gain
|
|
1070
|
-
.cancelScheduledValues(
|
|
1088
|
+
.cancelScheduledValues(scheduleTime)
|
|
1071
1089
|
.setValueAtTime(0, startTime)
|
|
1072
1090
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
1073
1091
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
1074
1092
|
.setValueAtTime(attackVolume, volHold)
|
|
1075
1093
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
1076
1094
|
}
|
|
1077
|
-
setPitchEnvelope(note) {
|
|
1078
|
-
const now = this.audioContext.currentTime;
|
|
1095
|
+
setPitchEnvelope(note, scheduleTime) {
|
|
1079
1096
|
const { voiceParams } = note;
|
|
1080
1097
|
const baseRate = voiceParams.playbackRate;
|
|
1081
1098
|
note.bufferSource.playbackRate
|
|
1082
|
-
.cancelScheduledValues(
|
|
1083
|
-
.setValueAtTime(baseRate,
|
|
1099
|
+
.cancelScheduledValues(scheduleTime)
|
|
1100
|
+
.setValueAtTime(baseRate, scheduleTime);
|
|
1084
1101
|
const modEnvToPitch = voiceParams.modEnvToPitch;
|
|
1085
1102
|
if (modEnvToPitch === 0)
|
|
1086
1103
|
return;
|
|
1087
1104
|
const basePitch = this.rateToCent(baseRate);
|
|
1088
1105
|
const peekPitch = basePitch + modEnvToPitch;
|
|
1089
1106
|
const peekRate = this.centToRate(peekPitch);
|
|
1090
|
-
const modDelay = startTime + voiceParams.modDelay;
|
|
1107
|
+
const modDelay = note.startTime + voiceParams.modDelay;
|
|
1091
1108
|
const modAttack = modDelay + voiceParams.modAttack;
|
|
1092
1109
|
const modHold = modAttack + voiceParams.modHold;
|
|
1093
1110
|
const modDecay = modHold + voiceParams.modDecay;
|
|
@@ -1102,8 +1119,7 @@ export class Midy {
|
|
|
1102
1119
|
const maxFrequency = 20000; // max Hz of initialFilterFc
|
|
1103
1120
|
return Math.max(minFrequency, Math.min(frequency, maxFrequency));
|
|
1104
1121
|
}
|
|
1105
|
-
setPortamentoStartFilterEnvelope(channel, note) {
|
|
1106
|
-
const now = this.audioContext.currentTime;
|
|
1122
|
+
setPortamentoStartFilterEnvelope(channel, note, scheduleTime) {
|
|
1107
1123
|
const state = channel.state;
|
|
1108
1124
|
const { voiceParams, noteNumber, startTime } = note;
|
|
1109
1125
|
const softPedalFactor = 1 -
|
|
@@ -1119,18 +1135,18 @@ export class Midy {
|
|
|
1119
1135
|
const portamentoTime = startTime + this.getPortamentoTime(channel);
|
|
1120
1136
|
const modDelay = startTime + voiceParams.modDelay;
|
|
1121
1137
|
note.filterNode.frequency
|
|
1122
|
-
.cancelScheduledValues(
|
|
1138
|
+
.cancelScheduledValues(scheduleTime)
|
|
1123
1139
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
1124
1140
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
1125
1141
|
.linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
|
|
1126
1142
|
}
|
|
1127
|
-
setFilterEnvelope(channel, note,
|
|
1128
|
-
const now = this.audioContext.currentTime;
|
|
1143
|
+
setFilterEnvelope(channel, note, scheduleTime) {
|
|
1129
1144
|
const state = channel.state;
|
|
1130
1145
|
const { voiceParams, noteNumber, startTime } = note;
|
|
1131
1146
|
const softPedalFactor = 1 -
|
|
1132
1147
|
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1133
|
-
const baseCent = voiceParams.initialFilterFc +
|
|
1148
|
+
const baseCent = voiceParams.initialFilterFc +
|
|
1149
|
+
this.getFilterCutoffControl(channel, note);
|
|
1134
1150
|
const baseFreq = this.centToHz(baseCent) * softPedalFactor *
|
|
1135
1151
|
state.brightness * 2;
|
|
1136
1152
|
const peekFreq = this.centToHz(baseCent + voiceParams.modEnvToFilterFc) *
|
|
@@ -1145,14 +1161,14 @@ export class Midy {
|
|
|
1145
1161
|
const modHold = modAttack + voiceParams.modHold;
|
|
1146
1162
|
const modDecay = modHold + voiceParams.modDecay;
|
|
1147
1163
|
note.filterNode.frequency
|
|
1148
|
-
.cancelScheduledValues(
|
|
1164
|
+
.cancelScheduledValues(scheduleTime)
|
|
1149
1165
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
1150
1166
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
1151
1167
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
1152
1168
|
.setValueAtTime(adjustedPeekFreq, modHold)
|
|
1153
1169
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
1154
1170
|
}
|
|
1155
|
-
startModulation(channel, note,
|
|
1171
|
+
startModulation(channel, note, scheduleTime) {
|
|
1156
1172
|
const { voiceParams } = note;
|
|
1157
1173
|
note.modulationLFO = new OscillatorNode(this.audioContext, {
|
|
1158
1174
|
frequency: this.centToHz(voiceParams.freqModLFO),
|
|
@@ -1161,10 +1177,10 @@ export class Midy {
|
|
|
1161
1177
|
gain: voiceParams.modLfoToFilterFc,
|
|
1162
1178
|
});
|
|
1163
1179
|
note.modulationDepth = new GainNode(this.audioContext);
|
|
1164
|
-
this.setModLfoToPitch(channel, note,
|
|
1180
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
1165
1181
|
note.volumeDepth = new GainNode(this.audioContext);
|
|
1166
|
-
this.setModLfoToVolume(note,
|
|
1167
|
-
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
1182
|
+
this.setModLfoToVolume(channel, note, scheduleTime);
|
|
1183
|
+
note.modulationLFO.start(note.startTime + voiceParams.delayModLFO);
|
|
1168
1184
|
note.modulationLFO.connect(note.filterDepth);
|
|
1169
1185
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
1170
1186
|
note.modulationLFO.connect(note.modulationDepth);
|
|
@@ -1172,15 +1188,15 @@ export class Midy {
|
|
|
1172
1188
|
note.modulationLFO.connect(note.volumeDepth);
|
|
1173
1189
|
note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
|
|
1174
1190
|
}
|
|
1175
|
-
startVibrato(channel, note,
|
|
1191
|
+
startVibrato(channel, note, scheduleTime) {
|
|
1176
1192
|
const { voiceParams } = note;
|
|
1177
1193
|
const state = channel.state;
|
|
1178
1194
|
note.vibratoLFO = new OscillatorNode(this.audioContext, {
|
|
1179
1195
|
frequency: this.centToHz(voiceParams.freqVibLFO) * state.vibratoRate * 2,
|
|
1180
1196
|
});
|
|
1181
|
-
note.vibratoLFO.start(startTime + voiceParams.delayVibLFO * state.vibratoDelay * 2);
|
|
1197
|
+
note.vibratoLFO.start(note.startTime + voiceParams.delayVibLFO * state.vibratoDelay * 2);
|
|
1182
1198
|
note.vibratoDepth = new GainNode(this.audioContext);
|
|
1183
|
-
this.setVibLfoToPitch(channel, note);
|
|
1199
|
+
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
1184
1200
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
1185
1201
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
1186
1202
|
}
|
|
@@ -1203,6 +1219,7 @@ export class Midy {
|
|
|
1203
1219
|
}
|
|
1204
1220
|
}
|
|
1205
1221
|
async createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3) {
|
|
1222
|
+
const now = this.audioContext.currentTime;
|
|
1206
1223
|
const state = channel.state;
|
|
1207
1224
|
const controllerState = this.getControllerState(channel, noteNumber, velocity);
|
|
1208
1225
|
const voiceParams = voice.getAllParams(controllerState);
|
|
@@ -1219,20 +1236,20 @@ export class Midy {
|
|
|
1219
1236
|
});
|
|
1220
1237
|
if (portamento) {
|
|
1221
1238
|
note.portamento = true;
|
|
1222
|
-
this.setPortamentoStartVolumeEnvelope(channel, note);
|
|
1223
|
-
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
1239
|
+
this.setPortamentoStartVolumeEnvelope(channel, note, now);
|
|
1240
|
+
this.setPortamentoStartFilterEnvelope(channel, note, now);
|
|
1224
1241
|
}
|
|
1225
1242
|
else {
|
|
1226
1243
|
note.portamento = false;
|
|
1227
|
-
this.setVolumeEnvelope(channel, note,
|
|
1228
|
-
this.setFilterEnvelope(channel, note,
|
|
1244
|
+
this.setVolumeEnvelope(channel, note, now);
|
|
1245
|
+
this.setFilterEnvelope(channel, note, now);
|
|
1229
1246
|
}
|
|
1230
1247
|
if (0 < state.vibratoDepth) {
|
|
1231
|
-
this.startVibrato(channel, note,
|
|
1248
|
+
this.startVibrato(channel, note, now);
|
|
1232
1249
|
}
|
|
1233
|
-
this.setPitchEnvelope(note);
|
|
1250
|
+
this.setPitchEnvelope(note, now);
|
|
1234
1251
|
if (0 < state.modulationDepth) {
|
|
1235
|
-
this.startModulation(channel, note,
|
|
1252
|
+
this.startModulation(channel, note, now);
|
|
1236
1253
|
}
|
|
1237
1254
|
if (this.mono && channel.currentBufferSource) {
|
|
1238
1255
|
channel.currentBufferSource.stop(startTime);
|
|
@@ -1244,10 +1261,10 @@ export class Midy {
|
|
|
1244
1261
|
note.volumeNode.connect(note.gainL);
|
|
1245
1262
|
note.volumeNode.connect(note.gainR);
|
|
1246
1263
|
if (0 < channel.chorusSendLevel) {
|
|
1247
|
-
this.setChorusEffectsSend(channel, note, 0);
|
|
1264
|
+
this.setChorusEffectsSend(channel, note, 0, now);
|
|
1248
1265
|
}
|
|
1249
1266
|
if (0 < channel.reverbSendLevel) {
|
|
1250
|
-
this.setReverbEffectsSend(channel, note, 0);
|
|
1267
|
+
this.setReverbEffectsSend(channel, note, 0, now);
|
|
1251
1268
|
}
|
|
1252
1269
|
note.bufferSource.start(startTime);
|
|
1253
1270
|
return note;
|
|
@@ -1284,9 +1301,9 @@ export class Midy {
|
|
|
1284
1301
|
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
1285
1302
|
const [prevNote, prevChannelNumber] = prevEntry;
|
|
1286
1303
|
if (!prevNote.ending) {
|
|
1287
|
-
this.
|
|
1288
|
-
startTime,
|
|
1289
|
-
|
|
1304
|
+
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1305
|
+
startTime, true, // force
|
|
1306
|
+
undefined);
|
|
1290
1307
|
}
|
|
1291
1308
|
}
|
|
1292
1309
|
this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
|
|
@@ -1299,9 +1316,9 @@ export class Midy {
|
|
|
1299
1316
|
scheduledNotes.set(noteNumber, [note]);
|
|
1300
1317
|
}
|
|
1301
1318
|
}
|
|
1302
|
-
noteOn(channelNumber, noteNumber, velocity,
|
|
1303
|
-
|
|
1304
|
-
return this.scheduleNoteOn(channelNumber, noteNumber, velocity,
|
|
1319
|
+
noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1320
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1321
|
+
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, false);
|
|
1305
1322
|
}
|
|
1306
1323
|
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
1307
1324
|
const note = scheduledNotes[index];
|
|
@@ -1341,7 +1358,7 @@ export class Midy {
|
|
|
1341
1358
|
note.bufferSource.stop(stopTime);
|
|
1342
1359
|
});
|
|
1343
1360
|
}
|
|
1344
|
-
|
|
1361
|
+
scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force, portamentoNoteNumber) {
|
|
1345
1362
|
const channel = this.channels[channelNumber];
|
|
1346
1363
|
const state = channel.state;
|
|
1347
1364
|
if (!force) {
|
|
@@ -1381,24 +1398,19 @@ export class Midy {
|
|
|
1381
1398
|
}
|
|
1382
1399
|
}
|
|
1383
1400
|
}
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
return this.
|
|
1401
|
+
noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
|
|
1402
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1403
|
+
return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false, // force
|
|
1404
|
+
undefined);
|
|
1387
1405
|
}
|
|
1388
|
-
releaseSustainPedal(channelNumber, halfVelocity) {
|
|
1406
|
+
releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
|
|
1389
1407
|
const velocity = halfVelocity * 2;
|
|
1390
1408
|
const channel = this.channels[channelNumber];
|
|
1391
1409
|
const promises = [];
|
|
1392
|
-
channel
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
if (!note)
|
|
1397
|
-
continue;
|
|
1398
|
-
const { noteNumber } = note;
|
|
1399
|
-
const promise = this.releaseNote(channelNumber, noteNumber, velocity);
|
|
1400
|
-
promises.push(promise);
|
|
1401
|
-
}
|
|
1410
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1411
|
+
const { noteNumber } = note;
|
|
1412
|
+
const promise = this.noteOff(channelNumber, noteNumber, velocity);
|
|
1413
|
+
promises.push(promise);
|
|
1402
1414
|
});
|
|
1403
1415
|
return promises;
|
|
1404
1416
|
}
|
|
@@ -1409,53 +1421,51 @@ export class Midy {
|
|
|
1409
1421
|
channel.state.sostenutoPedal = 0;
|
|
1410
1422
|
channel.sostenutoNotes.forEach((activeNote) => {
|
|
1411
1423
|
const { noteNumber } = activeNote;
|
|
1412
|
-
const promise = this.
|
|
1424
|
+
const promise = this.noteOff(channelNumber, noteNumber, velocity);
|
|
1413
1425
|
promises.push(promise);
|
|
1414
1426
|
});
|
|
1415
1427
|
channel.sostenutoNotes.clear();
|
|
1416
1428
|
return promises;
|
|
1417
1429
|
}
|
|
1418
|
-
handleMIDIMessage(statusByte, data1, data2) {
|
|
1430
|
+
handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
|
|
1419
1431
|
const channelNumber = omni ? 0 : statusByte & 0x0F;
|
|
1420
1432
|
const messageType = statusByte & 0xF0;
|
|
1421
1433
|
switch (messageType) {
|
|
1422
1434
|
case 0x80:
|
|
1423
|
-
return this.
|
|
1435
|
+
return this.noteOff(channelNumber, data1, data2, scheduleTime);
|
|
1424
1436
|
case 0x90:
|
|
1425
|
-
return this.noteOn(channelNumber, data1, data2);
|
|
1437
|
+
return this.noteOn(channelNumber, data1, data2, scheduleTime);
|
|
1426
1438
|
case 0xA0:
|
|
1427
|
-
return this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
|
|
1439
|
+
return this.handlePolyphonicKeyPressure(channelNumber, data1, data2, scheduleTime);
|
|
1428
1440
|
case 0xB0:
|
|
1429
|
-
return this.handleControlChange(channelNumber, data1, data2);
|
|
1441
|
+
return this.handleControlChange(channelNumber, data1, data2, scheduleTime);
|
|
1430
1442
|
case 0xC0:
|
|
1431
|
-
return this.handleProgramChange(channelNumber, data1);
|
|
1443
|
+
return this.handleProgramChange(channelNumber, data1, scheduleTime);
|
|
1432
1444
|
case 0xD0:
|
|
1433
|
-
return this.handleChannelPressure(channelNumber, data1);
|
|
1445
|
+
return this.handleChannelPressure(channelNumber, data1, scheduleTime);
|
|
1434
1446
|
case 0xE0:
|
|
1435
|
-
return this.handlePitchBendMessage(channelNumber, data1, data2);
|
|
1447
|
+
return this.handlePitchBendMessage(channelNumber, data1, data2, scheduleTime);
|
|
1436
1448
|
default:
|
|
1437
1449
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
1438
1450
|
}
|
|
1439
1451
|
}
|
|
1440
|
-
handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure) {
|
|
1441
|
-
const now = this.audioContext.currentTime;
|
|
1452
|
+
handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure, scheduleTime) {
|
|
1442
1453
|
const channel = this.channels[channelNumber];
|
|
1443
1454
|
channel.state.polyphonicKeyPressure = pressure / 127;
|
|
1444
1455
|
const table = channel.polyphonicKeyPressureTable;
|
|
1445
|
-
const activeNotes = this.getActiveNotes(channel,
|
|
1456
|
+
const activeNotes = this.getActiveNotes(channel, scheduleTime);
|
|
1446
1457
|
if (activeNotes.has(noteNumber)) {
|
|
1447
1458
|
const note = activeNotes.get(noteNumber);
|
|
1448
|
-
this.
|
|
1459
|
+
this.setControllerParameters(channel, note, table);
|
|
1449
1460
|
}
|
|
1450
1461
|
// this.applyVoiceParams(channel, 10);
|
|
1451
1462
|
}
|
|
1452
|
-
handleProgramChange(channelNumber, program) {
|
|
1463
|
+
handleProgramChange(channelNumber, program, _scheduleTime) {
|
|
1453
1464
|
const channel = this.channels[channelNumber];
|
|
1454
1465
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1455
1466
|
channel.program = program;
|
|
1456
1467
|
}
|
|
1457
|
-
handleChannelPressure(channelNumber, value) {
|
|
1458
|
-
const now = this.audioContext.currentTime;
|
|
1468
|
+
handleChannelPressure(channelNumber, value, scheduleTime) {
|
|
1459
1469
|
const channel = this.channels[channelNumber];
|
|
1460
1470
|
const prev = channel.state.channelPressure;
|
|
1461
1471
|
const next = value / 127;
|
|
@@ -1465,69 +1475,68 @@ export class Midy {
|
|
|
1465
1475
|
channel.detune += pressureDepth * (next - prev);
|
|
1466
1476
|
}
|
|
1467
1477
|
const table = channel.channelPressureTable;
|
|
1468
|
-
this.getActiveNotes(channel,
|
|
1469
|
-
this.
|
|
1478
|
+
this.getActiveNotes(channel, scheduleTime).forEach((note) => {
|
|
1479
|
+
this.setControllerParameters(channel, note, table);
|
|
1470
1480
|
});
|
|
1471
1481
|
// this.applyVoiceParams(channel, 13);
|
|
1472
1482
|
}
|
|
1473
|
-
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
1483
|
+
handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
|
|
1474
1484
|
const pitchBend = msb * 128 + lsb;
|
|
1475
|
-
this.setPitchBend(channelNumber, pitchBend);
|
|
1485
|
+
this.setPitchBend(channelNumber, pitchBend, scheduleTime);
|
|
1476
1486
|
}
|
|
1477
|
-
setPitchBend(channelNumber, value) {
|
|
1487
|
+
setPitchBend(channelNumber, value, scheduleTime) {
|
|
1488
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1478
1489
|
const channel = this.channels[channelNumber];
|
|
1479
1490
|
const state = channel.state;
|
|
1480
1491
|
const prev = state.pitchWheel * 2 - 1;
|
|
1481
1492
|
const next = (value - 8192) / 8192;
|
|
1482
1493
|
state.pitchWheel = value / 16383;
|
|
1483
1494
|
channel.detune += (next - prev) * state.pitchWheelSensitivity * 12800;
|
|
1484
|
-
this.updateChannelDetune(channel);
|
|
1485
|
-
this.applyVoiceParams(channel, 14);
|
|
1495
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
1496
|
+
this.applyVoiceParams(channel, 14, scheduleTime);
|
|
1486
1497
|
}
|
|
1487
|
-
setModLfoToPitch(channel, note,
|
|
1488
|
-
const
|
|
1489
|
-
|
|
1498
|
+
setModLfoToPitch(channel, note, scheduleTime) {
|
|
1499
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch +
|
|
1500
|
+
this.getLFOPitchDepth(channel, note);
|
|
1490
1501
|
const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
|
|
1491
1502
|
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
1492
1503
|
note.modulationDepth.gain
|
|
1493
|
-
.cancelScheduledValues(
|
|
1494
|
-
.setValueAtTime(modulationDepth,
|
|
1504
|
+
.cancelScheduledValues(scheduleTime)
|
|
1505
|
+
.setValueAtTime(modulationDepth, scheduleTime);
|
|
1495
1506
|
}
|
|
1496
|
-
setVibLfoToPitch(channel, note) {
|
|
1497
|
-
const now = this.audioContext.currentTime;
|
|
1507
|
+
setVibLfoToPitch(channel, note, scheduleTime) {
|
|
1498
1508
|
const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
|
|
1499
1509
|
const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
|
|
1500
1510
|
2;
|
|
1501
1511
|
const vibratoDepthSign = 0 < vibLfoToPitch;
|
|
1502
1512
|
note.vibratoDepth.gain
|
|
1503
|
-
.cancelScheduledValues(
|
|
1504
|
-
.setValueAtTime(vibratoDepth * vibratoDepthSign,
|
|
1513
|
+
.cancelScheduledValues(scheduleTime)
|
|
1514
|
+
.setValueAtTime(vibratoDepth * vibratoDepthSign, scheduleTime);
|
|
1505
1515
|
}
|
|
1506
|
-
setModLfoToFilterFc(note,
|
|
1507
|
-
const
|
|
1508
|
-
|
|
1516
|
+
setModLfoToFilterFc(channel, note, scheduleTime) {
|
|
1517
|
+
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc +
|
|
1518
|
+
this.getLFOFilterDepth(channel, note);
|
|
1509
1519
|
note.filterDepth.gain
|
|
1510
|
-
.cancelScheduledValues(
|
|
1511
|
-
.setValueAtTime(modLfoToFilterFc,
|
|
1520
|
+
.cancelScheduledValues(scheduleTime)
|
|
1521
|
+
.setValueAtTime(modLfoToFilterFc, scheduleTime);
|
|
1512
1522
|
}
|
|
1513
|
-
setModLfoToVolume(note,
|
|
1514
|
-
const now = this.audioContext.currentTime;
|
|
1523
|
+
setModLfoToVolume(channel, note, scheduleTime) {
|
|
1515
1524
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1516
1525
|
const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1517
|
-
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) *
|
|
1526
|
+
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) *
|
|
1527
|
+
(1 + this.getLFOAmplitudeDepth(channel, note));
|
|
1518
1528
|
note.volumeDepth.gain
|
|
1519
|
-
.cancelScheduledValues(
|
|
1520
|
-
.setValueAtTime(volumeDepth,
|
|
1529
|
+
.cancelScheduledValues(scheduleTime)
|
|
1530
|
+
.setValueAtTime(volumeDepth, scheduleTime);
|
|
1521
1531
|
}
|
|
1522
|
-
setReverbEffectsSend(channel, note, prevValue) {
|
|
1532
|
+
setReverbEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1523
1533
|
if (0 < prevValue) {
|
|
1524
1534
|
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1525
|
-
const now = this.audioContext.currentTime;
|
|
1526
1535
|
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
|
|
1527
1536
|
const value = note.voiceParams.reverbEffectsSend + keyBasedValue;
|
|
1528
1537
|
note.reverbEffectsSend.gain
|
|
1529
|
-
.cancelScheduledValues(
|
|
1530
|
-
.setValueAtTime(value,
|
|
1538
|
+
.cancelScheduledValues(scheduleTime)
|
|
1539
|
+
.setValueAtTime(value, scheduleTime);
|
|
1531
1540
|
}
|
|
1532
1541
|
else {
|
|
1533
1542
|
note.reverbEffectsSend.disconnect();
|
|
@@ -1545,15 +1554,14 @@ export class Midy {
|
|
|
1545
1554
|
}
|
|
1546
1555
|
}
|
|
1547
1556
|
}
|
|
1548
|
-
setChorusEffectsSend(channel, note, prevValue) {
|
|
1557
|
+
setChorusEffectsSend(channel, note, prevValue, scheduleTime) {
|
|
1549
1558
|
if (0 < prevValue) {
|
|
1550
1559
|
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1551
|
-
const now = this.audioContext.currentTime;
|
|
1552
1560
|
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
|
|
1553
1561
|
const value = note.voiceParams.chorusEffectsSend + keyBasedValue;
|
|
1554
1562
|
note.chorusEffectsSend.gain
|
|
1555
|
-
.cancelScheduledValues(
|
|
1556
|
-
.setValueAtTime(value,
|
|
1563
|
+
.cancelScheduledValues(scheduleTime)
|
|
1564
|
+
.setValueAtTime(value, scheduleTime);
|
|
1557
1565
|
}
|
|
1558
1566
|
else {
|
|
1559
1567
|
note.chorusEffectsSend.disconnect();
|
|
@@ -1571,75 +1579,71 @@ export class Midy {
|
|
|
1571
1579
|
}
|
|
1572
1580
|
}
|
|
1573
1581
|
}
|
|
1574
|
-
setDelayModLFO(note) {
|
|
1575
|
-
const now = this.audioContext.currentTime;
|
|
1582
|
+
setDelayModLFO(note, scheduleTime) {
|
|
1576
1583
|
const startTime = note.startTime;
|
|
1577
|
-
if (startTime <
|
|
1584
|
+
if (startTime < scheduleTime)
|
|
1578
1585
|
return;
|
|
1579
|
-
note.modulationLFO.stop(
|
|
1586
|
+
note.modulationLFO.stop(scheduleTime);
|
|
1580
1587
|
note.modulationLFO.start(startTime + note.voiceParams.delayModLFO);
|
|
1581
1588
|
note.modulationLFO.connect(note.filterDepth);
|
|
1582
1589
|
}
|
|
1583
|
-
setFreqModLFO(note) {
|
|
1584
|
-
const now = this.audioContext.currentTime;
|
|
1590
|
+
setFreqModLFO(note, scheduleTime) {
|
|
1585
1591
|
const freqModLFO = note.voiceParams.freqModLFO;
|
|
1586
1592
|
note.modulationLFO.frequency
|
|
1587
|
-
.cancelScheduledValues(
|
|
1588
|
-
.setValueAtTime(freqModLFO,
|
|
1593
|
+
.cancelScheduledValues(scheduleTime)
|
|
1594
|
+
.setValueAtTime(freqModLFO, scheduleTime);
|
|
1589
1595
|
}
|
|
1590
|
-
setFreqVibLFO(channel, note) {
|
|
1591
|
-
const now = this.audioContext.currentTime;
|
|
1596
|
+
setFreqVibLFO(channel, note, scheduleTime) {
|
|
1592
1597
|
const freqVibLFO = note.voiceParams.freqVibLFO;
|
|
1593
1598
|
note.vibratoLFO.frequency
|
|
1594
|
-
.cancelScheduledValues(
|
|
1595
|
-
.setValueAtTime(freqVibLFO * channel.state.vibratoRate * 2,
|
|
1599
|
+
.cancelScheduledValues(scheduleTime)
|
|
1600
|
+
.setValueAtTime(freqVibLFO * channel.state.vibratoRate * 2, scheduleTime);
|
|
1596
1601
|
}
|
|
1597
1602
|
createVoiceParamsHandlers() {
|
|
1598
1603
|
return {
|
|
1599
|
-
modLfoToPitch: (channel, note, _prevValue) => {
|
|
1604
|
+
modLfoToPitch: (channel, note, _prevValue, scheduleTime) => {
|
|
1600
1605
|
if (0 < channel.state.modulationDepth) {
|
|
1601
|
-
this.setModLfoToPitch(channel, note,
|
|
1606
|
+
this.setModLfoToPitch(channel, note, scheduleTime);
|
|
1602
1607
|
}
|
|
1603
1608
|
},
|
|
1604
|
-
vibLfoToPitch: (channel, note, _prevValue) => {
|
|
1609
|
+
vibLfoToPitch: (channel, note, _prevValue, scheduleTime) => {
|
|
1605
1610
|
if (0 < channel.state.vibratoDepth) {
|
|
1606
|
-
this.setVibLfoToPitch(channel, note);
|
|
1611
|
+
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
1607
1612
|
}
|
|
1608
1613
|
},
|
|
1609
|
-
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1614
|
+
modLfoToFilterFc: (channel, note, _prevValue, scheduleTime) => {
|
|
1610
1615
|
if (0 < channel.state.modulationDepth) {
|
|
1611
|
-
this.setModLfoToFilterFc(note,
|
|
1616
|
+
this.setModLfoToFilterFc(channel, note, scheduleTime);
|
|
1612
1617
|
}
|
|
1613
1618
|
},
|
|
1614
|
-
modLfoToVolume: (channel, note, _prevValue) => {
|
|
1619
|
+
modLfoToVolume: (channel, note, _prevValue, scheduleTime) => {
|
|
1615
1620
|
if (0 < channel.state.modulationDepth) {
|
|
1616
|
-
this.setModLfoToVolume(note,
|
|
1621
|
+
this.setModLfoToVolume(channel, note, scheduleTime);
|
|
1617
1622
|
}
|
|
1618
1623
|
},
|
|
1619
|
-
chorusEffectsSend: (channel, note, prevValue) => {
|
|
1620
|
-
this.setChorusEffectsSend(channel, note, prevValue);
|
|
1624
|
+
chorusEffectsSend: (channel, note, prevValue, scheduleTime) => {
|
|
1625
|
+
this.setChorusEffectsSend(channel, note, prevValue, scheduleTime);
|
|
1621
1626
|
},
|
|
1622
|
-
reverbEffectsSend: (channel, note, prevValue) => {
|
|
1623
|
-
this.setReverbEffectsSend(channel, note, prevValue);
|
|
1627
|
+
reverbEffectsSend: (channel, note, prevValue, scheduleTime) => {
|
|
1628
|
+
this.setReverbEffectsSend(channel, note, prevValue, scheduleTime);
|
|
1624
1629
|
},
|
|
1625
|
-
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
1626
|
-
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
1627
|
-
delayVibLFO: (channel, note, prevValue) => {
|
|
1630
|
+
delayModLFO: (_channel, note, _prevValue, scheduleTime) => this.setDelayModLFO(note, scheduleTime),
|
|
1631
|
+
freqModLFO: (_channel, note, _prevValue, scheduleTime) => this.setFreqModLFO(note, scheduleTime),
|
|
1632
|
+
delayVibLFO: (channel, note, prevValue, scheduleTime) => {
|
|
1628
1633
|
if (0 < channel.state.vibratoDepth) {
|
|
1629
|
-
const now = this.audioContext.currentTime;
|
|
1630
1634
|
const vibratoDelay = channel.state.vibratoDelay * 2;
|
|
1631
1635
|
const prevStartTime = note.startTime + prevValue * vibratoDelay;
|
|
1632
|
-
if (
|
|
1636
|
+
if (scheduleTime < prevStartTime)
|
|
1633
1637
|
return;
|
|
1634
1638
|
const value = note.voiceParams.delayVibLFO;
|
|
1635
1639
|
const startTime = note.startTime + value * vibratoDelay;
|
|
1636
|
-
note.vibratoLFO.stop(
|
|
1640
|
+
note.vibratoLFO.stop(scheduleTime);
|
|
1637
1641
|
note.vibratoLFO.start(startTime);
|
|
1638
1642
|
}
|
|
1639
1643
|
},
|
|
1640
|
-
freqVibLFO: (channel, note, _prevValue) => {
|
|
1644
|
+
freqVibLFO: (channel, note, _prevValue, scheduleTime) => {
|
|
1641
1645
|
if (0 < channel.state.vibratoDepth) {
|
|
1642
|
-
this.setFreqVibLFO(channel, note);
|
|
1646
|
+
this.setFreqVibLFO(channel, note, scheduleTime);
|
|
1643
1647
|
}
|
|
1644
1648
|
},
|
|
1645
1649
|
};
|
|
@@ -1651,7 +1655,7 @@ export class Midy {
|
|
|
1651
1655
|
state[3] = noteNumber / 127;
|
|
1652
1656
|
return state;
|
|
1653
1657
|
}
|
|
1654
|
-
applyVoiceParams(channel, controllerType) {
|
|
1658
|
+
applyVoiceParams(channel, controllerType, scheduleTime) {
|
|
1655
1659
|
channel.scheduledNotes.forEach((noteList) => {
|
|
1656
1660
|
for (let i = 0; i < noteList.length; i++) {
|
|
1657
1661
|
const note = noteList[i];
|
|
@@ -1667,7 +1671,7 @@ export class Midy {
|
|
|
1667
1671
|
continue;
|
|
1668
1672
|
note.voiceParams[key] = value;
|
|
1669
1673
|
if (key in this.voiceParamsHandlers) {
|
|
1670
|
-
this.voiceParamsHandlers[key](channel, note, prevValue);
|
|
1674
|
+
this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
|
|
1671
1675
|
}
|
|
1672
1676
|
else if (filterEnvelopeKeySet.has(key)) {
|
|
1673
1677
|
if (appliedFilterEnvelope)
|
|
@@ -1680,12 +1684,12 @@ export class Midy {
|
|
|
1680
1684
|
noteVoiceParams[key] = voiceParams[key];
|
|
1681
1685
|
}
|
|
1682
1686
|
if (note.portamento) {
|
|
1683
|
-
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
1687
|
+
this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
|
|
1684
1688
|
}
|
|
1685
1689
|
else {
|
|
1686
|
-
this.setFilterEnvelope(channel, note,
|
|
1690
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
1687
1691
|
}
|
|
1688
|
-
this.setPitchEnvelope(note);
|
|
1692
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1689
1693
|
}
|
|
1690
1694
|
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1691
1695
|
if (appliedVolumeEnvelope)
|
|
@@ -1697,7 +1701,7 @@ export class Midy {
|
|
|
1697
1701
|
if (key in voiceParams)
|
|
1698
1702
|
noteVoiceParams[key] = voiceParams[key];
|
|
1699
1703
|
}
|
|
1700
|
-
this.setVolumeEnvelope(channel, note,
|
|
1704
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1701
1705
|
}
|
|
1702
1706
|
}
|
|
1703
1707
|
}
|
|
@@ -1741,12 +1745,12 @@ export class Midy {
|
|
|
1741
1745
|
127: this.polyOn,
|
|
1742
1746
|
};
|
|
1743
1747
|
}
|
|
1744
|
-
handleControlChange(channelNumber, controllerType, value) {
|
|
1748
|
+
handleControlChange(channelNumber, controllerType, value, scheduleTime) {
|
|
1745
1749
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1746
1750
|
if (handler) {
|
|
1747
|
-
handler.call(this, channelNumber, value);
|
|
1751
|
+
handler.call(this, channelNumber, value, scheduleTime);
|
|
1748
1752
|
const channel = this.channels[channelNumber];
|
|
1749
|
-
this.applyVoiceParams(channel, controllerType + 128);
|
|
1753
|
+
this.applyVoiceParams(channel, controllerType + 128, scheduleTime);
|
|
1750
1754
|
this.applyControlTable(channel, controllerType);
|
|
1751
1755
|
}
|
|
1752
1756
|
else {
|
|
@@ -1756,55 +1760,45 @@ export class Midy {
|
|
|
1756
1760
|
setBankMSB(channelNumber, msb) {
|
|
1757
1761
|
this.channels[channelNumber].bankMSB = msb;
|
|
1758
1762
|
}
|
|
1759
|
-
updateModulation(channel) {
|
|
1760
|
-
|
|
1763
|
+
updateModulation(channel, scheduleTime) {
|
|
1764
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1761
1765
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
}
|
|
1770
|
-
else {
|
|
1771
|
-
this.setPitchEnvelope(note);
|
|
1772
|
-
this.startModulation(channel, note, now);
|
|
1773
|
-
}
|
|
1766
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1767
|
+
if (note.modulationDepth) {
|
|
1768
|
+
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1769
|
+
}
|
|
1770
|
+
else {
|
|
1771
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1772
|
+
this.startModulation(channel, note, scheduleTime);
|
|
1774
1773
|
}
|
|
1775
1774
|
});
|
|
1776
1775
|
}
|
|
1777
|
-
setModulationDepth(channelNumber, modulation) {
|
|
1776
|
+
setModulationDepth(channelNumber, modulation, scheduleTime) {
|
|
1778
1777
|
const channel = this.channels[channelNumber];
|
|
1779
1778
|
channel.state.modulationDepth = modulation / 127;
|
|
1780
|
-
this.updateModulation(channel);
|
|
1779
|
+
this.updateModulation(channel, scheduleTime);
|
|
1781
1780
|
}
|
|
1782
1781
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
1783
1782
|
const channel = this.channels[channelNumber];
|
|
1784
1783
|
const factor = 5 * Math.log(10) / 127;
|
|
1785
1784
|
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1786
1785
|
}
|
|
1787
|
-
setKeyBasedVolume(channel) {
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
if (!note)
|
|
1793
|
-
continue;
|
|
1794
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1795
|
-
if (keyBasedValue === 0)
|
|
1796
|
-
continue;
|
|
1786
|
+
setKeyBasedVolume(channel, scheduleTime) {
|
|
1787
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1788
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1789
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1790
|
+
if (keyBasedValue !== 0) {
|
|
1797
1791
|
note.volumeNode.gain
|
|
1798
|
-
.cancelScheduledValues(
|
|
1799
|
-
.setValueAtTime(1 + keyBasedValue,
|
|
1792
|
+
.cancelScheduledValues(scheduleTime)
|
|
1793
|
+
.setValueAtTime(1 + keyBasedValue, scheduleTime);
|
|
1800
1794
|
}
|
|
1801
1795
|
});
|
|
1802
1796
|
}
|
|
1803
|
-
setVolume(channelNumber, volume) {
|
|
1797
|
+
setVolume(channelNumber, volume, scheduleTime) {
|
|
1804
1798
|
const channel = this.channels[channelNumber];
|
|
1805
1799
|
channel.state.volume = volume / 127;
|
|
1806
|
-
this.updateChannelVolume(channel);
|
|
1807
|
-
this.setKeyBasedVolume(channel);
|
|
1800
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1801
|
+
this.setKeyBasedVolume(channel, scheduleTime);
|
|
1808
1802
|
}
|
|
1809
1803
|
panToGain(pan) {
|
|
1810
1804
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1813,82 +1807,75 @@ export class Midy {
|
|
|
1813
1807
|
gainRight: Math.sin(theta),
|
|
1814
1808
|
};
|
|
1815
1809
|
}
|
|
1816
|
-
setKeyBasedPan(channel) {
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
if (!note)
|
|
1822
|
-
continue;
|
|
1823
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1824
|
-
if (keyBasedValue === 0)
|
|
1825
|
-
continue;
|
|
1810
|
+
setKeyBasedPan(channel, scheduleTime) {
|
|
1811
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1812
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1813
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1814
|
+
if (keyBasedValue !== 0) {
|
|
1826
1815
|
const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
|
|
1827
1816
|
note.gainL.gain
|
|
1828
|
-
.cancelScheduledValues(
|
|
1829
|
-
.setValueAtTime(gainLeft,
|
|
1817
|
+
.cancelScheduledValues(scheduleTime)
|
|
1818
|
+
.setValueAtTime(gainLeft, scheduleTime);
|
|
1830
1819
|
note.gainR.gain
|
|
1831
|
-
.cancelScheduledValues(
|
|
1832
|
-
.setValueAtTime(gainRight,
|
|
1820
|
+
.cancelScheduledValues(scheduleTime)
|
|
1821
|
+
.setValueAtTime(gainRight, scheduleTime);
|
|
1833
1822
|
}
|
|
1834
1823
|
});
|
|
1835
1824
|
}
|
|
1836
|
-
setPan(channelNumber, pan) {
|
|
1825
|
+
setPan(channelNumber, pan, scheduleTime) {
|
|
1837
1826
|
const channel = this.channels[channelNumber];
|
|
1838
1827
|
channel.state.pan = pan / 127;
|
|
1839
|
-
this.updateChannelVolume(channel);
|
|
1840
|
-
this.setKeyBasedPan(channel);
|
|
1828
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1829
|
+
this.setKeyBasedPan(channel, scheduleTime);
|
|
1841
1830
|
}
|
|
1842
|
-
setExpression(channelNumber, expression) {
|
|
1831
|
+
setExpression(channelNumber, expression, scheduleTime) {
|
|
1843
1832
|
const channel = this.channels[channelNumber];
|
|
1844
1833
|
channel.state.expression = expression / 127;
|
|
1845
|
-
this.updateChannelVolume(channel);
|
|
1834
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1846
1835
|
}
|
|
1847
1836
|
setBankLSB(channelNumber, lsb) {
|
|
1848
1837
|
this.channels[channelNumber].bankLSB = lsb;
|
|
1849
1838
|
}
|
|
1850
|
-
dataEntryLSB(channelNumber, value) {
|
|
1839
|
+
dataEntryLSB(channelNumber, value, scheduleTime) {
|
|
1851
1840
|
this.channels[channelNumber].dataLSB = value;
|
|
1852
|
-
this.handleRPN(channelNumber,
|
|
1841
|
+
this.handleRPN(channelNumber, scheduleTime);
|
|
1853
1842
|
}
|
|
1854
|
-
updateChannelVolume(channel) {
|
|
1855
|
-
const now = this.audioContext.currentTime;
|
|
1843
|
+
updateChannelVolume(channel, scheduleTime) {
|
|
1856
1844
|
const state = channel.state;
|
|
1857
1845
|
const volume = state.volume * state.expression;
|
|
1858
1846
|
const { gainLeft, gainRight } = this.panToGain(state.pan);
|
|
1859
1847
|
channel.gainL.gain
|
|
1860
|
-
.cancelScheduledValues(
|
|
1861
|
-
.setValueAtTime(volume * gainLeft,
|
|
1848
|
+
.cancelScheduledValues(scheduleTime)
|
|
1849
|
+
.setValueAtTime(volume * gainLeft, scheduleTime);
|
|
1862
1850
|
channel.gainR.gain
|
|
1863
|
-
.cancelScheduledValues(
|
|
1864
|
-
.setValueAtTime(volume * gainRight,
|
|
1851
|
+
.cancelScheduledValues(scheduleTime)
|
|
1852
|
+
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1865
1853
|
}
|
|
1866
|
-
setSustainPedal(channelNumber, value) {
|
|
1854
|
+
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
1855
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1867
1856
|
this.channels[channelNumber].state.sustainPedal = value / 127;
|
|
1868
1857
|
if (value < 64) {
|
|
1869
|
-
this.releaseSustainPedal(channelNumber, value);
|
|
1858
|
+
this.releaseSustainPedal(channelNumber, value, scheduleTime);
|
|
1870
1859
|
}
|
|
1871
1860
|
}
|
|
1872
1861
|
setPortamento(channelNumber, value) {
|
|
1873
1862
|
this.channels[channelNumber].state.portamento = value / 127;
|
|
1874
1863
|
}
|
|
1875
|
-
setSostenutoPedal(channelNumber, value) {
|
|
1864
|
+
setSostenutoPedal(channelNumber, value, scheduleTime) {
|
|
1876
1865
|
const channel = this.channels[channelNumber];
|
|
1877
1866
|
channel.state.sostenutoPedal = value / 127;
|
|
1878
1867
|
if (64 <= value) {
|
|
1879
|
-
|
|
1880
|
-
channel.sostenutoNotes = this.getActiveNotes(channel, now);
|
|
1868
|
+
channel.sostenutoNotes = this.getActiveNotes(channel, scheduleTime);
|
|
1881
1869
|
}
|
|
1882
1870
|
else {
|
|
1883
1871
|
this.releaseSostenutoPedal(channelNumber, value);
|
|
1884
1872
|
}
|
|
1885
1873
|
}
|
|
1886
|
-
setSoftPedal(channelNumber, softPedal) {
|
|
1874
|
+
setSoftPedal(channelNumber, softPedal, _scheduleTime) {
|
|
1887
1875
|
const channel = this.channels[channelNumber];
|
|
1888
1876
|
channel.state.softPedal = softPedal / 127;
|
|
1889
1877
|
}
|
|
1890
|
-
setFilterResonance(channelNumber, filterResonance) {
|
|
1891
|
-
const now = this.audioContext.currentTime;
|
|
1878
|
+
setFilterResonance(channelNumber, filterResonance, scheduleTime) {
|
|
1892
1879
|
const channel = this.channels[channelNumber];
|
|
1893
1880
|
const state = channel.state;
|
|
1894
1881
|
state.filterResonance = filterResonance / 64;
|
|
@@ -1898,16 +1885,15 @@ export class Midy {
|
|
|
1898
1885
|
if (!note)
|
|
1899
1886
|
continue;
|
|
1900
1887
|
const Q = note.voiceParams.initialFilterQ / 5 * state.filterResonance;
|
|
1901
|
-
note.filterNode.Q.setValueAtTime(Q,
|
|
1888
|
+
note.filterNode.Q.setValueAtTime(Q, scheduleTime);
|
|
1902
1889
|
}
|
|
1903
1890
|
});
|
|
1904
1891
|
}
|
|
1905
|
-
setReleaseTime(channelNumber, releaseTime) {
|
|
1892
|
+
setReleaseTime(channelNumber, releaseTime, _scheduleTime) {
|
|
1906
1893
|
const channel = this.channels[channelNumber];
|
|
1907
1894
|
channel.state.releaseTime = releaseTime / 64;
|
|
1908
1895
|
}
|
|
1909
|
-
setAttackTime(channelNumber, attackTime) {
|
|
1910
|
-
const now = this.audioContext.currentTime;
|
|
1896
|
+
setAttackTime(channelNumber, attackTime, scheduleTime) {
|
|
1911
1897
|
const channel = this.channels[channelNumber];
|
|
1912
1898
|
channel.state.attackTime = attackTime / 64;
|
|
1913
1899
|
channel.scheduledNotes.forEach((noteList) => {
|
|
@@ -1915,13 +1901,13 @@ export class Midy {
|
|
|
1915
1901
|
const note = noteList[i];
|
|
1916
1902
|
if (!note)
|
|
1917
1903
|
continue;
|
|
1918
|
-
if (note.startTime <
|
|
1904
|
+
if (note.startTime < scheduleTime)
|
|
1919
1905
|
continue;
|
|
1920
|
-
this.setVolumeEnvelope(channel, note
|
|
1906
|
+
this.setVolumeEnvelope(channel, note);
|
|
1921
1907
|
}
|
|
1922
1908
|
});
|
|
1923
1909
|
}
|
|
1924
|
-
setBrightness(channelNumber, brightness) {
|
|
1910
|
+
setBrightness(channelNumber, brightness, scheduleTime) {
|
|
1925
1911
|
const channel = this.channels[channelNumber];
|
|
1926
1912
|
channel.state.brightness = brightness / 64;
|
|
1927
1913
|
channel.scheduledNotes.forEach((noteList) => {
|
|
@@ -1930,15 +1916,15 @@ export class Midy {
|
|
|
1930
1916
|
if (!note)
|
|
1931
1917
|
continue;
|
|
1932
1918
|
if (note.portamento) {
|
|
1933
|
-
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
1919
|
+
this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
|
|
1934
1920
|
}
|
|
1935
1921
|
else {
|
|
1936
|
-
this.setFilterEnvelope(channel, note
|
|
1922
|
+
this.setFilterEnvelope(channel, note);
|
|
1937
1923
|
}
|
|
1938
1924
|
}
|
|
1939
1925
|
});
|
|
1940
1926
|
}
|
|
1941
|
-
setDecayTime(channelNumber, dacayTime) {
|
|
1927
|
+
setDecayTime(channelNumber, dacayTime, scheduleTime) {
|
|
1942
1928
|
const channel = this.channels[channelNumber];
|
|
1943
1929
|
channel.state.decayTime = dacayTime / 64;
|
|
1944
1930
|
channel.scheduledNotes.forEach((noteList) => {
|
|
@@ -1946,11 +1932,11 @@ export class Midy {
|
|
|
1946
1932
|
const note = noteList[i];
|
|
1947
1933
|
if (!note)
|
|
1948
1934
|
continue;
|
|
1949
|
-
this.setVolumeEnvelope(channel, note,
|
|
1935
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1950
1936
|
}
|
|
1951
1937
|
});
|
|
1952
1938
|
}
|
|
1953
|
-
setVibratoRate(channelNumber, vibratoRate) {
|
|
1939
|
+
setVibratoRate(channelNumber, vibratoRate, scheduleTime) {
|
|
1954
1940
|
const channel = this.channels[channelNumber];
|
|
1955
1941
|
channel.state.vibratoRate = vibratoRate / 64;
|
|
1956
1942
|
if (channel.vibratoDepth <= 0)
|
|
@@ -1960,11 +1946,11 @@ export class Midy {
|
|
|
1960
1946
|
const note = noteList[i];
|
|
1961
1947
|
if (!note)
|
|
1962
1948
|
continue;
|
|
1963
|
-
this.setVibLfoToPitch(channel, note);
|
|
1949
|
+
this.setVibLfoToPitch(channel, note, scheduleTime);
|
|
1964
1950
|
}
|
|
1965
1951
|
});
|
|
1966
1952
|
}
|
|
1967
|
-
setVibratoDepth(channelNumber, vibratoDepth) {
|
|
1953
|
+
setVibratoDepth(channelNumber, vibratoDepth, scheduleTime) {
|
|
1968
1954
|
const channel = this.channels[channelNumber];
|
|
1969
1955
|
const prev = channel.state.vibratoDepth;
|
|
1970
1956
|
channel.state.vibratoDepth = vibratoDepth / 64;
|
|
@@ -1974,7 +1960,7 @@ export class Midy {
|
|
|
1974
1960
|
const note = noteList[i];
|
|
1975
1961
|
if (!note)
|
|
1976
1962
|
continue;
|
|
1977
|
-
this.setFreqVibLFO(channel, note);
|
|
1963
|
+
this.setFreqVibLFO(channel, note, scheduleTime);
|
|
1978
1964
|
}
|
|
1979
1965
|
});
|
|
1980
1966
|
}
|
|
@@ -1984,7 +1970,7 @@ export class Midy {
|
|
|
1984
1970
|
const note = noteList[i];
|
|
1985
1971
|
if (!note)
|
|
1986
1972
|
continue;
|
|
1987
|
-
this.startVibrato(channel, note,
|
|
1973
|
+
this.startVibrato(channel, note, scheduleTime);
|
|
1988
1974
|
}
|
|
1989
1975
|
});
|
|
1990
1976
|
}
|
|
@@ -1998,21 +1984,21 @@ export class Midy {
|
|
|
1998
1984
|
const note = noteList[i];
|
|
1999
1985
|
if (!note)
|
|
2000
1986
|
continue;
|
|
2001
|
-
this.startVibrato(channel, note,
|
|
1987
|
+
this.startVibrato(channel, note, scheduleTime);
|
|
2002
1988
|
}
|
|
2003
1989
|
});
|
|
2004
1990
|
}
|
|
2005
1991
|
}
|
|
2006
|
-
setReverbSendLevel(channelNumber, reverbSendLevel) {
|
|
1992
|
+
setReverbSendLevel(channelNumber, reverbSendLevel, scheduleTime) {
|
|
2007
1993
|
const channel = this.channels[channelNumber];
|
|
2008
1994
|
const state = channel.state;
|
|
2009
1995
|
const reverbEffect = this.reverbEffect;
|
|
2010
1996
|
if (0 < state.reverbSendLevel) {
|
|
2011
1997
|
if (0 < reverbSendLevel) {
|
|
2012
|
-
const now = this.audioContext.currentTime;
|
|
2013
1998
|
state.reverbSendLevel = reverbSendLevel / 127;
|
|
2014
|
-
reverbEffect.input.gain
|
|
2015
|
-
|
|
1999
|
+
reverbEffect.input.gain
|
|
2000
|
+
.cancelScheduledValues(scheduleTime)
|
|
2001
|
+
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
2016
2002
|
}
|
|
2017
2003
|
else {
|
|
2018
2004
|
channel.scheduledNotes.forEach((noteList) => {
|
|
@@ -2029,31 +2015,31 @@ export class Midy {
|
|
|
2029
2015
|
}
|
|
2030
2016
|
else {
|
|
2031
2017
|
if (0 < reverbSendLevel) {
|
|
2032
|
-
const now = this.audioContext.currentTime;
|
|
2033
2018
|
channel.scheduledNotes.forEach((noteList) => {
|
|
2034
2019
|
for (let i = 0; i < noteList.length; i++) {
|
|
2035
2020
|
const note = noteList[i];
|
|
2036
2021
|
if (!note)
|
|
2037
2022
|
continue;
|
|
2038
|
-
this.setReverbEffectsSend(channel, note, 0);
|
|
2023
|
+
this.setReverbEffectsSend(channel, note, 0, scheduleTime);
|
|
2039
2024
|
}
|
|
2040
2025
|
});
|
|
2041
2026
|
state.reverbSendLevel = reverbSendLevel / 127;
|
|
2042
|
-
reverbEffect.input.gain
|
|
2043
|
-
|
|
2027
|
+
reverbEffect.input.gain
|
|
2028
|
+
.cancelScheduledValues(scheduleTime)
|
|
2029
|
+
.setValueAtTime(state.reverbSendLevel, scheduleTime);
|
|
2044
2030
|
}
|
|
2045
2031
|
}
|
|
2046
2032
|
}
|
|
2047
|
-
setChorusSendLevel(channelNumber, chorusSendLevel) {
|
|
2033
|
+
setChorusSendLevel(channelNumber, chorusSendLevel, scheduleTime) {
|
|
2048
2034
|
const channel = this.channels[channelNumber];
|
|
2049
2035
|
const state = channel.state;
|
|
2050
2036
|
const chorusEffect = this.chorusEffect;
|
|
2051
2037
|
if (0 < state.chorusSendLevel) {
|
|
2052
2038
|
if (0 < chorusSendLevel) {
|
|
2053
|
-
const now = this.audioContext.currentTime;
|
|
2054
2039
|
state.chorusSendLevel = chorusSendLevel / 127;
|
|
2055
|
-
chorusEffect.input.gain
|
|
2056
|
-
|
|
2040
|
+
chorusEffect.input.gain
|
|
2041
|
+
.cancelScheduledValues(scheduleTime)
|
|
2042
|
+
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
2057
2043
|
}
|
|
2058
2044
|
else {
|
|
2059
2045
|
channel.scheduledNotes.forEach((noteList) => {
|
|
@@ -2070,18 +2056,18 @@ export class Midy {
|
|
|
2070
2056
|
}
|
|
2071
2057
|
else {
|
|
2072
2058
|
if (0 < chorusSendLevel) {
|
|
2073
|
-
const now = this.audioContext.currentTime;
|
|
2074
2059
|
channel.scheduledNotes.forEach((noteList) => {
|
|
2075
2060
|
for (let i = 0; i < noteList.length; i++) {
|
|
2076
2061
|
const note = noteList[i];
|
|
2077
2062
|
if (!note)
|
|
2078
2063
|
continue;
|
|
2079
|
-
this.setChorusEffectsSend(channel, note, 0);
|
|
2064
|
+
this.setChorusEffectsSend(channel, note, 0, scheduleTime);
|
|
2080
2065
|
}
|
|
2081
2066
|
});
|
|
2082
2067
|
state.chorusSendLevel = chorusSendLevel / 127;
|
|
2083
|
-
chorusEffect.input.gain
|
|
2084
|
-
|
|
2068
|
+
chorusEffect.input.gain
|
|
2069
|
+
.cancelScheduledValues(scheduleTime)
|
|
2070
|
+
.setValueAtTime(state.chorusSendLevel, scheduleTime);
|
|
2085
2071
|
}
|
|
2086
2072
|
}
|
|
2087
2073
|
}
|
|
@@ -2111,13 +2097,13 @@ export class Midy {
|
|
|
2111
2097
|
channel.dataMSB = minMSB;
|
|
2112
2098
|
}
|
|
2113
2099
|
}
|
|
2114
|
-
handleRPN(channelNumber, value) {
|
|
2100
|
+
handleRPN(channelNumber, value, scheduleTime) {
|
|
2115
2101
|
const channel = this.channels[channelNumber];
|
|
2116
2102
|
const rpn = channel.rpnMSB * 128 + channel.rpnLSB;
|
|
2117
2103
|
switch (rpn) {
|
|
2118
2104
|
case 0:
|
|
2119
2105
|
channel.dataLSB += value;
|
|
2120
|
-
this.handlePitchBendRangeRPN(channelNumber);
|
|
2106
|
+
this.handlePitchBendRangeRPN(channelNumber, scheduleTime);
|
|
2121
2107
|
break;
|
|
2122
2108
|
case 1:
|
|
2123
2109
|
channel.dataLSB += value;
|
|
@@ -2149,25 +2135,26 @@ export class Midy {
|
|
|
2149
2135
|
setRPNLSB(channelNumber, value) {
|
|
2150
2136
|
this.channels[channelNumber].rpnLSB = value;
|
|
2151
2137
|
}
|
|
2152
|
-
dataEntryMSB(channelNumber, value) {
|
|
2138
|
+
dataEntryMSB(channelNumber, value, scheduleTime) {
|
|
2153
2139
|
this.channels[channelNumber].dataMSB = value;
|
|
2154
|
-
this.handleRPN(channelNumber,
|
|
2140
|
+
this.handleRPN(channelNumber, scheduleTime);
|
|
2155
2141
|
}
|
|
2156
|
-
handlePitchBendRangeRPN(channelNumber) {
|
|
2142
|
+
handlePitchBendRangeRPN(channelNumber, scheduleTime) {
|
|
2157
2143
|
const channel = this.channels[channelNumber];
|
|
2158
2144
|
this.limitData(channel, 0, 127, 0, 99);
|
|
2159
2145
|
const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
|
|
2160
|
-
this.setPitchBendRange(channelNumber, pitchBendRange);
|
|
2146
|
+
this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
|
|
2161
2147
|
}
|
|
2162
|
-
setPitchBendRange(channelNumber, value) {
|
|
2148
|
+
setPitchBendRange(channelNumber, value, scheduleTime) {
|
|
2149
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2163
2150
|
const channel = this.channels[channelNumber];
|
|
2164
2151
|
const state = channel.state;
|
|
2165
2152
|
const prev = state.pitchWheelSensitivity;
|
|
2166
2153
|
const next = value / 128;
|
|
2167
2154
|
state.pitchWheelSensitivity = next;
|
|
2168
2155
|
channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
|
|
2169
|
-
this.updateChannelDetune(channel);
|
|
2170
|
-
this.applyVoiceParams(channel, 16);
|
|
2156
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2157
|
+
this.applyVoiceParams(channel, 16, scheduleTime);
|
|
2171
2158
|
}
|
|
2172
2159
|
handleFineTuningRPN(channelNumber) {
|
|
2173
2160
|
const channel = this.channels[channelNumber];
|
|
@@ -2208,8 +2195,9 @@ export class Midy {
|
|
|
2208
2195
|
channel.modulationDepthRange = modulationDepthRange;
|
|
2209
2196
|
this.updateModulation(channel);
|
|
2210
2197
|
}
|
|
2211
|
-
allSoundOff(channelNumber) {
|
|
2212
|
-
|
|
2198
|
+
allSoundOff(channelNumber, _value, scheduleTime) {
|
|
2199
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2200
|
+
return this.stopChannelNotes(channelNumber, 0, true, scheduleTime);
|
|
2213
2201
|
}
|
|
2214
2202
|
resetAllControllers(channelNumber) {
|
|
2215
2203
|
const stateTypes = [
|
|
@@ -2237,8 +2225,9 @@ export class Midy {
|
|
|
2237
2225
|
channel[type] = this.constructor.channelSettings[type];
|
|
2238
2226
|
}
|
|
2239
2227
|
}
|
|
2240
|
-
allNotesOff(channelNumber) {
|
|
2241
|
-
|
|
2228
|
+
allNotesOff(channelNumber, _value, scheduleTime) {
|
|
2229
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2230
|
+
return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
|
|
2242
2231
|
}
|
|
2243
2232
|
omniOff() {
|
|
2244
2233
|
this.omni = false;
|
|
@@ -2252,16 +2241,16 @@ export class Midy {
|
|
|
2252
2241
|
polyOn() {
|
|
2253
2242
|
this.mono = false;
|
|
2254
2243
|
}
|
|
2255
|
-
handleUniversalNonRealTimeExclusiveMessage(data) {
|
|
2244
|
+
handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime) {
|
|
2256
2245
|
switch (data[2]) {
|
|
2257
2246
|
case 8:
|
|
2258
2247
|
switch (data[3]) {
|
|
2259
2248
|
case 8:
|
|
2260
2249
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2261
|
-
return this.handleScaleOctaveTuning1ByteFormatSysEx(data, false);
|
|
2250
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data, false, scheduleTime);
|
|
2262
2251
|
case 9:
|
|
2263
2252
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2264
|
-
return this.handleScaleOctaveTuning2ByteFormatSysEx(data, false);
|
|
2253
|
+
return this.handleScaleOctaveTuning2ByteFormatSysEx(data, false, scheduleTime);
|
|
2265
2254
|
default:
|
|
2266
2255
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2267
2256
|
}
|
|
@@ -2304,18 +2293,18 @@ export class Midy {
|
|
|
2304
2293
|
this.channels[9].bankMSB = 120;
|
|
2305
2294
|
this.channels[9].bank = 120 * 128;
|
|
2306
2295
|
}
|
|
2307
|
-
handleUniversalRealTimeExclusiveMessage(data) {
|
|
2296
|
+
handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
|
|
2308
2297
|
switch (data[2]) {
|
|
2309
2298
|
case 4:
|
|
2310
2299
|
switch (data[3]) {
|
|
2311
2300
|
case 1:
|
|
2312
|
-
return this.handleMasterVolumeSysEx(data);
|
|
2301
|
+
return this.handleMasterVolumeSysEx(data, scheduleTime);
|
|
2313
2302
|
case 3: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
|
|
2314
|
-
return this.handleMasterFineTuningSysEx(data);
|
|
2303
|
+
return this.handleMasterFineTuningSysEx(data, scheduleTime);
|
|
2315
2304
|
case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
|
|
2316
|
-
return this.handleMasterCoarseTuningSysEx(data);
|
|
2305
|
+
return this.handleMasterCoarseTuningSysEx(data, scheduleTime);
|
|
2317
2306
|
case 5: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca24.pdf
|
|
2318
|
-
return this.handleGlobalParameterControlSysEx(data);
|
|
2307
|
+
return this.handleGlobalParameterControlSysEx(data, scheduleTime);
|
|
2319
2308
|
default:
|
|
2320
2309
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2321
2310
|
}
|
|
@@ -2323,10 +2312,10 @@ export class Midy {
|
|
|
2323
2312
|
case 8:
|
|
2324
2313
|
switch (data[3]) {
|
|
2325
2314
|
case 8: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2326
|
-
return this.handleScaleOctaveTuning1ByteFormatSysEx(data, true);
|
|
2315
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data, true, scheduleTime);
|
|
2327
2316
|
case 9:
|
|
2328
2317
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2329
|
-
return this.handleScaleOctaveTuning2ByteFormatSysEx(data, true);
|
|
2318
|
+
return this.handleScaleOctaveTuning2ByteFormatSysEx(data, true, scheduleTime);
|
|
2330
2319
|
default:
|
|
2331
2320
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2332
2321
|
}
|
|
@@ -2346,7 +2335,7 @@ export class Midy {
|
|
|
2346
2335
|
case 10:
|
|
2347
2336
|
switch (data[3]) {
|
|
2348
2337
|
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca23.pdf
|
|
2349
|
-
return this.handleKeyBasedInstrumentControlSysEx(data);
|
|
2338
|
+
return this.handleKeyBasedInstrumentControlSysEx(data, scheduleTime);
|
|
2350
2339
|
default:
|
|
2351
2340
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2352
2341
|
}
|
|
@@ -2355,49 +2344,50 @@ export class Midy {
|
|
|
2355
2344
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2356
2345
|
}
|
|
2357
2346
|
}
|
|
2358
|
-
handleMasterVolumeSysEx(data) {
|
|
2347
|
+
handleMasterVolumeSysEx(data, scheduleTime) {
|
|
2359
2348
|
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
2360
|
-
this.setMasterVolume(volume);
|
|
2349
|
+
this.setMasterVolume(volume, scheduleTime);
|
|
2361
2350
|
}
|
|
2362
|
-
setMasterVolume(volume) {
|
|
2351
|
+
setMasterVolume(volume, scheduleTime) {
|
|
2352
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2363
2353
|
if (volume < 0 && 1 < volume) {
|
|
2364
2354
|
console.error("Master Volume is out of range");
|
|
2365
2355
|
}
|
|
2366
2356
|
else {
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2357
|
+
this.masterVolume.gain
|
|
2358
|
+
.cancelScheduledValues(scheduleTime)
|
|
2359
|
+
.setValueAtTime(volume * volume, scheduleTime);
|
|
2370
2360
|
}
|
|
2371
2361
|
}
|
|
2372
|
-
handleMasterFineTuningSysEx(data) {
|
|
2362
|
+
handleMasterFineTuningSysEx(data, scheduleTime) {
|
|
2373
2363
|
const fineTuning = data[5] * 128 + data[4];
|
|
2374
|
-
this.setMasterFineTuning(fineTuning);
|
|
2364
|
+
this.setMasterFineTuning(fineTuning, scheduleTime);
|
|
2375
2365
|
}
|
|
2376
|
-
setMasterFineTuning(value) {
|
|
2366
|
+
setMasterFineTuning(value, scheduleTime) {
|
|
2377
2367
|
const prev = this.masterFineTuning;
|
|
2378
2368
|
const next = (value - 8192) / 8.192; // cent
|
|
2379
2369
|
this.masterFineTuning = next;
|
|
2380
2370
|
channel.detune += next - prev;
|
|
2381
|
-
this.updateChannelDetune(channel);
|
|
2371
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2382
2372
|
}
|
|
2383
|
-
handleMasterCoarseTuningSysEx(data) {
|
|
2373
|
+
handleMasterCoarseTuningSysEx(data, scheduleTime) {
|
|
2384
2374
|
const coarseTuning = data[4];
|
|
2385
|
-
this.setMasterCoarseTuning(coarseTuning);
|
|
2375
|
+
this.setMasterCoarseTuning(coarseTuning, scheduleTime);
|
|
2386
2376
|
}
|
|
2387
|
-
setMasterCoarseTuning(value) {
|
|
2377
|
+
setMasterCoarseTuning(value, scheduleTime) {
|
|
2388
2378
|
const prev = this.masterCoarseTuning;
|
|
2389
2379
|
const next = (value - 64) * 100; // cent
|
|
2390
2380
|
this.masterCoarseTuning = next;
|
|
2391
2381
|
channel.detune += next - prev;
|
|
2392
|
-
this.updateChannelDetune(channel);
|
|
2382
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2393
2383
|
}
|
|
2394
|
-
handleGlobalParameterControlSysEx(data) {
|
|
2384
|
+
handleGlobalParameterControlSysEx(data, scheduleTime) {
|
|
2395
2385
|
if (data[7] === 1) {
|
|
2396
2386
|
switch (data[8]) {
|
|
2397
2387
|
case 1:
|
|
2398
2388
|
return this.handleReverbParameterSysEx(data);
|
|
2399
2389
|
case 2:
|
|
2400
|
-
return this.handleChorusParameterSysEx(data);
|
|
2390
|
+
return this.handleChorusParameterSysEx(data, scheduleTime);
|
|
2401
2391
|
default:
|
|
2402
2392
|
console.warn(`Unsupported Global Parameter Control Message: ${data}`);
|
|
2403
2393
|
}
|
|
@@ -2476,88 +2466,84 @@ export class Midy {
|
|
|
2476
2466
|
calcDelay(rt60, feedback) {
|
|
2477
2467
|
return -rt60 * Math.log10(feedback) / 3;
|
|
2478
2468
|
}
|
|
2479
|
-
handleChorusParameterSysEx(data) {
|
|
2469
|
+
handleChorusParameterSysEx(data, scheduleTime) {
|
|
2480
2470
|
switch (data[9]) {
|
|
2481
2471
|
case 0:
|
|
2482
|
-
return this.setChorusType(data[10]);
|
|
2472
|
+
return this.setChorusType(data[10], scheduleTime);
|
|
2483
2473
|
case 1:
|
|
2484
|
-
return this.setChorusModRate(data[10]);
|
|
2474
|
+
return this.setChorusModRate(data[10], scheduleTime);
|
|
2485
2475
|
case 2:
|
|
2486
|
-
return this.setChorusModDepth(data[10]);
|
|
2476
|
+
return this.setChorusModDepth(data[10], scheduleTime);
|
|
2487
2477
|
case 3:
|
|
2488
|
-
return this.setChorusFeedback(data[10]);
|
|
2478
|
+
return this.setChorusFeedback(data[10], scheduleTime);
|
|
2489
2479
|
case 4:
|
|
2490
|
-
return this.setChorusSendToReverb(data[10]);
|
|
2480
|
+
return this.setChorusSendToReverb(data[10], scheduleTime);
|
|
2491
2481
|
}
|
|
2492
2482
|
}
|
|
2493
|
-
setChorusType(type) {
|
|
2483
|
+
setChorusType(type, scheduleTime) {
|
|
2494
2484
|
switch (type) {
|
|
2495
2485
|
case 0:
|
|
2496
|
-
return this.setChorusParameter(3, 5, 0, 0);
|
|
2486
|
+
return this.setChorusParameter(3, 5, 0, 0, scheduleTime);
|
|
2497
2487
|
case 1:
|
|
2498
|
-
return this.setChorusParameter(9, 19, 5, 0);
|
|
2488
|
+
return this.setChorusParameter(9, 19, 5, 0, scheduleTime);
|
|
2499
2489
|
case 2:
|
|
2500
|
-
return this.setChorusParameter(3, 19, 8, 0);
|
|
2490
|
+
return this.setChorusParameter(3, 19, 8, 0, scheduleTime);
|
|
2501
2491
|
case 3:
|
|
2502
|
-
return this.setChorusParameter(9, 16, 16, 0);
|
|
2492
|
+
return this.setChorusParameter(9, 16, 16, 0, scheduleTime);
|
|
2503
2493
|
case 4:
|
|
2504
|
-
return this.setChorusParameter(2, 24, 64, 0);
|
|
2494
|
+
return this.setChorusParameter(2, 24, 64, 0, scheduleTime);
|
|
2505
2495
|
case 5:
|
|
2506
|
-
return this.setChorusParameter(1, 5, 112, 0);
|
|
2496
|
+
return this.setChorusParameter(1, 5, 112, 0, scheduleTime);
|
|
2507
2497
|
default:
|
|
2508
2498
|
console.warn(`Unsupported Chorus Type: ${type}`);
|
|
2509
2499
|
}
|
|
2510
2500
|
}
|
|
2511
|
-
setChorusParameter(modRate, modDepth, feedback, sendToReverb) {
|
|
2512
|
-
this.setChorusModRate(modRate);
|
|
2513
|
-
this.setChorusModDepth(modDepth);
|
|
2514
|
-
this.setChorusFeedback(feedback);
|
|
2515
|
-
this.setChorusSendToReverb(sendToReverb);
|
|
2501
|
+
setChorusParameter(modRate, modDepth, feedback, sendToReverb, scheduleTime) {
|
|
2502
|
+
this.setChorusModRate(modRate, scheduleTime);
|
|
2503
|
+
this.setChorusModDepth(modDepth, scheduleTime);
|
|
2504
|
+
this.setChorusFeedback(feedback, scheduleTime);
|
|
2505
|
+
this.setChorusSendToReverb(sendToReverb, scheduleTime);
|
|
2516
2506
|
}
|
|
2517
|
-
setChorusModRate(value) {
|
|
2518
|
-
const now = this.audioContext.currentTime;
|
|
2507
|
+
setChorusModRate(value, scheduleTime) {
|
|
2519
2508
|
const modRate = this.getChorusModRate(value);
|
|
2520
2509
|
this.chorus.modRate = modRate;
|
|
2521
|
-
this.chorusEffect.lfo.frequency.setValueAtTime(modRate,
|
|
2510
|
+
this.chorusEffect.lfo.frequency.setValueAtTime(modRate, scheduleTime);
|
|
2522
2511
|
}
|
|
2523
2512
|
getChorusModRate(value) {
|
|
2524
2513
|
return value * 0.122; // Hz
|
|
2525
2514
|
}
|
|
2526
|
-
setChorusModDepth(value) {
|
|
2527
|
-
const now = this.audioContext.currentTime;
|
|
2515
|
+
setChorusModDepth(value, scheduleTime) {
|
|
2528
2516
|
const modDepth = this.getChorusModDepth(value);
|
|
2529
2517
|
this.chorus.modDepth = modDepth;
|
|
2530
2518
|
this.chorusEffect.lfoGain.gain
|
|
2531
|
-
.cancelScheduledValues(
|
|
2532
|
-
.setValueAtTime(modDepth / 2,
|
|
2519
|
+
.cancelScheduledValues(scheduleTime)
|
|
2520
|
+
.setValueAtTime(modDepth / 2, scheduleTime);
|
|
2533
2521
|
}
|
|
2534
2522
|
getChorusModDepth(value) {
|
|
2535
2523
|
return (value + 1) / 3200; // second
|
|
2536
2524
|
}
|
|
2537
|
-
setChorusFeedback(value) {
|
|
2538
|
-
const now = this.audioContext.currentTime;
|
|
2525
|
+
setChorusFeedback(value, scheduleTime) {
|
|
2539
2526
|
const feedback = this.getChorusFeedback(value);
|
|
2540
2527
|
this.chorus.feedback = feedback;
|
|
2541
2528
|
const chorusEffect = this.chorusEffect;
|
|
2542
2529
|
for (let i = 0; i < chorusEffect.feedbackGains.length; i++) {
|
|
2543
2530
|
chorusEffect.feedbackGains[i].gain
|
|
2544
|
-
.cancelScheduledValues(
|
|
2545
|
-
.setValueAtTime(feedback,
|
|
2531
|
+
.cancelScheduledValues(scheduleTime)
|
|
2532
|
+
.setValueAtTime(feedback, scheduleTime);
|
|
2546
2533
|
}
|
|
2547
2534
|
}
|
|
2548
2535
|
getChorusFeedback(value) {
|
|
2549
2536
|
return value * 0.00763;
|
|
2550
2537
|
}
|
|
2551
|
-
setChorusSendToReverb(value) {
|
|
2538
|
+
setChorusSendToReverb(value, scheduleTime) {
|
|
2552
2539
|
const sendToReverb = this.getChorusSendToReverb(value);
|
|
2553
2540
|
const sendGain = this.chorusEffect.sendGain;
|
|
2554
2541
|
if (0 < this.chorus.sendToReverb) {
|
|
2555
2542
|
this.chorus.sendToReverb = sendToReverb;
|
|
2556
2543
|
if (0 < sendToReverb) {
|
|
2557
|
-
const now = this.audioContext.currentTime;
|
|
2558
2544
|
sendGain.gain
|
|
2559
|
-
.cancelScheduledValues(
|
|
2560
|
-
.setValueAtTime(sendToReverb,
|
|
2545
|
+
.cancelScheduledValues(scheduleTime)
|
|
2546
|
+
.setValueAtTime(sendToReverb, scheduleTime);
|
|
2561
2547
|
}
|
|
2562
2548
|
else {
|
|
2563
2549
|
sendGain.disconnect();
|
|
@@ -2566,11 +2552,10 @@ export class Midy {
|
|
|
2566
2552
|
else {
|
|
2567
2553
|
this.chorus.sendToReverb = sendToReverb;
|
|
2568
2554
|
if (0 < sendToReverb) {
|
|
2569
|
-
const now = this.audioContext.currentTime;
|
|
2570
2555
|
sendGain.connect(this.reverbEffect.input);
|
|
2571
2556
|
sendGain.gain
|
|
2572
|
-
.cancelScheduledValues(
|
|
2573
|
-
.setValueAtTime(sendToReverb,
|
|
2557
|
+
.cancelScheduledValues(scheduleTime)
|
|
2558
|
+
.setValueAtTime(sendToReverb, scheduleTime);
|
|
2574
2559
|
}
|
|
2575
2560
|
}
|
|
2576
2561
|
}
|
|
@@ -2596,7 +2581,7 @@ export class Midy {
|
|
|
2596
2581
|
}
|
|
2597
2582
|
return bitmap;
|
|
2598
2583
|
}
|
|
2599
|
-
handleScaleOctaveTuning1ByteFormatSysEx(data, realtime) {
|
|
2584
|
+
handleScaleOctaveTuning1ByteFormatSysEx(data, realtime, scheduleTime) {
|
|
2600
2585
|
if (data.length < 19) {
|
|
2601
2586
|
console.error("Data length is too short");
|
|
2602
2587
|
return;
|
|
@@ -2611,10 +2596,10 @@ export class Midy {
|
|
|
2611
2596
|
channel.scaleOctaveTuningTable[j] = centValue;
|
|
2612
2597
|
}
|
|
2613
2598
|
if (realtime)
|
|
2614
|
-
this.updateChannelDetune(channel);
|
|
2599
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2615
2600
|
}
|
|
2616
2601
|
}
|
|
2617
|
-
handleScaleOctaveTuning2ByteFormatSysEx(data, realtime) {
|
|
2602
|
+
handleScaleOctaveTuning2ByteFormatSysEx(data, realtime, scheduleTime) {
|
|
2618
2603
|
if (data.length < 31) {
|
|
2619
2604
|
console.error("Data length is too short");
|
|
2620
2605
|
return;
|
|
@@ -2633,66 +2618,66 @@ export class Midy {
|
|
|
2633
2618
|
channel.scaleOctaveTuningTable[j] = centValue;
|
|
2634
2619
|
}
|
|
2635
2620
|
if (realtime)
|
|
2636
|
-
this.updateChannelDetune(channel);
|
|
2637
|
-
}
|
|
2638
|
-
}
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2621
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
getPitchControl(channel, note) {
|
|
2625
|
+
const polyphonicKeyPressure = (channel.polyphonicKeyPressureTable[0] - 64) *
|
|
2626
|
+
note.pressure;
|
|
2627
|
+
return polyphonicKeyPressure * note.pressure / 37.5; // 2400 / 64;
|
|
2628
|
+
}
|
|
2629
|
+
getFilterCutoffControl(channel, note) {
|
|
2630
|
+
const channelPressure = (channel.channelPressureTable[1] - 64) *
|
|
2631
|
+
channel.state.channelPressure;
|
|
2632
|
+
const polyphonicKeyPressure = (channel.polyphonicKeyPressureTable[1] - 64) *
|
|
2633
|
+
note.pressure;
|
|
2634
|
+
return (channelPressure + polyphonicKeyPressure) * 15;
|
|
2635
|
+
}
|
|
2636
|
+
getAmplitudeControl(channel, note) {
|
|
2637
|
+
const channelPressure = channel.channelPressureTable[2] *
|
|
2638
|
+
channel.state.channelPressure;
|
|
2639
|
+
const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[2] *
|
|
2640
|
+
note.pressure;
|
|
2641
|
+
return (channelPressure + polyphonicKeyPressure) / 128;
|
|
2642
|
+
}
|
|
2643
|
+
getLFOPitchDepth(channel, note) {
|
|
2644
|
+
const channelPressure = channel.channelPressureTable[3] *
|
|
2645
|
+
channel.state.channelPressure;
|
|
2646
|
+
const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[3] *
|
|
2647
|
+
note.pressure;
|
|
2648
|
+
return (channelPressure + polyphonicKeyPressure) / 254 * 600;
|
|
2649
|
+
}
|
|
2650
|
+
getLFOFilterDepth(channel, note) {
|
|
2651
|
+
const channelPressure = channel.channelPressureTable[4] *
|
|
2652
|
+
channel.state.channelPressure;
|
|
2653
|
+
const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[4] *
|
|
2654
|
+
note.pressure;
|
|
2655
|
+
return (channelPressure + polyphonicKeyPressure) / 254 * 2400;
|
|
2656
|
+
}
|
|
2657
|
+
getLFOAmplitudeDepth(channel, note) {
|
|
2658
|
+
const channelPressure = channel.channelPressureTable[5] *
|
|
2659
|
+
channel.state.channelPressure;
|
|
2660
|
+
const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[5] *
|
|
2661
|
+
note.pressure;
|
|
2662
|
+
return (channelPressure + polyphonicKeyPressure) / 254;
|
|
2663
|
+
}
|
|
2664
|
+
setControllerParameters(channel, note, table) {
|
|
2665
|
+
if (table[0] !== 64)
|
|
2666
|
+
this.updateDetune(channel, note);
|
|
2647
2667
|
if (!note.portamento) {
|
|
2648
|
-
if (table[1] !== 64)
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
? channel.polyphonicKeyPressureTable[2] * note.pressure
|
|
2662
|
-
: 0;
|
|
2663
|
-
const pressure = (channelPressure + polyphonicKeyPressure) / 128;
|
|
2664
|
-
this.setVolumeEnvelope(channel, note, pressure);
|
|
2665
|
-
}
|
|
2666
|
-
}
|
|
2667
|
-
if (table[3] !== 0) {
|
|
2668
|
-
const channelPressure = channel.channelPressureTable[3] *
|
|
2669
|
-
channel.state.channelPressure;
|
|
2670
|
-
const polyphonicKeyPressure = (0 < note.pressure)
|
|
2671
|
-
? channel.polyphonicKeyPressureTable[3] * note.pressure
|
|
2672
|
-
: 0;
|
|
2673
|
-
const pressure = (channelPressure + polyphonicKeyPressure) / 254 * 600;
|
|
2674
|
-
this.setModLfoToPitch(channel, note, pressure);
|
|
2675
|
-
}
|
|
2676
|
-
if (table[4] !== 0) {
|
|
2677
|
-
const channelPressure = channel.channelPressureTable[4] *
|
|
2678
|
-
channel.state.channelPressure;
|
|
2679
|
-
const polyphonicKeyPressure = (0 < note.pressure)
|
|
2680
|
-
? channel.polyphonicKeyPressureTable[4] * note.pressure
|
|
2681
|
-
: 0;
|
|
2682
|
-
const pressure = (channelPressure + polyphonicKeyPressure) / 254 * 2400;
|
|
2683
|
-
this.setModLfoToFilterFc(note, pressure);
|
|
2684
|
-
}
|
|
2685
|
-
if (table[5] !== 0) {
|
|
2686
|
-
const channelPressure = channel.channelPressureTable[5] *
|
|
2687
|
-
channel.state.channelPressure;
|
|
2688
|
-
const polyphonicKeyPressure = (0 < note.pressure)
|
|
2689
|
-
? channel.polyphonicKeyPressureTable[5] * note.pressure
|
|
2690
|
-
: 0;
|
|
2691
|
-
const pressure = (channelPressure + polyphonicKeyPressure) / 254;
|
|
2692
|
-
this.setModLfoToVolume(note, pressure);
|
|
2693
|
-
}
|
|
2694
|
-
}
|
|
2695
|
-
handleChannelPressureSysEx(data, tableName) {
|
|
2668
|
+
if (table[1] !== 64)
|
|
2669
|
+
this.setFilterEnvelope(channel, note);
|
|
2670
|
+
if (table[2] !== 64)
|
|
2671
|
+
this.setVolumeEnvelope(channel, note);
|
|
2672
|
+
}
|
|
2673
|
+
if (table[3] !== 0)
|
|
2674
|
+
this.setModLfoToPitch(channel, note);
|
|
2675
|
+
if (table[4] !== 0)
|
|
2676
|
+
this.setModLfoToFilterFc(channel, note);
|
|
2677
|
+
if (table[5] !== 0)
|
|
2678
|
+
this.setModLfoToVolume(channel, note);
|
|
2679
|
+
}
|
|
2680
|
+
handlePressureSysEx(data, tableName) {
|
|
2696
2681
|
const channelNumber = data[4];
|
|
2697
2682
|
const table = this.channels[channelNumber][tableName];
|
|
2698
2683
|
for (let i = 5; i < data.length - 1; i += 2) {
|
|
@@ -2721,7 +2706,7 @@ export class Midy {
|
|
|
2721
2706
|
const note = noteList[i];
|
|
2722
2707
|
if (!note)
|
|
2723
2708
|
continue;
|
|
2724
|
-
this.
|
|
2709
|
+
this.setControllerParameters(channel, note, table);
|
|
2725
2710
|
}
|
|
2726
2711
|
});
|
|
2727
2712
|
}
|
|
@@ -2740,7 +2725,7 @@ export class Midy {
|
|
|
2740
2725
|
const controlValue = channel.keyBasedInstrumentControlTable[index];
|
|
2741
2726
|
return (controlValue + 64) / 64;
|
|
2742
2727
|
}
|
|
2743
|
-
handleKeyBasedInstrumentControlSysEx(data) {
|
|
2728
|
+
handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
|
|
2744
2729
|
const channelNumber = data[4];
|
|
2745
2730
|
const keyNumber = data[5];
|
|
2746
2731
|
const table = this.channels[channelNumber].keyBasedInstrumentControlTable;
|
|
@@ -2750,30 +2735,27 @@ export class Midy {
|
|
|
2750
2735
|
const index = keyNumber * 128 + controllerType;
|
|
2751
2736
|
table[index] = value - 64;
|
|
2752
2737
|
}
|
|
2753
|
-
this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127);
|
|
2754
|
-
}
|
|
2755
|
-
handleExclusiveMessage(data) {
|
|
2756
|
-
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2738
|
+
this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
|
|
2757
2739
|
}
|
|
2758
|
-
handleSysEx(data) {
|
|
2740
|
+
handleSysEx(data, scheduleTime) {
|
|
2759
2741
|
switch (data[0]) {
|
|
2760
2742
|
case 126:
|
|
2761
|
-
return this.handleUniversalNonRealTimeExclusiveMessage(data);
|
|
2743
|
+
return this.handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime);
|
|
2762
2744
|
case 127:
|
|
2763
|
-
return this.handleUniversalRealTimeExclusiveMessage(data);
|
|
2745
|
+
return this.handleUniversalRealTimeExclusiveMessage(data, scheduleTime);
|
|
2764
2746
|
default:
|
|
2765
|
-
|
|
2747
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2766
2748
|
}
|
|
2767
2749
|
}
|
|
2768
|
-
scheduleTask(callback,
|
|
2750
|
+
scheduleTask(callback, scheduleTime) {
|
|
2769
2751
|
return new Promise((resolve) => {
|
|
2770
2752
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
2771
2753
|
bufferSource.onended = () => {
|
|
2772
2754
|
callback();
|
|
2773
2755
|
resolve();
|
|
2774
2756
|
};
|
|
2775
|
-
bufferSource.start(
|
|
2776
|
-
bufferSource.stop(
|
|
2757
|
+
bufferSource.start(scheduleTime);
|
|
2758
|
+
bufferSource.stop(scheduleTime);
|
|
2777
2759
|
});
|
|
2778
2760
|
}
|
|
2779
2761
|
}
|
|
@@ -2784,10 +2766,6 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
2784
2766
|
value: {
|
|
2785
2767
|
currentBufferSource: null,
|
|
2786
2768
|
detune: 0,
|
|
2787
|
-
scaleOctaveTuningTable: new Float32Array(12), // [-100, 100] cent
|
|
2788
|
-
channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
2789
|
-
polyphonicKeyPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
2790
|
-
keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
|
|
2791
2769
|
program: 0,
|
|
2792
2770
|
bank: 121 * 128,
|
|
2793
2771
|
bankMSB: 121,
|