@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-GM2.js
CHANGED
|
@@ -54,7 +54,7 @@ class Note {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
class MidyGM2 {
|
|
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 MidyGM2 {
|
|
|
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 MidyGM2 {
|
|
|
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 MidyGM2 {
|
|
|
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 MidyGM2 {
|
|
|
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,13 +284,13 @@ class MidyGM2 {
|
|
|
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
|
channelPressure: {
|
|
261
|
-
...
|
|
293
|
+
...this.constructor.controllerDestinationSettings,
|
|
262
294
|
},
|
|
263
295
|
};
|
|
264
296
|
});
|
|
@@ -586,8 +618,7 @@ class MidyGM2 {
|
|
|
586
618
|
}
|
|
587
619
|
return noteList[0];
|
|
588
620
|
}
|
|
589
|
-
|
|
590
|
-
const { decay = 0.8, preDecay = 0, } = options;
|
|
621
|
+
createConvolutionReverbImpulse(audioContext, decay, preDecay) {
|
|
591
622
|
const sampleRate = audioContext.sampleRate;
|
|
592
623
|
const length = sampleRate * decay;
|
|
593
624
|
const impulse = new AudioBuffer({
|
|
@@ -601,27 +632,85 @@ class MidyGM2 {
|
|
|
601
632
|
for (let i = 0; i < preDecayLength; i++) {
|
|
602
633
|
channelData[i] = Math.random() * 2 - 1;
|
|
603
634
|
}
|
|
635
|
+
const attenuationFactor = 1 / (sampleRate * decay);
|
|
604
636
|
for (let i = preDecayLength; i < length; i++) {
|
|
605
|
-
const attenuation = Math.exp(-(i - preDecayLength)
|
|
637
|
+
const attenuation = Math.exp(-(i - preDecayLength) * attenuationFactor);
|
|
606
638
|
channelData[i] = (Math.random() * 2 - 1) * attenuation;
|
|
607
639
|
}
|
|
608
640
|
}
|
|
641
|
+
return impulse;
|
|
642
|
+
}
|
|
643
|
+
createConvolutionReverb(audioContext, impulse) {
|
|
644
|
+
const output = new GainNode(audioContext);
|
|
609
645
|
const convolverNode = new ConvolverNode(audioContext, {
|
|
610
646
|
buffer: impulse,
|
|
611
647
|
});
|
|
612
|
-
|
|
613
|
-
const wetGain = new GainNode(audioContext);
|
|
614
|
-
convolverNode.connect(wetGain);
|
|
648
|
+
convolverNode.connect(output);
|
|
615
649
|
return {
|
|
650
|
+
input: convolverNode,
|
|
651
|
+
output,
|
|
616
652
|
convolverNode,
|
|
617
|
-
dryGain,
|
|
618
|
-
wetGain,
|
|
619
653
|
};
|
|
620
654
|
}
|
|
655
|
+
createCombFilter(audioContext, input, delay, feedback) {
|
|
656
|
+
const delayNode = new DelayNode(audioContext, {
|
|
657
|
+
maxDelayTime: delay,
|
|
658
|
+
delayTime: delay,
|
|
659
|
+
});
|
|
660
|
+
const feedbackGain = new GainNode(audioContext, { gain: feedback });
|
|
661
|
+
input.connect(delayNode);
|
|
662
|
+
delayNode.connect(feedbackGain);
|
|
663
|
+
feedbackGain.connect(delayNode);
|
|
664
|
+
return delayNode;
|
|
665
|
+
}
|
|
666
|
+
createAllpassFilter(audioContext, input, delay, feedback) {
|
|
667
|
+
const delayNode = new DelayNode(audioContext, {
|
|
668
|
+
maxDelayTime: delay,
|
|
669
|
+
delayTime: delay,
|
|
670
|
+
});
|
|
671
|
+
const feedbackGain = new GainNode(audioContext, { gain: feedback });
|
|
672
|
+
const passGain = new GainNode(audioContext, { gain: 1 - feedback });
|
|
673
|
+
input.connect(delayNode);
|
|
674
|
+
delayNode.connect(feedbackGain);
|
|
675
|
+
feedbackGain.connect(delayNode);
|
|
676
|
+
delayNode.connect(passGain);
|
|
677
|
+
return passGain;
|
|
678
|
+
}
|
|
679
|
+
generateDistributedArray(center, count, varianceRatio = 0.1, randomness = 0.05) {
|
|
680
|
+
const variance = center * varianceRatio;
|
|
681
|
+
const array = new Array(count);
|
|
682
|
+
for (let i = 0; i < count; i++) {
|
|
683
|
+
const fraction = i / (count - 1 || 1);
|
|
684
|
+
const value = center - variance + fraction * 2 * variance;
|
|
685
|
+
array[i] = value * (1 - (Math.random() * 2 - 1) * randomness);
|
|
686
|
+
}
|
|
687
|
+
return array;
|
|
688
|
+
}
|
|
689
|
+
// https://hajim.rochester.edu/ece/sites/zduan/teaching/ece472/reading/Schroeder_1962.pdf
|
|
690
|
+
// M.R.Schroeder, "Natural Sounding Artificial Reverberation", J.Audio Eng. Soc., vol.10, p.219, 1962
|
|
691
|
+
createSchroederReverb(audioContext, combDelays, combFeedbacks, allpassDelays, allpassFeedbacks) {
|
|
692
|
+
const input = new GainNode(audioContext);
|
|
693
|
+
const output = new GainNode(audioContext);
|
|
694
|
+
const mergerGain = new GainNode(audioContext, {
|
|
695
|
+
gain: 1 / (combDelays.length * 2),
|
|
696
|
+
});
|
|
697
|
+
for (let i = 0; i < combDelays.length; i++) {
|
|
698
|
+
const comb = this.createCombFilter(audioContext, input, combDelays[i], combFeedbacks[i]);
|
|
699
|
+
comb.connect(mergerGain);
|
|
700
|
+
}
|
|
701
|
+
const allpasses = [];
|
|
702
|
+
for (let i = 0; i < allpassDelays.length; i++) {
|
|
703
|
+
const allpass = this.createAllpassFilter(audioContext, (i === 0) ? mergerGain : allpasses.at(-1), allpassDelays[i], allpassFeedbacks[i]);
|
|
704
|
+
allpasses.push(allpass);
|
|
705
|
+
}
|
|
706
|
+
allpasses.at(-1).connect(output);
|
|
707
|
+
return { input, output };
|
|
708
|
+
}
|
|
621
709
|
createChorusEffect(audioContext, options = {}) {
|
|
622
710
|
const { chorusCount = 2, chorusRate = 0.6, chorusDepth = 0.15, delay = 0.01, variance = delay * 0.1, } = options;
|
|
623
711
|
const lfo = new OscillatorNode(audioContext, { frequency: chorusRate });
|
|
624
712
|
const lfoGain = new GainNode(audioContext, { gain: chorusDepth });
|
|
713
|
+
const output = new GainNode(audioContext);
|
|
625
714
|
const chorusGains = [];
|
|
626
715
|
const delayNodes = [];
|
|
627
716
|
const baseGain = 1 / chorusCount;
|
|
@@ -631,50 +720,45 @@ class MidyGM2 {
|
|
|
631
720
|
const delayNode = new DelayNode(audioContext, {
|
|
632
721
|
maxDelayTime: delayTime,
|
|
633
722
|
});
|
|
634
|
-
delayNodes.push(delayNode);
|
|
635
723
|
const chorusGain = new GainNode(audioContext, { gain: baseGain });
|
|
724
|
+
delayNodes.push(delayNode);
|
|
636
725
|
chorusGains.push(chorusGain);
|
|
637
|
-
lfo.connect(lfoGain);
|
|
638
726
|
lfoGain.connect(delayNode.delayTime);
|
|
639
727
|
delayNode.connect(chorusGain);
|
|
728
|
+
chorusGain.connect(output);
|
|
640
729
|
}
|
|
730
|
+
lfo.connect(lfoGain);
|
|
731
|
+
lfo.start();
|
|
641
732
|
return {
|
|
642
733
|
lfo,
|
|
643
734
|
lfoGain,
|
|
644
735
|
delayNodes,
|
|
645
736
|
chorusGains,
|
|
737
|
+
output,
|
|
646
738
|
};
|
|
647
739
|
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
}
|
|
654
|
-
else { // chorus
|
|
740
|
+
connectEffects(channel, gainNode) {
|
|
741
|
+
gainNode.connect(channel.merger);
|
|
742
|
+
channel.merger.connect(this.masterGain);
|
|
743
|
+
if (channel.reverbSendLevel === 0) {
|
|
744
|
+
if (channel.chorusSendLevel !== 0) { // chorus
|
|
655
745
|
channel.chorusEffect.delayNodes.forEach((delayNode) => {
|
|
656
|
-
|
|
657
|
-
});
|
|
658
|
-
channel.chorusEffect.chorusGains.forEach((chorusGain) => {
|
|
659
|
-
chorusGain.connect(channel.gainL);
|
|
660
|
-
chorusGain.connect(channel.gainR);
|
|
746
|
+
channel.merger.connect(delayNode);
|
|
661
747
|
});
|
|
748
|
+
channel.chorusEffect.output.connect(this.masterGain);
|
|
662
749
|
}
|
|
663
750
|
}
|
|
664
751
|
else {
|
|
665
|
-
if (channel.
|
|
666
|
-
|
|
667
|
-
|
|
752
|
+
if (channel.chorusSendLevel === 0) { // reverb
|
|
753
|
+
channel.merger.connect(channel.reverbEffect.input);
|
|
754
|
+
channel.reverbEffect.output.connect(this.masterGain);
|
|
668
755
|
}
|
|
669
756
|
else { // reverb + chorus
|
|
670
|
-
gainNode.connect(channel.reverbEffect.convolverNode);
|
|
671
|
-
gainNode.connect(channel.reverbEffect.dryGain);
|
|
672
757
|
channel.chorusEffect.delayNodes.forEach((delayNode) => {
|
|
673
|
-
|
|
674
|
-
});
|
|
675
|
-
channel.chorusEffect.chorusGains.forEach((chorusGain) => {
|
|
676
|
-
chorusGain.connect(channel.reverbEffect.convolverNode);
|
|
758
|
+
channel.merger.connect(delayNode);
|
|
677
759
|
});
|
|
760
|
+
channel.merger.connect(channel.reverbEffect.input);
|
|
761
|
+
channel.reverbEffect.output.connect(this.masterGain);
|
|
678
762
|
}
|
|
679
763
|
}
|
|
680
764
|
}
|
|
@@ -745,7 +829,7 @@ class MidyGM2 {
|
|
|
745
829
|
startModulation(channel, note, time) {
|
|
746
830
|
const { instrumentKey } = note;
|
|
747
831
|
note.modLFOGain = new GainNode(this.audioContext, {
|
|
748
|
-
gain: this.cbToRatio(instrumentKey.modLfoToVolume
|
|
832
|
+
gain: this.cbToRatio(instrumentKey.modLfoToVolume + channel.modulation),
|
|
749
833
|
});
|
|
750
834
|
note.modLFO = new OscillatorNode(this.audioContext, {
|
|
751
835
|
frequency: this.centToHz(instrumentKey.freqModLFO),
|
|
@@ -797,7 +881,7 @@ class MidyGM2 {
|
|
|
797
881
|
if (!instrumentKey)
|
|
798
882
|
return;
|
|
799
883
|
const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
|
|
800
|
-
this.
|
|
884
|
+
this.connectEffects(channel, note.gainNode);
|
|
801
885
|
if (channel.sostenutoPedal) {
|
|
802
886
|
channel.sostenutoNotes.set(noteNumber, note);
|
|
803
887
|
}
|
|
@@ -821,42 +905,48 @@ class MidyGM2 {
|
|
|
821
905
|
return;
|
|
822
906
|
if (!channel.scheduledNotes.has(noteNumber))
|
|
823
907
|
return;
|
|
824
|
-
const
|
|
825
|
-
for (let i = 0; i <
|
|
826
|
-
const
|
|
827
|
-
if (!
|
|
908
|
+
const scheduledNotes = channel.scheduledNotes.get(noteNumber);
|
|
909
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
910
|
+
const note = scheduledNotes[i];
|
|
911
|
+
if (!note)
|
|
828
912
|
continue;
|
|
829
|
-
if (
|
|
913
|
+
if (note.ending)
|
|
830
914
|
continue;
|
|
831
|
-
const { bufferSource, filterNode, gainNode, modLFO, modLFOGain, instrumentKey, } = targetNote;
|
|
832
915
|
const velocityRate = (velocity + 127) / 127;
|
|
833
|
-
const volEndTime = stopTime +
|
|
834
|
-
|
|
835
|
-
gainNode.gain
|
|
916
|
+
const volEndTime = stopTime +
|
|
917
|
+
note.instrumentKey.volRelease * velocityRate;
|
|
918
|
+
note.gainNode.gain
|
|
919
|
+
.cancelScheduledValues(stopTime)
|
|
920
|
+
.linearRampToValueAtTime(0, volEndTime);
|
|
836
921
|
const maxFreq = this.audioContext.sampleRate / 2;
|
|
837
|
-
const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
|
|
922
|
+
const baseFreq = this.centToHz(note.instrumentKey.initialFilterFc);
|
|
838
923
|
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
839
|
-
const modEndTime = stopTime +
|
|
840
|
-
|
|
924
|
+
const modEndTime = stopTime +
|
|
925
|
+
note.instrumentKey.modRelease * velocityRate;
|
|
926
|
+
note.filterNode.frequency
|
|
841
927
|
.cancelScheduledValues(stopTime)
|
|
842
928
|
.linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
|
|
843
|
-
|
|
929
|
+
note.ending = true;
|
|
844
930
|
this.scheduleTask(() => {
|
|
845
|
-
bufferSource.loop = false;
|
|
931
|
+
note.bufferSource.loop = false;
|
|
846
932
|
}, stopTime);
|
|
847
933
|
return new Promise((resolve) => {
|
|
848
|
-
bufferSource.onended = () => {
|
|
849
|
-
|
|
850
|
-
bufferSource.disconnect(
|
|
851
|
-
filterNode.disconnect(
|
|
852
|
-
gainNode.disconnect(
|
|
853
|
-
if (modLFOGain)
|
|
854
|
-
modLFOGain.disconnect(
|
|
855
|
-
if (
|
|
856
|
-
|
|
934
|
+
note.bufferSource.onended = () => {
|
|
935
|
+
scheduledNotes[i] = null;
|
|
936
|
+
note.bufferSource.disconnect();
|
|
937
|
+
note.filterNode.disconnect();
|
|
938
|
+
note.gainNode.disconnect();
|
|
939
|
+
if (note.modLFOGain)
|
|
940
|
+
note.modLFOGain.disconnect();
|
|
941
|
+
if (note.vibLFOGain)
|
|
942
|
+
note.vibLFOGain.disconnect();
|
|
943
|
+
if (note.modLFO)
|
|
944
|
+
note.modLFO.stop();
|
|
945
|
+
if (note.vibLFO)
|
|
946
|
+
note.vibLFO.stop();
|
|
857
947
|
resolve();
|
|
858
948
|
};
|
|
859
|
-
bufferSource.stop(volEndTime);
|
|
949
|
+
note.bufferSource.stop(volEndTime);
|
|
860
950
|
});
|
|
861
951
|
}
|
|
862
952
|
}
|
|
@@ -1070,23 +1160,22 @@ class MidyGM2 {
|
|
|
1070
1160
|
this.releaseSustainPedal(channelNumber, value);
|
|
1071
1161
|
}
|
|
1072
1162
|
}
|
|
1163
|
+
// TODO
|
|
1073
1164
|
setPortamento(channelNumber, value) {
|
|
1074
1165
|
this.channels[channelNumber].portamento = value >= 64;
|
|
1075
1166
|
}
|
|
1076
|
-
setReverbSendLevel(channelNumber,
|
|
1167
|
+
setReverbSendLevel(channelNumber, reverbSendLevel) {
|
|
1077
1168
|
const now = this.audioContext.currentTime;
|
|
1078
1169
|
const channel = this.channels[channelNumber];
|
|
1079
1170
|
const reverbEffect = channel.reverbEffect;
|
|
1080
|
-
channel.
|
|
1081
|
-
reverbEffect.
|
|
1082
|
-
reverbEffect.
|
|
1083
|
-
reverbEffect.wetGain.gain.cancelScheduledValues(now);
|
|
1084
|
-
reverbEffect.wetGain.gain.setValueAtTime(channel.reverb, now);
|
|
1171
|
+
channel.reverbSendLevel = reverbSendLevel / 127;
|
|
1172
|
+
reverbEffect.output.gain.cancelScheduledValues(now);
|
|
1173
|
+
reverbEffect.output.gain.setValueAtTime(channel.reverbSendLevel, now);
|
|
1085
1174
|
}
|
|
1086
|
-
setChorusSendLevel(channelNumber,
|
|
1175
|
+
setChorusSendLevel(channelNumber, chorusSendLevel) {
|
|
1087
1176
|
const channel = this.channels[channelNumber];
|
|
1088
|
-
channel.
|
|
1089
|
-
channel.chorusEffect.lfoGain = channel.
|
|
1177
|
+
channel.chorusSendLevel = chorusSendLevel / 127;
|
|
1178
|
+
channel.chorusEffect.lfoGain = channel.chorusSendLevel;
|
|
1090
1179
|
}
|
|
1091
1180
|
setSostenutoPedal(channelNumber, value) {
|
|
1092
1181
|
const isOn = value >= 64;
|
|
@@ -1282,11 +1371,11 @@ class MidyGM2 {
|
|
|
1282
1371
|
this.GM2SystemOn();
|
|
1283
1372
|
break;
|
|
1284
1373
|
default:
|
|
1285
|
-
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
1374
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1286
1375
|
}
|
|
1287
1376
|
break;
|
|
1288
1377
|
default:
|
|
1289
|
-
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
1378
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1290
1379
|
}
|
|
1291
1380
|
}
|
|
1292
1381
|
GM1SystemOn() {
|
|
@@ -1317,9 +1406,10 @@ class MidyGM2 {
|
|
|
1317
1406
|
return this.handleMasterFineTuningSysEx(data);
|
|
1318
1407
|
case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
|
|
1319
1408
|
return this.handleMasterCoarseTuningSysEx(data);
|
|
1320
|
-
|
|
1409
|
+
case 5:
|
|
1410
|
+
return this.handleGlobalParameterControl(data);
|
|
1321
1411
|
default:
|
|
1322
|
-
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
1412
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1323
1413
|
}
|
|
1324
1414
|
break;
|
|
1325
1415
|
case 8:
|
|
@@ -1328,7 +1418,7 @@ class MidyGM2 {
|
|
|
1328
1418
|
// // TODO
|
|
1329
1419
|
// return this.handleScaleOctaveTuning1ByteFormat();
|
|
1330
1420
|
default:
|
|
1331
|
-
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
1421
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1332
1422
|
}
|
|
1333
1423
|
break;
|
|
1334
1424
|
case 9:
|
|
@@ -1340,7 +1430,7 @@ class MidyGM2 {
|
|
|
1340
1430
|
// // TODO
|
|
1341
1431
|
// return this.setControlChange();
|
|
1342
1432
|
default:
|
|
1343
|
-
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
1433
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1344
1434
|
}
|
|
1345
1435
|
break;
|
|
1346
1436
|
case 10:
|
|
@@ -1349,11 +1439,11 @@ class MidyGM2 {
|
|
|
1349
1439
|
// // TODO
|
|
1350
1440
|
// return this.handleKeyBasedInstrumentControl();
|
|
1351
1441
|
default:
|
|
1352
|
-
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
1442
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1353
1443
|
}
|
|
1354
1444
|
break;
|
|
1355
1445
|
default:
|
|
1356
|
-
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
1446
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1357
1447
|
}
|
|
1358
1448
|
}
|
|
1359
1449
|
handleMasterVolumeSysEx(data) {
|
|
@@ -1394,8 +1484,124 @@ class MidyGM2 {
|
|
|
1394
1484
|
this.masterCoarseTuning = coarseTuning - 64;
|
|
1395
1485
|
}
|
|
1396
1486
|
}
|
|
1487
|
+
handleGlobalParameterControl(data) {
|
|
1488
|
+
if (data[5] === 1) {
|
|
1489
|
+
switch (data[6]) {
|
|
1490
|
+
case 1:
|
|
1491
|
+
return this.handleReverbParameter(data);
|
|
1492
|
+
case 2:
|
|
1493
|
+
return this.handleChorusParameter(data);
|
|
1494
|
+
default:
|
|
1495
|
+
console.warn(`Unsupported Global Parameter Control Message: ${data}`);
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
else {
|
|
1499
|
+
console.warn(`Unsupported Global Parameter Control Message: ${data}`);
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
handleReverbParameter(data) {
|
|
1503
|
+
switch (data[7]) {
|
|
1504
|
+
case 0:
|
|
1505
|
+
return this.setReverbType(data[8]);
|
|
1506
|
+
case 1:
|
|
1507
|
+
return this.setReverbTime(data[8]);
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
setReverbType(type) {
|
|
1511
|
+
this.reverb.time = this.getReverbTimeFromType(type);
|
|
1512
|
+
this.reverb.feedback = (type === 8) ? 0.1 : 0.2;
|
|
1513
|
+
const { audioContext, channels, options } = this;
|
|
1514
|
+
for (let i = 0; i < channels.length; i++) {
|
|
1515
|
+
channels[i].reverbEffect = options.reverbAlgorithm(audioContext);
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
getReverbTimeFromType(type) {
|
|
1519
|
+
switch (type) {
|
|
1520
|
+
case 0:
|
|
1521
|
+
return this.getReverbTime(44);
|
|
1522
|
+
case 1:
|
|
1523
|
+
return this.getReverbTime(50);
|
|
1524
|
+
case 2:
|
|
1525
|
+
return this.getReverbTime(56);
|
|
1526
|
+
case 3:
|
|
1527
|
+
return this.getReverbTime(64);
|
|
1528
|
+
case 4:
|
|
1529
|
+
return this.getReverbTime(64);
|
|
1530
|
+
case 8:
|
|
1531
|
+
return this.getReverbTime(50);
|
|
1532
|
+
default:
|
|
1533
|
+
console.warn(`Unsupported Reverb Time: ${type}`);
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
setReverbTime(value) {
|
|
1537
|
+
this.reverb.time = this.getReverbTime(value);
|
|
1538
|
+
const { audioContext, channels, options } = this;
|
|
1539
|
+
for (let i = 0; i < channels.length; i++) {
|
|
1540
|
+
channels[i].reverbEffect = options.reverbAlgorithm(audioContext);
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
getReverbTime(value) {
|
|
1544
|
+
return Math.pow(Math.E, (value - 40) * 0.025);
|
|
1545
|
+
}
|
|
1546
|
+
// mean free path equation
|
|
1547
|
+
// https://repository.dl.itc.u-tokyo.ac.jp/record/8550/files/A31912.pdf
|
|
1548
|
+
// 江田和司, 拡散性制御に基づく室内音響設計に向けた音場解析に関する研究, 2015
|
|
1549
|
+
// V: room size (m^3)
|
|
1550
|
+
// S: room surface area (m^2)
|
|
1551
|
+
// meanFreePath = 4V / S (m)
|
|
1552
|
+
// delay estimation using mean free path
|
|
1553
|
+
// t: degree Celsius, generally used 20
|
|
1554
|
+
// c: speed of sound = 331.5 + 0.61t = 331.5 * 0.61 * 20 = 343.7 (m/s)
|
|
1555
|
+
// delay = meanFreePath / c (s)
|
|
1556
|
+
// feedback equation
|
|
1557
|
+
// RT60 means that the energy is reduced to Math.pow(10, -6).
|
|
1558
|
+
// Since energy is proportional to the square of the amplitude,
|
|
1559
|
+
// the amplitude is reduced to Math.pow(10, -3).
|
|
1560
|
+
// When this is done through n feedbacks,
|
|
1561
|
+
// Math.pow(feedback, n) = Math.pow(10, -3)
|
|
1562
|
+
// Math.pow(feedback, RT60 / delay) = Math.pow(10, -3)
|
|
1563
|
+
// RT60 / delay * Math.log10(feedback) = -3
|
|
1564
|
+
// RT60 = -3 * delay / Math.log10(feedback)
|
|
1565
|
+
// feedback = Math.pow(10, -3 * delay / RT60)
|
|
1566
|
+
// delay estimation using ideal feedback
|
|
1567
|
+
// A suitable average sound absorption coefficient is 0.18-0.28.
|
|
1568
|
+
// Since the structure of the hall is complex,
|
|
1569
|
+
// It would be easier to determine the delay based on the ideal feedback.
|
|
1570
|
+
// delay = -RT60 * Math.log10(feedback) / 3
|
|
1571
|
+
calcDelay(rt60, feedback) {
|
|
1572
|
+
return -rt60 * Math.log10(feedback) / 3;
|
|
1573
|
+
}
|
|
1574
|
+
handleChorusParameter(data) {
|
|
1575
|
+
switch (data[7]) {
|
|
1576
|
+
case 0:
|
|
1577
|
+
return this.setChorusType(data[8]);
|
|
1578
|
+
case 1:
|
|
1579
|
+
return this.setChorusModRate(data[8]);
|
|
1580
|
+
case 2:
|
|
1581
|
+
return this.setChorusModDepth(data[8]);
|
|
1582
|
+
case 3:
|
|
1583
|
+
return this.setChorusFeedback(data[8]);
|
|
1584
|
+
case 4:
|
|
1585
|
+
return this.setChorusSendToReverb(data[8]);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
setChorusType(type) {
|
|
1589
|
+
// TODO
|
|
1590
|
+
}
|
|
1591
|
+
setChorusModRate(value) {
|
|
1592
|
+
// TODO
|
|
1593
|
+
}
|
|
1594
|
+
setChorusModDepth(value) {
|
|
1595
|
+
// TODO
|
|
1596
|
+
}
|
|
1597
|
+
setChorusFeedback(value) {
|
|
1598
|
+
// TODO
|
|
1599
|
+
}
|
|
1600
|
+
setChorusSendToReverb(value) {
|
|
1601
|
+
// TODO
|
|
1602
|
+
}
|
|
1397
1603
|
handleExclusiveMessage(data) {
|
|
1398
|
-
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
1604
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1399
1605
|
}
|
|
1400
1606
|
handleSysEx(data) {
|
|
1401
1607
|
switch (data[0]) {
|
|
@@ -1429,8 +1635,8 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
1429
1635
|
volume: 100 / 127,
|
|
1430
1636
|
pan: 64,
|
|
1431
1637
|
portamentoTime: 0,
|
|
1432
|
-
|
|
1433
|
-
|
|
1638
|
+
reverbSendLevel: 0,
|
|
1639
|
+
chorusSendLevel: 0,
|
|
1434
1640
|
bank: 121 * 128,
|
|
1435
1641
|
bankMSB: 121,
|
|
1436
1642
|
bankLSB: 0,
|
package/script/midy-GMLite.d.ts
CHANGED
|
@@ -37,25 +37,7 @@ export class MidyGMLite {
|
|
|
37
37
|
notePromises: any[];
|
|
38
38
|
audioContext: any;
|
|
39
39
|
masterGain: any;
|
|
40
|
-
channels:
|
|
41
|
-
scheduledNotes: Map<any, any>;
|
|
42
|
-
gainL: any;
|
|
43
|
-
gainR: any;
|
|
44
|
-
expression: number;
|
|
45
|
-
modulation: number;
|
|
46
|
-
sustainPedal: boolean;
|
|
47
|
-
rpnMSB: number;
|
|
48
|
-
rpnLSB: number;
|
|
49
|
-
pitchBendRange: number;
|
|
50
|
-
volume: number;
|
|
51
|
-
pan: number;
|
|
52
|
-
bank: number;
|
|
53
|
-
dataMSB: number;
|
|
54
|
-
dataLSB: number;
|
|
55
|
-
program: number;
|
|
56
|
-
pitchBend: number;
|
|
57
|
-
modulationDepthRange: number;
|
|
58
|
-
}[];
|
|
40
|
+
channels: any[];
|
|
59
41
|
initSoundFontTable(): any[];
|
|
60
42
|
addSoundFont(soundFont: any): void;
|
|
61
43
|
loadSoundFont(soundFontUrl: any): Promise<void>;
|
|
@@ -63,26 +45,9 @@ export class MidyGMLite {
|
|
|
63
45
|
setChannelAudioNodes(audioContext: any): {
|
|
64
46
|
gainL: any;
|
|
65
47
|
gainR: any;
|
|
48
|
+
merger: any;
|
|
66
49
|
};
|
|
67
|
-
createChannels(audioContext: any):
|
|
68
|
-
scheduledNotes: Map<any, any>;
|
|
69
|
-
gainL: any;
|
|
70
|
-
gainR: any;
|
|
71
|
-
expression: number;
|
|
72
|
-
modulation: number;
|
|
73
|
-
sustainPedal: boolean;
|
|
74
|
-
rpnMSB: number;
|
|
75
|
-
rpnLSB: number;
|
|
76
|
-
pitchBendRange: number;
|
|
77
|
-
volume: number;
|
|
78
|
-
pan: number;
|
|
79
|
-
bank: number;
|
|
80
|
-
dataMSB: number;
|
|
81
|
-
dataLSB: number;
|
|
82
|
-
program: number;
|
|
83
|
-
pitchBend: number;
|
|
84
|
-
modulationDepthRange: number;
|
|
85
|
-
}[];
|
|
50
|
+
createChannels(audioContext: any): any[];
|
|
86
51
|
createNoteBuffer(instrumentKey: any, isSF3: any): Promise<any>;
|
|
87
52
|
createNoteBufferNode(instrumentKey: any, isSF3: any): Promise<any>;
|
|
88
53
|
convertToFloat32Array(uint8Array: any): Float32Array;
|
|
@@ -105,7 +70,7 @@ export class MidyGMLite {
|
|
|
105
70
|
currentTime(): number;
|
|
106
71
|
getActiveNotes(channel: any, time: any): Map<any, any>;
|
|
107
72
|
getActiveNote(noteList: any, time: any): any;
|
|
108
|
-
|
|
73
|
+
connectEffects(channel: any, gainNode: any): void;
|
|
109
74
|
cbToRatio(cb: any): number;
|
|
110
75
|
centToHz(cent: any): number;
|
|
111
76
|
calcSemitoneOffset(channel: any): number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AAqBA;IAmBE;;;;;;;;;MASE;IAEF;;;;;;;MAOE;IAEF,+BAMC;IA5CD,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IAuBhB,kBAAgC;IAChC,gBAA4C;IAE5C
|
|
1
|
+
{"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AAqBA;IAmBE;;;;;;;;;MASE;IAEF;;;;;;;MAOE;IAEF,+BAMC;IA5CD,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IAuBhB,kBAAgC;IAChC,gBAA4C;IAE5C,gBAAiD;IAInD,4BAMC;IAED,mCASC;IAED,gDAMC;IAED,sCASC;IAED;;;;MAcC;IAED,yCAUC;IAED,+DAyBC;IAED,mEAWC;IAED,qDAOC;IAED,2EA+CC;IAED,mCAOC;IAED,0BA+CC;IAED,uDAEC;IAED,wDAEC;IAED;;;MA8DC;IAED,4BAsBC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,uDASC;IAED,6CAQC;IAED,kDAGC;IAED,2BAEC;IAED,4BAEC;IAED,yCAEC;IAED,mFAGC;IAED,iDAiBC;IAED,iDAiCC;IAED,0DAmBC;IAED,wHA6BC;IAED,kGA6BC;IAED,0EAGC;IAED,sIA8CC;IAED,0FAGC;IAED,kEAeC;IAED,wFAiBC;IAED,4DAGC;IAED,qEAGC;IAED,uDAOC;IAED,mFA+BC;IAED,qCAcC;IAED,yDAIC;IACD,iDAIC;IAED;;;MAMC;IAED,2CAIC;IAED,yDAIC;IAED,mDAGC;IAED,sCAUC;IAED,sDAMC;IAED,oCAYC;IAED,gDAEC;IAED,gDAEC;IAED,mDAGC;IAED,oDAUC;IAED,kDAKC;IAED,iEAOC;IAED,uCAoBC;IAED,8CAEC;IAED,uCAoBC;IAED,4DAgBC;IAED,oBAQC;IAED,yDAaC;IAED,yCAGC;IAED,mCAQC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF;AAn/BD;IAOE,gFAKC;IAXD,kBAAa;IACb,cAAS;IACT,gBAAW;IACX,YAAO;IACP,gBAAW;IAGT,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,mBAAkC;CAErC"}
|