@marmooo/midy 0.0.3 → 0.0.5
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.1 → soundfont-parser@0.0.2}/+esm.d.ts +13 -6
- package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.2/+esm.d.ts.map +1 -0
- package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/{soundfont-parser@0.0.1 → soundfont-parser@0.0.2}/+esm.js +5 -5
- package/esm/midy-GM1.d.ts +27 -36
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +199 -135
- package/esm/midy-GM2.d.ts +51 -35
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +234 -141
- package/esm/midy-GMLite.d.ts +25 -36
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +187 -135
- package/esm/midy.d.ts +68 -24
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +274 -141
- package/package.json +1 -1
- package/script/deps/cdn.jsdelivr.net/npm/@marmooo/{soundfont-parser@0.0.1 → soundfont-parser@0.0.2}/+esm.d.ts +13 -6
- package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.2/+esm.d.ts.map +1 -0
- package/script/deps/cdn.jsdelivr.net/npm/@marmooo/{soundfont-parser@0.0.1 → soundfont-parser@0.0.2}/+esm.js +5 -5
- package/script/midy-GM1.d.ts +27 -36
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +199 -135
- package/script/midy-GM2.d.ts +51 -35
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +234 -141
- package/script/midy-GMLite.d.ts +25 -36
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +187 -135
- package/script/midy.d.ts +68 -24
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +274 -141
- package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.1/+esm.d.ts.map +0 -1
- package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.1/+esm.d.ts.map +0 -1
package/script/midy-GM2.js
CHANGED
|
@@ -2,7 +2,57 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MidyGM2 = 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.2/+esm.js");
|
|
6
|
+
class Note {
|
|
7
|
+
constructor(noteNumber, velocity, startTime, instrumentKey) {
|
|
8
|
+
Object.defineProperty(this, "bufferSource", {
|
|
9
|
+
enumerable: true,
|
|
10
|
+
configurable: true,
|
|
11
|
+
writable: true,
|
|
12
|
+
value: void 0
|
|
13
|
+
});
|
|
14
|
+
Object.defineProperty(this, "gainNode", {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
configurable: true,
|
|
17
|
+
writable: true,
|
|
18
|
+
value: void 0
|
|
19
|
+
});
|
|
20
|
+
Object.defineProperty(this, "filterNode", {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
configurable: true,
|
|
23
|
+
writable: true,
|
|
24
|
+
value: void 0
|
|
25
|
+
});
|
|
26
|
+
Object.defineProperty(this, "modLFO", {
|
|
27
|
+
enumerable: true,
|
|
28
|
+
configurable: true,
|
|
29
|
+
writable: true,
|
|
30
|
+
value: void 0
|
|
31
|
+
});
|
|
32
|
+
Object.defineProperty(this, "modLFOGain", {
|
|
33
|
+
enumerable: true,
|
|
34
|
+
configurable: true,
|
|
35
|
+
writable: true,
|
|
36
|
+
value: void 0
|
|
37
|
+
});
|
|
38
|
+
Object.defineProperty(this, "vibLFO", {
|
|
39
|
+
enumerable: true,
|
|
40
|
+
configurable: true,
|
|
41
|
+
writable: true,
|
|
42
|
+
value: void 0
|
|
43
|
+
});
|
|
44
|
+
Object.defineProperty(this, "vibLFOGain", {
|
|
45
|
+
enumerable: true,
|
|
46
|
+
configurable: true,
|
|
47
|
+
writable: true,
|
|
48
|
+
value: void 0
|
|
49
|
+
});
|
|
50
|
+
this.noteNumber = noteNumber;
|
|
51
|
+
this.velocity = velocity;
|
|
52
|
+
this.startTime = startTime;
|
|
53
|
+
this.instrumentKey = instrumentKey;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
6
56
|
class MidyGM2 {
|
|
7
57
|
constructor(audioContext) {
|
|
8
58
|
Object.defineProperty(this, "ticksPerBeat", {
|
|
@@ -184,10 +234,8 @@ class MidyGM2 {
|
|
|
184
234
|
const pannerNode = new StereoPannerNode(audioContext, {
|
|
185
235
|
pan: MidyGM2.channelSettings.pan,
|
|
186
236
|
});
|
|
187
|
-
const modulationEffect = this.createModulationEffect(audioContext);
|
|
188
237
|
const reverbEffect = this.createReverbEffect(audioContext);
|
|
189
238
|
const chorusEffect = this.createChorusEffect(audioContext);
|
|
190
|
-
modulationEffect.lfo.start();
|
|
191
239
|
chorusEffect.lfo.start();
|
|
192
240
|
reverbEffect.dryGain.connect(pannerNode);
|
|
193
241
|
reverbEffect.wetGain.connect(pannerNode);
|
|
@@ -196,7 +244,6 @@ class MidyGM2 {
|
|
|
196
244
|
return {
|
|
197
245
|
gainNode,
|
|
198
246
|
pannerNode,
|
|
199
|
-
modulationEffect,
|
|
200
247
|
reverbEffect,
|
|
201
248
|
chorusEffect,
|
|
202
249
|
};
|
|
@@ -209,15 +256,18 @@ class MidyGM2 {
|
|
|
209
256
|
...this.setChannelAudioNodes(audioContext),
|
|
210
257
|
scheduledNotes: new Map(),
|
|
211
258
|
sostenutoNotes: new Map(),
|
|
259
|
+
channelPressure: {
|
|
260
|
+
...MidyGM2.controllerDestinationSettings,
|
|
261
|
+
},
|
|
212
262
|
};
|
|
213
263
|
});
|
|
214
264
|
return channels;
|
|
215
265
|
}
|
|
216
|
-
async createNoteBuffer(
|
|
217
|
-
const sampleEnd =
|
|
266
|
+
async createNoteBuffer(instrumentKey, isSF3) {
|
|
267
|
+
const sampleEnd = instrumentKey.sample.length + instrumentKey.end;
|
|
218
268
|
if (isSF3) {
|
|
219
|
-
const sample = new Uint8Array(
|
|
220
|
-
sample.set(
|
|
269
|
+
const sample = new Uint8Array(instrumentKey.sample.length);
|
|
270
|
+
sample.set(instrumentKey.sample);
|
|
221
271
|
const audioBuffer = await this.audioContext.decodeAudioData(sample.buffer);
|
|
222
272
|
for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {
|
|
223
273
|
const channelData = audioBuffer.getChannelData(channel);
|
|
@@ -226,26 +276,27 @@ class MidyGM2 {
|
|
|
226
276
|
return audioBuffer;
|
|
227
277
|
}
|
|
228
278
|
else {
|
|
229
|
-
const sample =
|
|
279
|
+
const sample = instrumentKey.sample.subarray(0, sampleEnd);
|
|
230
280
|
const floatSample = this.convertToFloat32Array(sample);
|
|
231
281
|
const audioBuffer = new AudioBuffer({
|
|
232
282
|
numberOfChannels: 1,
|
|
233
283
|
length: sample.length,
|
|
234
|
-
sampleRate:
|
|
284
|
+
sampleRate: instrumentKey.sampleRate,
|
|
235
285
|
});
|
|
236
286
|
const channelData = audioBuffer.getChannelData(0);
|
|
237
287
|
channelData.set(floatSample);
|
|
238
288
|
return audioBuffer;
|
|
239
289
|
}
|
|
240
290
|
}
|
|
241
|
-
async createNoteBufferNode(
|
|
291
|
+
async createNoteBufferNode(instrumentKey, isSF3) {
|
|
242
292
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
243
|
-
const audioBuffer = await this.createNoteBuffer(
|
|
293
|
+
const audioBuffer = await this.createNoteBuffer(instrumentKey, isSF3);
|
|
244
294
|
bufferSource.buffer = audioBuffer;
|
|
245
|
-
bufferSource.loop =
|
|
295
|
+
bufferSource.loop = instrumentKey.sampleModes % 2 !== 0;
|
|
246
296
|
if (bufferSource.loop) {
|
|
247
|
-
bufferSource.loopStart =
|
|
248
|
-
|
|
297
|
+
bufferSource.loopStart = instrumentKey.loopStart /
|
|
298
|
+
instrumentKey.sampleRate;
|
|
299
|
+
bufferSource.loopEnd = instrumentKey.loopEnd / instrumentKey.sampleRate;
|
|
249
300
|
}
|
|
250
301
|
return bufferSource;
|
|
251
302
|
}
|
|
@@ -263,9 +314,6 @@ class MidyGM2 {
|
|
|
263
314
|
if (event.startTime > t + this.lookAhead)
|
|
264
315
|
break;
|
|
265
316
|
switch (event.type) {
|
|
266
|
-
case "controller":
|
|
267
|
-
this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
|
|
268
|
-
break;
|
|
269
317
|
case "noteOn":
|
|
270
318
|
if (event.velocity !== 0) {
|
|
271
319
|
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
|
|
@@ -279,9 +327,18 @@ class MidyGM2 {
|
|
|
279
327
|
}
|
|
280
328
|
break;
|
|
281
329
|
}
|
|
330
|
+
case "controller":
|
|
331
|
+
this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
|
|
332
|
+
break;
|
|
282
333
|
case "programChange":
|
|
283
334
|
this.handleProgramChange(event.channel, event.programNumber);
|
|
284
335
|
break;
|
|
336
|
+
case "channelAftertouch":
|
|
337
|
+
this.handleChannelPressure(event.channel, event.amount);
|
|
338
|
+
break;
|
|
339
|
+
case "pitchBend":
|
|
340
|
+
this.handlePitchBend(event.channel, event.value);
|
|
341
|
+
break;
|
|
285
342
|
case "sysEx":
|
|
286
343
|
this.handleSysEx(event.data);
|
|
287
344
|
}
|
|
@@ -518,30 +575,26 @@ class MidyGM2 {
|
|
|
518
575
|
const now = this.audioContext.currentTime;
|
|
519
576
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
520
577
|
}
|
|
521
|
-
getActiveNotes(channel) {
|
|
578
|
+
getActiveNotes(channel, time) {
|
|
522
579
|
const activeNotes = new Map();
|
|
523
|
-
channel.scheduledNotes.forEach((
|
|
524
|
-
const activeNote = this.
|
|
580
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
581
|
+
const activeNote = this.getActiveNote(noteList, time);
|
|
525
582
|
if (activeNote) {
|
|
526
583
|
activeNotes.set(activeNote.noteNumber, activeNote);
|
|
527
584
|
}
|
|
528
585
|
});
|
|
529
586
|
return activeNotes;
|
|
530
587
|
}
|
|
531
|
-
|
|
532
|
-
for (let i =
|
|
533
|
-
const
|
|
534
|
-
if (
|
|
535
|
-
return
|
|
588
|
+
getActiveNote(noteList, time) {
|
|
589
|
+
for (let i = noteList.length - 1; i >= 0; i--) {
|
|
590
|
+
const note = noteList[i];
|
|
591
|
+
if (!note)
|
|
592
|
+
return;
|
|
593
|
+
if (time < note.startTime)
|
|
594
|
+
continue;
|
|
595
|
+
return (note.ending) ? null : note;
|
|
536
596
|
}
|
|
537
|
-
|
|
538
|
-
createModulationEffect(audioContext) {
|
|
539
|
-
const lfo = new OscillatorNode(audioContext, {
|
|
540
|
-
frequency: 5,
|
|
541
|
-
});
|
|
542
|
-
return {
|
|
543
|
-
lfo,
|
|
544
|
-
};
|
|
597
|
+
return noteList[0];
|
|
545
598
|
}
|
|
546
599
|
createReverbEffect(audioContext, options = {}) {
|
|
547
600
|
const { decay = 0.8, preDecay = 0, } = options;
|
|
@@ -645,79 +698,99 @@ class MidyGM2 {
|
|
|
645
698
|
centToHz(cent) {
|
|
646
699
|
return 8.176 * Math.pow(2, cent / 1200);
|
|
647
700
|
}
|
|
648
|
-
|
|
701
|
+
calcSemitoneOffset(channel) {
|
|
649
702
|
const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
|
|
650
703
|
const channelTuning = channel.coarseTuning + channel.fineTuning;
|
|
651
704
|
const tuning = masterTuning + channelTuning;
|
|
652
|
-
|
|
653
|
-
|
|
705
|
+
return channel.pitchBend * channel.pitchBendRange + tuning;
|
|
706
|
+
}
|
|
707
|
+
calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset) {
|
|
708
|
+
return instrumentKey.playbackRate(noteNumber) *
|
|
654
709
|
Math.pow(2, semitoneOffset / 12);
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
710
|
+
}
|
|
711
|
+
setVolumeEnvelope(channel, note) {
|
|
712
|
+
const { instrumentKey, startTime, velocity } = note;
|
|
713
|
+
note.gainNode = new GainNode(this.audioContext, {
|
|
659
714
|
gain: 0,
|
|
660
715
|
});
|
|
661
716
|
let volume = (velocity / 127) * channel.volume * channel.expression;
|
|
662
717
|
if (volume === 0)
|
|
663
718
|
volume = 1e-6; // exponentialRampToValueAtTime() requires a non-zero value
|
|
664
|
-
const attackVolume = this.cbToRatio(-
|
|
665
|
-
|
|
666
|
-
const
|
|
667
|
-
const
|
|
668
|
-
const
|
|
669
|
-
const
|
|
670
|
-
|
|
719
|
+
const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation) *
|
|
720
|
+
volume;
|
|
721
|
+
const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
|
|
722
|
+
const volDelay = startTime + instrumentKey.volDelay;
|
|
723
|
+
const volAttack = volDelay + instrumentKey.volAttack;
|
|
724
|
+
const volHold = volAttack + instrumentKey.volHold;
|
|
725
|
+
const volDecay = volHold + instrumentKey.volDecay;
|
|
726
|
+
note.gainNode.gain
|
|
671
727
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
672
728
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
673
729
|
.setValueAtTime(attackVolume, volHold)
|
|
674
730
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
675
|
-
|
|
731
|
+
}
|
|
732
|
+
setFilterEnvelope(channel, note) {
|
|
733
|
+
const { instrumentKey, startTime, noteNumber } = note;
|
|
676
734
|
const softPedalFactor = 1 -
|
|
677
735
|
(0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
|
|
678
736
|
const maxFreq = this.audioContext.sampleRate / 2;
|
|
679
|
-
const baseFreq = this.centToHz(
|
|
680
|
-
|
|
737
|
+
const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
|
|
738
|
+
softPedalFactor;
|
|
739
|
+
const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
|
|
681
740
|
const sustainFreq = (baseFreq +
|
|
682
|
-
(peekFreq - baseFreq) * (1 -
|
|
741
|
+
(peekFreq - baseFreq) * (1 - instrumentKey.modSustain)) * softPedalFactor;
|
|
742
|
+
const modDelay = startTime + instrumentKey.modDelay;
|
|
743
|
+
const modAttack = modDelay + instrumentKey.modAttack;
|
|
744
|
+
const modHold = modAttack + instrumentKey.modHold;
|
|
745
|
+
const modDecay = modHold + instrumentKey.modDecay;
|
|
683
746
|
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
684
747
|
const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
|
|
685
748
|
const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
|
|
686
|
-
|
|
749
|
+
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
687
750
|
type: "lowpass",
|
|
688
|
-
Q:
|
|
751
|
+
Q: instrumentKey.initialFilterQ / 10, // dB
|
|
689
752
|
frequency: adjustedBaseFreq,
|
|
690
753
|
});
|
|
691
|
-
|
|
692
|
-
const modAttack = modDelay + noteInfo.modAttack;
|
|
693
|
-
const modHold = modAttack + noteInfo.modHold;
|
|
694
|
-
const modDecay = modHold + noteInfo.modDecay;
|
|
695
|
-
filterNode.frequency
|
|
754
|
+
note.filterNode.frequency
|
|
696
755
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
697
756
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
698
757
|
.setValueAtTime(adjustedPeekFreq, modHold)
|
|
699
758
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
700
|
-
|
|
759
|
+
note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modEnvToPitch, modDelay);
|
|
760
|
+
}
|
|
761
|
+
startModulation(channel, note, time) {
|
|
762
|
+
const { instrumentKey } = note;
|
|
763
|
+
note.modLFOGain = new GainNode(this.audioContext, {
|
|
764
|
+
gain: this.cbToRatio(instrumentKey.modLfoToVolume) * channel.modulation,
|
|
765
|
+
});
|
|
766
|
+
note.modLFO = new OscillatorNode(this.audioContext, {
|
|
767
|
+
frequency: this.centToHz(instrumentKey.freqModLFO),
|
|
768
|
+
});
|
|
769
|
+
note.modLFO.start(time);
|
|
770
|
+
note.filterNode.frequency.setValueAtTime(note.filterNode.frequency.value + instrumentKey.modLfoToFilterFc, time);
|
|
771
|
+
note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modLfoToPitch, time);
|
|
772
|
+
note.modLFO.connect(note.modLFOGain);
|
|
773
|
+
note.modLFOGain.connect(note.bufferSource.detune);
|
|
774
|
+
}
|
|
775
|
+
async createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3) {
|
|
776
|
+
const semitoneOffset = this.calcSemitoneOffset(channel);
|
|
777
|
+
const note = new Note(noteNumber, velocity, startTime, instrumentKey);
|
|
778
|
+
note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
|
|
779
|
+
note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
|
|
780
|
+
this.setVolumeEnvelope(channel, note);
|
|
781
|
+
this.setFilterEnvelope(channel, note);
|
|
701
782
|
if (channel.modulation > 0) {
|
|
702
|
-
const
|
|
703
|
-
|
|
704
|
-
lfoGain = new GainNode(this.audioContext, {
|
|
705
|
-
gain: 0,
|
|
706
|
-
});
|
|
707
|
-
lfoGain.gain
|
|
708
|
-
.setValueAtTime(1e-6, vibratoDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
709
|
-
.exponentialRampToValueAtTime(channel.modulation, vibratoAttack);
|
|
710
|
-
channel.modulationEffect.lfo.connect(lfoGain);
|
|
711
|
-
lfoGain.connect(bufferSource.detune);
|
|
783
|
+
const delayModLFO = startTime + instrumentKey.delayModLFO;
|
|
784
|
+
this.startModulation(channel, note, delayModLFO);
|
|
712
785
|
}
|
|
713
|
-
bufferSource.connect(filterNode);
|
|
714
|
-
filterNode.connect(gainNode);
|
|
715
786
|
if (this.mono && channel.currentBufferSource) {
|
|
716
787
|
channel.currentBufferSource.stop(startTime);
|
|
717
|
-
channel.currentBufferSource = bufferSource;
|
|
788
|
+
channel.currentBufferSource = note.bufferSource;
|
|
718
789
|
}
|
|
719
|
-
bufferSource.
|
|
720
|
-
|
|
790
|
+
note.bufferSource.connect(note.filterNode);
|
|
791
|
+
note.filterNode.connect(note.gainNode);
|
|
792
|
+
note.bufferSource.start(startTime, instrumentKey.start / instrumentKey.sampleRate);
|
|
793
|
+
return note;
|
|
721
794
|
}
|
|
722
795
|
calcBank(channel, channelNumber) {
|
|
723
796
|
if (channel.bankMSB === 121) {
|
|
@@ -736,36 +809,20 @@ class MidyGM2 {
|
|
|
736
809
|
return;
|
|
737
810
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
738
811
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
739
|
-
const
|
|
740
|
-
if (!
|
|
812
|
+
const instrumentKey = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
|
|
813
|
+
if (!instrumentKey)
|
|
741
814
|
return;
|
|
742
|
-
const
|
|
743
|
-
|
|
744
|
-
this.connectNoteEffects(channel, gainNode);
|
|
815
|
+
const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
|
|
816
|
+
this.connectNoteEffects(channel, note.gainNode);
|
|
745
817
|
if (channel.sostenutoPedal) {
|
|
746
|
-
channel.sostenutoNotes.set(noteNumber,
|
|
747
|
-
gainNode,
|
|
748
|
-
filterNode,
|
|
749
|
-
bufferSource,
|
|
750
|
-
noteNumber,
|
|
751
|
-
noteInfo,
|
|
752
|
-
});
|
|
818
|
+
channel.sostenutoNotes.set(noteNumber, note);
|
|
753
819
|
}
|
|
754
820
|
const scheduledNotes = channel.scheduledNotes;
|
|
755
|
-
const scheduledNote = {
|
|
756
|
-
bufferSource,
|
|
757
|
-
filterNode,
|
|
758
|
-
gainNode,
|
|
759
|
-
lfoGain,
|
|
760
|
-
noteInfo,
|
|
761
|
-
noteNumber,
|
|
762
|
-
startTime,
|
|
763
|
-
};
|
|
764
821
|
if (scheduledNotes.has(noteNumber)) {
|
|
765
|
-
scheduledNotes.get(noteNumber).push(
|
|
822
|
+
scheduledNotes.get(noteNumber).push(note);
|
|
766
823
|
}
|
|
767
824
|
else {
|
|
768
|
-
scheduledNotes.set(noteNumber, [
|
|
825
|
+
scheduledNotes.set(noteNumber, [note]);
|
|
769
826
|
}
|
|
770
827
|
}
|
|
771
828
|
noteOn(channelNumber, noteNumber, velocity) {
|
|
@@ -787,15 +844,15 @@ class MidyGM2 {
|
|
|
787
844
|
continue;
|
|
788
845
|
if (targetNote.ending)
|
|
789
846
|
continue;
|
|
790
|
-
const { bufferSource, filterNode, gainNode,
|
|
847
|
+
const { bufferSource, filterNode, gainNode, modLFO, modLFOGain, instrumentKey, } = targetNote;
|
|
791
848
|
const velocityRate = (velocity + 127) / 127;
|
|
792
|
-
const volEndTime = stopTime +
|
|
849
|
+
const volEndTime = stopTime + instrumentKey.volRelease * velocityRate;
|
|
793
850
|
gainNode.gain.cancelScheduledValues(stopTime);
|
|
794
851
|
gainNode.gain.linearRampToValueAtTime(0, volEndTime);
|
|
795
852
|
const maxFreq = this.audioContext.sampleRate / 2;
|
|
796
|
-
const baseFreq = this.centToHz(
|
|
853
|
+
const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
|
|
797
854
|
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
798
|
-
const modEndTime = stopTime +
|
|
855
|
+
const modEndTime = stopTime + instrumentKey.modRelease * velocityRate;
|
|
799
856
|
filterNode.frequency
|
|
800
857
|
.cancelScheduledValues(stopTime)
|
|
801
858
|
.linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
|
|
@@ -809,8 +866,10 @@ class MidyGM2 {
|
|
|
809
866
|
bufferSource.disconnect(0);
|
|
810
867
|
filterNode.disconnect(0);
|
|
811
868
|
gainNode.disconnect(0);
|
|
812
|
-
if (
|
|
813
|
-
|
|
869
|
+
if (modLFOGain)
|
|
870
|
+
modLFOGain.disconnect(0);
|
|
871
|
+
if (modLFO)
|
|
872
|
+
modLFO.stop();
|
|
814
873
|
resolve();
|
|
815
874
|
};
|
|
816
875
|
bufferSource.stop(volEndTime);
|
|
@@ -858,8 +917,6 @@ class MidyGM2 {
|
|
|
858
917
|
return this.releaseNote(channelNumber, data1, data2);
|
|
859
918
|
case 0x90:
|
|
860
919
|
return this.noteOn(channelNumber, data1, data2);
|
|
861
|
-
case 0xA0:
|
|
862
|
-
return this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
|
|
863
920
|
case 0xB0:
|
|
864
921
|
return this.handleControlChange(channelNumber, data1, data2);
|
|
865
922
|
case 0xC0:
|
|
@@ -867,38 +924,48 @@ class MidyGM2 {
|
|
|
867
924
|
case 0xD0:
|
|
868
925
|
return this.handleChannelPressure(channelNumber, data1);
|
|
869
926
|
case 0xE0:
|
|
870
|
-
return this.
|
|
927
|
+
return this.handlePitchBendMessage(channelNumber, data1, data2);
|
|
871
928
|
default:
|
|
872
929
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
873
930
|
}
|
|
874
931
|
}
|
|
875
|
-
handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure) {
|
|
876
|
-
const now = this.audioContext.currentTime;
|
|
877
|
-
const channel = this.channels[channelNumber];
|
|
878
|
-
const scheduledNotes = channel.scheduledNotes.get(noteNumber);
|
|
879
|
-
pressure /= 127;
|
|
880
|
-
if (scheduledNotes) {
|
|
881
|
-
scheduledNotes.forEach((scheduledNote) => {
|
|
882
|
-
if (scheduledNote) {
|
|
883
|
-
const { initialAttenuation } = scheduledNote.noteInfo;
|
|
884
|
-
const gain = this.cbToRatio(-initialAttenuation) * pressure;
|
|
885
|
-
scheduledNote.gainNode.gain.cancelScheduledValues(now);
|
|
886
|
-
scheduledNote.gainNode.gain.setValueAtTime(gain, now);
|
|
887
|
-
}
|
|
888
|
-
});
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
932
|
handleProgramChange(channelNumber, program) {
|
|
892
933
|
const channel = this.channels[channelNumber];
|
|
893
934
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
894
935
|
channel.program = program;
|
|
895
936
|
}
|
|
896
937
|
handleChannelPressure(channelNumber, pressure) {
|
|
897
|
-
this.
|
|
938
|
+
const now = this.audioContext.currentTime;
|
|
939
|
+
const channel = this.channels[channelNumber];
|
|
940
|
+
pressure /= 64;
|
|
941
|
+
channel.channelPressure = pressure;
|
|
942
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
943
|
+
if (channel.channelPressure.amplitudeControl !== 1) {
|
|
944
|
+
activeNotes.forEach((activeNote) => {
|
|
945
|
+
const gain = activeNote.gainNode.gain.value;
|
|
946
|
+
activeNote.gainNode.gain
|
|
947
|
+
.cancelScheduledValues(now)
|
|
948
|
+
.setValueAtTime(gain * pressure, now);
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
953
|
+
const pitchBend = msb * 128 + lsb;
|
|
954
|
+
this.handlePitchBend(channelNumber, pitchBend);
|
|
898
955
|
}
|
|
899
|
-
handlePitchBend(channelNumber,
|
|
900
|
-
const
|
|
901
|
-
this.channels[channelNumber]
|
|
956
|
+
handlePitchBend(channelNumber, pitchBend) {
|
|
957
|
+
const now = this.audioContext.currentTime;
|
|
958
|
+
const channel = this.channels[channelNumber];
|
|
959
|
+
channel.pitchBend = (pitchBend - 8192) / 8192;
|
|
960
|
+
const semitoneOffset = this.calcSemitoneOffset(channel);
|
|
961
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
962
|
+
activeNotes.forEach((activeNote) => {
|
|
963
|
+
const { bufferSource, instrumentKey, noteNumber } = activeNote;
|
|
964
|
+
const playbackRate = calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
|
|
965
|
+
bufferSource.playbackRate
|
|
966
|
+
.cancelScheduledValues(now)
|
|
967
|
+
.setValueAtTime(playbackRate * pressure, now);
|
|
968
|
+
});
|
|
902
969
|
}
|
|
903
970
|
handleControlChange(channelNumber, controller, value) {
|
|
904
971
|
switch (controller) {
|
|
@@ -958,9 +1025,20 @@ class MidyGM2 {
|
|
|
958
1025
|
this.channels[channelNumber].bankMSB = msb;
|
|
959
1026
|
}
|
|
960
1027
|
setModulation(channelNumber, modulation) {
|
|
1028
|
+
const now = this.audioContext.currentTime;
|
|
961
1029
|
const channel = this.channels[channelNumber];
|
|
962
1030
|
channel.modulation = (modulation / 127) *
|
|
963
1031
|
(channel.modulationDepthRange * 100);
|
|
1032
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
1033
|
+
activeNotes.forEach((activeNote) => {
|
|
1034
|
+
if (activeNote.modLFO) {
|
|
1035
|
+
activeNote.gainNode.gain.setValueAtTime(this.cbToRatio(activeNote.instrumentKey.modLfoToVolume) *
|
|
1036
|
+
channel.modulation, now);
|
|
1037
|
+
}
|
|
1038
|
+
else {
|
|
1039
|
+
this.startModulation(channel, activeNote, now);
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
964
1042
|
}
|
|
965
1043
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
966
1044
|
this.channels[channelNumber].portamentoTime = portamentoTime / 127;
|
|
@@ -1066,8 +1144,8 @@ class MidyGM2 {
|
|
|
1066
1144
|
const velocity = 0;
|
|
1067
1145
|
const stopPedal = true;
|
|
1068
1146
|
const promises = [];
|
|
1069
|
-
channel.scheduledNotes.forEach((
|
|
1070
|
-
const activeNote = this.
|
|
1147
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1148
|
+
const activeNote = this.getActiveNote(noteList, now);
|
|
1071
1149
|
if (activeNote) {
|
|
1072
1150
|
const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
|
|
1073
1151
|
promises.push(notePromise);
|
|
@@ -1084,8 +1162,8 @@ class MidyGM2 {
|
|
|
1084
1162
|
const velocity = 0;
|
|
1085
1163
|
const stopPedal = false;
|
|
1086
1164
|
const promises = [];
|
|
1087
|
-
channel.scheduledNotes.forEach((
|
|
1088
|
-
const activeNote = this.
|
|
1165
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1166
|
+
const activeNote = this.getActiveNote(noteList, now);
|
|
1089
1167
|
if (activeNote) {
|
|
1090
1168
|
const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
|
|
1091
1169
|
promises.push(notePromise);
|
|
@@ -1171,10 +1249,10 @@ class MidyGM2 {
|
|
|
1171
1249
|
switch (data[3]) {
|
|
1172
1250
|
// case 1:
|
|
1173
1251
|
// // TODO
|
|
1174
|
-
// return this.
|
|
1252
|
+
// return this.setChannelPressure();
|
|
1175
1253
|
// case 3:
|
|
1176
1254
|
// // TODO
|
|
1177
|
-
// return this.
|
|
1255
|
+
// return this.setControlChange();
|
|
1178
1256
|
default:
|
|
1179
1257
|
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
1180
1258
|
}
|
|
@@ -1193,20 +1271,25 @@ class MidyGM2 {
|
|
|
1193
1271
|
}
|
|
1194
1272
|
}
|
|
1195
1273
|
handleMasterVolumeSysEx(data) {
|
|
1196
|
-
const volume = (data[5] * 128 + data[4]
|
|
1274
|
+
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
1197
1275
|
this.handleMasterVolume(volume);
|
|
1198
1276
|
}
|
|
1199
1277
|
handleMasterVolume(volume) {
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1278
|
+
if (volume < 0 && 1 < volume) {
|
|
1279
|
+
console.error("Master Volume is out of range");
|
|
1280
|
+
}
|
|
1281
|
+
else {
|
|
1282
|
+
const now = this.audioContext.currentTime;
|
|
1283
|
+
this.masterGain.gain.cancelScheduledValues(now);
|
|
1284
|
+
this.masterGain.gain.setValueAtTime(volume * volume, now);
|
|
1285
|
+
}
|
|
1203
1286
|
}
|
|
1204
1287
|
handleMasterFineTuningSysEx(data) {
|
|
1205
1288
|
const fineTuning = (data[5] * 128 + data[4] - 8192) / 8192;
|
|
1206
1289
|
this.handleMasterFineTuning(fineTuning);
|
|
1207
1290
|
}
|
|
1208
1291
|
handleMasterFineTuning(fineTuning) {
|
|
1209
|
-
if (fineTuning <
|
|
1292
|
+
if (fineTuning < -1 && 1 < fineTuning) {
|
|
1210
1293
|
console.error("Master Fine Tuning value is out of range");
|
|
1211
1294
|
}
|
|
1212
1295
|
else {
|
|
@@ -1262,9 +1345,6 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
1262
1345
|
portamentoTime: 0,
|
|
1263
1346
|
reverb: 0,
|
|
1264
1347
|
chorus: 0,
|
|
1265
|
-
vibratoRate: 5,
|
|
1266
|
-
vibratoDepth: 0.5,
|
|
1267
|
-
vibratoDelay: 2.5,
|
|
1268
1348
|
bank: 121 * 128,
|
|
1269
1349
|
bankMSB: 121,
|
|
1270
1350
|
bankLSB: 0,
|
|
@@ -1294,3 +1374,16 @@ Object.defineProperty(MidyGM2, "effectSettings", {
|
|
|
1294
1374
|
pitchBendRange: 2,
|
|
1295
1375
|
}
|
|
1296
1376
|
});
|
|
1377
|
+
Object.defineProperty(MidyGM2, "controllerDestinationSettings", {
|
|
1378
|
+
enumerable: true,
|
|
1379
|
+
configurable: true,
|
|
1380
|
+
writable: true,
|
|
1381
|
+
value: {
|
|
1382
|
+
pitchControl: 0,
|
|
1383
|
+
filterCutoffControl: 0,
|
|
1384
|
+
amplitudeControl: 1,
|
|
1385
|
+
lfoPitchDepth: 0,
|
|
1386
|
+
lfoFilterDepth: 0,
|
|
1387
|
+
lfoAmplitudeDepth: 0,
|
|
1388
|
+
}
|
|
1389
|
+
});
|