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