@marmooo/midy 0.0.7 → 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 +12 -47
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +70 -53
- package/esm/midy-GM2.d.ts +52 -109
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +187 -95
- package/esm/midy-GMLite.d.ts +8 -41
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +53 -46
- package/esm/midy.d.ts +55 -134
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +191 -100
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +12 -47
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +70 -53
- package/script/midy-GM2.d.ts +52 -109
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +187 -95
- package/script/midy-GMLite.d.ts +8 -41
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +53 -46
- package/script/midy.d.ts +55 -134
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +191 -100
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,
|
|
@@ -78,13 +78,13 @@ class Midy {
|
|
|
78
78
|
configurable: true,
|
|
79
79
|
writable: true,
|
|
80
80
|
value: 0
|
|
81
|
-
});
|
|
81
|
+
}); // cb
|
|
82
82
|
Object.defineProperty(this, "masterCoarseTuning", {
|
|
83
83
|
enumerable: true,
|
|
84
84
|
configurable: true,
|
|
85
85
|
writable: true,
|
|
86
86
|
value: 0
|
|
87
|
-
});
|
|
87
|
+
}); // cb
|
|
88
88
|
Object.defineProperty(this, "mono", {
|
|
89
89
|
enumerable: true,
|
|
90
90
|
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
|
}
|
|
@@ -751,7 +814,7 @@ class Midy {
|
|
|
751
814
|
startModulation(channel, note, time) {
|
|
752
815
|
const { instrumentKey } = note;
|
|
753
816
|
note.modLFOGain = new GainNode(this.audioContext, {
|
|
754
|
-
gain: this.cbToRatio(instrumentKey.modLfoToVolume
|
|
817
|
+
gain: this.cbToRatio(instrumentKey.modLfoToVolume + channel.modulation),
|
|
755
818
|
});
|
|
756
819
|
note.modLFO = new OscillatorNode(this.audioContext, {
|
|
757
820
|
frequency: this.centToHz(instrumentKey.freqModLFO),
|
|
@@ -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
|
}
|
|
@@ -998,7 +1063,7 @@ class Midy {
|
|
|
998
1063
|
case 5:
|
|
999
1064
|
return this.setPortamentoTime(channelNumber, value);
|
|
1000
1065
|
case 6:
|
|
1001
|
-
return this.
|
|
1066
|
+
return this.dataEntryMSB(channelNumber, value);
|
|
1002
1067
|
case 7:
|
|
1003
1068
|
return this.setVolume(channelNumber, value);
|
|
1004
1069
|
case 10:
|
|
@@ -1008,7 +1073,7 @@ class Midy {
|
|
|
1008
1073
|
case 32:
|
|
1009
1074
|
return this.setBankLSB(channelNumber, value);
|
|
1010
1075
|
case 38:
|
|
1011
|
-
return this.
|
|
1076
|
+
return this.dataEntryLSB(channelNumber, value);
|
|
1012
1077
|
case 64:
|
|
1013
1078
|
return this.setSustainPedal(channelNumber, value);
|
|
1014
1079
|
case 65:
|
|
@@ -1025,13 +1090,13 @@ class Midy {
|
|
|
1025
1090
|
case 78:
|
|
1026
1091
|
return this.setVibratoDelay(channelNumber, value);
|
|
1027
1092
|
case 91:
|
|
1028
|
-
return this.
|
|
1093
|
+
return this.setReverbSendLevel(channelNumber, value);
|
|
1029
1094
|
case 93:
|
|
1030
|
-
return this.
|
|
1095
|
+
return this.setChorusSendLevel(channelNumber, value);
|
|
1031
1096
|
case 96: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp18.pdf
|
|
1032
|
-
return
|
|
1097
|
+
return this.dataIncrement(channelNumber);
|
|
1033
1098
|
case 97: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp18.pdf
|
|
1034
|
-
return
|
|
1099
|
+
return this.dataDecrement(channelNumber);
|
|
1035
1100
|
case 100:
|
|
1036
1101
|
return this.setRPNLSB(channelNumber, value);
|
|
1037
1102
|
case 101:
|
|
@@ -1057,22 +1122,24 @@ class Midy {
|
|
|
1057
1122
|
setBankMSB(channelNumber, msb) {
|
|
1058
1123
|
this.channels[channelNumber].bankMSB = msb;
|
|
1059
1124
|
}
|
|
1060
|
-
|
|
1125
|
+
updateModulation(channel) {
|
|
1061
1126
|
const now = this.audioContext.currentTime;
|
|
1062
|
-
const channel = this.channels[channelNumber];
|
|
1063
|
-
channel.modulation = (modulation / 127) *
|
|
1064
|
-
(channel.modulationDepthRange * 100);
|
|
1065
1127
|
const activeNotes = this.getActiveNotes(channel, now);
|
|
1066
1128
|
activeNotes.forEach((activeNote) => {
|
|
1067
1129
|
if (activeNote.modLFO) {
|
|
1068
|
-
|
|
1069
|
-
|
|
1130
|
+
const { gainNode, instrumentKey } = activeNote;
|
|
1131
|
+
gainNode.gain.setValueAtTime(this.cbToRatio(instrumentKey.modLfoToVolume + channel.modulation), now);
|
|
1070
1132
|
}
|
|
1071
1133
|
else {
|
|
1072
1134
|
this.startModulation(channel, activeNote, now);
|
|
1073
1135
|
}
|
|
1074
1136
|
});
|
|
1075
1137
|
}
|
|
1138
|
+
setModulation(channelNumber, modulation) {
|
|
1139
|
+
const channel = this.channels[channelNumber];
|
|
1140
|
+
channel.modulation = (modulation / 127) * channel.modulationDepthRange;
|
|
1141
|
+
this.updateModulation(channel);
|
|
1142
|
+
}
|
|
1076
1143
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
1077
1144
|
this.channels[channelNumber].portamentoTime = portamentoTime / 127;
|
|
1078
1145
|
}
|
|
@@ -1101,6 +1168,10 @@ class Midy {
|
|
|
1101
1168
|
setBankLSB(channelNumber, lsb) {
|
|
1102
1169
|
this.channels[channelNumber].bankLSB = lsb;
|
|
1103
1170
|
}
|
|
1171
|
+
dataEntryLSB(channelNumber, value) {
|
|
1172
|
+
this.channels[channelNumber].dataLSB = value;
|
|
1173
|
+
this.handleRPN(channelNumber, 0);
|
|
1174
|
+
}
|
|
1104
1175
|
updateChannelGain(channel) {
|
|
1105
1176
|
const now = this.audioContext.currentTime;
|
|
1106
1177
|
const volume = channel.volume * channel.expression;
|
|
@@ -1122,7 +1193,7 @@ class Midy {
|
|
|
1122
1193
|
setPortamento(channelNumber, value) {
|
|
1123
1194
|
this.channels[channelNumber].portamento = value >= 64;
|
|
1124
1195
|
}
|
|
1125
|
-
|
|
1196
|
+
setReverbSendLevel(channelNumber, reverb) {
|
|
1126
1197
|
const now = this.audioContext.currentTime;
|
|
1127
1198
|
const channel = this.channels[channelNumber];
|
|
1128
1199
|
const reverbEffect = channel.reverbEffect;
|
|
@@ -1132,7 +1203,7 @@ class Midy {
|
|
|
1132
1203
|
reverbEffect.wetGain.gain.cancelScheduledValues(now);
|
|
1133
1204
|
reverbEffect.wetGain.gain.setValueAtTime(channel.reverb, now);
|
|
1134
1205
|
}
|
|
1135
|
-
|
|
1206
|
+
setChorusSendLevel(channelNumber, chorus) {
|
|
1136
1207
|
const channel = this.channels[channelNumber];
|
|
1137
1208
|
channel.chorus = chorus / 127;
|
|
1138
1209
|
channel.chorusEffect.lfoGain = channel.chorus;
|
|
@@ -1198,31 +1269,34 @@ class Midy {
|
|
|
1198
1269
|
channel.dataMSB = minMSB;
|
|
1199
1270
|
}
|
|
1200
1271
|
}
|
|
1201
|
-
// TODO: support 3-4?
|
|
1202
1272
|
handleRPN(channelNumber, value) {
|
|
1203
1273
|
const channel = this.channels[channelNumber];
|
|
1204
1274
|
const rpn = channel.rpnMSB * 128 + channel.rpnLSB;
|
|
1205
1275
|
switch (rpn) {
|
|
1206
1276
|
case 0:
|
|
1207
1277
|
channel.dataLSB += value;
|
|
1208
|
-
this.
|
|
1278
|
+
this.handlePitchBendRangeRPN(channelNumber);
|
|
1209
1279
|
break;
|
|
1210
1280
|
case 1:
|
|
1211
1281
|
channel.dataLSB += value;
|
|
1212
|
-
this.
|
|
1282
|
+
this.handleFineTuningRPN(channelNumber);
|
|
1213
1283
|
break;
|
|
1214
1284
|
case 2:
|
|
1215
1285
|
channel.dataMSB += value;
|
|
1216
|
-
this.
|
|
1286
|
+
this.handleCoarseTuningRPN(channelNumber);
|
|
1287
|
+
break;
|
|
1288
|
+
case 5:
|
|
1289
|
+
channel.dataLSB += value;
|
|
1290
|
+
this.handleModulationDepthRangeRPN(channelNumber);
|
|
1217
1291
|
break;
|
|
1218
1292
|
default:
|
|
1219
1293
|
console.warn(`Channel ${channelNumber}: Unsupported RPN MSB=${channel.rpnMSB} LSB=${channel.rpnLSB}`);
|
|
1220
1294
|
}
|
|
1221
1295
|
}
|
|
1222
|
-
|
|
1296
|
+
dataIncrement(channelNumber) {
|
|
1223
1297
|
this.handleRPN(channelNumber, 1);
|
|
1224
1298
|
}
|
|
1225
|
-
|
|
1299
|
+
dataDecrement(channelNumber) {
|
|
1226
1300
|
this.handleRPN(channelNumber, -1);
|
|
1227
1301
|
}
|
|
1228
1302
|
setRPNMSB(channelNumber, value) {
|
|
@@ -1231,9 +1305,8 @@ class Midy {
|
|
|
1231
1305
|
setRPNLSB(channelNumber, value) {
|
|
1232
1306
|
this.channels[channelNumber].rpnLSB = value;
|
|
1233
1307
|
}
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
isMSB ? channel.dataMSB = value : channel.dataLSB = value;
|
|
1308
|
+
dataEntryMSB(channelNumber, value) {
|
|
1309
|
+
this.channels[channelNumber].dataMSB = value;
|
|
1237
1310
|
this.handleRPN(channelNumber, 0);
|
|
1238
1311
|
}
|
|
1239
1312
|
updateDetune(channel, detuneChange) {
|
|
@@ -1247,7 +1320,7 @@ class Midy {
|
|
|
1247
1320
|
.setValueAtTime(detune, now);
|
|
1248
1321
|
});
|
|
1249
1322
|
}
|
|
1250
|
-
|
|
1323
|
+
handlePitchBendRangeRPN(channelNumber) {
|
|
1251
1324
|
const channel = this.channels[channelNumber];
|
|
1252
1325
|
this.limitData(channel, 0, 127, 0, 99);
|
|
1253
1326
|
const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
|
|
@@ -1261,7 +1334,7 @@ class Midy {
|
|
|
1261
1334
|
channel.pitchBend * 100;
|
|
1262
1335
|
this.updateDetune(channel, detuneChange);
|
|
1263
1336
|
}
|
|
1264
|
-
|
|
1337
|
+
handleFineTuningRPN(channelNumber) {
|
|
1265
1338
|
const channel = this.channels[channelNumber];
|
|
1266
1339
|
this.limitData(channel, 0, 127, 0, 127);
|
|
1267
1340
|
const fineTuning = (channel.dataMSB * 128 + channel.dataLSB - 8192) / 8192;
|
|
@@ -1269,9 +1342,12 @@ class Midy {
|
|
|
1269
1342
|
}
|
|
1270
1343
|
setFineTuning(channelNumber, fineTuning) {
|
|
1271
1344
|
const channel = this.channels[channelNumber];
|
|
1345
|
+
const prevFineTuning = channel.fineTuning;
|
|
1272
1346
|
channel.fineTuning = fineTuning;
|
|
1347
|
+
const detuneChange = channel.fineTuning - prevFineTuning;
|
|
1348
|
+
this.updateDetune(channel, detuneChange);
|
|
1273
1349
|
}
|
|
1274
|
-
|
|
1350
|
+
handleCoarseTuningRPN(channelNumber) {
|
|
1275
1351
|
const channel = this.channels[channelNumber];
|
|
1276
1352
|
this.limitDataMSB(channel, 0, 127);
|
|
1277
1353
|
const coarseTuning = channel.dataMSB - 64;
|
|
@@ -1279,7 +1355,22 @@ class Midy {
|
|
|
1279
1355
|
}
|
|
1280
1356
|
setCoarseTuning(channelNumber, coarseTuning) {
|
|
1281
1357
|
const channel = this.channels[channelNumber];
|
|
1282
|
-
|
|
1358
|
+
const prevCoarseTuning = channel.coarseTuning;
|
|
1359
|
+
channel.coarseTuning = coarseTuning;
|
|
1360
|
+
const detuneChange = channel.coarseTuning - prevCoarseTuning;
|
|
1361
|
+
this.updateDetune(channel, detuneChange);
|
|
1362
|
+
}
|
|
1363
|
+
handleModulationDepthRangeRPN(channelNumber) {
|
|
1364
|
+
const channel = this.channels[channelNumber];
|
|
1365
|
+
this.limitData(channel, 0, 127, 0, 127);
|
|
1366
|
+
const modulationDepthRange = dataMSB + dataLSB / 128;
|
|
1367
|
+
this.setModulationDepthRange(channelNumber, modulationDepthRange);
|
|
1368
|
+
}
|
|
1369
|
+
setModulationDepthRange(channelNumber, modulationDepthRange) {
|
|
1370
|
+
const channel = this.channels[channelNumber];
|
|
1371
|
+
channel.modulationDepthRange = modulationDepthRange;
|
|
1372
|
+
channel.modulation = (modulation / 127) * channel.modulationDepthRange;
|
|
1373
|
+
this.updateModulation(channel);
|
|
1283
1374
|
}
|
|
1284
1375
|
allSoundOff(channelNumber) {
|
|
1285
1376
|
const now = this.audioContext.currentTime;
|
|
@@ -1370,9 +1461,9 @@ class Midy {
|
|
|
1370
1461
|
switch (data[3]) {
|
|
1371
1462
|
case 1:
|
|
1372
1463
|
return this.handleMasterVolumeSysEx(data);
|
|
1373
|
-
case 3:
|
|
1464
|
+
case 3: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
|
|
1374
1465
|
return this.handleMasterFineTuningSysEx(data);
|
|
1375
|
-
case 4:
|
|
1466
|
+
case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
|
|
1376
1467
|
return this.handleMasterCoarseTuningSysEx(data);
|
|
1377
1468
|
// case 5: // TODO: Global Parameter Control
|
|
1378
1469
|
default:
|
|
@@ -1498,9 +1589,9 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
1498
1589
|
dataLSB: 0,
|
|
1499
1590
|
program: 0,
|
|
1500
1591
|
pitchBend: 0,
|
|
1501
|
-
fineTuning: 0,
|
|
1502
|
-
coarseTuning: 0,
|
|
1503
|
-
modulationDepthRange: 0.5,
|
|
1592
|
+
fineTuning: 0, // cb
|
|
1593
|
+
coarseTuning: 0, // cb
|
|
1594
|
+
modulationDepthRange: 0.5, // cb
|
|
1504
1595
|
}
|
|
1505
1596
|
});
|
|
1506
1597
|
Object.defineProperty(Midy, "effectSettings", {
|