@marmooo/midy 0.0.8 → 0.0.9
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 +6 -43
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +38 -32
- package/esm/midy-GM2.d.ts +41 -102
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +130 -61
- package/esm/midy-GMLite.d.ts +4 -39
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +34 -32
- package/esm/midy.d.ts +41 -124
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +130 -65
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +6 -43
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +38 -32
- package/script/midy-GM2.d.ts +41 -102
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +130 -61
- package/script/midy-GMLite.d.ts +4 -39
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +34 -32
- package/script/midy.d.ts +41 -124
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +130 -65
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,
|
|
@@ -187,7 +187,19 @@ class Midy {
|
|
|
187
187
|
writable: true,
|
|
188
188
|
value: []
|
|
189
189
|
});
|
|
190
|
+
Object.defineProperty(this, "defaultOptions", {
|
|
191
|
+
enumerable: true,
|
|
192
|
+
configurable: true,
|
|
193
|
+
writable: true,
|
|
194
|
+
value: {
|
|
195
|
+
reverbAlgorithm: (audioContext) => {
|
|
196
|
+
// return this.createConvolutionReverb(audioContext);
|
|
197
|
+
return this.createSchroederReverb(audioContext);
|
|
198
|
+
},
|
|
199
|
+
}
|
|
200
|
+
});
|
|
190
201
|
this.audioContext = audioContext;
|
|
202
|
+
this.options = { ...this.defaultOptions, ...options };
|
|
191
203
|
this.masterGain = new GainNode(audioContext);
|
|
192
204
|
this.masterGain.connect(audioContext.destination);
|
|
193
205
|
this.channels = this.createChannels(audioContext);
|
|
@@ -228,23 +240,18 @@ class Midy {
|
|
|
228
240
|
this.totalTime = this.calcTotalTime();
|
|
229
241
|
}
|
|
230
242
|
setChannelAudioNodes(audioContext) {
|
|
231
|
-
const { gainLeft, gainRight } = this.panToGain(
|
|
243
|
+
const { gainLeft, gainRight } = this.panToGain(this.constructor.channelSettings.pan);
|
|
232
244
|
const gainL = new GainNode(audioContext, { gain: gainLeft });
|
|
233
245
|
const gainR = new GainNode(audioContext, { gain: gainRight });
|
|
234
246
|
const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
|
|
235
247
|
gainL.connect(merger, 0, 0);
|
|
236
248
|
gainR.connect(merger, 0, 1);
|
|
237
|
-
|
|
238
|
-
const reverbEffect = this.createReverbEffect(audioContext);
|
|
249
|
+
const reverbEffect = this.options.reverbAlgorithm(audioContext);
|
|
239
250
|
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
251
|
return {
|
|
246
252
|
gainL,
|
|
247
253
|
gainR,
|
|
254
|
+
merger,
|
|
248
255
|
reverbEffect,
|
|
249
256
|
chorusEffect,
|
|
250
257
|
};
|
|
@@ -252,16 +259,16 @@ class Midy {
|
|
|
252
259
|
createChannels(audioContext) {
|
|
253
260
|
const channels = Array.from({ length: 16 }, () => {
|
|
254
261
|
return {
|
|
255
|
-
...
|
|
256
|
-
...
|
|
262
|
+
...this.constructor.channelSettings,
|
|
263
|
+
...this.constructor.effectSettings,
|
|
257
264
|
...this.setChannelAudioNodes(audioContext),
|
|
258
265
|
scheduledNotes: new Map(),
|
|
259
266
|
sostenutoNotes: new Map(),
|
|
260
267
|
polyphonicKeyPressure: {
|
|
261
|
-
...
|
|
268
|
+
...this.constructor.controllerDestinationSettings,
|
|
262
269
|
},
|
|
263
270
|
channelPressure: {
|
|
264
|
-
...
|
|
271
|
+
...this.constructor.controllerDestinationSettings,
|
|
265
272
|
},
|
|
266
273
|
};
|
|
267
274
|
});
|
|
@@ -592,8 +599,12 @@ class Midy {
|
|
|
592
599
|
}
|
|
593
600
|
return noteList[0];
|
|
594
601
|
}
|
|
595
|
-
|
|
602
|
+
createConvolutionReverb(audioContext, options = {}) {
|
|
596
603
|
const { decay = 0.8, preDecay = 0, } = options;
|
|
604
|
+
const input = new GainNode(audioContext);
|
|
605
|
+
const output = new GainNode(audioContext);
|
|
606
|
+
const dryGain = new GainNode(audioContext);
|
|
607
|
+
const wetGain = new GainNode(audioContext);
|
|
597
608
|
const sampleRate = audioContext.sampleRate;
|
|
598
609
|
const length = sampleRate * decay;
|
|
599
610
|
const impulse = new AudioBuffer({
|
|
@@ -607,27 +618,82 @@ class Midy {
|
|
|
607
618
|
for (let i = 0; i < preDecayLength; i++) {
|
|
608
619
|
channelData[i] = Math.random() * 2 - 1;
|
|
609
620
|
}
|
|
621
|
+
const attenuationFactor = 1 / (sampleRate * decay);
|
|
610
622
|
for (let i = preDecayLength; i < length; i++) {
|
|
611
|
-
const attenuation = Math.exp(-(i - preDecayLength)
|
|
623
|
+
const attenuation = Math.exp(-(i - preDecayLength) * attenuationFactor);
|
|
612
624
|
channelData[i] = (Math.random() * 2 - 1) * attenuation;
|
|
613
625
|
}
|
|
614
626
|
}
|
|
615
627
|
const convolverNode = new ConvolverNode(audioContext, {
|
|
616
628
|
buffer: impulse,
|
|
617
629
|
});
|
|
618
|
-
|
|
619
|
-
const wetGain = new GainNode(audioContext);
|
|
630
|
+
input.connect(convolverNode);
|
|
620
631
|
convolverNode.connect(wetGain);
|
|
632
|
+
wetGain.connect(output);
|
|
633
|
+
dryGain.connect(output);
|
|
621
634
|
return {
|
|
622
|
-
|
|
635
|
+
input,
|
|
636
|
+
output,
|
|
623
637
|
dryGain,
|
|
624
638
|
wetGain,
|
|
639
|
+
convolverNode,
|
|
625
640
|
};
|
|
626
641
|
}
|
|
642
|
+
createCombFilter(audioContext, input, delay, feedback) {
|
|
643
|
+
const delayNode = new DelayNode(audioContext, {
|
|
644
|
+
maxDelayTime: delay,
|
|
645
|
+
delayTime: delay,
|
|
646
|
+
});
|
|
647
|
+
const feedbackGain = new GainNode(audioContext, { gain: feedback });
|
|
648
|
+
input.connect(delayNode);
|
|
649
|
+
delayNode.connect(feedbackGain);
|
|
650
|
+
feedbackGain.connect(delayNode);
|
|
651
|
+
return delayNode;
|
|
652
|
+
}
|
|
653
|
+
createAllpassFilter(audioContext, input, delay, feedback) {
|
|
654
|
+
const delayNode = new DelayNode(audioContext, {
|
|
655
|
+
maxDelayTime: delay,
|
|
656
|
+
delayTime: delay,
|
|
657
|
+
});
|
|
658
|
+
const feedbackGain = new GainNode(audioContext, { gain: feedback });
|
|
659
|
+
const passGain = new GainNode(audioContext, { gain: 1 - feedback });
|
|
660
|
+
input.connect(delayNode);
|
|
661
|
+
delayNode.connect(feedbackGain);
|
|
662
|
+
feedbackGain.connect(delayNode);
|
|
663
|
+
delayNode.connect(passGain);
|
|
664
|
+
return passGain;
|
|
665
|
+
}
|
|
666
|
+
// https://hajim.rochester.edu/ece/sites/zduan/teaching/ece472/reading/Schroeder_1962.pdf
|
|
667
|
+
// M.R.Schroeder, "Natural Sounding Artificial Reverberation", J.Audio Eng. Soc., vol.10, p.219, 1962
|
|
668
|
+
createSchroederReverb(audioContext, options = {}) {
|
|
669
|
+
const { combDelays = [0.31, 0.34, 0.37, 0.40], combFeedbacks = [0.86, 0.87, 0.88, 0.89], allpassDelays = [0.02, 0.05], allpassFeedbacks = [0.7, 0.7], mix = 0.5, } = options;
|
|
670
|
+
const input = new GainNode(audioContext);
|
|
671
|
+
const output = new GainNode(audioContext);
|
|
672
|
+
const mergerGain = new GainNode(audioContext, {
|
|
673
|
+
gain: 1 / (combDelays.length * 2),
|
|
674
|
+
});
|
|
675
|
+
const dryGain = new GainNode(audioContext, { gain: 1 - mix });
|
|
676
|
+
const wetGain = new GainNode(audioContext, { gain: mix });
|
|
677
|
+
for (let i = 0; i < combDelays.length; i++) {
|
|
678
|
+
const comb = this.createCombFilter(audioContext, input, combDelays[i], combFeedbacks[i]);
|
|
679
|
+
comb.connect(mergerGain);
|
|
680
|
+
}
|
|
681
|
+
const allpasses = [];
|
|
682
|
+
for (let i = 0; i < allpassDelays.length; i++) {
|
|
683
|
+
const allpass = this.createAllpassFilter(audioContext, (i === 0) ? mergerGain : allpasses.at(-1), allpassDelays[i], allpassFeedbacks[i]);
|
|
684
|
+
allpasses.push(allpass);
|
|
685
|
+
}
|
|
686
|
+
allpasses.at(-1).connect(wetGain);
|
|
687
|
+
input.connect(dryGain);
|
|
688
|
+
dryGain.connect(output);
|
|
689
|
+
wetGain.connect(output);
|
|
690
|
+
return { input, output, dryGain, wetGain };
|
|
691
|
+
}
|
|
627
692
|
createChorusEffect(audioContext, options = {}) {
|
|
628
693
|
const { chorusCount = 2, chorusRate = 0.6, chorusDepth = 0.15, delay = 0.01, variance = delay * 0.1, } = options;
|
|
629
694
|
const lfo = new OscillatorNode(audioContext, { frequency: chorusRate });
|
|
630
695
|
const lfoGain = new GainNode(audioContext, { gain: chorusDepth });
|
|
696
|
+
const output = new GainNode(audioContext);
|
|
631
697
|
const chorusGains = [];
|
|
632
698
|
const delayNodes = [];
|
|
633
699
|
const baseGain = 1 / chorusCount;
|
|
@@ -637,50 +703,47 @@ class Midy {
|
|
|
637
703
|
const delayNode = new DelayNode(audioContext, {
|
|
638
704
|
maxDelayTime: delayTime,
|
|
639
705
|
});
|
|
640
|
-
delayNodes.push(delayNode);
|
|
641
706
|
const chorusGain = new GainNode(audioContext, { gain: baseGain });
|
|
707
|
+
delayNodes.push(delayNode);
|
|
642
708
|
chorusGains.push(chorusGain);
|
|
643
|
-
lfo.connect(lfoGain);
|
|
644
709
|
lfoGain.connect(delayNode.delayTime);
|
|
645
710
|
delayNode.connect(chorusGain);
|
|
711
|
+
chorusGain.connect(output);
|
|
646
712
|
}
|
|
713
|
+
lfo.connect(lfoGain);
|
|
714
|
+
lfo.start();
|
|
647
715
|
return {
|
|
648
716
|
lfo,
|
|
649
717
|
lfoGain,
|
|
650
718
|
delayNodes,
|
|
651
719
|
chorusGains,
|
|
720
|
+
output,
|
|
652
721
|
};
|
|
653
722
|
}
|
|
654
|
-
|
|
723
|
+
connectEffects(channel, gainNode) {
|
|
724
|
+
gainNode.connect(channel.merger);
|
|
655
725
|
if (channel.reverb === 0) {
|
|
656
726
|
if (channel.chorus === 0) { // no effect
|
|
657
|
-
|
|
658
|
-
gainNode.connect(channel.gainR);
|
|
727
|
+
channel.merger.connect(this.masterGain);
|
|
659
728
|
}
|
|
660
729
|
else { // chorus
|
|
661
730
|
channel.chorusEffect.delayNodes.forEach((delayNode) => {
|
|
662
|
-
|
|
663
|
-
});
|
|
664
|
-
channel.chorusEffect.chorusGains.forEach((chorusGain) => {
|
|
665
|
-
chorusGain.connect(channel.gainL);
|
|
666
|
-
chorusGain.connect(channel.gainR);
|
|
731
|
+
channel.merger.connect(delayNode);
|
|
667
732
|
});
|
|
733
|
+
channel.chorusEffect.output.connect(this.masterGain);
|
|
668
734
|
}
|
|
669
735
|
}
|
|
670
736
|
else {
|
|
671
737
|
if (channel.chorus === 0) { // reverb
|
|
672
|
-
|
|
673
|
-
|
|
738
|
+
channel.merger.connect(channel.reverbEffect.input);
|
|
739
|
+
channel.reverbEffect.output.connect(this.masterGain);
|
|
674
740
|
}
|
|
675
741
|
else { // reverb + chorus
|
|
676
|
-
gainNode.connect(channel.reverbEffect.convolverNode);
|
|
677
|
-
gainNode.connect(channel.reverbEffect.dryGain);
|
|
678
742
|
channel.chorusEffect.delayNodes.forEach((delayNode) => {
|
|
679
|
-
|
|
680
|
-
});
|
|
681
|
-
channel.chorusEffect.chorusGains.forEach((chorusGain) => {
|
|
682
|
-
chorusGain.connect(channel.reverbEffect.convolverNode);
|
|
743
|
+
channel.merger.connect(delayNode);
|
|
683
744
|
});
|
|
745
|
+
channel.merger.connect(channel.reverbEffect.input);
|
|
746
|
+
channel.reverbEffect.output.connect(this.masterGain);
|
|
684
747
|
}
|
|
685
748
|
}
|
|
686
749
|
}
|
|
@@ -820,7 +883,7 @@ class Midy {
|
|
|
820
883
|
if (!instrumentKey)
|
|
821
884
|
return;
|
|
822
885
|
const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
|
|
823
|
-
this.
|
|
886
|
+
this.connectEffects(channel, note.gainNode);
|
|
824
887
|
if (channel.sostenutoPedal) {
|
|
825
888
|
channel.sostenutoNotes.set(noteNumber, note);
|
|
826
889
|
}
|
|
@@ -844,46 +907,48 @@ class Midy {
|
|
|
844
907
|
return;
|
|
845
908
|
if (!channel.scheduledNotes.has(noteNumber))
|
|
846
909
|
return;
|
|
847
|
-
const
|
|
848
|
-
for (let i = 0; i <
|
|
849
|
-
const
|
|
850
|
-
if (!
|
|
910
|
+
const scheduledNotes = channel.scheduledNotes.get(noteNumber);
|
|
911
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
912
|
+
const note = scheduledNotes[i];
|
|
913
|
+
if (!note)
|
|
851
914
|
continue;
|
|
852
|
-
if (
|
|
915
|
+
if (note.ending)
|
|
853
916
|
continue;
|
|
854
|
-
const { bufferSource, filterNode, gainNode, modLFO, modLFOGain, vibLFO, vibLFOGain, instrumentKey, } = targetNote;
|
|
855
917
|
const velocityRate = (velocity + 127) / 127;
|
|
856
|
-
const volEndTime = stopTime +
|
|
857
|
-
|
|
858
|
-
gainNode.gain
|
|
918
|
+
const volEndTime = stopTime +
|
|
919
|
+
note.instrumentKey.volRelease * velocityRate;
|
|
920
|
+
note.gainNode.gain
|
|
921
|
+
.cancelScheduledValues(stopTime)
|
|
922
|
+
.linearRampToValueAtTime(0, volEndTime);
|
|
859
923
|
const maxFreq = this.audioContext.sampleRate / 2;
|
|
860
|
-
const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
|
|
924
|
+
const baseFreq = this.centToHz(note.instrumentKey.initialFilterFc);
|
|
861
925
|
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
862
|
-
const modEndTime = stopTime +
|
|
863
|
-
|
|
926
|
+
const modEndTime = stopTime +
|
|
927
|
+
note.instrumentKey.modRelease * velocityRate;
|
|
928
|
+
note.filterNode.frequency
|
|
864
929
|
.cancelScheduledValues(stopTime)
|
|
865
930
|
.linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
|
|
866
|
-
|
|
931
|
+
note.ending = true;
|
|
867
932
|
this.scheduleTask(() => {
|
|
868
|
-
bufferSource.loop = false;
|
|
933
|
+
note.bufferSource.loop = false;
|
|
869
934
|
}, stopTime);
|
|
870
935
|
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();
|
|
936
|
+
note.bufferSource.onended = () => {
|
|
937
|
+
scheduledNotes[i] = null;
|
|
938
|
+
note.bufferSource.disconnect();
|
|
939
|
+
note.filterNode.disconnect();
|
|
940
|
+
note.gainNode.disconnect();
|
|
941
|
+
if (note.modLFOGain)
|
|
942
|
+
note.modLFOGain.disconnect();
|
|
943
|
+
if (note.vibLFOGain)
|
|
944
|
+
note.vibLFOGain.disconnect();
|
|
945
|
+
if (note.modLFO)
|
|
946
|
+
note.modLFO.stop();
|
|
947
|
+
if (note.vibLFO)
|
|
948
|
+
note.vibLFO.stop();
|
|
884
949
|
resolve();
|
|
885
950
|
};
|
|
886
|
-
bufferSource.stop(volEndTime);
|
|
951
|
+
note.bufferSource.stop(volEndTime);
|
|
887
952
|
});
|
|
888
953
|
}
|
|
889
954
|
}
|