@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-GM2.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 MidyGM2 {
|
|
|
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 MidyGM2 {
|
|
|
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 MidyGM2 {
|
|
|
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) {
|
|
@@ -403,7 +410,7 @@ export class MidyGM2 {
|
|
|
403
410
|
const t = this.audioContext.currentTime + offset;
|
|
404
411
|
queueIndex = await this.scheduleTimelineEvents(t, offset, queueIndex);
|
|
405
412
|
if (this.isPausing) {
|
|
406
|
-
await this.stopNotes();
|
|
413
|
+
await this.stopNotes(0, true);
|
|
407
414
|
this.notePromises = [];
|
|
408
415
|
resolve();
|
|
409
416
|
this.isPausing = false;
|
|
@@ -411,7 +418,7 @@ export class MidyGM2 {
|
|
|
411
418
|
return;
|
|
412
419
|
}
|
|
413
420
|
else if (this.isStopping) {
|
|
414
|
-
await this.stopNotes();
|
|
421
|
+
await this.stopNotes(0, true);
|
|
415
422
|
this.notePromises = [];
|
|
416
423
|
resolve();
|
|
417
424
|
this.isStopping = false;
|
|
@@ -419,7 +426,7 @@ export class MidyGM2 {
|
|
|
419
426
|
return;
|
|
420
427
|
}
|
|
421
428
|
else if (this.isSeeking) {
|
|
422
|
-
this.stopNotes();
|
|
429
|
+
this.stopNotes(0, true);
|
|
423
430
|
this.startTime = this.audioContext.currentTime;
|
|
424
431
|
queueIndex = this.getQueueIndex(this.resumeTime);
|
|
425
432
|
offset = this.resumeTime - this.startTime;
|
|
@@ -534,21 +541,24 @@ export class MidyGM2 {
|
|
|
534
541
|
}
|
|
535
542
|
return { instruments, timeline };
|
|
536
543
|
}
|
|
537
|
-
|
|
544
|
+
async stopChannelNotes(channelNumber, velocity, stopPedal) {
|
|
538
545
|
const now = this.audioContext.currentTime;
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
this.notePromises.push(promise);
|
|
547
|
-
}
|
|
548
|
-
});
|
|
546
|
+
const channel = this.channels[channelNumber];
|
|
547
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
548
|
+
noteList.forEach((note) => {
|
|
549
|
+
if (note) {
|
|
550
|
+
const promise = this.scheduleNoteRelease(channelNumber, note.noteNumber, velocity, now, stopPedal);
|
|
551
|
+
this.notePromises.push(promise);
|
|
552
|
+
}
|
|
549
553
|
});
|
|
550
|
-
channel.scheduledNotes.clear();
|
|
551
554
|
});
|
|
555
|
+
channel.scheduledNotes.clear();
|
|
556
|
+
await Promise.all(this.notePromises);
|
|
557
|
+
}
|
|
558
|
+
stopNotes(velocity, stopPedal) {
|
|
559
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
560
|
+
this.stopChannelNotes(i, velocity, stopPedal);
|
|
561
|
+
}
|
|
552
562
|
return Promise.all(this.notePromises);
|
|
553
563
|
}
|
|
554
564
|
async start() {
|
|
@@ -686,7 +696,7 @@ export class MidyGM2 {
|
|
|
686
696
|
}
|
|
687
697
|
// https://hajim.rochester.edu/ece/sites/zduan/teaching/ece472/reading/Schroeder_1962.pdf
|
|
688
698
|
// M.R.Schroeder, "Natural Sounding Artificial Reverberation", J.Audio Eng. Soc., vol.10, p.219, 1962
|
|
689
|
-
createSchroederReverb(audioContext, combDelays,
|
|
699
|
+
createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays) {
|
|
690
700
|
const input = new GainNode(audioContext);
|
|
691
701
|
const output = new GainNode(audioContext);
|
|
692
702
|
const mergerGain = new GainNode(audioContext);
|
|
@@ -745,21 +755,6 @@ export class MidyGM2 {
|
|
|
745
755
|
feedbackGains,
|
|
746
756
|
};
|
|
747
757
|
}
|
|
748
|
-
connectEffects(channel, gainNode) {
|
|
749
|
-
gainNode.connect(channel.merger);
|
|
750
|
-
channel.merger.connect(this.masterGain);
|
|
751
|
-
if (0 < channel.reverbSendLevel) {
|
|
752
|
-
channel.merger.connect(channel.reverbEffect.input);
|
|
753
|
-
channel.reverbEffect.output.connect(this.masterGain);
|
|
754
|
-
}
|
|
755
|
-
if (0 < channel.chorusSendLevel) {
|
|
756
|
-
channel.merger.connect(channel.chorusEffect.input);
|
|
757
|
-
channel.reverbEffect.output.connect(this.masterGain);
|
|
758
|
-
}
|
|
759
|
-
if (0 < this.chorus.sendToReverb) {
|
|
760
|
-
channel.chorusEffect.sendGain.connect(channel.reverbEffect.input);
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
758
|
cbToRatio(cb) {
|
|
764
759
|
return Math.pow(10, cb / 200);
|
|
765
760
|
}
|
|
@@ -776,42 +771,56 @@ export class MidyGM2 {
|
|
|
776
771
|
return instrumentKey.playbackRate(noteNumber) *
|
|
777
772
|
Math.pow(2, semitoneOffset / 12);
|
|
778
773
|
}
|
|
779
|
-
setVolumeEnvelope(
|
|
780
|
-
const { instrumentKey, startTime
|
|
781
|
-
note.
|
|
782
|
-
|
|
783
|
-
if (volume === 0)
|
|
784
|
-
volume = 1e-6; // exponentialRampToValueAtTime() requires a non-zero value
|
|
785
|
-
const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation) *
|
|
786
|
-
volume;
|
|
774
|
+
setVolumeEnvelope(note) {
|
|
775
|
+
const { instrumentKey, startTime } = note;
|
|
776
|
+
note.volumeNode = new GainNode(this.audioContext, { gain: 0 });
|
|
777
|
+
const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
|
|
787
778
|
const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
|
|
788
779
|
const volDelay = startTime + instrumentKey.volDelay;
|
|
789
780
|
const volAttack = volDelay + instrumentKey.volAttack;
|
|
790
781
|
const volHold = volAttack + instrumentKey.volHold;
|
|
791
782
|
const volDecay = volHold + instrumentKey.volDecay;
|
|
792
|
-
note.
|
|
783
|
+
note.volumeNode.gain
|
|
793
784
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
794
785
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
795
786
|
.setValueAtTime(attackVolume, volHold)
|
|
796
787
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
797
788
|
}
|
|
798
|
-
|
|
799
|
-
const { instrumentKey,
|
|
789
|
+
setPitch(note, semitoneOffset) {
|
|
790
|
+
const { instrumentKey, noteNumber, startTime } = note;
|
|
791
|
+
const modEnvToPitch = instrumentKey.modEnvToPitch / 100;
|
|
792
|
+
note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
|
|
793
|
+
if (modEnvToPitch === 0)
|
|
794
|
+
return;
|
|
795
|
+
const basePitch = note.bufferSource.playbackRate.value;
|
|
796
|
+
const peekPitch = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset + modEnvToPitch);
|
|
797
|
+
const modDelay = startTime + instrumentKey.modDelay;
|
|
798
|
+
const modAttack = modDelay + instrumentKey.modAttack;
|
|
799
|
+
const modHold = modAttack + instrumentKey.modHold;
|
|
800
|
+
const modDecay = modHold + instrumentKey.modDecay;
|
|
801
|
+
note.bufferSource.playbackRate.value
|
|
802
|
+
.setValueAtTime(basePitch, modDelay)
|
|
803
|
+
.exponentialRampToValueAtTime(peekPitch, modAttack)
|
|
804
|
+
.setValueAtTime(peekPitch, modHold)
|
|
805
|
+
.linearRampToValueAtTime(basePitch, modDecay);
|
|
806
|
+
}
|
|
807
|
+
setFilterNode(channel, note) {
|
|
808
|
+
const { instrumentKey, noteNumber, startTime } = note;
|
|
800
809
|
const softPedalFactor = 1 -
|
|
801
810
|
(0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
|
|
802
811
|
const maxFreq = this.audioContext.sampleRate / 2;
|
|
803
812
|
const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
|
|
804
813
|
softPedalFactor;
|
|
805
814
|
const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
|
|
806
|
-
const sustainFreq =
|
|
807
|
-
(peekFreq - baseFreq) * (1 - instrumentKey.modSustain)
|
|
815
|
+
const sustainFreq = baseFreq +
|
|
816
|
+
(peekFreq - baseFreq) * (1 - instrumentKey.modSustain);
|
|
817
|
+
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
818
|
+
const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
|
|
819
|
+
const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
|
|
808
820
|
const modDelay = startTime + instrumentKey.modDelay;
|
|
809
821
|
const modAttack = modDelay + instrumentKey.modAttack;
|
|
810
822
|
const modHold = modAttack + instrumentKey.modHold;
|
|
811
823
|
const modDecay = modHold + instrumentKey.modDecay;
|
|
812
|
-
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
813
|
-
const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
|
|
814
|
-
const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
|
|
815
824
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
816
825
|
type: "lowpass",
|
|
817
826
|
Q: instrumentKey.initialFilterQ / 10, // dB
|
|
@@ -822,39 +831,72 @@ export class MidyGM2 {
|
|
|
822
831
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
823
832
|
.setValueAtTime(adjustedPeekFreq, modHold)
|
|
824
833
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
825
|
-
note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modEnvToPitch, modDelay);
|
|
826
834
|
}
|
|
827
|
-
startModulation(channel, note,
|
|
835
|
+
startModulation(channel, note, startTime) {
|
|
828
836
|
const { instrumentKey } = note;
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
});
|
|
832
|
-
note.modLFO = new OscillatorNode(this.audioContext, {
|
|
837
|
+
const { modLfoToPitch, modLfoToVolume } = instrumentKey;
|
|
838
|
+
note.modulationLFO = new OscillatorNode(this.audioContext, {
|
|
833
839
|
frequency: this.centToHz(instrumentKey.freqModLFO),
|
|
834
840
|
});
|
|
835
|
-
note.
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
841
|
+
note.filterDepth = new GainNode(this.audioContext, {
|
|
842
|
+
gain: instrumentKey.modLfoToFilterFc,
|
|
843
|
+
});
|
|
844
|
+
const modulationDepth = Math.abs(modLfoToPitch) + channel.modulationDepth;
|
|
845
|
+
const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
|
|
846
|
+
note.modulationDepth = new GainNode(this.audioContext, {
|
|
847
|
+
gain: modulationDepth * modulationDepthSign,
|
|
848
|
+
});
|
|
849
|
+
const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
850
|
+
const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
|
|
851
|
+
note.volumeDepth = new GainNode(this.audioContext, {
|
|
852
|
+
gain: volumeDepth * volumeDepthSign,
|
|
853
|
+
});
|
|
854
|
+
note.modulationLFO.start(startTime + instrumentKey.delayModLFO);
|
|
855
|
+
note.modulationLFO.connect(note.filterDepth);
|
|
856
|
+
note.filterDepth.connect(note.filterNode.frequency);
|
|
857
|
+
note.modulationLFO.connect(note.modulationDepth);
|
|
858
|
+
note.modulationDepth.connect(note.bufferSource.detune);
|
|
859
|
+
note.modulationLFO.connect(note.volumeDepth);
|
|
860
|
+
note.volumeDepth.connect(note.volumeNode.gain);
|
|
861
|
+
}
|
|
862
|
+
startVibrato(channel, note, startTime) {
|
|
863
|
+
const { instrumentKey } = note;
|
|
864
|
+
const { vibLfoToPitch } = instrumentKey;
|
|
865
|
+
note.vibratoLFO = new OscillatorNode(this.audioContext, {
|
|
866
|
+
frequency: this.centToHz(instrumentKey.freqVibLFO) *
|
|
867
|
+
channel.vibratoRate,
|
|
868
|
+
});
|
|
869
|
+
const vibratoDepth = Math.abs(vibLfoToPitch) * channel.vibratoDepth;
|
|
870
|
+
const vibratoDepthSign = 0 < vibLfoToPitch;
|
|
871
|
+
note.vibratoDepth = new GainNode(this.audioContext, {
|
|
872
|
+
gain: vibratoDepth * vibratoDepthSign,
|
|
873
|
+
});
|
|
874
|
+
note.vibratoLFO.start(startTime + instrumentKey.delayVibLFO * channel.vibratoDelay);
|
|
875
|
+
note.vibratoLFO.connect(note.vibratoDepth);
|
|
876
|
+
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
840
877
|
}
|
|
841
878
|
async createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3) {
|
|
842
879
|
const semitoneOffset = this.calcSemitoneOffset(channel);
|
|
843
880
|
const note = new Note(noteNumber, velocity, startTime, instrumentKey);
|
|
844
881
|
note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
|
|
845
|
-
|
|
846
|
-
this.setVolumeEnvelope(
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
882
|
+
this.setFilterNode(channel, note);
|
|
883
|
+
this.setVolumeEnvelope(note);
|
|
884
|
+
if (0 < channel.vibratoDepth) {
|
|
885
|
+
this.startVibrato(channel, note, startTime);
|
|
886
|
+
}
|
|
887
|
+
if (0 < channel.modulationDepth) {
|
|
888
|
+
this.setPitch(note, semitoneOffset);
|
|
889
|
+
this.startModulation(channel, note, startTime);
|
|
890
|
+
}
|
|
891
|
+
else {
|
|
892
|
+
note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
|
|
851
893
|
}
|
|
852
894
|
if (this.mono && channel.currentBufferSource) {
|
|
853
895
|
channel.currentBufferSource.stop(startTime);
|
|
854
896
|
channel.currentBufferSource = note.bufferSource;
|
|
855
897
|
}
|
|
856
898
|
note.bufferSource.connect(note.filterNode);
|
|
857
|
-
note.filterNode.connect(note.
|
|
899
|
+
note.filterNode.connect(note.volumeNode);
|
|
858
900
|
note.bufferSource.start(startTime, instrumentKey.start / instrumentKey.sampleRate);
|
|
859
901
|
return note;
|
|
860
902
|
}
|
|
@@ -879,7 +921,8 @@ export class MidyGM2 {
|
|
|
879
921
|
if (!instrumentKey)
|
|
880
922
|
return;
|
|
881
923
|
const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
|
|
882
|
-
|
|
924
|
+
note.volumeNode.connect(channel.gainL);
|
|
925
|
+
note.volumeNode.connect(channel.gainR);
|
|
883
926
|
if (channel.sostenutoPedal) {
|
|
884
927
|
channel.sostenutoNotes.set(noteNumber, note);
|
|
885
928
|
}
|
|
@@ -913,17 +956,14 @@ export class MidyGM2 {
|
|
|
913
956
|
const velocityRate = (velocity + 127) / 127;
|
|
914
957
|
const volEndTime = stopTime +
|
|
915
958
|
note.instrumentKey.volRelease * velocityRate;
|
|
916
|
-
note.
|
|
959
|
+
note.volumeNode.gain
|
|
917
960
|
.cancelScheduledValues(stopTime)
|
|
918
961
|
.linearRampToValueAtTime(0, volEndTime);
|
|
919
|
-
const
|
|
920
|
-
const baseFreq = this.centToHz(note.instrumentKey.initialFilterFc);
|
|
921
|
-
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
922
|
-
const modEndTime = stopTime +
|
|
962
|
+
const modRelease = stopTime +
|
|
923
963
|
note.instrumentKey.modRelease * velocityRate;
|
|
924
964
|
note.filterNode.frequency
|
|
925
965
|
.cancelScheduledValues(stopTime)
|
|
926
|
-
.linearRampToValueAtTime(
|
|
966
|
+
.linearRampToValueAtTime(0, modRelease);
|
|
927
967
|
note.ending = true;
|
|
928
968
|
this.scheduleTask(() => {
|
|
929
969
|
note.bufferSource.loop = false;
|
|
@@ -932,16 +972,18 @@ export class MidyGM2 {
|
|
|
932
972
|
note.bufferSource.onended = () => {
|
|
933
973
|
scheduledNotes[i] = null;
|
|
934
974
|
note.bufferSource.disconnect();
|
|
975
|
+
note.volumeNode.disconnect();
|
|
935
976
|
note.filterNode.disconnect();
|
|
936
|
-
note.
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
977
|
+
if (note.volumeDepth)
|
|
978
|
+
note.volumeDepth.disconnect();
|
|
979
|
+
if (note.modulationDepth)
|
|
980
|
+
note.modulationDepth.disconnect();
|
|
981
|
+
if (note.modulationLFO)
|
|
982
|
+
note.modulationLFO.stop();
|
|
983
|
+
if (note.vibratoDepth)
|
|
984
|
+
note.vibratoDepth.disconnect();
|
|
985
|
+
if (note.vibratoLFO)
|
|
986
|
+
note.vibratoLFO.stop();
|
|
945
987
|
resolve();
|
|
946
988
|
};
|
|
947
989
|
note.bufferSource.stop(volEndTime);
|
|
@@ -957,10 +999,10 @@ export class MidyGM2 {
|
|
|
957
999
|
const channel = this.channels[channelNumber];
|
|
958
1000
|
const promises = [];
|
|
959
1001
|
channel.sustainPedal = false;
|
|
960
|
-
channel.scheduledNotes.forEach((
|
|
961
|
-
|
|
962
|
-
if (
|
|
963
|
-
const { noteNumber } =
|
|
1002
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1003
|
+
noteList.forEach((note) => {
|
|
1004
|
+
if (note) {
|
|
1005
|
+
const { noteNumber } = note;
|
|
964
1006
|
const promise = this.releaseNote(channelNumber, noteNumber, velocity);
|
|
965
1007
|
promises.push(promise);
|
|
966
1008
|
}
|
|
@@ -1014,21 +1056,21 @@ export class MidyGM2 {
|
|
|
1014
1056
|
const activeNotes = this.getActiveNotes(channel, now);
|
|
1015
1057
|
if (channel.channelPressure.amplitudeControl !== 1) {
|
|
1016
1058
|
activeNotes.forEach((activeNote) => {
|
|
1017
|
-
const gain = activeNote.
|
|
1018
|
-
activeNote.
|
|
1059
|
+
const gain = activeNote.volumeNode.gain.value;
|
|
1060
|
+
activeNote.volumeNode.gain
|
|
1019
1061
|
.cancelScheduledValues(now)
|
|
1020
1062
|
.setValueAtTime(gain * pressure, now);
|
|
1021
1063
|
});
|
|
1022
1064
|
}
|
|
1023
1065
|
}
|
|
1024
1066
|
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
1025
|
-
const pitchBend = msb * 128 + lsb;
|
|
1067
|
+
const pitchBend = msb * 128 + lsb - 8192;
|
|
1026
1068
|
this.setPitchBend(channelNumber, pitchBend);
|
|
1027
1069
|
}
|
|
1028
1070
|
setPitchBend(channelNumber, pitchBend) {
|
|
1029
1071
|
const channel = this.channels[channelNumber];
|
|
1030
1072
|
const prevPitchBend = channel.pitchBend;
|
|
1031
|
-
channel.pitchBend =
|
|
1073
|
+
channel.pitchBend = pitchBend / 8192;
|
|
1032
1074
|
const detuneChange = (channel.pitchBend - prevPitchBend) *
|
|
1033
1075
|
channel.pitchBendRange * 100;
|
|
1034
1076
|
this.updateDetune(channel, detuneChange);
|
|
@@ -1038,7 +1080,7 @@ export class MidyGM2 {
|
|
|
1038
1080
|
case 0:
|
|
1039
1081
|
return this.setBankMSB(channelNumber, value);
|
|
1040
1082
|
case 1:
|
|
1041
|
-
return this.
|
|
1083
|
+
return this.setModulationDepth(channelNumber, value);
|
|
1042
1084
|
case 5:
|
|
1043
1085
|
return this.setPortamentoTime(channelNumber, value);
|
|
1044
1086
|
case 6:
|
|
@@ -1094,18 +1136,19 @@ export class MidyGM2 {
|
|
|
1094
1136
|
const now = this.audioContext.currentTime;
|
|
1095
1137
|
const activeNotes = this.getActiveNotes(channel, now);
|
|
1096
1138
|
activeNotes.forEach((activeNote) => {
|
|
1097
|
-
if (activeNote.
|
|
1098
|
-
|
|
1099
|
-
gainNode.gain.setValueAtTime(this.cbToRatio(instrumentKey.modLfoToVolume + channel.modulation), now);
|
|
1139
|
+
if (activeNote.modulationDepth) {
|
|
1140
|
+
activeNote.modulationDepth.gain.setValueAtTime(channel.modulationDepth, now);
|
|
1100
1141
|
}
|
|
1101
1142
|
else {
|
|
1143
|
+
const semitoneOffset = this.calcSemitoneOffset(channel);
|
|
1144
|
+
this.setPitch(activeNote, semitoneOffset);
|
|
1102
1145
|
this.startModulation(channel, activeNote, now);
|
|
1103
1146
|
}
|
|
1104
1147
|
});
|
|
1105
1148
|
}
|
|
1106
|
-
|
|
1149
|
+
setModulationDepth(channelNumber, modulation) {
|
|
1107
1150
|
const channel = this.channels[channelNumber];
|
|
1108
|
-
channel.
|
|
1151
|
+
channel.modulationDepth = (modulation / 127) * channel.modulationDepthRange;
|
|
1109
1152
|
this.updateModulation(channel);
|
|
1110
1153
|
}
|
|
1111
1154
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
@@ -1163,20 +1206,30 @@ export class MidyGM2 {
|
|
|
1163
1206
|
this.channels[channelNumber].portamento = value >= 64;
|
|
1164
1207
|
}
|
|
1165
1208
|
setReverbSendLevel(channelNumber, reverbSendLevel) {
|
|
1166
|
-
const now = this.audioContext.currentTime;
|
|
1167
1209
|
const channel = this.channels[channelNumber];
|
|
1168
|
-
const reverbEffect =
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1210
|
+
const reverbEffect = this.reverbEffect;
|
|
1211
|
+
if (0 < reverbSendLevel) {
|
|
1212
|
+
const now = this.audioContext.currentTime;
|
|
1213
|
+
channel.reverbSendLevel = reverbSendLevel / 127;
|
|
1214
|
+
reverbEffect.output.gain.cancelScheduledValues(now);
|
|
1215
|
+
reverbEffect.output.gain.setValueAtTime(channel.reverbSendLevel, now);
|
|
1216
|
+
}
|
|
1217
|
+
else if (channel.reverbSendLevel !== 0) {
|
|
1218
|
+
channel.merger.disconnect(reverbEffect.input);
|
|
1219
|
+
}
|
|
1172
1220
|
}
|
|
1173
1221
|
setChorusSendLevel(channelNumber, chorusSendLevel) {
|
|
1174
|
-
const now = this.audioContext.currentTime;
|
|
1175
1222
|
const channel = this.channels[channelNumber];
|
|
1176
|
-
const chorusEffect =
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1223
|
+
const chorusEffect = this.chorusEffect;
|
|
1224
|
+
if (0 < chorusSendLevel) {
|
|
1225
|
+
const now = this.audioContext.currentTime;
|
|
1226
|
+
channel.chorusSendLevel = chorusSendLevel / 127;
|
|
1227
|
+
chorusEffect.output.gain.cancelScheduledValues(now);
|
|
1228
|
+
chorusEffect.output.gain.setValueAtTime(channel.chorusSendLevel, now);
|
|
1229
|
+
}
|
|
1230
|
+
else if (channel.chorusSendLevel !== 0) {
|
|
1231
|
+
channel.merger.disconnect(chorusEffect.input);
|
|
1232
|
+
}
|
|
1180
1233
|
}
|
|
1181
1234
|
setSostenutoPedal(channelNumber, value) {
|
|
1182
1235
|
const isOn = value >= 64;
|
|
@@ -1305,47 +1358,23 @@ export class MidyGM2 {
|
|
|
1305
1358
|
handleModulationDepthRangeRPN(channelNumber) {
|
|
1306
1359
|
const channel = this.channels[channelNumber];
|
|
1307
1360
|
this.limitData(channel, 0, 127, 0, 127);
|
|
1308
|
-
const modulationDepthRange = dataMSB + dataLSB / 128;
|
|
1361
|
+
const modulationDepthRange = (dataMSB + dataLSB / 128) * 100;
|
|
1309
1362
|
this.setModulationDepthRange(channelNumber, modulationDepthRange);
|
|
1310
1363
|
}
|
|
1311
1364
|
setModulationDepthRange(channelNumber, modulationDepthRange) {
|
|
1312
1365
|
const channel = this.channels[channelNumber];
|
|
1313
1366
|
channel.modulationDepthRange = modulationDepthRange;
|
|
1314
|
-
channel.
|
|
1367
|
+
channel.modulationDepth = (modulation / 127) * modulationDepthRange;
|
|
1315
1368
|
this.updateModulation(channel);
|
|
1316
1369
|
}
|
|
1317
1370
|
allSoundOff(channelNumber) {
|
|
1318
|
-
|
|
1319
|
-
const channel = this.channels[channelNumber];
|
|
1320
|
-
const velocity = 0;
|
|
1321
|
-
const stopPedal = true;
|
|
1322
|
-
const promises = [];
|
|
1323
|
-
channel.scheduledNotes.forEach((noteList) => {
|
|
1324
|
-
const activeNote = this.getActiveNote(noteList, now);
|
|
1325
|
-
if (activeNote) {
|
|
1326
|
-
const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
|
|
1327
|
-
promises.push(notePromise);
|
|
1328
|
-
}
|
|
1329
|
-
});
|
|
1330
|
-
return promises;
|
|
1371
|
+
return this.stopChannelNotes(channelNumber, 0, true);
|
|
1331
1372
|
}
|
|
1332
1373
|
resetAllControllers(channelNumber) {
|
|
1333
1374
|
Object.assign(this.channels[channelNumber], this.effectSettings);
|
|
1334
1375
|
}
|
|
1335
1376
|
allNotesOff(channelNumber) {
|
|
1336
|
-
|
|
1337
|
-
const channel = this.channels[channelNumber];
|
|
1338
|
-
const velocity = 0;
|
|
1339
|
-
const stopPedal = false;
|
|
1340
|
-
const promises = [];
|
|
1341
|
-
channel.scheduledNotes.forEach((noteList) => {
|
|
1342
|
-
const activeNote = this.getActiveNote(noteList, now);
|
|
1343
|
-
if (activeNote) {
|
|
1344
|
-
const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
|
|
1345
|
-
promises.push(notePromise);
|
|
1346
|
-
}
|
|
1347
|
-
});
|
|
1348
|
-
return promises;
|
|
1377
|
+
return this.stopChannelNotes(channelNumber, 0, false);
|
|
1349
1378
|
}
|
|
1350
1379
|
omniOff() {
|
|
1351
1380
|
this.omni = false;
|
|
@@ -1510,11 +1539,9 @@ export class MidyGM2 {
|
|
|
1510
1539
|
}
|
|
1511
1540
|
setReverbType(type) {
|
|
1512
1541
|
this.reverb.time = this.getReverbTimeFromType(type);
|
|
1513
|
-
this.reverb.feedback = (type === 8) ? 0.
|
|
1514
|
-
const { audioContext,
|
|
1515
|
-
|
|
1516
|
-
channels[i].reverbEffect = options.reverbAlgorithm(audioContext);
|
|
1517
|
-
}
|
|
1542
|
+
this.reverb.feedback = (type === 8) ? 0.9 : 0.8;
|
|
1543
|
+
const { audioContext, options } = this;
|
|
1544
|
+
this.reverbEffect = options.reverbAlgorithm(audioContext);
|
|
1518
1545
|
}
|
|
1519
1546
|
getReverbTimeFromType(type) {
|
|
1520
1547
|
switch (type) {
|
|
@@ -1656,15 +1683,17 @@ export class MidyGM2 {
|
|
|
1656
1683
|
return value * 0.00763;
|
|
1657
1684
|
}
|
|
1658
1685
|
setChorusSendToReverb(value) {
|
|
1659
|
-
const now = this.audioContext.currentTime;
|
|
1660
1686
|
const sendToReverb = this.getChorusSendToReverb(value);
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
chorusEffect.sendGain.gain
|
|
1687
|
+
if (0 < sendToReverb) {
|
|
1688
|
+
const now = this.audioContext.currentTime;
|
|
1689
|
+
this.chorus.sendToReverb = sendToReverb;
|
|
1690
|
+
this.chorusEffect.sendGain.gain
|
|
1665
1691
|
.cancelScheduledValues(now)
|
|
1666
1692
|
.setValueAtTime(sendToReverb, now);
|
|
1667
1693
|
}
|
|
1694
|
+
else if (this.chorus.sendToReverb !== 0) {
|
|
1695
|
+
this.chorusEffect.sendGain.disconnect(this.reverbEffect.input);
|
|
1696
|
+
}
|
|
1668
1697
|
}
|
|
1669
1698
|
getChorusSendToReverb(value) {
|
|
1670
1699
|
return value * 0.00787;
|
|
@@ -1705,6 +1734,9 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
1705
1734
|
portamentoTime: 0,
|
|
1706
1735
|
reverbSendLevel: 0,
|
|
1707
1736
|
chorusSendLevel: 0,
|
|
1737
|
+
vibratoRate: 1,
|
|
1738
|
+
vibratoDepth: 1,
|
|
1739
|
+
vibratoDelay: 1,
|
|
1708
1740
|
bank: 121 * 128,
|
|
1709
1741
|
bankMSB: 121,
|
|
1710
1742
|
bankLSB: 0,
|
|
@@ -1714,7 +1746,7 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
1714
1746
|
pitchBend: 0,
|
|
1715
1747
|
fineTuning: 0, // cb
|
|
1716
1748
|
coarseTuning: 0, // cb
|
|
1717
|
-
modulationDepthRange:
|
|
1749
|
+
modulationDepthRange: 50, // cent
|
|
1718
1750
|
}
|
|
1719
1751
|
});
|
|
1720
1752
|
Object.defineProperty(MidyGM2, "effectSettings", {
|
|
@@ -1723,7 +1755,7 @@ Object.defineProperty(MidyGM2, "effectSettings", {
|
|
|
1723
1755
|
writable: true,
|
|
1724
1756
|
value: {
|
|
1725
1757
|
expression: 1,
|
|
1726
|
-
|
|
1758
|
+
modulationDepth: 0,
|
|
1727
1759
|
sustainPedal: false,
|
|
1728
1760
|
portamento: false,
|
|
1729
1761
|
sostenutoPedal: false,
|