@marmooo/midy 0.0.8 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/midy-GM1.d.ts +4 -43
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +39 -37
- package/esm/midy-GM2.d.ts +64 -113
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +301 -95
- package/esm/midy-GMLite.d.ts +4 -39
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +39 -37
- package/esm/midy.d.ts +64 -135
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +301 -99
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +4 -43
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +39 -37
- package/script/midy-GM2.d.ts +64 -113
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +301 -95
- package/script/midy-GMLite.d.ts +4 -39
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +39 -37
- package/script/midy.d.ts +64 -135
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +301 -99
package/script/midy.js
CHANGED
|
@@ -54,7 +54,7 @@ class Note {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
class Midy {
|
|
57
|
-
constructor(audioContext) {
|
|
57
|
+
constructor(audioContext, options = this.defaultOptions) {
|
|
58
58
|
Object.defineProperty(this, "ticksPerBeat", {
|
|
59
59
|
enumerable: true,
|
|
60
60
|
configurable: true,
|
|
@@ -67,12 +67,6 @@ class Midy {
|
|
|
67
67
|
writable: true,
|
|
68
68
|
value: 0
|
|
69
69
|
});
|
|
70
|
-
Object.defineProperty(this, "reverbFactor", {
|
|
71
|
-
enumerable: true,
|
|
72
|
-
configurable: true,
|
|
73
|
-
writable: true,
|
|
74
|
-
value: 0.1
|
|
75
|
-
});
|
|
76
70
|
Object.defineProperty(this, "masterFineTuning", {
|
|
77
71
|
enumerable: true,
|
|
78
72
|
configurable: true,
|
|
@@ -85,6 +79,26 @@ class Midy {
|
|
|
85
79
|
writable: true,
|
|
86
80
|
value: 0
|
|
87
81
|
}); // cb
|
|
82
|
+
Object.defineProperty(this, "reverb", {
|
|
83
|
+
enumerable: true,
|
|
84
|
+
configurable: true,
|
|
85
|
+
writable: true,
|
|
86
|
+
value: {
|
|
87
|
+
time: this.getReverbTime(64),
|
|
88
|
+
feedback: 0.2,
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
Object.defineProperty(this, "chorus", {
|
|
92
|
+
enumerable: true,
|
|
93
|
+
configurable: true,
|
|
94
|
+
writable: true,
|
|
95
|
+
value: {
|
|
96
|
+
modRate: 3 * 0.122,
|
|
97
|
+
modDepth: (3 + 1) / 3.2,
|
|
98
|
+
feedback: 8 * 0.763,
|
|
99
|
+
sendToReverb: 0 * 0.787,
|
|
100
|
+
}
|
|
101
|
+
});
|
|
88
102
|
Object.defineProperty(this, "mono", {
|
|
89
103
|
enumerable: true,
|
|
90
104
|
configurable: true,
|
|
@@ -187,7 +201,30 @@ class Midy {
|
|
|
187
201
|
writable: true,
|
|
188
202
|
value: []
|
|
189
203
|
});
|
|
204
|
+
Object.defineProperty(this, "defaultOptions", {
|
|
205
|
+
enumerable: true,
|
|
206
|
+
configurable: true,
|
|
207
|
+
writable: true,
|
|
208
|
+
value: {
|
|
209
|
+
reverbAlgorithm: (audioContext) => {
|
|
210
|
+
const { time: rt60, feedback } = this.reverb;
|
|
211
|
+
// const delay = this.calcDelay(rt60, feedback);
|
|
212
|
+
// const impulse = this.createConvolutionReverbImpulse(
|
|
213
|
+
// audioContext,
|
|
214
|
+
// rt60,
|
|
215
|
+
// delay,
|
|
216
|
+
// );
|
|
217
|
+
// return this.createConvolutionReverb(audioContext, impulse);
|
|
218
|
+
const combFeedbacks = this.generateDistributedArray(feedback, 4);
|
|
219
|
+
const combDelays = combFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
|
|
220
|
+
const allpassFeedbacks = this.generateDistributedArray(feedback, 4);
|
|
221
|
+
const allpassDelays = allpassFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
|
|
222
|
+
return this.createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays);
|
|
223
|
+
},
|
|
224
|
+
}
|
|
225
|
+
});
|
|
190
226
|
this.audioContext = audioContext;
|
|
227
|
+
this.options = { ...this.defaultOptions, ...options };
|
|
191
228
|
this.masterGain = new GainNode(audioContext);
|
|
192
229
|
this.masterGain.connect(audioContext.destination);
|
|
193
230
|
this.channels = this.createChannels(audioContext);
|
|
@@ -228,23 +265,18 @@ class Midy {
|
|
|
228
265
|
this.totalTime = this.calcTotalTime();
|
|
229
266
|
}
|
|
230
267
|
setChannelAudioNodes(audioContext) {
|
|
231
|
-
const { gainLeft, gainRight } = this.panToGain(
|
|
268
|
+
const { gainLeft, gainRight } = this.panToGain(this.constructor.channelSettings.pan);
|
|
232
269
|
const gainL = new GainNode(audioContext, { gain: gainLeft });
|
|
233
270
|
const gainR = new GainNode(audioContext, { gain: gainRight });
|
|
234
271
|
const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
|
|
235
272
|
gainL.connect(merger, 0, 0);
|
|
236
273
|
gainR.connect(merger, 0, 1);
|
|
237
|
-
|
|
238
|
-
const reverbEffect = this.createReverbEffect(audioContext);
|
|
274
|
+
const reverbEffect = this.options.reverbAlgorithm(audioContext);
|
|
239
275
|
const chorusEffect = this.createChorusEffect(audioContext);
|
|
240
|
-
chorusEffect.lfo.start();
|
|
241
|
-
reverbEffect.dryGain.connect(gainL);
|
|
242
|
-
reverbEffect.dryGain.connect(gainR);
|
|
243
|
-
reverbEffect.wetGain.connect(gainL);
|
|
244
|
-
reverbEffect.wetGain.connect(gainR);
|
|
245
276
|
return {
|
|
246
277
|
gainL,
|
|
247
278
|
gainR,
|
|
279
|
+
merger,
|
|
248
280
|
reverbEffect,
|
|
249
281
|
chorusEffect,
|
|
250
282
|
};
|
|
@@ -252,16 +284,16 @@ class Midy {
|
|
|
252
284
|
createChannels(audioContext) {
|
|
253
285
|
const channels = Array.from({ length: 16 }, () => {
|
|
254
286
|
return {
|
|
255
|
-
...
|
|
256
|
-
...
|
|
287
|
+
...this.constructor.channelSettings,
|
|
288
|
+
...this.constructor.effectSettings,
|
|
257
289
|
...this.setChannelAudioNodes(audioContext),
|
|
258
290
|
scheduledNotes: new Map(),
|
|
259
291
|
sostenutoNotes: new Map(),
|
|
260
292
|
polyphonicKeyPressure: {
|
|
261
|
-
...
|
|
293
|
+
...this.constructor.controllerDestinationSettings,
|
|
262
294
|
},
|
|
263
295
|
channelPressure: {
|
|
264
|
-
...
|
|
296
|
+
...this.constructor.controllerDestinationSettings,
|
|
265
297
|
},
|
|
266
298
|
};
|
|
267
299
|
});
|
|
@@ -592,8 +624,7 @@ class Midy {
|
|
|
592
624
|
}
|
|
593
625
|
return noteList[0];
|
|
594
626
|
}
|
|
595
|
-
|
|
596
|
-
const { decay = 0.8, preDecay = 0, } = options;
|
|
627
|
+
createConvolutionReverbImpulse(audioContext, decay, preDecay) {
|
|
597
628
|
const sampleRate = audioContext.sampleRate;
|
|
598
629
|
const length = sampleRate * decay;
|
|
599
630
|
const impulse = new AudioBuffer({
|
|
@@ -607,27 +638,85 @@ class Midy {
|
|
|
607
638
|
for (let i = 0; i < preDecayLength; i++) {
|
|
608
639
|
channelData[i] = Math.random() * 2 - 1;
|
|
609
640
|
}
|
|
641
|
+
const attenuationFactor = 1 / (sampleRate * decay);
|
|
610
642
|
for (let i = preDecayLength; i < length; i++) {
|
|
611
|
-
const attenuation = Math.exp(-(i - preDecayLength)
|
|
643
|
+
const attenuation = Math.exp(-(i - preDecayLength) * attenuationFactor);
|
|
612
644
|
channelData[i] = (Math.random() * 2 - 1) * attenuation;
|
|
613
645
|
}
|
|
614
646
|
}
|
|
647
|
+
return impulse;
|
|
648
|
+
}
|
|
649
|
+
createConvolutionReverb(audioContext, impulse) {
|
|
650
|
+
const output = new GainNode(audioContext);
|
|
615
651
|
const convolverNode = new ConvolverNode(audioContext, {
|
|
616
652
|
buffer: impulse,
|
|
617
653
|
});
|
|
618
|
-
|
|
619
|
-
const wetGain = new GainNode(audioContext);
|
|
620
|
-
convolverNode.connect(wetGain);
|
|
654
|
+
convolverNode.connect(output);
|
|
621
655
|
return {
|
|
656
|
+
input: convolverNode,
|
|
657
|
+
output,
|
|
622
658
|
convolverNode,
|
|
623
|
-
dryGain,
|
|
624
|
-
wetGain,
|
|
625
659
|
};
|
|
626
660
|
}
|
|
661
|
+
createCombFilter(audioContext, input, delay, feedback) {
|
|
662
|
+
const delayNode = new DelayNode(audioContext, {
|
|
663
|
+
maxDelayTime: delay,
|
|
664
|
+
delayTime: delay,
|
|
665
|
+
});
|
|
666
|
+
const feedbackGain = new GainNode(audioContext, { gain: feedback });
|
|
667
|
+
input.connect(delayNode);
|
|
668
|
+
delayNode.connect(feedbackGain);
|
|
669
|
+
feedbackGain.connect(delayNode);
|
|
670
|
+
return delayNode;
|
|
671
|
+
}
|
|
672
|
+
createAllpassFilter(audioContext, input, delay, feedback) {
|
|
673
|
+
const delayNode = new DelayNode(audioContext, {
|
|
674
|
+
maxDelayTime: delay,
|
|
675
|
+
delayTime: delay,
|
|
676
|
+
});
|
|
677
|
+
const feedbackGain = new GainNode(audioContext, { gain: feedback });
|
|
678
|
+
const passGain = new GainNode(audioContext, { gain: 1 - feedback });
|
|
679
|
+
input.connect(delayNode);
|
|
680
|
+
delayNode.connect(feedbackGain);
|
|
681
|
+
feedbackGain.connect(delayNode);
|
|
682
|
+
delayNode.connect(passGain);
|
|
683
|
+
return passGain;
|
|
684
|
+
}
|
|
685
|
+
generateDistributedArray(center, count, varianceRatio = 0.1, randomness = 0.05) {
|
|
686
|
+
const variance = center * varianceRatio;
|
|
687
|
+
const array = new Array(count);
|
|
688
|
+
for (let i = 0; i < count; i++) {
|
|
689
|
+
const fraction = i / (count - 1 || 1);
|
|
690
|
+
const value = center - variance + fraction * 2 * variance;
|
|
691
|
+
array[i] = value * (1 - (Math.random() * 2 - 1) * randomness);
|
|
692
|
+
}
|
|
693
|
+
return array;
|
|
694
|
+
}
|
|
695
|
+
// https://hajim.rochester.edu/ece/sites/zduan/teaching/ece472/reading/Schroeder_1962.pdf
|
|
696
|
+
// M.R.Schroeder, "Natural Sounding Artificial Reverberation", J.Audio Eng. Soc., vol.10, p.219, 1962
|
|
697
|
+
createSchroederReverb(audioContext, combDelays, combFeedbacks, allpassDelays, allpassFeedbacks) {
|
|
698
|
+
const input = new GainNode(audioContext);
|
|
699
|
+
const output = new GainNode(audioContext);
|
|
700
|
+
const mergerGain = new GainNode(audioContext, {
|
|
701
|
+
gain: 1 / (combDelays.length * 2),
|
|
702
|
+
});
|
|
703
|
+
for (let i = 0; i < combDelays.length; i++) {
|
|
704
|
+
const comb = this.createCombFilter(audioContext, input, combDelays[i], combFeedbacks[i]);
|
|
705
|
+
comb.connect(mergerGain);
|
|
706
|
+
}
|
|
707
|
+
const allpasses = [];
|
|
708
|
+
for (let i = 0; i < allpassDelays.length; i++) {
|
|
709
|
+
const allpass = this.createAllpassFilter(audioContext, (i === 0) ? mergerGain : allpasses.at(-1), allpassDelays[i], allpassFeedbacks[i]);
|
|
710
|
+
allpasses.push(allpass);
|
|
711
|
+
}
|
|
712
|
+
allpasses.at(-1).connect(output);
|
|
713
|
+
return { input, output };
|
|
714
|
+
}
|
|
627
715
|
createChorusEffect(audioContext, options = {}) {
|
|
628
716
|
const { chorusCount = 2, chorusRate = 0.6, chorusDepth = 0.15, delay = 0.01, variance = delay * 0.1, } = options;
|
|
629
717
|
const lfo = new OscillatorNode(audioContext, { frequency: chorusRate });
|
|
630
718
|
const lfoGain = new GainNode(audioContext, { gain: chorusDepth });
|
|
719
|
+
const output = new GainNode(audioContext);
|
|
631
720
|
const chorusGains = [];
|
|
632
721
|
const delayNodes = [];
|
|
633
722
|
const baseGain = 1 / chorusCount;
|
|
@@ -637,50 +726,45 @@ class Midy {
|
|
|
637
726
|
const delayNode = new DelayNode(audioContext, {
|
|
638
727
|
maxDelayTime: delayTime,
|
|
639
728
|
});
|
|
640
|
-
delayNodes.push(delayNode);
|
|
641
729
|
const chorusGain = new GainNode(audioContext, { gain: baseGain });
|
|
730
|
+
delayNodes.push(delayNode);
|
|
642
731
|
chorusGains.push(chorusGain);
|
|
643
|
-
lfo.connect(lfoGain);
|
|
644
732
|
lfoGain.connect(delayNode.delayTime);
|
|
645
733
|
delayNode.connect(chorusGain);
|
|
734
|
+
chorusGain.connect(output);
|
|
646
735
|
}
|
|
736
|
+
lfo.connect(lfoGain);
|
|
737
|
+
lfo.start();
|
|
647
738
|
return {
|
|
648
739
|
lfo,
|
|
649
740
|
lfoGain,
|
|
650
741
|
delayNodes,
|
|
651
742
|
chorusGains,
|
|
743
|
+
output,
|
|
652
744
|
};
|
|
653
745
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
}
|
|
660
|
-
else { // chorus
|
|
746
|
+
connectEffects(channel, gainNode) {
|
|
747
|
+
gainNode.connect(channel.merger);
|
|
748
|
+
channel.merger.connect(this.masterGain);
|
|
749
|
+
if (channel.reverbSendLevel === 0) {
|
|
750
|
+
if (channel.chorusSendLevel !== 0) { // chorus
|
|
661
751
|
channel.chorusEffect.delayNodes.forEach((delayNode) => {
|
|
662
|
-
|
|
663
|
-
});
|
|
664
|
-
channel.chorusEffect.chorusGains.forEach((chorusGain) => {
|
|
665
|
-
chorusGain.connect(channel.gainL);
|
|
666
|
-
chorusGain.connect(channel.gainR);
|
|
752
|
+
channel.merger.connect(delayNode);
|
|
667
753
|
});
|
|
754
|
+
channel.chorusEffect.output.connect(this.masterGain);
|
|
668
755
|
}
|
|
669
756
|
}
|
|
670
757
|
else {
|
|
671
|
-
if (channel.
|
|
672
|
-
|
|
673
|
-
|
|
758
|
+
if (channel.chorusSendLevel === 0) { // reverb
|
|
759
|
+
channel.merger.connect(channel.reverbEffect.input);
|
|
760
|
+
channel.reverbEffect.output.connect(this.masterGain);
|
|
674
761
|
}
|
|
675
762
|
else { // reverb + chorus
|
|
676
|
-
gainNode.connect(channel.reverbEffect.convolverNode);
|
|
677
|
-
gainNode.connect(channel.reverbEffect.dryGain);
|
|
678
763
|
channel.chorusEffect.delayNodes.forEach((delayNode) => {
|
|
679
|
-
|
|
680
|
-
});
|
|
681
|
-
channel.chorusEffect.chorusGains.forEach((chorusGain) => {
|
|
682
|
-
chorusGain.connect(channel.reverbEffect.convolverNode);
|
|
764
|
+
channel.merger.connect(delayNode);
|
|
683
765
|
});
|
|
766
|
+
channel.merger.connect(channel.reverbEffect.input);
|
|
767
|
+
channel.reverbEffect.output.connect(this.masterGain);
|
|
684
768
|
}
|
|
685
769
|
}
|
|
686
770
|
}
|
|
@@ -820,7 +904,7 @@ class Midy {
|
|
|
820
904
|
if (!instrumentKey)
|
|
821
905
|
return;
|
|
822
906
|
const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
|
|
823
|
-
this.
|
|
907
|
+
this.connectEffects(channel, note.gainNode);
|
|
824
908
|
if (channel.sostenutoPedal) {
|
|
825
909
|
channel.sostenutoNotes.set(noteNumber, note);
|
|
826
910
|
}
|
|
@@ -844,46 +928,48 @@ class Midy {
|
|
|
844
928
|
return;
|
|
845
929
|
if (!channel.scheduledNotes.has(noteNumber))
|
|
846
930
|
return;
|
|
847
|
-
const
|
|
848
|
-
for (let i = 0; i <
|
|
849
|
-
const
|
|
850
|
-
if (!
|
|
931
|
+
const scheduledNotes = channel.scheduledNotes.get(noteNumber);
|
|
932
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
933
|
+
const note = scheduledNotes[i];
|
|
934
|
+
if (!note)
|
|
851
935
|
continue;
|
|
852
|
-
if (
|
|
936
|
+
if (note.ending)
|
|
853
937
|
continue;
|
|
854
|
-
const { bufferSource, filterNode, gainNode, modLFO, modLFOGain, vibLFO, vibLFOGain, instrumentKey, } = targetNote;
|
|
855
938
|
const velocityRate = (velocity + 127) / 127;
|
|
856
|
-
const volEndTime = stopTime +
|
|
857
|
-
|
|
858
|
-
gainNode.gain
|
|
939
|
+
const volEndTime = stopTime +
|
|
940
|
+
note.instrumentKey.volRelease * velocityRate;
|
|
941
|
+
note.gainNode.gain
|
|
942
|
+
.cancelScheduledValues(stopTime)
|
|
943
|
+
.linearRampToValueAtTime(0, volEndTime);
|
|
859
944
|
const maxFreq = this.audioContext.sampleRate / 2;
|
|
860
|
-
const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
|
|
945
|
+
const baseFreq = this.centToHz(note.instrumentKey.initialFilterFc);
|
|
861
946
|
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
862
|
-
const modEndTime = stopTime +
|
|
863
|
-
|
|
947
|
+
const modEndTime = stopTime +
|
|
948
|
+
note.instrumentKey.modRelease * velocityRate;
|
|
949
|
+
note.filterNode.frequency
|
|
864
950
|
.cancelScheduledValues(stopTime)
|
|
865
951
|
.linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
|
|
866
|
-
|
|
952
|
+
note.ending = true;
|
|
867
953
|
this.scheduleTask(() => {
|
|
868
|
-
bufferSource.loop = false;
|
|
954
|
+
note.bufferSource.loop = false;
|
|
869
955
|
}, stopTime);
|
|
870
956
|
return new Promise((resolve) => {
|
|
871
|
-
bufferSource.onended = () => {
|
|
872
|
-
|
|
873
|
-
bufferSource.disconnect(
|
|
874
|
-
filterNode.disconnect(
|
|
875
|
-
gainNode.disconnect(
|
|
876
|
-
if (modLFOGain)
|
|
877
|
-
modLFOGain.disconnect(
|
|
878
|
-
if (vibLFOGain)
|
|
879
|
-
vibLFOGain.disconnect(
|
|
880
|
-
if (modLFO)
|
|
881
|
-
modLFO.stop();
|
|
882
|
-
if (vibLFO)
|
|
883
|
-
vibLFO.stop();
|
|
957
|
+
note.bufferSource.onended = () => {
|
|
958
|
+
scheduledNotes[i] = null;
|
|
959
|
+
note.bufferSource.disconnect();
|
|
960
|
+
note.filterNode.disconnect();
|
|
961
|
+
note.gainNode.disconnect();
|
|
962
|
+
if (note.modLFOGain)
|
|
963
|
+
note.modLFOGain.disconnect();
|
|
964
|
+
if (note.vibLFOGain)
|
|
965
|
+
note.vibLFOGain.disconnect();
|
|
966
|
+
if (note.modLFO)
|
|
967
|
+
note.modLFO.stop();
|
|
968
|
+
if (note.vibLFO)
|
|
969
|
+
note.vibLFO.stop();
|
|
884
970
|
resolve();
|
|
885
971
|
};
|
|
886
|
-
bufferSource.stop(volEndTime);
|
|
972
|
+
note.bufferSource.stop(volEndTime);
|
|
887
973
|
});
|
|
888
974
|
}
|
|
889
975
|
}
|
|
@@ -1125,23 +1211,22 @@ class Midy {
|
|
|
1125
1211
|
this.releaseSustainPedal(channelNumber, value);
|
|
1126
1212
|
}
|
|
1127
1213
|
}
|
|
1214
|
+
// TODO
|
|
1128
1215
|
setPortamento(channelNumber, value) {
|
|
1129
1216
|
this.channels[channelNumber].portamento = value >= 64;
|
|
1130
1217
|
}
|
|
1131
|
-
setReverbSendLevel(channelNumber,
|
|
1218
|
+
setReverbSendLevel(channelNumber, reverbSendLevel) {
|
|
1132
1219
|
const now = this.audioContext.currentTime;
|
|
1133
1220
|
const channel = this.channels[channelNumber];
|
|
1134
1221
|
const reverbEffect = channel.reverbEffect;
|
|
1135
|
-
channel.
|
|
1136
|
-
reverbEffect.
|
|
1137
|
-
reverbEffect.
|
|
1138
|
-
reverbEffect.wetGain.gain.cancelScheduledValues(now);
|
|
1139
|
-
reverbEffect.wetGain.gain.setValueAtTime(channel.reverb, now);
|
|
1222
|
+
channel.reverbSendLevel = reverbSendLevel / 127;
|
|
1223
|
+
reverbEffect.output.gain.cancelScheduledValues(now);
|
|
1224
|
+
reverbEffect.output.gain.setValueAtTime(channel.reverbSendLevel, now);
|
|
1140
1225
|
}
|
|
1141
|
-
setChorusSendLevel(channelNumber,
|
|
1226
|
+
setChorusSendLevel(channelNumber, chorusSendLevel) {
|
|
1142
1227
|
const channel = this.channels[channelNumber];
|
|
1143
|
-
channel.
|
|
1144
|
-
channel.chorusEffect.lfoGain = channel.
|
|
1228
|
+
channel.chorusSendLevel = chorusSendLevel / 127;
|
|
1229
|
+
channel.chorusEffect.lfoGain = channel.chorusSendLevel;
|
|
1145
1230
|
}
|
|
1146
1231
|
setSostenutoPedal(channelNumber, value) {
|
|
1147
1232
|
const isOn = value >= 64;
|
|
@@ -1365,11 +1450,11 @@ class Midy {
|
|
|
1365
1450
|
this.GM2SystemOn();
|
|
1366
1451
|
break;
|
|
1367
1452
|
default:
|
|
1368
|
-
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
1453
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1369
1454
|
}
|
|
1370
1455
|
break;
|
|
1371
1456
|
default:
|
|
1372
|
-
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
1457
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1373
1458
|
}
|
|
1374
1459
|
}
|
|
1375
1460
|
GM1SystemOn() {
|
|
@@ -1400,9 +1485,10 @@ class Midy {
|
|
|
1400
1485
|
return this.handleMasterFineTuningSysEx(data);
|
|
1401
1486
|
case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
|
|
1402
1487
|
return this.handleMasterCoarseTuningSysEx(data);
|
|
1403
|
-
|
|
1488
|
+
case 5:
|
|
1489
|
+
return this.handleGlobalParameterControl(data);
|
|
1404
1490
|
default:
|
|
1405
|
-
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
1491
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1406
1492
|
}
|
|
1407
1493
|
break;
|
|
1408
1494
|
case 8:
|
|
@@ -1411,7 +1497,7 @@ class Midy {
|
|
|
1411
1497
|
// // TODO
|
|
1412
1498
|
// return this.handleScaleOctaveTuning1ByteFormat();
|
|
1413
1499
|
default:
|
|
1414
|
-
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
1500
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1415
1501
|
}
|
|
1416
1502
|
break;
|
|
1417
1503
|
case 9:
|
|
@@ -1423,7 +1509,7 @@ class Midy {
|
|
|
1423
1509
|
// // TODO
|
|
1424
1510
|
// return this.setControlChange();
|
|
1425
1511
|
default:
|
|
1426
|
-
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
1512
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1427
1513
|
}
|
|
1428
1514
|
break;
|
|
1429
1515
|
case 10:
|
|
@@ -1432,11 +1518,11 @@ class Midy {
|
|
|
1432
1518
|
// // TODO
|
|
1433
1519
|
// return this.handleKeyBasedInstrumentControl();
|
|
1434
1520
|
default:
|
|
1435
|
-
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
1521
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1436
1522
|
}
|
|
1437
1523
|
break;
|
|
1438
1524
|
default:
|
|
1439
|
-
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
1525
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1440
1526
|
}
|
|
1441
1527
|
}
|
|
1442
1528
|
handleMasterVolumeSysEx(data) {
|
|
@@ -1477,8 +1563,124 @@ class Midy {
|
|
|
1477
1563
|
this.masterCoarseTuning = coarseTuning - 64;
|
|
1478
1564
|
}
|
|
1479
1565
|
}
|
|
1566
|
+
handleGlobalParameterControl(data) {
|
|
1567
|
+
if (data[5] === 1) {
|
|
1568
|
+
switch (data[6]) {
|
|
1569
|
+
case 1:
|
|
1570
|
+
return this.handleReverbParameter(data);
|
|
1571
|
+
case 2:
|
|
1572
|
+
return this.handleChorusParameter(data);
|
|
1573
|
+
default:
|
|
1574
|
+
console.warn(`Unsupported Global Parameter Control Message: ${data}`);
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
else {
|
|
1578
|
+
console.warn(`Unsupported Global Parameter Control Message: ${data}`);
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
handleReverbParameter(data) {
|
|
1582
|
+
switch (data[7]) {
|
|
1583
|
+
case 0:
|
|
1584
|
+
return this.setReverbType(data[8]);
|
|
1585
|
+
case 1:
|
|
1586
|
+
return this.setReverbTime(data[8]);
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
setReverbType(type) {
|
|
1590
|
+
this.reverb.time = this.getReverbTimeFromType(type);
|
|
1591
|
+
this.reverb.feedback = (type === 8) ? 0.1 : 0.2;
|
|
1592
|
+
const { audioContext, channels, options } = this;
|
|
1593
|
+
for (let i = 0; i < channels.length; i++) {
|
|
1594
|
+
channels[i].reverbEffect = options.reverbAlgorithm(audioContext);
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
getReverbTimeFromType(type) {
|
|
1598
|
+
switch (type) {
|
|
1599
|
+
case 0:
|
|
1600
|
+
return this.getReverbTime(44);
|
|
1601
|
+
case 1:
|
|
1602
|
+
return this.getReverbTime(50);
|
|
1603
|
+
case 2:
|
|
1604
|
+
return this.getReverbTime(56);
|
|
1605
|
+
case 3:
|
|
1606
|
+
return this.getReverbTime(64);
|
|
1607
|
+
case 4:
|
|
1608
|
+
return this.getReverbTime(64);
|
|
1609
|
+
case 8:
|
|
1610
|
+
return this.getReverbTime(50);
|
|
1611
|
+
default:
|
|
1612
|
+
console.warn(`Unsupported Reverb Time: ${type}`);
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
setReverbTime(value) {
|
|
1616
|
+
this.reverb.time = this.getReverbTime(value);
|
|
1617
|
+
const { audioContext, channels, options } = this;
|
|
1618
|
+
for (let i = 0; i < channels.length; i++) {
|
|
1619
|
+
channels[i].reverbEffect = options.reverbAlgorithm(audioContext);
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
getReverbTime(value) {
|
|
1623
|
+
return Math.pow(Math.E, (value - 40) * 0.025);
|
|
1624
|
+
}
|
|
1625
|
+
// mean free path equation
|
|
1626
|
+
// https://repository.dl.itc.u-tokyo.ac.jp/record/8550/files/A31912.pdf
|
|
1627
|
+
// 江田和司, 拡散性制御に基づく室内音響設計に向けた音場解析に関する研究, 2015
|
|
1628
|
+
// V: room size (m^3)
|
|
1629
|
+
// S: room surface area (m^2)
|
|
1630
|
+
// meanFreePath = 4V / S (m)
|
|
1631
|
+
// delay estimation using mean free path
|
|
1632
|
+
// t: degree Celsius, generally used 20
|
|
1633
|
+
// c: speed of sound = 331.5 + 0.61t = 331.5 * 0.61 * 20 = 343.7 (m/s)
|
|
1634
|
+
// delay = meanFreePath / c (s)
|
|
1635
|
+
// feedback equation
|
|
1636
|
+
// RT60 means that the energy is reduced to Math.pow(10, -6).
|
|
1637
|
+
// Since energy is proportional to the square of the amplitude,
|
|
1638
|
+
// the amplitude is reduced to Math.pow(10, -3).
|
|
1639
|
+
// When this is done through n feedbacks,
|
|
1640
|
+
// Math.pow(feedback, n) = Math.pow(10, -3)
|
|
1641
|
+
// Math.pow(feedback, RT60 / delay) = Math.pow(10, -3)
|
|
1642
|
+
// RT60 / delay * Math.log10(feedback) = -3
|
|
1643
|
+
// RT60 = -3 * delay / Math.log10(feedback)
|
|
1644
|
+
// feedback = Math.pow(10, -3 * delay / RT60)
|
|
1645
|
+
// delay estimation using ideal feedback
|
|
1646
|
+
// A suitable average sound absorption coefficient is 0.18-0.28.
|
|
1647
|
+
// Since the structure of the hall is complex,
|
|
1648
|
+
// It would be easier to determine the delay based on the ideal feedback.
|
|
1649
|
+
// delay = -RT60 * Math.log10(feedback) / 3
|
|
1650
|
+
calcDelay(rt60, feedback) {
|
|
1651
|
+
return -rt60 * Math.log10(feedback) / 3;
|
|
1652
|
+
}
|
|
1653
|
+
handleChorusParameter(data) {
|
|
1654
|
+
switch (data[7]) {
|
|
1655
|
+
case 0:
|
|
1656
|
+
return this.setChorusType(data[8]);
|
|
1657
|
+
case 1:
|
|
1658
|
+
return this.setChorusModRate(data[8]);
|
|
1659
|
+
case 2:
|
|
1660
|
+
return this.setChorusModDepth(data[8]);
|
|
1661
|
+
case 3:
|
|
1662
|
+
return this.setChorusFeedback(data[8]);
|
|
1663
|
+
case 4:
|
|
1664
|
+
return this.setChorusSendToReverb(data[8]);
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
setChorusType(type) {
|
|
1668
|
+
// TODO
|
|
1669
|
+
}
|
|
1670
|
+
setChorusModRate(value) {
|
|
1671
|
+
// TODO
|
|
1672
|
+
}
|
|
1673
|
+
setChorusModDepth(value) {
|
|
1674
|
+
// TODO
|
|
1675
|
+
}
|
|
1676
|
+
setChorusFeedback(value) {
|
|
1677
|
+
// TODO
|
|
1678
|
+
}
|
|
1679
|
+
setChorusSendToReverb(value) {
|
|
1680
|
+
// TODO
|
|
1681
|
+
}
|
|
1480
1682
|
handleExclusiveMessage(data) {
|
|
1481
|
-
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
1683
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1482
1684
|
}
|
|
1483
1685
|
handleSysEx(data) {
|
|
1484
1686
|
switch (data[0]) {
|
|
@@ -1512,8 +1714,8 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
1512
1714
|
volume: 100 / 127,
|
|
1513
1715
|
pan: 64,
|
|
1514
1716
|
portamentoTime: 0,
|
|
1515
|
-
|
|
1516
|
-
|
|
1717
|
+
reverbSendLevel: 0,
|
|
1718
|
+
chorusSendLevel: 0,
|
|
1517
1719
|
vibratoRate: 5,
|
|
1518
1720
|
vibratoDepth: 0.5,
|
|
1519
1721
|
vibratoDelay: 2.5,
|