@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/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,
|
|
@@ -75,13 +75,13 @@ export class Midy {
|
|
|
75
75
|
configurable: true,
|
|
76
76
|
writable: true,
|
|
77
77
|
value: 0
|
|
78
|
-
});
|
|
78
|
+
}); // cb
|
|
79
79
|
Object.defineProperty(this, "masterCoarseTuning", {
|
|
80
80
|
enumerable: true,
|
|
81
81
|
configurable: true,
|
|
82
82
|
writable: true,
|
|
83
83
|
value: 0
|
|
84
|
-
});
|
|
84
|
+
}); // cb
|
|
85
85
|
Object.defineProperty(this, "mono", {
|
|
86
86
|
enumerable: true,
|
|
87
87
|
configurable: true,
|
|
@@ -184,7 +184,19 @@ export class Midy {
|
|
|
184
184
|
writable: true,
|
|
185
185
|
value: []
|
|
186
186
|
});
|
|
187
|
+
Object.defineProperty(this, "defaultOptions", {
|
|
188
|
+
enumerable: true,
|
|
189
|
+
configurable: true,
|
|
190
|
+
writable: true,
|
|
191
|
+
value: {
|
|
192
|
+
reverbAlgorithm: (audioContext) => {
|
|
193
|
+
// return this.createConvolutionReverb(audioContext);
|
|
194
|
+
return this.createSchroederReverb(audioContext);
|
|
195
|
+
},
|
|
196
|
+
}
|
|
197
|
+
});
|
|
187
198
|
this.audioContext = audioContext;
|
|
199
|
+
this.options = { ...this.defaultOptions, ...options };
|
|
188
200
|
this.masterGain = new GainNode(audioContext);
|
|
189
201
|
this.masterGain.connect(audioContext.destination);
|
|
190
202
|
this.channels = this.createChannels(audioContext);
|
|
@@ -225,23 +237,18 @@ export class Midy {
|
|
|
225
237
|
this.totalTime = this.calcTotalTime();
|
|
226
238
|
}
|
|
227
239
|
setChannelAudioNodes(audioContext) {
|
|
228
|
-
const { gainLeft, gainRight } = this.panToGain(
|
|
240
|
+
const { gainLeft, gainRight } = this.panToGain(this.constructor.channelSettings.pan);
|
|
229
241
|
const gainL = new GainNode(audioContext, { gain: gainLeft });
|
|
230
242
|
const gainR = new GainNode(audioContext, { gain: gainRight });
|
|
231
243
|
const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
|
|
232
244
|
gainL.connect(merger, 0, 0);
|
|
233
245
|
gainR.connect(merger, 0, 1);
|
|
234
|
-
|
|
235
|
-
const reverbEffect = this.createReverbEffect(audioContext);
|
|
246
|
+
const reverbEffect = this.options.reverbAlgorithm(audioContext);
|
|
236
247
|
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
248
|
return {
|
|
243
249
|
gainL,
|
|
244
250
|
gainR,
|
|
251
|
+
merger,
|
|
245
252
|
reverbEffect,
|
|
246
253
|
chorusEffect,
|
|
247
254
|
};
|
|
@@ -249,16 +256,16 @@ export class Midy {
|
|
|
249
256
|
createChannels(audioContext) {
|
|
250
257
|
const channels = Array.from({ length: 16 }, () => {
|
|
251
258
|
return {
|
|
252
|
-
...
|
|
253
|
-
...
|
|
259
|
+
...this.constructor.channelSettings,
|
|
260
|
+
...this.constructor.effectSettings,
|
|
254
261
|
...this.setChannelAudioNodes(audioContext),
|
|
255
262
|
scheduledNotes: new Map(),
|
|
256
263
|
sostenutoNotes: new Map(),
|
|
257
264
|
polyphonicKeyPressure: {
|
|
258
|
-
...
|
|
265
|
+
...this.constructor.controllerDestinationSettings,
|
|
259
266
|
},
|
|
260
267
|
channelPressure: {
|
|
261
|
-
...
|
|
268
|
+
...this.constructor.controllerDestinationSettings,
|
|
262
269
|
},
|
|
263
270
|
};
|
|
264
271
|
});
|
|
@@ -589,8 +596,12 @@ export class Midy {
|
|
|
589
596
|
}
|
|
590
597
|
return noteList[0];
|
|
591
598
|
}
|
|
592
|
-
|
|
599
|
+
createConvolutionReverb(audioContext, options = {}) {
|
|
593
600
|
const { decay = 0.8, preDecay = 0, } = options;
|
|
601
|
+
const input = new GainNode(audioContext);
|
|
602
|
+
const output = new GainNode(audioContext);
|
|
603
|
+
const dryGain = new GainNode(audioContext);
|
|
604
|
+
const wetGain = new GainNode(audioContext);
|
|
594
605
|
const sampleRate = audioContext.sampleRate;
|
|
595
606
|
const length = sampleRate * decay;
|
|
596
607
|
const impulse = new AudioBuffer({
|
|
@@ -604,27 +615,82 @@ export class Midy {
|
|
|
604
615
|
for (let i = 0; i < preDecayLength; i++) {
|
|
605
616
|
channelData[i] = Math.random() * 2 - 1;
|
|
606
617
|
}
|
|
618
|
+
const attenuationFactor = 1 / (sampleRate * decay);
|
|
607
619
|
for (let i = preDecayLength; i < length; i++) {
|
|
608
|
-
const attenuation = Math.exp(-(i - preDecayLength)
|
|
620
|
+
const attenuation = Math.exp(-(i - preDecayLength) * attenuationFactor);
|
|
609
621
|
channelData[i] = (Math.random() * 2 - 1) * attenuation;
|
|
610
622
|
}
|
|
611
623
|
}
|
|
612
624
|
const convolverNode = new ConvolverNode(audioContext, {
|
|
613
625
|
buffer: impulse,
|
|
614
626
|
});
|
|
615
|
-
|
|
616
|
-
const wetGain = new GainNode(audioContext);
|
|
627
|
+
input.connect(convolverNode);
|
|
617
628
|
convolverNode.connect(wetGain);
|
|
629
|
+
wetGain.connect(output);
|
|
630
|
+
dryGain.connect(output);
|
|
618
631
|
return {
|
|
619
|
-
|
|
632
|
+
input,
|
|
633
|
+
output,
|
|
620
634
|
dryGain,
|
|
621
635
|
wetGain,
|
|
636
|
+
convolverNode,
|
|
622
637
|
};
|
|
623
638
|
}
|
|
639
|
+
createCombFilter(audioContext, input, delay, feedback) {
|
|
640
|
+
const delayNode = new DelayNode(audioContext, {
|
|
641
|
+
maxDelayTime: delay,
|
|
642
|
+
delayTime: delay,
|
|
643
|
+
});
|
|
644
|
+
const feedbackGain = new GainNode(audioContext, { gain: feedback });
|
|
645
|
+
input.connect(delayNode);
|
|
646
|
+
delayNode.connect(feedbackGain);
|
|
647
|
+
feedbackGain.connect(delayNode);
|
|
648
|
+
return delayNode;
|
|
649
|
+
}
|
|
650
|
+
createAllpassFilter(audioContext, input, delay, feedback) {
|
|
651
|
+
const delayNode = new DelayNode(audioContext, {
|
|
652
|
+
maxDelayTime: delay,
|
|
653
|
+
delayTime: delay,
|
|
654
|
+
});
|
|
655
|
+
const feedbackGain = new GainNode(audioContext, { gain: feedback });
|
|
656
|
+
const passGain = new GainNode(audioContext, { gain: 1 - feedback });
|
|
657
|
+
input.connect(delayNode);
|
|
658
|
+
delayNode.connect(feedbackGain);
|
|
659
|
+
feedbackGain.connect(delayNode);
|
|
660
|
+
delayNode.connect(passGain);
|
|
661
|
+
return passGain;
|
|
662
|
+
}
|
|
663
|
+
// https://hajim.rochester.edu/ece/sites/zduan/teaching/ece472/reading/Schroeder_1962.pdf
|
|
664
|
+
// M.R.Schroeder, "Natural Sounding Artificial Reverberation", J.Audio Eng. Soc., vol.10, p.219, 1962
|
|
665
|
+
createSchroederReverb(audioContext, options = {}) {
|
|
666
|
+
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;
|
|
667
|
+
const input = new GainNode(audioContext);
|
|
668
|
+
const output = new GainNode(audioContext);
|
|
669
|
+
const mergerGain = new GainNode(audioContext, {
|
|
670
|
+
gain: 1 / (combDelays.length * 2),
|
|
671
|
+
});
|
|
672
|
+
const dryGain = new GainNode(audioContext, { gain: 1 - mix });
|
|
673
|
+
const wetGain = new GainNode(audioContext, { gain: mix });
|
|
674
|
+
for (let i = 0; i < combDelays.length; i++) {
|
|
675
|
+
const comb = this.createCombFilter(audioContext, input, combDelays[i], combFeedbacks[i]);
|
|
676
|
+
comb.connect(mergerGain);
|
|
677
|
+
}
|
|
678
|
+
const allpasses = [];
|
|
679
|
+
for (let i = 0; i < allpassDelays.length; i++) {
|
|
680
|
+
const allpass = this.createAllpassFilter(audioContext, (i === 0) ? mergerGain : allpasses.at(-1), allpassDelays[i], allpassFeedbacks[i]);
|
|
681
|
+
allpasses.push(allpass);
|
|
682
|
+
}
|
|
683
|
+
allpasses.at(-1).connect(wetGain);
|
|
684
|
+
input.connect(dryGain);
|
|
685
|
+
dryGain.connect(output);
|
|
686
|
+
wetGain.connect(output);
|
|
687
|
+
return { input, output, dryGain, wetGain };
|
|
688
|
+
}
|
|
624
689
|
createChorusEffect(audioContext, options = {}) {
|
|
625
690
|
const { chorusCount = 2, chorusRate = 0.6, chorusDepth = 0.15, delay = 0.01, variance = delay * 0.1, } = options;
|
|
626
691
|
const lfo = new OscillatorNode(audioContext, { frequency: chorusRate });
|
|
627
692
|
const lfoGain = new GainNode(audioContext, { gain: chorusDepth });
|
|
693
|
+
const output = new GainNode(audioContext);
|
|
628
694
|
const chorusGains = [];
|
|
629
695
|
const delayNodes = [];
|
|
630
696
|
const baseGain = 1 / chorusCount;
|
|
@@ -634,50 +700,47 @@ export class Midy {
|
|
|
634
700
|
const delayNode = new DelayNode(audioContext, {
|
|
635
701
|
maxDelayTime: delayTime,
|
|
636
702
|
});
|
|
637
|
-
delayNodes.push(delayNode);
|
|
638
703
|
const chorusGain = new GainNode(audioContext, { gain: baseGain });
|
|
704
|
+
delayNodes.push(delayNode);
|
|
639
705
|
chorusGains.push(chorusGain);
|
|
640
|
-
lfo.connect(lfoGain);
|
|
641
706
|
lfoGain.connect(delayNode.delayTime);
|
|
642
707
|
delayNode.connect(chorusGain);
|
|
708
|
+
chorusGain.connect(output);
|
|
643
709
|
}
|
|
710
|
+
lfo.connect(lfoGain);
|
|
711
|
+
lfo.start();
|
|
644
712
|
return {
|
|
645
713
|
lfo,
|
|
646
714
|
lfoGain,
|
|
647
715
|
delayNodes,
|
|
648
716
|
chorusGains,
|
|
717
|
+
output,
|
|
649
718
|
};
|
|
650
719
|
}
|
|
651
|
-
|
|
720
|
+
connectEffects(channel, gainNode) {
|
|
721
|
+
gainNode.connect(channel.merger);
|
|
652
722
|
if (channel.reverb === 0) {
|
|
653
723
|
if (channel.chorus === 0) { // no effect
|
|
654
|
-
|
|
655
|
-
gainNode.connect(channel.gainR);
|
|
724
|
+
channel.merger.connect(this.masterGain);
|
|
656
725
|
}
|
|
657
726
|
else { // chorus
|
|
658
727
|
channel.chorusEffect.delayNodes.forEach((delayNode) => {
|
|
659
|
-
|
|
660
|
-
});
|
|
661
|
-
channel.chorusEffect.chorusGains.forEach((chorusGain) => {
|
|
662
|
-
chorusGain.connect(channel.gainL);
|
|
663
|
-
chorusGain.connect(channel.gainR);
|
|
728
|
+
channel.merger.connect(delayNode);
|
|
664
729
|
});
|
|
730
|
+
channel.chorusEffect.output.connect(this.masterGain);
|
|
665
731
|
}
|
|
666
732
|
}
|
|
667
733
|
else {
|
|
668
734
|
if (channel.chorus === 0) { // reverb
|
|
669
|
-
|
|
670
|
-
|
|
735
|
+
channel.merger.connect(channel.reverbEffect.input);
|
|
736
|
+
channel.reverbEffect.output.connect(this.masterGain);
|
|
671
737
|
}
|
|
672
738
|
else { // reverb + chorus
|
|
673
|
-
gainNode.connect(channel.reverbEffect.convolverNode);
|
|
674
|
-
gainNode.connect(channel.reverbEffect.dryGain);
|
|
675
739
|
channel.chorusEffect.delayNodes.forEach((delayNode) => {
|
|
676
|
-
|
|
677
|
-
});
|
|
678
|
-
channel.chorusEffect.chorusGains.forEach((chorusGain) => {
|
|
679
|
-
chorusGain.connect(channel.reverbEffect.convolverNode);
|
|
740
|
+
channel.merger.connect(delayNode);
|
|
680
741
|
});
|
|
742
|
+
channel.merger.connect(channel.reverbEffect.input);
|
|
743
|
+
channel.reverbEffect.output.connect(this.masterGain);
|
|
681
744
|
}
|
|
682
745
|
}
|
|
683
746
|
}
|
|
@@ -748,7 +811,7 @@ export class Midy {
|
|
|
748
811
|
startModulation(channel, note, time) {
|
|
749
812
|
const { instrumentKey } = note;
|
|
750
813
|
note.modLFOGain = new GainNode(this.audioContext, {
|
|
751
|
-
gain: this.cbToRatio(instrumentKey.modLfoToVolume
|
|
814
|
+
gain: this.cbToRatio(instrumentKey.modLfoToVolume + channel.modulation),
|
|
752
815
|
});
|
|
753
816
|
note.modLFO = new OscillatorNode(this.audioContext, {
|
|
754
817
|
frequency: this.centToHz(instrumentKey.freqModLFO),
|
|
@@ -817,7 +880,7 @@ export class Midy {
|
|
|
817
880
|
if (!instrumentKey)
|
|
818
881
|
return;
|
|
819
882
|
const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
|
|
820
|
-
this.
|
|
883
|
+
this.connectEffects(channel, note.gainNode);
|
|
821
884
|
if (channel.sostenutoPedal) {
|
|
822
885
|
channel.sostenutoNotes.set(noteNumber, note);
|
|
823
886
|
}
|
|
@@ -841,46 +904,48 @@ export class Midy {
|
|
|
841
904
|
return;
|
|
842
905
|
if (!channel.scheduledNotes.has(noteNumber))
|
|
843
906
|
return;
|
|
844
|
-
const
|
|
845
|
-
for (let i = 0; i <
|
|
846
|
-
const
|
|
847
|
-
if (!
|
|
907
|
+
const scheduledNotes = channel.scheduledNotes.get(noteNumber);
|
|
908
|
+
for (let i = 0; i < scheduledNotes.length; i++) {
|
|
909
|
+
const note = scheduledNotes[i];
|
|
910
|
+
if (!note)
|
|
848
911
|
continue;
|
|
849
|
-
if (
|
|
912
|
+
if (note.ending)
|
|
850
913
|
continue;
|
|
851
|
-
const { bufferSource, filterNode, gainNode, modLFO, modLFOGain, vibLFO, vibLFOGain, instrumentKey, } = targetNote;
|
|
852
914
|
const velocityRate = (velocity + 127) / 127;
|
|
853
|
-
const volEndTime = stopTime +
|
|
854
|
-
|
|
855
|
-
gainNode.gain
|
|
915
|
+
const volEndTime = stopTime +
|
|
916
|
+
note.instrumentKey.volRelease * velocityRate;
|
|
917
|
+
note.gainNode.gain
|
|
918
|
+
.cancelScheduledValues(stopTime)
|
|
919
|
+
.linearRampToValueAtTime(0, volEndTime);
|
|
856
920
|
const maxFreq = this.audioContext.sampleRate / 2;
|
|
857
|
-
const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
|
|
921
|
+
const baseFreq = this.centToHz(note.instrumentKey.initialFilterFc);
|
|
858
922
|
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
859
|
-
const modEndTime = stopTime +
|
|
860
|
-
|
|
923
|
+
const modEndTime = stopTime +
|
|
924
|
+
note.instrumentKey.modRelease * velocityRate;
|
|
925
|
+
note.filterNode.frequency
|
|
861
926
|
.cancelScheduledValues(stopTime)
|
|
862
927
|
.linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
|
|
863
|
-
|
|
928
|
+
note.ending = true;
|
|
864
929
|
this.scheduleTask(() => {
|
|
865
|
-
bufferSource.loop = false;
|
|
930
|
+
note.bufferSource.loop = false;
|
|
866
931
|
}, stopTime);
|
|
867
932
|
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();
|
|
933
|
+
note.bufferSource.onended = () => {
|
|
934
|
+
scheduledNotes[i] = null;
|
|
935
|
+
note.bufferSource.disconnect();
|
|
936
|
+
note.filterNode.disconnect();
|
|
937
|
+
note.gainNode.disconnect();
|
|
938
|
+
if (note.modLFOGain)
|
|
939
|
+
note.modLFOGain.disconnect();
|
|
940
|
+
if (note.vibLFOGain)
|
|
941
|
+
note.vibLFOGain.disconnect();
|
|
942
|
+
if (note.modLFO)
|
|
943
|
+
note.modLFO.stop();
|
|
944
|
+
if (note.vibLFO)
|
|
945
|
+
note.vibLFO.stop();
|
|
881
946
|
resolve();
|
|
882
947
|
};
|
|
883
|
-
bufferSource.stop(volEndTime);
|
|
948
|
+
note.bufferSource.stop(volEndTime);
|
|
884
949
|
});
|
|
885
950
|
}
|
|
886
951
|
}
|
|
@@ -995,7 +1060,7 @@ export class Midy {
|
|
|
995
1060
|
case 5:
|
|
996
1061
|
return this.setPortamentoTime(channelNumber, value);
|
|
997
1062
|
case 6:
|
|
998
|
-
return this.
|
|
1063
|
+
return this.dataEntryMSB(channelNumber, value);
|
|
999
1064
|
case 7:
|
|
1000
1065
|
return this.setVolume(channelNumber, value);
|
|
1001
1066
|
case 10:
|
|
@@ -1005,7 +1070,7 @@ export class Midy {
|
|
|
1005
1070
|
case 32:
|
|
1006
1071
|
return this.setBankLSB(channelNumber, value);
|
|
1007
1072
|
case 38:
|
|
1008
|
-
return this.
|
|
1073
|
+
return this.dataEntryLSB(channelNumber, value);
|
|
1009
1074
|
case 64:
|
|
1010
1075
|
return this.setSustainPedal(channelNumber, value);
|
|
1011
1076
|
case 65:
|
|
@@ -1022,13 +1087,13 @@ export class Midy {
|
|
|
1022
1087
|
case 78:
|
|
1023
1088
|
return this.setVibratoDelay(channelNumber, value);
|
|
1024
1089
|
case 91:
|
|
1025
|
-
return this.
|
|
1090
|
+
return this.setReverbSendLevel(channelNumber, value);
|
|
1026
1091
|
case 93:
|
|
1027
|
-
return this.
|
|
1092
|
+
return this.setChorusSendLevel(channelNumber, value);
|
|
1028
1093
|
case 96: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp18.pdf
|
|
1029
|
-
return
|
|
1094
|
+
return this.dataIncrement(channelNumber);
|
|
1030
1095
|
case 97: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp18.pdf
|
|
1031
|
-
return
|
|
1096
|
+
return this.dataDecrement(channelNumber);
|
|
1032
1097
|
case 100:
|
|
1033
1098
|
return this.setRPNLSB(channelNumber, value);
|
|
1034
1099
|
case 101:
|
|
@@ -1054,22 +1119,24 @@ export class Midy {
|
|
|
1054
1119
|
setBankMSB(channelNumber, msb) {
|
|
1055
1120
|
this.channels[channelNumber].bankMSB = msb;
|
|
1056
1121
|
}
|
|
1057
|
-
|
|
1122
|
+
updateModulation(channel) {
|
|
1058
1123
|
const now = this.audioContext.currentTime;
|
|
1059
|
-
const channel = this.channels[channelNumber];
|
|
1060
|
-
channel.modulation = (modulation / 127) *
|
|
1061
|
-
(channel.modulationDepthRange * 100);
|
|
1062
1124
|
const activeNotes = this.getActiveNotes(channel, now);
|
|
1063
1125
|
activeNotes.forEach((activeNote) => {
|
|
1064
1126
|
if (activeNote.modLFO) {
|
|
1065
|
-
|
|
1066
|
-
|
|
1127
|
+
const { gainNode, instrumentKey } = activeNote;
|
|
1128
|
+
gainNode.gain.setValueAtTime(this.cbToRatio(instrumentKey.modLfoToVolume + channel.modulation), now);
|
|
1067
1129
|
}
|
|
1068
1130
|
else {
|
|
1069
1131
|
this.startModulation(channel, activeNote, now);
|
|
1070
1132
|
}
|
|
1071
1133
|
});
|
|
1072
1134
|
}
|
|
1135
|
+
setModulation(channelNumber, modulation) {
|
|
1136
|
+
const channel = this.channels[channelNumber];
|
|
1137
|
+
channel.modulation = (modulation / 127) * channel.modulationDepthRange;
|
|
1138
|
+
this.updateModulation(channel);
|
|
1139
|
+
}
|
|
1073
1140
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
1074
1141
|
this.channels[channelNumber].portamentoTime = portamentoTime / 127;
|
|
1075
1142
|
}
|
|
@@ -1098,6 +1165,10 @@ export class Midy {
|
|
|
1098
1165
|
setBankLSB(channelNumber, lsb) {
|
|
1099
1166
|
this.channels[channelNumber].bankLSB = lsb;
|
|
1100
1167
|
}
|
|
1168
|
+
dataEntryLSB(channelNumber, value) {
|
|
1169
|
+
this.channels[channelNumber].dataLSB = value;
|
|
1170
|
+
this.handleRPN(channelNumber, 0);
|
|
1171
|
+
}
|
|
1101
1172
|
updateChannelGain(channel) {
|
|
1102
1173
|
const now = this.audioContext.currentTime;
|
|
1103
1174
|
const volume = channel.volume * channel.expression;
|
|
@@ -1119,7 +1190,7 @@ export class Midy {
|
|
|
1119
1190
|
setPortamento(channelNumber, value) {
|
|
1120
1191
|
this.channels[channelNumber].portamento = value >= 64;
|
|
1121
1192
|
}
|
|
1122
|
-
|
|
1193
|
+
setReverbSendLevel(channelNumber, reverb) {
|
|
1123
1194
|
const now = this.audioContext.currentTime;
|
|
1124
1195
|
const channel = this.channels[channelNumber];
|
|
1125
1196
|
const reverbEffect = channel.reverbEffect;
|
|
@@ -1129,7 +1200,7 @@ export class Midy {
|
|
|
1129
1200
|
reverbEffect.wetGain.gain.cancelScheduledValues(now);
|
|
1130
1201
|
reverbEffect.wetGain.gain.setValueAtTime(channel.reverb, now);
|
|
1131
1202
|
}
|
|
1132
|
-
|
|
1203
|
+
setChorusSendLevel(channelNumber, chorus) {
|
|
1133
1204
|
const channel = this.channels[channelNumber];
|
|
1134
1205
|
channel.chorus = chorus / 127;
|
|
1135
1206
|
channel.chorusEffect.lfoGain = channel.chorus;
|
|
@@ -1195,31 +1266,34 @@ export class Midy {
|
|
|
1195
1266
|
channel.dataMSB = minMSB;
|
|
1196
1267
|
}
|
|
1197
1268
|
}
|
|
1198
|
-
// TODO: support 3-4?
|
|
1199
1269
|
handleRPN(channelNumber, value) {
|
|
1200
1270
|
const channel = this.channels[channelNumber];
|
|
1201
1271
|
const rpn = channel.rpnMSB * 128 + channel.rpnLSB;
|
|
1202
1272
|
switch (rpn) {
|
|
1203
1273
|
case 0:
|
|
1204
1274
|
channel.dataLSB += value;
|
|
1205
|
-
this.
|
|
1275
|
+
this.handlePitchBendRangeRPN(channelNumber);
|
|
1206
1276
|
break;
|
|
1207
1277
|
case 1:
|
|
1208
1278
|
channel.dataLSB += value;
|
|
1209
|
-
this.
|
|
1279
|
+
this.handleFineTuningRPN(channelNumber);
|
|
1210
1280
|
break;
|
|
1211
1281
|
case 2:
|
|
1212
1282
|
channel.dataMSB += value;
|
|
1213
|
-
this.
|
|
1283
|
+
this.handleCoarseTuningRPN(channelNumber);
|
|
1284
|
+
break;
|
|
1285
|
+
case 5:
|
|
1286
|
+
channel.dataLSB += value;
|
|
1287
|
+
this.handleModulationDepthRangeRPN(channelNumber);
|
|
1214
1288
|
break;
|
|
1215
1289
|
default:
|
|
1216
1290
|
console.warn(`Channel ${channelNumber}: Unsupported RPN MSB=${channel.rpnMSB} LSB=${channel.rpnLSB}`);
|
|
1217
1291
|
}
|
|
1218
1292
|
}
|
|
1219
|
-
|
|
1293
|
+
dataIncrement(channelNumber) {
|
|
1220
1294
|
this.handleRPN(channelNumber, 1);
|
|
1221
1295
|
}
|
|
1222
|
-
|
|
1296
|
+
dataDecrement(channelNumber) {
|
|
1223
1297
|
this.handleRPN(channelNumber, -1);
|
|
1224
1298
|
}
|
|
1225
1299
|
setRPNMSB(channelNumber, value) {
|
|
@@ -1228,9 +1302,8 @@ export class Midy {
|
|
|
1228
1302
|
setRPNLSB(channelNumber, value) {
|
|
1229
1303
|
this.channels[channelNumber].rpnLSB = value;
|
|
1230
1304
|
}
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
isMSB ? channel.dataMSB = value : channel.dataLSB = value;
|
|
1305
|
+
dataEntryMSB(channelNumber, value) {
|
|
1306
|
+
this.channels[channelNumber].dataMSB = value;
|
|
1234
1307
|
this.handleRPN(channelNumber, 0);
|
|
1235
1308
|
}
|
|
1236
1309
|
updateDetune(channel, detuneChange) {
|
|
@@ -1244,7 +1317,7 @@ export class Midy {
|
|
|
1244
1317
|
.setValueAtTime(detune, now);
|
|
1245
1318
|
});
|
|
1246
1319
|
}
|
|
1247
|
-
|
|
1320
|
+
handlePitchBendRangeRPN(channelNumber) {
|
|
1248
1321
|
const channel = this.channels[channelNumber];
|
|
1249
1322
|
this.limitData(channel, 0, 127, 0, 99);
|
|
1250
1323
|
const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
|
|
@@ -1258,7 +1331,7 @@ export class Midy {
|
|
|
1258
1331
|
channel.pitchBend * 100;
|
|
1259
1332
|
this.updateDetune(channel, detuneChange);
|
|
1260
1333
|
}
|
|
1261
|
-
|
|
1334
|
+
handleFineTuningRPN(channelNumber) {
|
|
1262
1335
|
const channel = this.channels[channelNumber];
|
|
1263
1336
|
this.limitData(channel, 0, 127, 0, 127);
|
|
1264
1337
|
const fineTuning = (channel.dataMSB * 128 + channel.dataLSB - 8192) / 8192;
|
|
@@ -1266,9 +1339,12 @@ export class Midy {
|
|
|
1266
1339
|
}
|
|
1267
1340
|
setFineTuning(channelNumber, fineTuning) {
|
|
1268
1341
|
const channel = this.channels[channelNumber];
|
|
1342
|
+
const prevFineTuning = channel.fineTuning;
|
|
1269
1343
|
channel.fineTuning = fineTuning;
|
|
1344
|
+
const detuneChange = channel.fineTuning - prevFineTuning;
|
|
1345
|
+
this.updateDetune(channel, detuneChange);
|
|
1270
1346
|
}
|
|
1271
|
-
|
|
1347
|
+
handleCoarseTuningRPN(channelNumber) {
|
|
1272
1348
|
const channel = this.channels[channelNumber];
|
|
1273
1349
|
this.limitDataMSB(channel, 0, 127);
|
|
1274
1350
|
const coarseTuning = channel.dataMSB - 64;
|
|
@@ -1276,7 +1352,22 @@ export class Midy {
|
|
|
1276
1352
|
}
|
|
1277
1353
|
setCoarseTuning(channelNumber, coarseTuning) {
|
|
1278
1354
|
const channel = this.channels[channelNumber];
|
|
1279
|
-
|
|
1355
|
+
const prevCoarseTuning = channel.coarseTuning;
|
|
1356
|
+
channel.coarseTuning = coarseTuning;
|
|
1357
|
+
const detuneChange = channel.coarseTuning - prevCoarseTuning;
|
|
1358
|
+
this.updateDetune(channel, detuneChange);
|
|
1359
|
+
}
|
|
1360
|
+
handleModulationDepthRangeRPN(channelNumber) {
|
|
1361
|
+
const channel = this.channels[channelNumber];
|
|
1362
|
+
this.limitData(channel, 0, 127, 0, 127);
|
|
1363
|
+
const modulationDepthRange = dataMSB + dataLSB / 128;
|
|
1364
|
+
this.setModulationDepthRange(channelNumber, modulationDepthRange);
|
|
1365
|
+
}
|
|
1366
|
+
setModulationDepthRange(channelNumber, modulationDepthRange) {
|
|
1367
|
+
const channel = this.channels[channelNumber];
|
|
1368
|
+
channel.modulationDepthRange = modulationDepthRange;
|
|
1369
|
+
channel.modulation = (modulation / 127) * channel.modulationDepthRange;
|
|
1370
|
+
this.updateModulation(channel);
|
|
1280
1371
|
}
|
|
1281
1372
|
allSoundOff(channelNumber) {
|
|
1282
1373
|
const now = this.audioContext.currentTime;
|
|
@@ -1367,9 +1458,9 @@ export class Midy {
|
|
|
1367
1458
|
switch (data[3]) {
|
|
1368
1459
|
case 1:
|
|
1369
1460
|
return this.handleMasterVolumeSysEx(data);
|
|
1370
|
-
case 3:
|
|
1461
|
+
case 3: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
|
|
1371
1462
|
return this.handleMasterFineTuningSysEx(data);
|
|
1372
|
-
case 4:
|
|
1463
|
+
case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
|
|
1373
1464
|
return this.handleMasterCoarseTuningSysEx(data);
|
|
1374
1465
|
// case 5: // TODO: Global Parameter Control
|
|
1375
1466
|
default:
|
|
@@ -1494,9 +1585,9 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
1494
1585
|
dataLSB: 0,
|
|
1495
1586
|
program: 0,
|
|
1496
1587
|
pitchBend: 0,
|
|
1497
|
-
fineTuning: 0,
|
|
1498
|
-
coarseTuning: 0,
|
|
1499
|
-
modulationDepthRange: 0.5,
|
|
1588
|
+
fineTuning: 0, // cb
|
|
1589
|
+
coarseTuning: 0, // cb
|
|
1590
|
+
modulationDepthRange: 0.5, // cb
|
|
1500
1591
|
}
|
|
1501
1592
|
});
|
|
1502
1593
|
Object.defineProperty(Midy, "effectSettings", {
|