@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-GM2.js
CHANGED
|
@@ -51,7 +51,7 @@ class Note {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
export class MidyGM2 {
|
|
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 MidyGM2 {
|
|
|
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 MidyGM2 {
|
|
|
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 MidyGM2 {
|
|
|
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 MidyGM2 {
|
|
|
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,13 +281,13 @@ export class MidyGM2 {
|
|
|
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
|
channelPressure: {
|
|
258
|
-
...
|
|
290
|
+
...this.constructor.controllerDestinationSettings,
|
|
259
291
|
},
|
|
260
292
|
};
|
|
261
293
|
});
|
|
@@ -583,8 +615,7 @@ export class MidyGM2 {
|
|
|
583
615
|
}
|
|
584
616
|
return noteList[0];
|
|
585
617
|
}
|
|
586
|
-
|
|
587
|
-
const { decay = 0.8, preDecay = 0, } = options;
|
|
618
|
+
createConvolutionReverbImpulse(audioContext, decay, preDecay) {
|
|
588
619
|
const sampleRate = audioContext.sampleRate;
|
|
589
620
|
const length = sampleRate * decay;
|
|
590
621
|
const impulse = new AudioBuffer({
|
|
@@ -598,27 +629,85 @@ export class MidyGM2 {
|
|
|
598
629
|
for (let i = 0; i < preDecayLength; i++) {
|
|
599
630
|
channelData[i] = Math.random() * 2 - 1;
|
|
600
631
|
}
|
|
632
|
+
const attenuationFactor = 1 / (sampleRate * decay);
|
|
601
633
|
for (let i = preDecayLength; i < length; i++) {
|
|
602
|
-
const attenuation = Math.exp(-(i - preDecayLength)
|
|
634
|
+
const attenuation = Math.exp(-(i - preDecayLength) * attenuationFactor);
|
|
603
635
|
channelData[i] = (Math.random() * 2 - 1) * attenuation;
|
|
604
636
|
}
|
|
605
637
|
}
|
|
638
|
+
return impulse;
|
|
639
|
+
}
|
|
640
|
+
createConvolutionReverb(audioContext, impulse) {
|
|
641
|
+
const output = new GainNode(audioContext);
|
|
606
642
|
const convolverNode = new ConvolverNode(audioContext, {
|
|
607
643
|
buffer: impulse,
|
|
608
644
|
});
|
|
609
|
-
|
|
610
|
-
const wetGain = new GainNode(audioContext);
|
|
611
|
-
convolverNode.connect(wetGain);
|
|
645
|
+
convolverNode.connect(output);
|
|
612
646
|
return {
|
|
647
|
+
input: convolverNode,
|
|
648
|
+
output,
|
|
613
649
|
convolverNode,
|
|
614
|
-
dryGain,
|
|
615
|
-
wetGain,
|
|
616
650
|
};
|
|
617
651
|
}
|
|
652
|
+
createCombFilter(audioContext, input, delay, feedback) {
|
|
653
|
+
const delayNode = new DelayNode(audioContext, {
|
|
654
|
+
maxDelayTime: delay,
|
|
655
|
+
delayTime: delay,
|
|
656
|
+
});
|
|
657
|
+
const feedbackGain = new GainNode(audioContext, { gain: feedback });
|
|
658
|
+
input.connect(delayNode);
|
|
659
|
+
delayNode.connect(feedbackGain);
|
|
660
|
+
feedbackGain.connect(delayNode);
|
|
661
|
+
return delayNode;
|
|
662
|
+
}
|
|
663
|
+
createAllpassFilter(audioContext, input, delay, feedback) {
|
|
664
|
+
const delayNode = new DelayNode(audioContext, {
|
|
665
|
+
maxDelayTime: delay,
|
|
666
|
+
delayTime: delay,
|
|
667
|
+
});
|
|
668
|
+
const feedbackGain = new GainNode(audioContext, { gain: feedback });
|
|
669
|
+
const passGain = new GainNode(audioContext, { gain: 1 - feedback });
|
|
670
|
+
input.connect(delayNode);
|
|
671
|
+
delayNode.connect(feedbackGain);
|
|
672
|
+
feedbackGain.connect(delayNode);
|
|
673
|
+
delayNode.connect(passGain);
|
|
674
|
+
return passGain;
|
|
675
|
+
}
|
|
676
|
+
generateDistributedArray(center, count, varianceRatio = 0.1, randomness = 0.05) {
|
|
677
|
+
const variance = center * varianceRatio;
|
|
678
|
+
const array = new Array(count);
|
|
679
|
+
for (let i = 0; i < count; i++) {
|
|
680
|
+
const fraction = i / (count - 1 || 1);
|
|
681
|
+
const value = center - variance + fraction * 2 * variance;
|
|
682
|
+
array[i] = value * (1 - (Math.random() * 2 - 1) * randomness);
|
|
683
|
+
}
|
|
684
|
+
return array;
|
|
685
|
+
}
|
|
686
|
+
// https://hajim.rochester.edu/ece/sites/zduan/teaching/ece472/reading/Schroeder_1962.pdf
|
|
687
|
+
// M.R.Schroeder, "Natural Sounding Artificial Reverberation", J.Audio Eng. Soc., vol.10, p.219, 1962
|
|
688
|
+
createSchroederReverb(audioContext, combDelays, combFeedbacks, allpassDelays, allpassFeedbacks) {
|
|
689
|
+
const input = new GainNode(audioContext);
|
|
690
|
+
const output = new GainNode(audioContext);
|
|
691
|
+
const mergerGain = new GainNode(audioContext, {
|
|
692
|
+
gain: 1 / (combDelays.length * 2),
|
|
693
|
+
});
|
|
694
|
+
for (let i = 0; i < combDelays.length; i++) {
|
|
695
|
+
const comb = this.createCombFilter(audioContext, input, combDelays[i], combFeedbacks[i]);
|
|
696
|
+
comb.connect(mergerGain);
|
|
697
|
+
}
|
|
698
|
+
const allpasses = [];
|
|
699
|
+
for (let i = 0; i < allpassDelays.length; i++) {
|
|
700
|
+
const allpass = this.createAllpassFilter(audioContext, (i === 0) ? mergerGain : allpasses.at(-1), allpassDelays[i], allpassFeedbacks[i]);
|
|
701
|
+
allpasses.push(allpass);
|
|
702
|
+
}
|
|
703
|
+
allpasses.at(-1).connect(output);
|
|
704
|
+
return { input, output };
|
|
705
|
+
}
|
|
618
706
|
createChorusEffect(audioContext, options = {}) {
|
|
619
707
|
const { chorusCount = 2, chorusRate = 0.6, chorusDepth = 0.15, delay = 0.01, variance = delay * 0.1, } = options;
|
|
620
708
|
const lfo = new OscillatorNode(audioContext, { frequency: chorusRate });
|
|
621
709
|
const lfoGain = new GainNode(audioContext, { gain: chorusDepth });
|
|
710
|
+
const output = new GainNode(audioContext);
|
|
622
711
|
const chorusGains = [];
|
|
623
712
|
const delayNodes = [];
|
|
624
713
|
const baseGain = 1 / chorusCount;
|
|
@@ -628,50 +717,45 @@ export class MidyGM2 {
|
|
|
628
717
|
const delayNode = new DelayNode(audioContext, {
|
|
629
718
|
maxDelayTime: delayTime,
|
|
630
719
|
});
|
|
631
|
-
delayNodes.push(delayNode);
|
|
632
720
|
const chorusGain = new GainNode(audioContext, { gain: baseGain });
|
|
721
|
+
delayNodes.push(delayNode);
|
|
633
722
|
chorusGains.push(chorusGain);
|
|
634
|
-
lfo.connect(lfoGain);
|
|
635
723
|
lfoGain.connect(delayNode.delayTime);
|
|
636
724
|
delayNode.connect(chorusGain);
|
|
725
|
+
chorusGain.connect(output);
|
|
637
726
|
}
|
|
727
|
+
lfo.connect(lfoGain);
|
|
728
|
+
lfo.start();
|
|
638
729
|
return {
|
|
639
730
|
lfo,
|
|
640
731
|
lfoGain,
|
|
641
732
|
delayNodes,
|
|
642
733
|
chorusGains,
|
|
734
|
+
output,
|
|
643
735
|
};
|
|
644
736
|
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
}
|
|
651
|
-
else { // chorus
|
|
737
|
+
connectEffects(channel, gainNode) {
|
|
738
|
+
gainNode.connect(channel.merger);
|
|
739
|
+
channel.merger.connect(this.masterGain);
|
|
740
|
+
if (channel.reverbSendLevel === 0) {
|
|
741
|
+
if (channel.chorusSendLevel !== 0) { // chorus
|
|
652
742
|
channel.chorusEffect.delayNodes.forEach((delayNode) => {
|
|
653
|
-
|
|
654
|
-
});
|
|
655
|
-
channel.chorusEffect.chorusGains.forEach((chorusGain) => {
|
|
656
|
-
chorusGain.connect(channel.gainL);
|
|
657
|
-
chorusGain.connect(channel.gainR);
|
|
743
|
+
channel.merger.connect(delayNode);
|
|
658
744
|
});
|
|
745
|
+
channel.chorusEffect.output.connect(this.masterGain);
|
|
659
746
|
}
|
|
660
747
|
}
|
|
661
748
|
else {
|
|
662
|
-
if (channel.
|
|
663
|
-
|
|
664
|
-
|
|
749
|
+
if (channel.chorusSendLevel === 0) { // reverb
|
|
750
|
+
channel.merger.connect(channel.reverbEffect.input);
|
|
751
|
+
channel.reverbEffect.output.connect(this.masterGain);
|
|
665
752
|
}
|
|
666
753
|
else { // reverb + chorus
|
|
667
|
-
gainNode.connect(channel.reverbEffect.convolverNode);
|
|
668
|
-
gainNode.connect(channel.reverbEffect.dryGain);
|
|
669
754
|
channel.chorusEffect.delayNodes.forEach((delayNode) => {
|
|
670
|
-
|
|
671
|
-
});
|
|
672
|
-
channel.chorusEffect.chorusGains.forEach((chorusGain) => {
|
|
673
|
-
chorusGain.connect(channel.reverbEffect.convolverNode);
|
|
755
|
+
channel.merger.connect(delayNode);
|
|
674
756
|
});
|
|
757
|
+
channel.merger.connect(channel.reverbEffect.input);
|
|
758
|
+
channel.reverbEffect.output.connect(this.masterGain);
|
|
675
759
|
}
|
|
676
760
|
}
|
|
677
761
|
}
|
|
@@ -742,7 +826,7 @@ export class MidyGM2 {
|
|
|
742
826
|
startModulation(channel, note, time) {
|
|
743
827
|
const { instrumentKey } = note;
|
|
744
828
|
note.modLFOGain = new GainNode(this.audioContext, {
|
|
745
|
-
gain: this.cbToRatio(instrumentKey.modLfoToVolume
|
|
829
|
+
gain: this.cbToRatio(instrumentKey.modLfoToVolume + channel.modulation),
|
|
746
830
|
});
|
|
747
831
|
note.modLFO = new OscillatorNode(this.audioContext, {
|
|
748
832
|
frequency: this.centToHz(instrumentKey.freqModLFO),
|
|
@@ -794,7 +878,7 @@ export class MidyGM2 {
|
|
|
794
878
|
if (!instrumentKey)
|
|
795
879
|
return;
|
|
796
880
|
const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
|
|
797
|
-
this.
|
|
881
|
+
this.connectEffects(channel, note.gainNode);
|
|
798
882
|
if (channel.sostenutoPedal) {
|
|
799
883
|
channel.sostenutoNotes.set(noteNumber, note);
|
|
800
884
|
}
|
|
@@ -818,42 +902,48 @@ export class MidyGM2 {
|
|
|
818
902
|
return;
|
|
819
903
|
if (!channel.scheduledNotes.has(noteNumber))
|
|
820
904
|
return;
|
|
821
|
-
const
|
|
822
|
-
for (let i = 0; i <
|
|
823
|
-
const
|
|
824
|
-
if (!
|
|
905
|
+
const scheduledNotes = channel.scheduledNotes.get(noteNumber);
|
|
906
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
907
|
+
const note = scheduledNotes[i];
|
|
908
|
+
if (!note)
|
|
825
909
|
continue;
|
|
826
|
-
if (
|
|
910
|
+
if (note.ending)
|
|
827
911
|
continue;
|
|
828
|
-
const { bufferSource, filterNode, gainNode, modLFO, modLFOGain, instrumentKey, } = targetNote;
|
|
829
912
|
const velocityRate = (velocity + 127) / 127;
|
|
830
|
-
const volEndTime = stopTime +
|
|
831
|
-
|
|
832
|
-
gainNode.gain
|
|
913
|
+
const volEndTime = stopTime +
|
|
914
|
+
note.instrumentKey.volRelease * velocityRate;
|
|
915
|
+
note.gainNode.gain
|
|
916
|
+
.cancelScheduledValues(stopTime)
|
|
917
|
+
.linearRampToValueAtTime(0, volEndTime);
|
|
833
918
|
const maxFreq = this.audioContext.sampleRate / 2;
|
|
834
|
-
const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
|
|
919
|
+
const baseFreq = this.centToHz(note.instrumentKey.initialFilterFc);
|
|
835
920
|
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
836
|
-
const modEndTime = stopTime +
|
|
837
|
-
|
|
921
|
+
const modEndTime = stopTime +
|
|
922
|
+
note.instrumentKey.modRelease * velocityRate;
|
|
923
|
+
note.filterNode.frequency
|
|
838
924
|
.cancelScheduledValues(stopTime)
|
|
839
925
|
.linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
|
|
840
|
-
|
|
926
|
+
note.ending = true;
|
|
841
927
|
this.scheduleTask(() => {
|
|
842
|
-
bufferSource.loop = false;
|
|
928
|
+
note.bufferSource.loop = false;
|
|
843
929
|
}, stopTime);
|
|
844
930
|
return new Promise((resolve) => {
|
|
845
|
-
bufferSource.onended = () => {
|
|
846
|
-
|
|
847
|
-
bufferSource.disconnect(
|
|
848
|
-
filterNode.disconnect(
|
|
849
|
-
gainNode.disconnect(
|
|
850
|
-
if (modLFOGain)
|
|
851
|
-
modLFOGain.disconnect(
|
|
852
|
-
if (
|
|
853
|
-
|
|
931
|
+
note.bufferSource.onended = () => {
|
|
932
|
+
scheduledNotes[i] = null;
|
|
933
|
+
note.bufferSource.disconnect();
|
|
934
|
+
note.filterNode.disconnect();
|
|
935
|
+
note.gainNode.disconnect();
|
|
936
|
+
if (note.modLFOGain)
|
|
937
|
+
note.modLFOGain.disconnect();
|
|
938
|
+
if (note.vibLFOGain)
|
|
939
|
+
note.vibLFOGain.disconnect();
|
|
940
|
+
if (note.modLFO)
|
|
941
|
+
note.modLFO.stop();
|
|
942
|
+
if (note.vibLFO)
|
|
943
|
+
note.vibLFO.stop();
|
|
854
944
|
resolve();
|
|
855
945
|
};
|
|
856
|
-
bufferSource.stop(volEndTime);
|
|
946
|
+
note.bufferSource.stop(volEndTime);
|
|
857
947
|
});
|
|
858
948
|
}
|
|
859
949
|
}
|
|
@@ -1067,23 +1157,22 @@ export class MidyGM2 {
|
|
|
1067
1157
|
this.releaseSustainPedal(channelNumber, value);
|
|
1068
1158
|
}
|
|
1069
1159
|
}
|
|
1160
|
+
// TODO
|
|
1070
1161
|
setPortamento(channelNumber, value) {
|
|
1071
1162
|
this.channels[channelNumber].portamento = value >= 64;
|
|
1072
1163
|
}
|
|
1073
|
-
setReverbSendLevel(channelNumber,
|
|
1164
|
+
setReverbSendLevel(channelNumber, reverbSendLevel) {
|
|
1074
1165
|
const now = this.audioContext.currentTime;
|
|
1075
1166
|
const channel = this.channels[channelNumber];
|
|
1076
1167
|
const reverbEffect = channel.reverbEffect;
|
|
1077
|
-
channel.
|
|
1078
|
-
reverbEffect.
|
|
1079
|
-
reverbEffect.
|
|
1080
|
-
reverbEffect.wetGain.gain.cancelScheduledValues(now);
|
|
1081
|
-
reverbEffect.wetGain.gain.setValueAtTime(channel.reverb, now);
|
|
1168
|
+
channel.reverbSendLevel = reverbSendLevel / 127;
|
|
1169
|
+
reverbEffect.output.gain.cancelScheduledValues(now);
|
|
1170
|
+
reverbEffect.output.gain.setValueAtTime(channel.reverbSendLevel, now);
|
|
1082
1171
|
}
|
|
1083
|
-
setChorusSendLevel(channelNumber,
|
|
1172
|
+
setChorusSendLevel(channelNumber, chorusSendLevel) {
|
|
1084
1173
|
const channel = this.channels[channelNumber];
|
|
1085
|
-
channel.
|
|
1086
|
-
channel.chorusEffect.lfoGain = channel.
|
|
1174
|
+
channel.chorusSendLevel = chorusSendLevel / 127;
|
|
1175
|
+
channel.chorusEffect.lfoGain = channel.chorusSendLevel;
|
|
1087
1176
|
}
|
|
1088
1177
|
setSostenutoPedal(channelNumber, value) {
|
|
1089
1178
|
const isOn = value >= 64;
|
|
@@ -1279,11 +1368,11 @@ export class MidyGM2 {
|
|
|
1279
1368
|
this.GM2SystemOn();
|
|
1280
1369
|
break;
|
|
1281
1370
|
default:
|
|
1282
|
-
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
1371
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1283
1372
|
}
|
|
1284
1373
|
break;
|
|
1285
1374
|
default:
|
|
1286
|
-
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
1375
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1287
1376
|
}
|
|
1288
1377
|
}
|
|
1289
1378
|
GM1SystemOn() {
|
|
@@ -1314,9 +1403,10 @@ export class MidyGM2 {
|
|
|
1314
1403
|
return this.handleMasterFineTuningSysEx(data);
|
|
1315
1404
|
case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
|
|
1316
1405
|
return this.handleMasterCoarseTuningSysEx(data);
|
|
1317
|
-
|
|
1406
|
+
case 5:
|
|
1407
|
+
return this.handleGlobalParameterControl(data);
|
|
1318
1408
|
default:
|
|
1319
|
-
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
1409
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1320
1410
|
}
|
|
1321
1411
|
break;
|
|
1322
1412
|
case 8:
|
|
@@ -1325,7 +1415,7 @@ export class MidyGM2 {
|
|
|
1325
1415
|
// // TODO
|
|
1326
1416
|
// return this.handleScaleOctaveTuning1ByteFormat();
|
|
1327
1417
|
default:
|
|
1328
|
-
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
1418
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1329
1419
|
}
|
|
1330
1420
|
break;
|
|
1331
1421
|
case 9:
|
|
@@ -1337,7 +1427,7 @@ export class MidyGM2 {
|
|
|
1337
1427
|
// // TODO
|
|
1338
1428
|
// return this.setControlChange();
|
|
1339
1429
|
default:
|
|
1340
|
-
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
1430
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1341
1431
|
}
|
|
1342
1432
|
break;
|
|
1343
1433
|
case 10:
|
|
@@ -1346,11 +1436,11 @@ export class MidyGM2 {
|
|
|
1346
1436
|
// // TODO
|
|
1347
1437
|
// return this.handleKeyBasedInstrumentControl();
|
|
1348
1438
|
default:
|
|
1349
|
-
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
1439
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1350
1440
|
}
|
|
1351
1441
|
break;
|
|
1352
1442
|
default:
|
|
1353
|
-
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
1443
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1354
1444
|
}
|
|
1355
1445
|
}
|
|
1356
1446
|
handleMasterVolumeSysEx(data) {
|
|
@@ -1391,8 +1481,124 @@ export class MidyGM2 {
|
|
|
1391
1481
|
this.masterCoarseTuning = coarseTuning - 64;
|
|
1392
1482
|
}
|
|
1393
1483
|
}
|
|
1484
|
+
handleGlobalParameterControl(data) {
|
|
1485
|
+
if (data[5] === 1) {
|
|
1486
|
+
switch (data[6]) {
|
|
1487
|
+
case 1:
|
|
1488
|
+
return this.handleReverbParameter(data);
|
|
1489
|
+
case 2:
|
|
1490
|
+
return this.handleChorusParameter(data);
|
|
1491
|
+
default:
|
|
1492
|
+
console.warn(`Unsupported Global Parameter Control Message: ${data}`);
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
else {
|
|
1496
|
+
console.warn(`Unsupported Global Parameter Control Message: ${data}`);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
handleReverbParameter(data) {
|
|
1500
|
+
switch (data[7]) {
|
|
1501
|
+
case 0:
|
|
1502
|
+
return this.setReverbType(data[8]);
|
|
1503
|
+
case 1:
|
|
1504
|
+
return this.setReverbTime(data[8]);
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
setReverbType(type) {
|
|
1508
|
+
this.reverb.time = this.getReverbTimeFromType(type);
|
|
1509
|
+
this.reverb.feedback = (type === 8) ? 0.1 : 0.2;
|
|
1510
|
+
const { audioContext, channels, options } = this;
|
|
1511
|
+
for (let i = 0; i < channels.length; i++) {
|
|
1512
|
+
channels[i].reverbEffect = options.reverbAlgorithm(audioContext);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
getReverbTimeFromType(type) {
|
|
1516
|
+
switch (type) {
|
|
1517
|
+
case 0:
|
|
1518
|
+
return this.getReverbTime(44);
|
|
1519
|
+
case 1:
|
|
1520
|
+
return this.getReverbTime(50);
|
|
1521
|
+
case 2:
|
|
1522
|
+
return this.getReverbTime(56);
|
|
1523
|
+
case 3:
|
|
1524
|
+
return this.getReverbTime(64);
|
|
1525
|
+
case 4:
|
|
1526
|
+
return this.getReverbTime(64);
|
|
1527
|
+
case 8:
|
|
1528
|
+
return this.getReverbTime(50);
|
|
1529
|
+
default:
|
|
1530
|
+
console.warn(`Unsupported Reverb Time: ${type}`);
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
setReverbTime(value) {
|
|
1534
|
+
this.reverb.time = this.getReverbTime(value);
|
|
1535
|
+
const { audioContext, channels, options } = this;
|
|
1536
|
+
for (let i = 0; i < channels.length; i++) {
|
|
1537
|
+
channels[i].reverbEffect = options.reverbAlgorithm(audioContext);
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
getReverbTime(value) {
|
|
1541
|
+
return Math.pow(Math.E, (value - 40) * 0.025);
|
|
1542
|
+
}
|
|
1543
|
+
// mean free path equation
|
|
1544
|
+
// https://repository.dl.itc.u-tokyo.ac.jp/record/8550/files/A31912.pdf
|
|
1545
|
+
// 江田和司, 拡散性制御に基づく室内音響設計に向けた音場解析に関する研究, 2015
|
|
1546
|
+
// V: room size (m^3)
|
|
1547
|
+
// S: room surface area (m^2)
|
|
1548
|
+
// meanFreePath = 4V / S (m)
|
|
1549
|
+
// delay estimation using mean free path
|
|
1550
|
+
// t: degree Celsius, generally used 20
|
|
1551
|
+
// c: speed of sound = 331.5 + 0.61t = 331.5 * 0.61 * 20 = 343.7 (m/s)
|
|
1552
|
+
// delay = meanFreePath / c (s)
|
|
1553
|
+
// feedback equation
|
|
1554
|
+
// RT60 means that the energy is reduced to Math.pow(10, -6).
|
|
1555
|
+
// Since energy is proportional to the square of the amplitude,
|
|
1556
|
+
// the amplitude is reduced to Math.pow(10, -3).
|
|
1557
|
+
// When this is done through n feedbacks,
|
|
1558
|
+
// Math.pow(feedback, n) = Math.pow(10, -3)
|
|
1559
|
+
// Math.pow(feedback, RT60 / delay) = Math.pow(10, -3)
|
|
1560
|
+
// RT60 / delay * Math.log10(feedback) = -3
|
|
1561
|
+
// RT60 = -3 * delay / Math.log10(feedback)
|
|
1562
|
+
// feedback = Math.pow(10, -3 * delay / RT60)
|
|
1563
|
+
// delay estimation using ideal feedback
|
|
1564
|
+
// A suitable average sound absorption coefficient is 0.18-0.28.
|
|
1565
|
+
// Since the structure of the hall is complex,
|
|
1566
|
+
// It would be easier to determine the delay based on the ideal feedback.
|
|
1567
|
+
// delay = -RT60 * Math.log10(feedback) / 3
|
|
1568
|
+
calcDelay(rt60, feedback) {
|
|
1569
|
+
return -rt60 * Math.log10(feedback) / 3;
|
|
1570
|
+
}
|
|
1571
|
+
handleChorusParameter(data) {
|
|
1572
|
+
switch (data[7]) {
|
|
1573
|
+
case 0:
|
|
1574
|
+
return this.setChorusType(data[8]);
|
|
1575
|
+
case 1:
|
|
1576
|
+
return this.setChorusModRate(data[8]);
|
|
1577
|
+
case 2:
|
|
1578
|
+
return this.setChorusModDepth(data[8]);
|
|
1579
|
+
case 3:
|
|
1580
|
+
return this.setChorusFeedback(data[8]);
|
|
1581
|
+
case 4:
|
|
1582
|
+
return this.setChorusSendToReverb(data[8]);
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
setChorusType(type) {
|
|
1586
|
+
// TODO
|
|
1587
|
+
}
|
|
1588
|
+
setChorusModRate(value) {
|
|
1589
|
+
// TODO
|
|
1590
|
+
}
|
|
1591
|
+
setChorusModDepth(value) {
|
|
1592
|
+
// TODO
|
|
1593
|
+
}
|
|
1594
|
+
setChorusFeedback(value) {
|
|
1595
|
+
// TODO
|
|
1596
|
+
}
|
|
1597
|
+
setChorusSendToReverb(value) {
|
|
1598
|
+
// TODO
|
|
1599
|
+
}
|
|
1394
1600
|
handleExclusiveMessage(data) {
|
|
1395
|
-
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
1601
|
+
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1396
1602
|
}
|
|
1397
1603
|
handleSysEx(data) {
|
|
1398
1604
|
switch (data[0]) {
|
|
@@ -1425,8 +1631,8 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
1425
1631
|
volume: 100 / 127,
|
|
1426
1632
|
pan: 64,
|
|
1427
1633
|
portamentoTime: 0,
|
|
1428
|
-
|
|
1429
|
-
|
|
1634
|
+
reverbSendLevel: 0,
|
|
1635
|
+
chorusSendLevel: 0,
|
|
1430
1636
|
bank: 121 * 128,
|
|
1431
1637
|
bankMSB: 121,
|
|
1432
1638
|
bankLSB: 0,
|
package/esm/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;
|
package/esm/midy-GMLite.d.ts.map
CHANGED
|
@@ -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"}
|