@marmooo/midy 0.1.1 → 0.1.3
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/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.4/+esm.d.ts +153 -0
- package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.4/+esm.d.ts.map +1 -0
- package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/{soundfont-parser@0.0.2 → soundfont-parser@0.0.4}/+esm.js +73 -66
- package/esm/midy-GM1.d.ts +18 -14
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +133 -110
- package/esm/midy-GM2.d.ts +36 -30
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +190 -158
- package/esm/midy-GMLite.d.ts +16 -14
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +123 -112
- package/esm/midy.d.ts +33 -31
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +191 -185
- package/package.json +1 -1
- package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.4/+esm.d.ts +153 -0
- package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.4/+esm.d.ts.map +1 -0
- package/script/deps/cdn.jsdelivr.net/npm/@marmooo/{soundfont-parser@0.0.2 → soundfont-parser@0.0.4}/+esm.js +75 -68
- package/script/midy-GM1.d.ts +18 -14
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +133 -110
- package/script/midy-GM2.d.ts +36 -30
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +190 -158
- package/script/midy-GMLite.d.ts +16 -14
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +123 -112
- package/script/midy.d.ts +33 -31
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +191 -185
- package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.2/+esm.d.ts +0 -135
- package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.2/+esm.d.ts.map +0 -1
- package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.2/+esm.d.ts +0 -135
- package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.2/+esm.d.ts.map +0 -1
package/script/midy.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Midy = void 0;
|
|
4
4
|
const _esm_js_1 = require("./deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.js");
|
|
5
|
-
const _esm_js_2 = require("./deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.
|
|
5
|
+
const _esm_js_2 = require("./deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.4/+esm.js");
|
|
6
6
|
class Note {
|
|
7
7
|
constructor(noteNumber, velocity, startTime, instrumentKey) {
|
|
8
8
|
Object.defineProperty(this, "bufferSource", {
|
|
@@ -11,37 +11,43 @@ class Note {
|
|
|
11
11
|
writable: true,
|
|
12
12
|
value: void 0
|
|
13
13
|
});
|
|
14
|
-
Object.defineProperty(this, "
|
|
14
|
+
Object.defineProperty(this, "filterNode", {
|
|
15
15
|
enumerable: true,
|
|
16
16
|
configurable: true,
|
|
17
17
|
writable: true,
|
|
18
18
|
value: void 0
|
|
19
19
|
});
|
|
20
|
-
Object.defineProperty(this, "
|
|
20
|
+
Object.defineProperty(this, "volumeNode", {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
configurable: true,
|
|
23
|
+
writable: true,
|
|
24
|
+
value: void 0
|
|
25
|
+
});
|
|
26
|
+
Object.defineProperty(this, "volumeDepth", {
|
|
21
27
|
enumerable: true,
|
|
22
28
|
configurable: true,
|
|
23
29
|
writable: true,
|
|
24
30
|
value: void 0
|
|
25
31
|
});
|
|
26
|
-
Object.defineProperty(this, "
|
|
32
|
+
Object.defineProperty(this, "modulationLFO", {
|
|
27
33
|
enumerable: true,
|
|
28
34
|
configurable: true,
|
|
29
35
|
writable: true,
|
|
30
36
|
value: void 0
|
|
31
37
|
});
|
|
32
|
-
Object.defineProperty(this, "
|
|
38
|
+
Object.defineProperty(this, "modulationDepth", {
|
|
33
39
|
enumerable: true,
|
|
34
40
|
configurable: true,
|
|
35
41
|
writable: true,
|
|
36
42
|
value: void 0
|
|
37
43
|
});
|
|
38
|
-
Object.defineProperty(this, "
|
|
44
|
+
Object.defineProperty(this, "vibratoLFO", {
|
|
39
45
|
enumerable: true,
|
|
40
46
|
configurable: true,
|
|
41
47
|
writable: true,
|
|
42
48
|
value: void 0
|
|
43
49
|
});
|
|
44
|
-
Object.defineProperty(this, "
|
|
50
|
+
Object.defineProperty(this, "vibratoDepth", {
|
|
45
51
|
enumerable: true,
|
|
46
52
|
configurable: true,
|
|
47
53
|
writable: true,
|
|
@@ -85,7 +91,7 @@ class Midy {
|
|
|
85
91
|
writable: true,
|
|
86
92
|
value: {
|
|
87
93
|
time: this.getReverbTime(64),
|
|
88
|
-
feedback: 0.
|
|
94
|
+
feedback: 0.8,
|
|
89
95
|
}
|
|
90
96
|
});
|
|
91
97
|
Object.defineProperty(this, "chorus", {
|
|
@@ -227,8 +233,12 @@ class Midy {
|
|
|
227
233
|
this.audioContext = audioContext;
|
|
228
234
|
this.options = { ...this.defaultOptions, ...options };
|
|
229
235
|
this.masterGain = new GainNode(audioContext);
|
|
230
|
-
this.masterGain.connect(audioContext.destination);
|
|
231
236
|
this.channels = this.createChannels(audioContext);
|
|
237
|
+
this.reverbEffect = this.options.reverbAlgorithm(audioContext);
|
|
238
|
+
this.chorusEffect = this.createChorusEffect(audioContext);
|
|
239
|
+
this.chorusEffect.output.connect(this.masterGain);
|
|
240
|
+
this.reverbEffect.output.connect(this.masterGain);
|
|
241
|
+
this.masterGain.connect(audioContext.destination);
|
|
232
242
|
this.GM2SystemOn();
|
|
233
243
|
}
|
|
234
244
|
initSoundFontTable() {
|
|
@@ -272,14 +282,11 @@ class Midy {
|
|
|
272
282
|
const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
|
|
273
283
|
gainL.connect(merger, 0, 0);
|
|
274
284
|
gainR.connect(merger, 0, 1);
|
|
275
|
-
|
|
276
|
-
const chorusEffect = this.createChorusEffect(audioContext);
|
|
285
|
+
merger.connect(this.masterGain);
|
|
277
286
|
return {
|
|
278
287
|
gainL,
|
|
279
288
|
gainR,
|
|
280
289
|
merger,
|
|
281
|
-
reverbEffect,
|
|
282
|
-
chorusEffect,
|
|
283
290
|
};
|
|
284
291
|
}
|
|
285
292
|
createChannels(audioContext) {
|
|
@@ -412,7 +419,7 @@ class Midy {
|
|
|
412
419
|
const t = this.audioContext.currentTime + offset;
|
|
413
420
|
queueIndex = await this.scheduleTimelineEvents(t, offset, queueIndex);
|
|
414
421
|
if (this.isPausing) {
|
|
415
|
-
await this.stopNotes();
|
|
422
|
+
await this.stopNotes(0, true);
|
|
416
423
|
this.notePromises = [];
|
|
417
424
|
resolve();
|
|
418
425
|
this.isPausing = false;
|
|
@@ -420,7 +427,7 @@ class Midy {
|
|
|
420
427
|
return;
|
|
421
428
|
}
|
|
422
429
|
else if (this.isStopping) {
|
|
423
|
-
await this.stopNotes();
|
|
430
|
+
await this.stopNotes(0, true);
|
|
424
431
|
this.notePromises = [];
|
|
425
432
|
resolve();
|
|
426
433
|
this.isStopping = false;
|
|
@@ -428,7 +435,7 @@ class Midy {
|
|
|
428
435
|
return;
|
|
429
436
|
}
|
|
430
437
|
else if (this.isSeeking) {
|
|
431
|
-
this.stopNotes();
|
|
438
|
+
this.stopNotes(0, true);
|
|
432
439
|
this.startTime = this.audioContext.currentTime;
|
|
433
440
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
434
441
|
offset = this.resumeTime - this.startTime;
|
|
@@ -543,21 +550,24 @@ class Midy {
|
|
|
543
550
|
}
|
|
544
551
|
return { instruments, timeline };
|
|
545
552
|
}
|
|
546
|
-
|
|
553
|
+
async stopChannelNotes(channelNumber, velocity, stopPedal) {
|
|
547
554
|
const now = this.audioContext.currentTime;
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
this.notePromises.push(promise);
|
|
556
|
-
}
|
|
557
|
-
});
|
|
555
|
+
const channel = this.channels[channelNumber];
|
|
556
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
557
|
+
noteList.forEach((note) => {
|
|
558
|
+
if (note) {
|
|
559
|
+
const promise = this.scheduleNoteRelease(channelNumber, note.noteNumber, velocity, now, stopPedal);
|
|
560
|
+
this.notePromises.push(promise);
|
|
561
|
+
}
|
|
558
562
|
});
|
|
559
|
-
channel.scheduledNotes.clear();
|
|
560
563
|
});
|
|
564
|
+
channel.scheduledNotes.clear();
|
|
565
|
+
await Promise.all(this.notePromises);
|
|
566
|
+
}
|
|
567
|
+
stopNotes(velocity, stopPedal) {
|
|
568
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
569
|
+
this.stopChannelNotes(i, velocity, stopPedal);
|
|
570
|
+
}
|
|
561
571
|
return Promise.all(this.notePromises);
|
|
562
572
|
}
|
|
563
573
|
async start() {
|
|
@@ -695,7 +705,7 @@ class Midy {
|
|
|
695
705
|
}
|
|
696
706
|
// https://hajim.rochester.edu/ece/sites/zduan/teaching/ece472/reading/Schroeder_1962.pdf
|
|
697
707
|
// M.R.Schroeder, "Natural Sounding Artificial Reverberation", J.Audio Eng. Soc., vol.10, p.219, 1962
|
|
698
|
-
createSchroederReverb(audioContext, combDelays,
|
|
708
|
+
createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays) {
|
|
699
709
|
const input = new GainNode(audioContext);
|
|
700
710
|
const output = new GainNode(audioContext);
|
|
701
711
|
const mergerGain = new GainNode(audioContext);
|
|
@@ -754,21 +764,6 @@ class Midy {
|
|
|
754
764
|
feedbackGains,
|
|
755
765
|
};
|
|
756
766
|
}
|
|
757
|
-
connectEffects(channel, gainNode) {
|
|
758
|
-
gainNode.connect(channel.merger);
|
|
759
|
-
channel.merger.connect(this.masterGain);
|
|
760
|
-
if (0 < channel.reverbSendLevel) {
|
|
761
|
-
channel.merger.connect(channel.reverbEffect.input);
|
|
762
|
-
channel.reverbEffect.output.connect(this.masterGain);
|
|
763
|
-
}
|
|
764
|
-
if (0 < channel.chorusSendLevel) {
|
|
765
|
-
channel.merger.connect(channel.chorusEffect.input);
|
|
766
|
-
channel.reverbEffect.output.connect(this.masterGain);
|
|
767
|
-
}
|
|
768
|
-
if (0 < this.chorus.sendToReverb) {
|
|
769
|
-
channel.chorusEffect.sendGain.connect(channel.reverbEffect.input);
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
767
|
cbToRatio(cb) {
|
|
773
768
|
return Math.pow(10, cb / 200);
|
|
774
769
|
}
|
|
@@ -785,42 +780,56 @@ class Midy {
|
|
|
785
780
|
return instrumentKey.playbackRate(noteNumber) *
|
|
786
781
|
Math.pow(2, semitoneOffset / 12);
|
|
787
782
|
}
|
|
788
|
-
setVolumeEnvelope(
|
|
789
|
-
const { instrumentKey, startTime
|
|
790
|
-
note.
|
|
791
|
-
|
|
792
|
-
if (volume === 0)
|
|
793
|
-
volume = 1e-6; // exponentialRampToValueAtTime() requires a non-zero value
|
|
794
|
-
const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation) *
|
|
795
|
-
volume;
|
|
783
|
+
setVolumeEnvelope(note) {
|
|
784
|
+
const { instrumentKey, startTime } = note;
|
|
785
|
+
note.volumeNode = new GainNode(this.audioContext, { gain: 0 });
|
|
786
|
+
const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
|
|
796
787
|
const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
|
|
797
788
|
const volDelay = startTime + instrumentKey.volDelay;
|
|
798
789
|
const volAttack = volDelay + instrumentKey.volAttack;
|
|
799
790
|
const volHold = volAttack + instrumentKey.volHold;
|
|
800
791
|
const volDecay = volHold + instrumentKey.volDecay;
|
|
801
|
-
note.
|
|
792
|
+
note.volumeNode.gain
|
|
802
793
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
803
794
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
804
795
|
.setValueAtTime(attackVolume, volHold)
|
|
805
796
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
806
797
|
}
|
|
807
|
-
|
|
808
|
-
const { instrumentKey,
|
|
798
|
+
setPitch(note, semitoneOffset) {
|
|
799
|
+
const { instrumentKey, noteNumber, startTime } = note;
|
|
800
|
+
const modEnvToPitch = instrumentKey.modEnvToPitch / 100;
|
|
801
|
+
note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
|
|
802
|
+
if (modEnvToPitch === 0)
|
|
803
|
+
return;
|
|
804
|
+
const basePitch = note.bufferSource.playbackRate.value;
|
|
805
|
+
const peekPitch = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset + modEnvToPitch);
|
|
806
|
+
const modDelay = startTime + instrumentKey.modDelay;
|
|
807
|
+
const modAttack = modDelay + instrumentKey.modAttack;
|
|
808
|
+
const modHold = modAttack + instrumentKey.modHold;
|
|
809
|
+
const modDecay = modHold + instrumentKey.modDecay;
|
|
810
|
+
note.bufferSource.playbackRate.value
|
|
811
|
+
.setValueAtTime(basePitch, modDelay)
|
|
812
|
+
.exponentialRampToValueAtTime(peekPitch, modAttack)
|
|
813
|
+
.setValueAtTime(peekPitch, modHold)
|
|
814
|
+
.linearRampToValueAtTime(basePitch, modDecay);
|
|
815
|
+
}
|
|
816
|
+
setFilterNode(channel, note) {
|
|
817
|
+
const { instrumentKey, noteNumber, startTime } = note;
|
|
809
818
|
const softPedalFactor = 1 -
|
|
810
819
|
(0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
|
|
811
820
|
const maxFreq = this.audioContext.sampleRate / 2;
|
|
812
821
|
const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
|
|
813
822
|
softPedalFactor;
|
|
814
823
|
const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
|
|
815
|
-
const sustainFreq =
|
|
816
|
-
(peekFreq - baseFreq) * (1 - instrumentKey.modSustain)
|
|
824
|
+
const sustainFreq = baseFreq +
|
|
825
|
+
(peekFreq - baseFreq) * (1 - instrumentKey.modSustain);
|
|
826
|
+
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
827
|
+
const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
|
|
828
|
+
const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
|
|
817
829
|
const modDelay = startTime + instrumentKey.modDelay;
|
|
818
830
|
const modAttack = modDelay + instrumentKey.modAttack;
|
|
819
831
|
const modHold = modAttack + instrumentKey.modHold;
|
|
820
832
|
const modDecay = modHold + instrumentKey.modDecay;
|
|
821
|
-
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
822
|
-
const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
|
|
823
|
-
const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
|
|
824
833
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
825
834
|
type: "lowpass",
|
|
826
835
|
Q: instrumentKey.initialFilterQ / 10, // dB
|
|
@@ -831,56 +840,72 @@ class Midy {
|
|
|
831
840
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
832
841
|
.setValueAtTime(adjustedPeekFreq, modHold)
|
|
833
842
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
834
|
-
note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modEnvToPitch, modDelay);
|
|
835
843
|
}
|
|
836
|
-
startModulation(channel, note,
|
|
844
|
+
startModulation(channel, note, startTime) {
|
|
837
845
|
const { instrumentKey } = note;
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
});
|
|
841
|
-
note.modLFO = new OscillatorNode(this.audioContext, {
|
|
846
|
+
const { modLfoToPitch, modLfoToVolume } = instrumentKey;
|
|
847
|
+
note.modulationLFO = new OscillatorNode(this.audioContext, {
|
|
842
848
|
frequency: this.centToHz(instrumentKey.freqModLFO),
|
|
843
849
|
});
|
|
844
|
-
note.
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
const { instrumentKey } = note;
|
|
852
|
-
note.vibLFOGain = new GainNode(this.audioContext, {
|
|
853
|
-
gain: channel.vibratoDepth,
|
|
850
|
+
note.filterDepth = new GainNode(this.audioContext, {
|
|
851
|
+
gain: instrumentKey.modLfoToFilterFc,
|
|
852
|
+
});
|
|
853
|
+
const modulationDepth = Math.abs(modLfoToPitch) + channel.modulationDepth;
|
|
854
|
+
const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
|
|
855
|
+
note.modulationDepth = new GainNode(this.audioContext, {
|
|
856
|
+
gain: modulationDepth * modulationDepthSign,
|
|
854
857
|
});
|
|
855
|
-
|
|
856
|
-
|
|
858
|
+
const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
859
|
+
const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
|
|
860
|
+
note.volumeDepth = new GainNode(this.audioContext, {
|
|
861
|
+
gain: volumeDepth * volumeDepthSign,
|
|
862
|
+
});
|
|
863
|
+
note.modulationLFO.start(startTime + instrumentKey.delayModLFO);
|
|
864
|
+
note.modulationLFO.connect(note.filterDepth);
|
|
865
|
+
note.filterDepth.connect(note.filterNode.frequency);
|
|
866
|
+
note.modulationLFO.connect(note.modulationDepth);
|
|
867
|
+
note.modulationDepth.connect(note.bufferSource.detune);
|
|
868
|
+
note.modulationLFO.connect(note.volumeDepth);
|
|
869
|
+
note.volumeDepth.connect(note.volumeNode.gain);
|
|
870
|
+
}
|
|
871
|
+
startVibrato(channel, note, startTime) {
|
|
872
|
+
const { instrumentKey } = note;
|
|
873
|
+
const { vibLfoToPitch } = instrumentKey;
|
|
874
|
+
note.vibratoLFO = new OscillatorNode(this.audioContext, {
|
|
875
|
+
frequency: this.centToHz(instrumentKey.freqVibLFO) *
|
|
857
876
|
channel.vibratoRate,
|
|
858
877
|
});
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
note.
|
|
878
|
+
const vibratoDepth = Math.abs(vibLfoToPitch) * channel.vibratoDepth;
|
|
879
|
+
const vibratoDepthSign = 0 < vibLfoToPitch;
|
|
880
|
+
note.vibratoDepth = new GainNode(this.audioContext, {
|
|
881
|
+
gain: vibratoDepth * vibratoDepthSign,
|
|
882
|
+
});
|
|
883
|
+
note.vibratoLFO.start(startTime + instrumentKey.delayVibLFO * channel.vibratoDelay);
|
|
884
|
+
note.vibratoLFO.connect(note.vibratoDepth);
|
|
885
|
+
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
862
886
|
}
|
|
863
887
|
async createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3) {
|
|
864
888
|
const semitoneOffset = this.calcSemitoneOffset(channel);
|
|
865
889
|
const note = new Note(noteNumber, velocity, startTime, instrumentKey);
|
|
866
890
|
note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
|
|
867
|
-
|
|
868
|
-
this.setVolumeEnvelope(
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
const delayModLFO = startTime + instrumentKey.delayModLFO;
|
|
872
|
-
this.startModulation(channel, note, delayModLFO);
|
|
891
|
+
this.setFilterNode(channel, note);
|
|
892
|
+
this.setVolumeEnvelope(note);
|
|
893
|
+
if (0 < channel.vibratoDepth) {
|
|
894
|
+
this.startVibrato(channel, note, startTime);
|
|
873
895
|
}
|
|
874
|
-
if (channel.
|
|
875
|
-
|
|
876
|
-
this.
|
|
896
|
+
if (0 < channel.modulationDepth) {
|
|
897
|
+
this.setPitch(note, semitoneOffset);
|
|
898
|
+
this.startModulation(channel, note, startTime);
|
|
899
|
+
}
|
|
900
|
+
else {
|
|
901
|
+
note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
|
|
877
902
|
}
|
|
878
903
|
if (this.mono && channel.currentBufferSource) {
|
|
879
904
|
channel.currentBufferSource.stop(startTime);
|
|
880
905
|
channel.currentBufferSource = note.bufferSource;
|
|
881
906
|
}
|
|
882
907
|
note.bufferSource.connect(note.filterNode);
|
|
883
|
-
note.filterNode.connect(note.
|
|
908
|
+
note.filterNode.connect(note.volumeNode);
|
|
884
909
|
note.bufferSource.start(startTime, instrumentKey.start / instrumentKey.sampleRate);
|
|
885
910
|
return note;
|
|
886
911
|
}
|
|
@@ -905,7 +930,8 @@ class Midy {
|
|
|
905
930
|
if (!instrumentKey)
|
|
906
931
|
return;
|
|
907
932
|
const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
|
|
908
|
-
|
|
933
|
+
note.volumeNode.connect(channel.gainL);
|
|
934
|
+
note.volumeNode.connect(channel.gainR);
|
|
909
935
|
if (channel.sostenutoPedal) {
|
|
910
936
|
channel.sostenutoNotes.set(noteNumber, note);
|
|
911
937
|
}
|
|
@@ -939,17 +965,14 @@ class Midy {
|
|
|
939
965
|
const velocityRate = (velocity + 127) / 127;
|
|
940
966
|
const volEndTime = stopTime +
|
|
941
967
|
note.instrumentKey.volRelease * velocityRate;
|
|
942
|
-
note.
|
|
968
|
+
note.volumeNode.gain
|
|
943
969
|
.cancelScheduledValues(stopTime)
|
|
944
970
|
.linearRampToValueAtTime(0, volEndTime);
|
|
945
|
-
const
|
|
946
|
-
const baseFreq = this.centToHz(note.instrumentKey.initialFilterFc);
|
|
947
|
-
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
948
|
-
const modEndTime = stopTime +
|
|
971
|
+
const modRelease = stopTime +
|
|
949
972
|
note.instrumentKey.modRelease * velocityRate;
|
|
950
973
|
note.filterNode.frequency
|
|
951
974
|
.cancelScheduledValues(stopTime)
|
|
952
|
-
.linearRampToValueAtTime(
|
|
975
|
+
.linearRampToValueAtTime(0, modRelease);
|
|
953
976
|
note.ending = true;
|
|
954
977
|
this.scheduleTask(() => {
|
|
955
978
|
note.bufferSource.loop = false;
|
|
@@ -958,16 +981,18 @@ class Midy {
|
|
|
958
981
|
note.bufferSource.onended = () => {
|
|
959
982
|
scheduledNotes[i] = null;
|
|
960
983
|
note.bufferSource.disconnect();
|
|
984
|
+
note.volumeNode.disconnect();
|
|
961
985
|
note.filterNode.disconnect();
|
|
962
|
-
note.
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
986
|
+
if (note.volumeDepth)
|
|
987
|
+
note.volumeDepth.disconnect();
|
|
988
|
+
if (note.modulationDepth)
|
|
989
|
+
note.modulationDepth.disconnect();
|
|
990
|
+
if (note.modulationLFO)
|
|
991
|
+
note.modulationLFO.stop();
|
|
992
|
+
if (note.vibratoDepth)
|
|
993
|
+
note.vibratoDepth.disconnect();
|
|
994
|
+
if (note.vibratoLFO)
|
|
995
|
+
note.vibratoLFO.stop();
|
|
971
996
|
resolve();
|
|
972
997
|
};
|
|
973
998
|
note.bufferSource.stop(volEndTime);
|
|
@@ -983,10 +1008,10 @@ class Midy {
|
|
|
983
1008
|
const channel = this.channels[channelNumber];
|
|
984
1009
|
const promises = [];
|
|
985
1010
|
channel.sustainPedal = false;
|
|
986
|
-
channel.scheduledNotes.forEach((
|
|
987
|
-
|
|
988
|
-
if (
|
|
989
|
-
const { noteNumber } =
|
|
1011
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1012
|
+
noteList.forEach((note) => {
|
|
1013
|
+
if (note) {
|
|
1014
|
+
const { noteNumber } = note;
|
|
990
1015
|
const promise = this.releaseNote(channelNumber, noteNumber, velocity);
|
|
991
1016
|
promises.push(promise);
|
|
992
1017
|
}
|
|
@@ -1037,8 +1062,8 @@ class Midy {
|
|
|
1037
1062
|
if (channel.polyphonicKeyPressure.amplitudeControl !== 1) {
|
|
1038
1063
|
if (activeNotes.has(noteNumber)) {
|
|
1039
1064
|
const activeNote = activeNotes.get(noteNumber);
|
|
1040
|
-
const gain = activeNote.
|
|
1041
|
-
activeNote.
|
|
1065
|
+
const gain = activeNote.volumeNode.gain.value;
|
|
1066
|
+
activeNote.volumeNode.gain
|
|
1042
1067
|
.cancelScheduledValues(now)
|
|
1043
1068
|
.setValueAtTime(gain * pressure, now);
|
|
1044
1069
|
}
|
|
@@ -1057,21 +1082,21 @@ class Midy {
|
|
|
1057
1082
|
const activeNotes = this.getActiveNotes(channel, now);
|
|
1058
1083
|
if (channel.channelPressure.amplitudeControl !== 1) {
|
|
1059
1084
|
activeNotes.forEach((activeNote) => {
|
|
1060
|
-
const gain = activeNote.
|
|
1061
|
-
activeNote.
|
|
1085
|
+
const gain = activeNote.volumeNode.gain.value;
|
|
1086
|
+
activeNote.volumeNode.gain
|
|
1062
1087
|
.cancelScheduledValues(now)
|
|
1063
1088
|
.setValueAtTime(gain * pressure, now);
|
|
1064
1089
|
});
|
|
1065
1090
|
}
|
|
1066
1091
|
}
|
|
1067
1092
|
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
1068
|
-
const pitchBend = msb * 128 + lsb;
|
|
1093
|
+
const pitchBend = msb * 128 + lsb - 8192;
|
|
1069
1094
|
this.setPitchBend(channelNumber, pitchBend);
|
|
1070
1095
|
}
|
|
1071
1096
|
setPitchBend(channelNumber, pitchBend) {
|
|
1072
1097
|
const channel = this.channels[channelNumber];
|
|
1073
1098
|
const prevPitchBend = channel.pitchBend;
|
|
1074
|
-
channel.pitchBend =
|
|
1099
|
+
channel.pitchBend = pitchBend / 8192;
|
|
1075
1100
|
const detuneChange = (channel.pitchBend - prevPitchBend) *
|
|
1076
1101
|
channel.pitchBendRange * 100;
|
|
1077
1102
|
this.updateDetune(channel, detuneChange);
|
|
@@ -1081,7 +1106,7 @@ class Midy {
|
|
|
1081
1106
|
case 0:
|
|
1082
1107
|
return this.setBankMSB(channelNumber, value);
|
|
1083
1108
|
case 1:
|
|
1084
|
-
return this.
|
|
1109
|
+
return this.setModulationDepth(channelNumber, value);
|
|
1085
1110
|
case 5:
|
|
1086
1111
|
return this.setPortamentoTime(channelNumber, value);
|
|
1087
1112
|
case 6:
|
|
@@ -1148,18 +1173,19 @@ class Midy {
|
|
|
1148
1173
|
const now = this.audioContext.currentTime;
|
|
1149
1174
|
const activeNotes = this.getActiveNotes(channel, now);
|
|
1150
1175
|
activeNotes.forEach((activeNote) => {
|
|
1151
|
-
if (activeNote.
|
|
1152
|
-
|
|
1153
|
-
gainNode.gain.setValueAtTime(this.cbToRatio(instrumentKey.modLfoToVolume + channel.modulation), now);
|
|
1176
|
+
if (activeNote.modulationDepth) {
|
|
1177
|
+
activeNote.modulationDepth.gain.setValueAtTime(channel.modulationDepth, now);
|
|
1154
1178
|
}
|
|
1155
1179
|
else {
|
|
1180
|
+
const semitoneOffset = this.calcSemitoneOffset(channel);
|
|
1181
|
+
this.setPitch(activeNote, semitoneOffset);
|
|
1156
1182
|
this.startModulation(channel, activeNote, now);
|
|
1157
1183
|
}
|
|
1158
1184
|
});
|
|
1159
1185
|
}
|
|
1160
|
-
|
|
1186
|
+
setModulationDepth(channelNumber, modulation) {
|
|
1161
1187
|
const channel = this.channels[channelNumber];
|
|
1162
|
-
channel.
|
|
1188
|
+
channel.modulationDepth = (modulation / 127) * channel.modulationDepthRange;
|
|
1163
1189
|
this.updateModulation(channel);
|
|
1164
1190
|
}
|
|
1165
1191
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
@@ -1217,20 +1243,30 @@ class Midy {
|
|
|
1217
1243
|
this.channels[channelNumber].portamento = value >= 64;
|
|
1218
1244
|
}
|
|
1219
1245
|
setReverbSendLevel(channelNumber, reverbSendLevel) {
|
|
1220
|
-
const now = this.audioContext.currentTime;
|
|
1221
1246
|
const channel = this.channels[channelNumber];
|
|
1222
|
-
const reverbEffect =
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1247
|
+
const reverbEffect = this.reverbEffect;
|
|
1248
|
+
if (0 < reverbSendLevel) {
|
|
1249
|
+
const now = this.audioContext.currentTime;
|
|
1250
|
+
channel.reverbSendLevel = reverbSendLevel / 127;
|
|
1251
|
+
reverbEffect.output.gain.cancelScheduledValues(now);
|
|
1252
|
+
reverbEffect.output.gain.setValueAtTime(channel.reverbSendLevel, now);
|
|
1253
|
+
}
|
|
1254
|
+
else if (channel.reverbSendLevel !== 0) {
|
|
1255
|
+
channel.merger.disconnect(reverbEffect.input);
|
|
1256
|
+
}
|
|
1226
1257
|
}
|
|
1227
1258
|
setChorusSendLevel(channelNumber, chorusSendLevel) {
|
|
1228
|
-
const now = this.audioContext.currentTime;
|
|
1229
1259
|
const channel = this.channels[channelNumber];
|
|
1230
|
-
const chorusEffect =
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1260
|
+
const chorusEffect = this.chorusEffect;
|
|
1261
|
+
if (0 < chorusSendLevel) {
|
|
1262
|
+
const now = this.audioContext.currentTime;
|
|
1263
|
+
channel.chorusSendLevel = chorusSendLevel / 127;
|
|
1264
|
+
chorusEffect.output.gain.cancelScheduledValues(now);
|
|
1265
|
+
chorusEffect.output.gain.setValueAtTime(channel.chorusSendLevel, now);
|
|
1266
|
+
}
|
|
1267
|
+
else if (channel.chorusSendLevel !== 0) {
|
|
1268
|
+
channel.merger.disconnect(chorusEffect.input);
|
|
1269
|
+
}
|
|
1234
1270
|
}
|
|
1235
1271
|
setSostenutoPedal(channelNumber, value) {
|
|
1236
1272
|
const isOn = value >= 64;
|
|
@@ -1251,21 +1287,15 @@ class Midy {
|
|
|
1251
1287
|
}
|
|
1252
1288
|
setVibratoRate(channelNumber, vibratoRate) {
|
|
1253
1289
|
const channel = this.channels[channelNumber];
|
|
1254
|
-
channel.vibratoRate = vibratoRate /
|
|
1290
|
+
channel.vibratoRate = vibratoRate / 64;
|
|
1255
1291
|
}
|
|
1256
1292
|
setVibratoDepth(channelNumber, vibratoDepth) {
|
|
1257
1293
|
const channel = this.channels[channelNumber];
|
|
1258
|
-
channel.vibratoDepth = vibratoDepth /
|
|
1294
|
+
channel.vibratoDepth = vibratoDepth / 64;
|
|
1259
1295
|
}
|
|
1260
1296
|
setVibratoDelay(channelNumber, vibratoDelay) {
|
|
1261
|
-
// Access Virus: 0-10sec
|
|
1262
|
-
// Elektron: 0-5sec
|
|
1263
|
-
// Korg: 0-5sec
|
|
1264
|
-
// Nord: 0-5sec
|
|
1265
|
-
// Roland: 0-5sec
|
|
1266
|
-
// Yamaha: 0-8sec
|
|
1267
1297
|
const channel = this.channels[channelNumber];
|
|
1268
|
-
channel.vibratoDelay = vibratoDelay /
|
|
1298
|
+
channel.vibratoDelay = vibratoDelay / 64;
|
|
1269
1299
|
}
|
|
1270
1300
|
limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
|
|
1271
1301
|
if (maxLSB < channel.dataLSB) {
|
|
@@ -1387,47 +1417,23 @@ class Midy {
|
|
|
1387
1417
|
handleModulationDepthRangeRPN(channelNumber) {
|
|
1388
1418
|
const channel = this.channels[channelNumber];
|
|
1389
1419
|
this.limitData(channel, 0, 127, 0, 127);
|
|
1390
|
-
const modulationDepthRange = dataMSB + dataLSB / 128;
|
|
1420
|
+
const modulationDepthRange = (dataMSB + dataLSB / 128) * 100;
|
|
1391
1421
|
this.setModulationDepthRange(channelNumber, modulationDepthRange);
|
|
1392
1422
|
}
|
|
1393
1423
|
setModulationDepthRange(channelNumber, modulationDepthRange) {
|
|
1394
1424
|
const channel = this.channels[channelNumber];
|
|
1395
1425
|
channel.modulationDepthRange = modulationDepthRange;
|
|
1396
|
-
channel.
|
|
1426
|
+
channel.modulationDepth = (modulation / 127) * modulationDepthRange;
|
|
1397
1427
|
this.updateModulation(channel);
|
|
1398
1428
|
}
|
|
1399
1429
|
allSoundOff(channelNumber) {
|
|
1400
|
-
|
|
1401
|
-
const channel = this.channels[channelNumber];
|
|
1402
|
-
const velocity = 0;
|
|
1403
|
-
const stopPedal = true;
|
|
1404
|
-
const promises = [];
|
|
1405
|
-
channel.scheduledNotes.forEach((noteList) => {
|
|
1406
|
-
const activeNote = this.getActiveNote(noteList, now);
|
|
1407
|
-
if (activeNote) {
|
|
1408
|
-
const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
|
|
1409
|
-
promises.push(notePromise);
|
|
1410
|
-
}
|
|
1411
|
-
});
|
|
1412
|
-
return promises;
|
|
1430
|
+
return this.stopChannelNotes(channelNumber, 0, true);
|
|
1413
1431
|
}
|
|
1414
1432
|
resetAllControllers(channelNumber) {
|
|
1415
1433
|
Object.assign(this.channels[channelNumber], this.effectSettings);
|
|
1416
1434
|
}
|
|
1417
1435
|
allNotesOff(channelNumber) {
|
|
1418
|
-
|
|
1419
|
-
const channel = this.channels[channelNumber];
|
|
1420
|
-
const velocity = 0;
|
|
1421
|
-
const stopPedal = false;
|
|
1422
|
-
const promises = [];
|
|
1423
|
-
channel.scheduledNotes.forEach((noteList) => {
|
|
1424
|
-
const activeNote = this.getActiveNote(noteList, now);
|
|
1425
|
-
if (activeNote) {
|
|
1426
|
-
const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
|
|
1427
|
-
promises.push(notePromise);
|
|
1428
|
-
}
|
|
1429
|
-
});
|
|
1430
|
-
return promises;
|
|
1436
|
+
return this.stopChannelNotes(channelNumber, 0, false);
|
|
1431
1437
|
}
|
|
1432
1438
|
omniOff() {
|
|
1433
1439
|
this.omni = false;
|
|
@@ -1592,11 +1598,9 @@ class Midy {
|
|
|
1592
1598
|
}
|
|
1593
1599
|
setReverbType(type) {
|
|
1594
1600
|
this.reverb.time = this.getReverbTimeFromType(type);
|
|
1595
|
-
this.reverb.feedback = (type === 8) ? 0.
|
|
1596
|
-
const { audioContext,
|
|
1597
|
-
|
|
1598
|
-
channels[i].reverbEffect = options.reverbAlgorithm(audioContext);
|
|
1599
|
-
}
|
|
1601
|
+
this.reverb.feedback = (type === 8) ? 0.9 : 0.8;
|
|
1602
|
+
const { audioContext, options } = this;
|
|
1603
|
+
this.reverbEffect = options.reverbAlgorithm(audioContext);
|
|
1600
1604
|
}
|
|
1601
1605
|
getReverbTimeFromType(type) {
|
|
1602
1606
|
switch (type) {
|
|
@@ -1738,15 +1742,17 @@ class Midy {
|
|
|
1738
1742
|
return value * 0.00763;
|
|
1739
1743
|
}
|
|
1740
1744
|
setChorusSendToReverb(value) {
|
|
1741
|
-
const now = this.audioContext.currentTime;
|
|
1742
1745
|
const sendToReverb = this.getChorusSendToReverb(value);
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
chorusEffect.sendGain.gain
|
|
1746
|
+
if (0 < sendToReverb) {
|
|
1747
|
+
const now = this.audioContext.currentTime;
|
|
1748
|
+
this.chorus.sendToReverb = sendToReverb;
|
|
1749
|
+
this.chorusEffect.sendGain.gain
|
|
1747
1750
|
.cancelScheduledValues(now)
|
|
1748
1751
|
.setValueAtTime(sendToReverb, now);
|
|
1749
1752
|
}
|
|
1753
|
+
else if (this.chorus.sendToReverb !== 0) {
|
|
1754
|
+
this.chorusEffect.sendGain.disconnect(this.reverbEffect.input);
|
|
1755
|
+
}
|
|
1750
1756
|
}
|
|
1751
1757
|
getChorusSendToReverb(value) {
|
|
1752
1758
|
return value * 0.00787;
|
|
@@ -1788,9 +1794,9 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
1788
1794
|
portamentoTime: 0,
|
|
1789
1795
|
reverbSendLevel: 0,
|
|
1790
1796
|
chorusSendLevel: 0,
|
|
1791
|
-
vibratoRate:
|
|
1792
|
-
vibratoDepth:
|
|
1793
|
-
vibratoDelay:
|
|
1797
|
+
vibratoRate: 1,
|
|
1798
|
+
vibratoDepth: 1,
|
|
1799
|
+
vibratoDelay: 1,
|
|
1794
1800
|
bank: 121 * 128,
|
|
1795
1801
|
bankMSB: 121,
|
|
1796
1802
|
bankLSB: 0,
|
|
@@ -1800,7 +1806,7 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
1800
1806
|
pitchBend: 0,
|
|
1801
1807
|
fineTuning: 0, // cb
|
|
1802
1808
|
coarseTuning: 0, // cb
|
|
1803
|
-
modulationDepthRange:
|
|
1809
|
+
modulationDepthRange: 50, // cent
|
|
1804
1810
|
}
|
|
1805
1811
|
});
|
|
1806
1812
|
Object.defineProperty(Midy, "effectSettings", {
|
|
@@ -1809,7 +1815,7 @@ Object.defineProperty(Midy, "effectSettings", {
|
|
|
1809
1815
|
writable: true,
|
|
1810
1816
|
value: {
|
|
1811
1817
|
expression: 1,
|
|
1812
|
-
|
|
1818
|
+
modulationDepth: 0,
|
|
1813
1819
|
sustainPedal: false,
|
|
1814
1820
|
portamento: false,
|
|
1815
1821
|
sostenutoPedal: false,
|