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