@marmooo/midy 0.0.4 → 0.0.6
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 +36 -42
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +214 -148
- package/esm/midy-GM2.d.ts +133 -25
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +230 -163
- package/esm/midy-GMLite.d.ts +37 -43
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +215 -149
- package/esm/midy.d.ts +41 -33
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +263 -179
- 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 +36 -42
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +214 -148
- package/script/midy-GM2.d.ts +133 -25
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +230 -163
- package/script/midy-GMLite.d.ts +37 -43
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +215 -149
- package/script/midy.d.ts +41 -33
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +263 -179
- 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
|
};
|
|
@@ -204,23 +251,23 @@ class MidyGM2 {
|
|
|
204
251
|
createChannels(audioContext) {
|
|
205
252
|
const channels = Array.from({ length: 16 }, () => {
|
|
206
253
|
return {
|
|
207
|
-
...
|
|
208
|
-
...
|
|
254
|
+
...MidyGM2.channelSettings,
|
|
255
|
+
...MidyGM2.effectSettings,
|
|
209
256
|
...this.setChannelAudioNodes(audioContext),
|
|
210
257
|
scheduledNotes: new Map(),
|
|
211
258
|
sostenutoNotes: new Map(),
|
|
212
259
|
channelPressure: {
|
|
213
|
-
...
|
|
260
|
+
...MidyGM2.controllerDestinationSettings,
|
|
214
261
|
},
|
|
215
262
|
};
|
|
216
263
|
});
|
|
217
264
|
return channels;
|
|
218
265
|
}
|
|
219
|
-
async createNoteBuffer(
|
|
220
|
-
const sampleEnd =
|
|
266
|
+
async createNoteBuffer(instrumentKey, isSF3) {
|
|
267
|
+
const sampleEnd = instrumentKey.sample.length + instrumentKey.end;
|
|
221
268
|
if (isSF3) {
|
|
222
|
-
const sample = new Uint8Array(
|
|
223
|
-
sample.set(
|
|
269
|
+
const sample = new Uint8Array(instrumentKey.sample.length);
|
|
270
|
+
sample.set(instrumentKey.sample);
|
|
224
271
|
const audioBuffer = await this.audioContext.decodeAudioData(sample.buffer);
|
|
225
272
|
for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {
|
|
226
273
|
const channelData = audioBuffer.getChannelData(channel);
|
|
@@ -229,26 +276,27 @@ class MidyGM2 {
|
|
|
229
276
|
return audioBuffer;
|
|
230
277
|
}
|
|
231
278
|
else {
|
|
232
|
-
const sample =
|
|
279
|
+
const sample = instrumentKey.sample.subarray(0, sampleEnd);
|
|
233
280
|
const floatSample = this.convertToFloat32Array(sample);
|
|
234
281
|
const audioBuffer = new AudioBuffer({
|
|
235
282
|
numberOfChannels: 1,
|
|
236
283
|
length: sample.length,
|
|
237
|
-
sampleRate:
|
|
284
|
+
sampleRate: instrumentKey.sampleRate,
|
|
238
285
|
});
|
|
239
286
|
const channelData = audioBuffer.getChannelData(0);
|
|
240
287
|
channelData.set(floatSample);
|
|
241
288
|
return audioBuffer;
|
|
242
289
|
}
|
|
243
290
|
}
|
|
244
|
-
async createNoteBufferNode(
|
|
291
|
+
async createNoteBufferNode(instrumentKey, isSF3) {
|
|
245
292
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
246
|
-
const audioBuffer = await this.createNoteBuffer(
|
|
293
|
+
const audioBuffer = await this.createNoteBuffer(instrumentKey, isSF3);
|
|
247
294
|
bufferSource.buffer = audioBuffer;
|
|
248
|
-
bufferSource.loop =
|
|
295
|
+
bufferSource.loop = instrumentKey.sampleModes % 2 !== 0;
|
|
249
296
|
if (bufferSource.loop) {
|
|
250
|
-
bufferSource.loopStart =
|
|
251
|
-
|
|
297
|
+
bufferSource.loopStart = instrumentKey.loopStart /
|
|
298
|
+
instrumentKey.sampleRate;
|
|
299
|
+
bufferSource.loopEnd = instrumentKey.loopEnd / instrumentKey.sampleRate;
|
|
252
300
|
}
|
|
253
301
|
return bufferSource;
|
|
254
302
|
}
|
|
@@ -289,7 +337,7 @@ class MidyGM2 {
|
|
|
289
337
|
this.handleChannelPressure(event.channel, event.amount);
|
|
290
338
|
break;
|
|
291
339
|
case "pitchBend":
|
|
292
|
-
this.
|
|
340
|
+
this.setPitchBend(event.channel, event.value);
|
|
293
341
|
break;
|
|
294
342
|
case "sysEx":
|
|
295
343
|
this.handleSysEx(event.data);
|
|
@@ -369,7 +417,6 @@ class MidyGM2 {
|
|
|
369
417
|
const tmpChannels = new Array(16);
|
|
370
418
|
for (let i = 0; i < tmpChannels.length; i++) {
|
|
371
419
|
tmpChannels[i] = {
|
|
372
|
-
durationTicks: new Map(),
|
|
373
420
|
programNumber: -1,
|
|
374
421
|
bankMSB: this.channels[i].bankMSB,
|
|
375
422
|
bankLSB: this.channels[i].bankLSB,
|
|
@@ -399,16 +446,6 @@ class MidyGM2 {
|
|
|
399
446
|
}
|
|
400
447
|
channel.programNumber = 0;
|
|
401
448
|
}
|
|
402
|
-
channel.durationTicks.set(event.noteNumber, {
|
|
403
|
-
ticks: event.ticks,
|
|
404
|
-
noteOn: event,
|
|
405
|
-
});
|
|
406
|
-
break;
|
|
407
|
-
}
|
|
408
|
-
case "noteOff": {
|
|
409
|
-
const { ticks, noteOn } = tmpChannels[event.channel].durationTicks
|
|
410
|
-
.get(event.noteNumber);
|
|
411
|
-
noteOn.durationTicks = event.ticks - ticks;
|
|
412
449
|
break;
|
|
413
450
|
}
|
|
414
451
|
case "controller":
|
|
@@ -443,8 +480,8 @@ class MidyGM2 {
|
|
|
443
480
|
});
|
|
444
481
|
});
|
|
445
482
|
const priority = {
|
|
446
|
-
|
|
447
|
-
|
|
483
|
+
controller: 0,
|
|
484
|
+
sysEx: 1,
|
|
448
485
|
};
|
|
449
486
|
timeline.sort((a, b) => {
|
|
450
487
|
if (a.ticks !== b.ticks)
|
|
@@ -527,30 +564,26 @@ class MidyGM2 {
|
|
|
527
564
|
const now = this.audioContext.currentTime;
|
|
528
565
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
529
566
|
}
|
|
530
|
-
getActiveNotes(channel) {
|
|
567
|
+
getActiveNotes(channel, time) {
|
|
531
568
|
const activeNotes = new Map();
|
|
532
|
-
channel.scheduledNotes.forEach((
|
|
533
|
-
const activeNote = this.
|
|
569
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
570
|
+
const activeNote = this.getActiveNote(noteList, time);
|
|
534
571
|
if (activeNote) {
|
|
535
572
|
activeNotes.set(activeNote.noteNumber, activeNote);
|
|
536
573
|
}
|
|
537
574
|
});
|
|
538
575
|
return activeNotes;
|
|
539
576
|
}
|
|
540
|
-
|
|
541
|
-
for (let i =
|
|
542
|
-
const
|
|
543
|
-
if (
|
|
544
|
-
return
|
|
577
|
+
getActiveNote(noteList, time) {
|
|
578
|
+
for (let i = noteList.length - 1; i >= 0; i--) {
|
|
579
|
+
const note = noteList[i];
|
|
580
|
+
if (!note)
|
|
581
|
+
return;
|
|
582
|
+
if (time < note.startTime)
|
|
583
|
+
continue;
|
|
584
|
+
return (note.ending) ? null : note;
|
|
545
585
|
}
|
|
546
|
-
|
|
547
|
-
createModulationEffect(audioContext) {
|
|
548
|
-
const lfo = new OscillatorNode(audioContext, {
|
|
549
|
-
frequency: 5,
|
|
550
|
-
});
|
|
551
|
-
return {
|
|
552
|
-
lfo,
|
|
553
|
-
};
|
|
586
|
+
return noteList[0];
|
|
554
587
|
}
|
|
555
588
|
createReverbEffect(audioContext, options = {}) {
|
|
556
589
|
const { decay = 0.8, preDecay = 0, } = options;
|
|
@@ -586,12 +619,8 @@ class MidyGM2 {
|
|
|
586
619
|
}
|
|
587
620
|
createChorusEffect(audioContext, options = {}) {
|
|
588
621
|
const { chorusCount = 2, chorusRate = 0.6, chorusDepth = 0.15, delay = 0.01, variance = delay * 0.1, } = options;
|
|
589
|
-
const lfo = new OscillatorNode(audioContext, {
|
|
590
|
-
|
|
591
|
-
});
|
|
592
|
-
const lfoGain = new GainNode(audioContext, {
|
|
593
|
-
gain: chorusDepth,
|
|
594
|
-
});
|
|
622
|
+
const lfo = new OscillatorNode(audioContext, { frequency: chorusRate });
|
|
623
|
+
const lfoGain = new GainNode(audioContext, { gain: chorusDepth });
|
|
595
624
|
const chorusGains = [];
|
|
596
625
|
const delayNodes = [];
|
|
597
626
|
const baseGain = 1 / chorusCount;
|
|
@@ -602,9 +631,7 @@ class MidyGM2 {
|
|
|
602
631
|
maxDelayTime: delayTime,
|
|
603
632
|
});
|
|
604
633
|
delayNodes.push(delayNode);
|
|
605
|
-
const chorusGain = new GainNode(audioContext, {
|
|
606
|
-
gain: baseGain,
|
|
607
|
-
});
|
|
634
|
+
const chorusGain = new GainNode(audioContext, { gain: baseGain });
|
|
608
635
|
chorusGains.push(chorusGain);
|
|
609
636
|
lfo.connect(lfoGain);
|
|
610
637
|
lfoGain.connect(delayNode.delayTime);
|
|
@@ -660,77 +687,91 @@ class MidyGM2 {
|
|
|
660
687
|
const tuning = masterTuning + channelTuning;
|
|
661
688
|
return channel.pitchBend * channel.pitchBendRange + tuning;
|
|
662
689
|
}
|
|
663
|
-
calcPlaybackRate(
|
|
664
|
-
return
|
|
690
|
+
calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset) {
|
|
691
|
+
return instrumentKey.playbackRate(noteNumber) *
|
|
692
|
+
Math.pow(2, semitoneOffset / 12);
|
|
665
693
|
}
|
|
666
|
-
|
|
667
|
-
const
|
|
668
|
-
|
|
669
|
-
bufferSource.playbackRate.value = this.calcPlaybackRate(noteInfo, noteNumber, semitoneOffset);
|
|
670
|
-
// volume envelope
|
|
671
|
-
const gainNode = new GainNode(this.audioContext, {
|
|
672
|
-
gain: 0,
|
|
673
|
-
});
|
|
694
|
+
setVolumeEnvelope(channel, note) {
|
|
695
|
+
const { instrumentKey, startTime, velocity } = note;
|
|
696
|
+
note.gainNode = new GainNode(this.audioContext, { gain: 0 });
|
|
674
697
|
let volume = (velocity / 127) * channel.volume * channel.expression;
|
|
675
698
|
if (volume === 0)
|
|
676
699
|
volume = 1e-6; // exponentialRampToValueAtTime() requires a non-zero value
|
|
677
|
-
const attackVolume = this.cbToRatio(-
|
|
678
|
-
|
|
679
|
-
const
|
|
680
|
-
const
|
|
681
|
-
const
|
|
682
|
-
const
|
|
683
|
-
|
|
700
|
+
const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation) *
|
|
701
|
+
volume;
|
|
702
|
+
const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
|
|
703
|
+
const volDelay = startTime + instrumentKey.volDelay;
|
|
704
|
+
const volAttack = volDelay + instrumentKey.volAttack;
|
|
705
|
+
const volHold = volAttack + instrumentKey.volHold;
|
|
706
|
+
const volDecay = volHold + instrumentKey.volDecay;
|
|
707
|
+
note.gainNode.gain
|
|
684
708
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
685
709
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
686
710
|
.setValueAtTime(attackVolume, volHold)
|
|
687
711
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
688
|
-
|
|
712
|
+
}
|
|
713
|
+
setFilterEnvelope(channel, note) {
|
|
714
|
+
const { instrumentKey, startTime, noteNumber } = note;
|
|
689
715
|
const softPedalFactor = 1 -
|
|
690
716
|
(0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
|
|
691
717
|
const maxFreq = this.audioContext.sampleRate / 2;
|
|
692
|
-
const baseFreq = this.centToHz(
|
|
693
|
-
|
|
718
|
+
const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
|
|
719
|
+
softPedalFactor;
|
|
720
|
+
const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
|
|
694
721
|
const sustainFreq = (baseFreq +
|
|
695
|
-
(peekFreq - baseFreq) * (1 -
|
|
722
|
+
(peekFreq - baseFreq) * (1 - instrumentKey.modSustain)) * softPedalFactor;
|
|
723
|
+
const modDelay = startTime + instrumentKey.modDelay;
|
|
724
|
+
const modAttack = modDelay + instrumentKey.modAttack;
|
|
725
|
+
const modHold = modAttack + instrumentKey.modHold;
|
|
726
|
+
const modDecay = modHold + instrumentKey.modDecay;
|
|
696
727
|
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
697
728
|
const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
|
|
698
729
|
const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
|
|
699
|
-
|
|
730
|
+
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
700
731
|
type: "lowpass",
|
|
701
|
-
Q:
|
|
732
|
+
Q: instrumentKey.initialFilterQ / 10, // dB
|
|
702
733
|
frequency: adjustedBaseFreq,
|
|
703
734
|
});
|
|
704
|
-
|
|
705
|
-
const modAttack = modDelay + noteInfo.modAttack;
|
|
706
|
-
const modHold = modAttack + noteInfo.modHold;
|
|
707
|
-
const modDecay = modHold + noteInfo.modDecay;
|
|
708
|
-
filterNode.frequency
|
|
735
|
+
note.filterNode.frequency
|
|
709
736
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
710
737
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
711
738
|
.setValueAtTime(adjustedPeekFreq, modHold)
|
|
712
739
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
713
|
-
|
|
740
|
+
note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modEnvToPitch, modDelay);
|
|
741
|
+
}
|
|
742
|
+
startModulation(channel, note, time) {
|
|
743
|
+
const { instrumentKey } = note;
|
|
744
|
+
note.modLFOGain = new GainNode(this.audioContext, {
|
|
745
|
+
gain: this.cbToRatio(instrumentKey.modLfoToVolume) * channel.modulation,
|
|
746
|
+
});
|
|
747
|
+
note.modLFO = new OscillatorNode(this.audioContext, {
|
|
748
|
+
frequency: this.centToHz(instrumentKey.freqModLFO),
|
|
749
|
+
});
|
|
750
|
+
note.modLFO.start(time);
|
|
751
|
+
note.filterNode.frequency.setValueAtTime(note.filterNode.frequency.value + instrumentKey.modLfoToFilterFc, time);
|
|
752
|
+
note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modLfoToPitch, time);
|
|
753
|
+
note.modLFO.connect(note.modLFOGain);
|
|
754
|
+
note.modLFOGain.connect(note.bufferSource.detune);
|
|
755
|
+
}
|
|
756
|
+
async createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3) {
|
|
757
|
+
const semitoneOffset = this.calcSemitoneOffset(channel);
|
|
758
|
+
const note = new Note(noteNumber, velocity, startTime, instrumentKey);
|
|
759
|
+
note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
|
|
760
|
+
note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
|
|
761
|
+
this.setVolumeEnvelope(channel, note);
|
|
762
|
+
this.setFilterEnvelope(channel, note);
|
|
714
763
|
if (channel.modulation > 0) {
|
|
715
|
-
const
|
|
716
|
-
|
|
717
|
-
lfoGain = new GainNode(this.audioContext, {
|
|
718
|
-
gain: 0,
|
|
719
|
-
});
|
|
720
|
-
lfoGain.gain
|
|
721
|
-
.setValueAtTime(1e-6, vibratoDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
722
|
-
.exponentialRampToValueAtTime(channel.modulation, vibratoAttack);
|
|
723
|
-
channel.modulationEffect.lfo.connect(lfoGain);
|
|
724
|
-
lfoGain.connect(bufferSource.detune);
|
|
764
|
+
const delayModLFO = startTime + instrumentKey.delayModLFO;
|
|
765
|
+
this.startModulation(channel, note, delayModLFO);
|
|
725
766
|
}
|
|
726
|
-
bufferSource.connect(filterNode);
|
|
727
|
-
filterNode.connect(gainNode);
|
|
728
767
|
if (this.mono && channel.currentBufferSource) {
|
|
729
768
|
channel.currentBufferSource.stop(startTime);
|
|
730
|
-
channel.currentBufferSource = bufferSource;
|
|
769
|
+
channel.currentBufferSource = note.bufferSource;
|
|
731
770
|
}
|
|
732
|
-
bufferSource.
|
|
733
|
-
|
|
771
|
+
note.bufferSource.connect(note.filterNode);
|
|
772
|
+
note.filterNode.connect(note.gainNode);
|
|
773
|
+
note.bufferSource.start(startTime, instrumentKey.start / instrumentKey.sampleRate);
|
|
774
|
+
return note;
|
|
734
775
|
}
|
|
735
776
|
calcBank(channel, channelNumber) {
|
|
736
777
|
if (channel.bankMSB === 121) {
|
|
@@ -749,36 +790,20 @@ class MidyGM2 {
|
|
|
749
790
|
return;
|
|
750
791
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
751
792
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
752
|
-
const
|
|
753
|
-
if (!
|
|
793
|
+
const instrumentKey = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
|
|
794
|
+
if (!instrumentKey)
|
|
754
795
|
return;
|
|
755
|
-
const
|
|
756
|
-
|
|
757
|
-
this.connectNoteEffects(channel, gainNode);
|
|
796
|
+
const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
|
|
797
|
+
this.connectNoteEffects(channel, note.gainNode);
|
|
758
798
|
if (channel.sostenutoPedal) {
|
|
759
|
-
channel.sostenutoNotes.set(noteNumber,
|
|
760
|
-
gainNode,
|
|
761
|
-
filterNode,
|
|
762
|
-
bufferSource,
|
|
763
|
-
noteNumber,
|
|
764
|
-
noteInfo,
|
|
765
|
-
});
|
|
799
|
+
channel.sostenutoNotes.set(noteNumber, note);
|
|
766
800
|
}
|
|
767
801
|
const scheduledNotes = channel.scheduledNotes;
|
|
768
|
-
const scheduledNote = {
|
|
769
|
-
bufferSource,
|
|
770
|
-
filterNode,
|
|
771
|
-
gainNode,
|
|
772
|
-
lfoGain,
|
|
773
|
-
noteInfo,
|
|
774
|
-
noteNumber,
|
|
775
|
-
startTime,
|
|
776
|
-
};
|
|
777
802
|
if (scheduledNotes.has(noteNumber)) {
|
|
778
|
-
scheduledNotes.get(noteNumber).push(
|
|
803
|
+
scheduledNotes.get(noteNumber).push(note);
|
|
779
804
|
}
|
|
780
805
|
else {
|
|
781
|
-
scheduledNotes.set(noteNumber, [
|
|
806
|
+
scheduledNotes.set(noteNumber, [note]);
|
|
782
807
|
}
|
|
783
808
|
}
|
|
784
809
|
noteOn(channelNumber, noteNumber, velocity) {
|
|
@@ -800,15 +825,15 @@ class MidyGM2 {
|
|
|
800
825
|
continue;
|
|
801
826
|
if (targetNote.ending)
|
|
802
827
|
continue;
|
|
803
|
-
const { bufferSource, filterNode, gainNode,
|
|
828
|
+
const { bufferSource, filterNode, gainNode, modLFO, modLFOGain, instrumentKey, } = targetNote;
|
|
804
829
|
const velocityRate = (velocity + 127) / 127;
|
|
805
|
-
const volEndTime = stopTime +
|
|
830
|
+
const volEndTime = stopTime + instrumentKey.volRelease * velocityRate;
|
|
806
831
|
gainNode.gain.cancelScheduledValues(stopTime);
|
|
807
832
|
gainNode.gain.linearRampToValueAtTime(0, volEndTime);
|
|
808
833
|
const maxFreq = this.audioContext.sampleRate / 2;
|
|
809
|
-
const baseFreq = this.centToHz(
|
|
834
|
+
const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
|
|
810
835
|
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
811
|
-
const modEndTime = stopTime +
|
|
836
|
+
const modEndTime = stopTime + instrumentKey.modRelease * velocityRate;
|
|
812
837
|
filterNode.frequency
|
|
813
838
|
.cancelScheduledValues(stopTime)
|
|
814
839
|
.linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
|
|
@@ -822,8 +847,10 @@ class MidyGM2 {
|
|
|
822
847
|
bufferSource.disconnect(0);
|
|
823
848
|
filterNode.disconnect(0);
|
|
824
849
|
gainNode.disconnect(0);
|
|
825
|
-
if (
|
|
826
|
-
|
|
850
|
+
if (modLFOGain)
|
|
851
|
+
modLFOGain.disconnect(0);
|
|
852
|
+
if (modLFO)
|
|
853
|
+
modLFO.stop();
|
|
827
854
|
resolve();
|
|
828
855
|
};
|
|
829
856
|
bufferSource.stop(volEndTime);
|
|
@@ -893,7 +920,7 @@ class MidyGM2 {
|
|
|
893
920
|
const channel = this.channels[channelNumber];
|
|
894
921
|
pressure /= 64;
|
|
895
922
|
channel.channelPressure = pressure;
|
|
896
|
-
const activeNotes = this.getActiveNotes(channel);
|
|
923
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
897
924
|
if (channel.channelPressure.amplitudeControl !== 1) {
|
|
898
925
|
activeNotes.forEach((activeNote) => {
|
|
899
926
|
const gain = activeNote.gainNode.gain.value;
|
|
@@ -905,20 +932,22 @@ class MidyGM2 {
|
|
|
905
932
|
}
|
|
906
933
|
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
907
934
|
const pitchBend = msb * 128 + lsb;
|
|
908
|
-
this.
|
|
935
|
+
this.setPitchBend(channelNumber, pitchBend);
|
|
909
936
|
}
|
|
910
|
-
|
|
937
|
+
setPitchBend(channelNumber, pitchBend) {
|
|
911
938
|
const now = this.audioContext.currentTime;
|
|
912
939
|
const channel = this.channels[channelNumber];
|
|
940
|
+
const prevPitchBend = channel.pitchBend;
|
|
913
941
|
channel.pitchBend = (pitchBend - 8192) / 8192;
|
|
914
|
-
const
|
|
915
|
-
|
|
942
|
+
const detuneChange = (channel.pitchBend - prevPitchBend) *
|
|
943
|
+
channel.pitchBendRange * 100;
|
|
944
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
916
945
|
activeNotes.forEach((activeNote) => {
|
|
917
|
-
const { bufferSource
|
|
918
|
-
const
|
|
919
|
-
bufferSource.
|
|
946
|
+
const { bufferSource } = activeNote;
|
|
947
|
+
const detune = bufferSource.detune.value + detuneChange;
|
|
948
|
+
bufferSource.detune
|
|
920
949
|
.cancelScheduledValues(now)
|
|
921
|
-
.setValueAtTime(
|
|
950
|
+
.setValueAtTime(detune, now);
|
|
922
951
|
});
|
|
923
952
|
}
|
|
924
953
|
handleControlChange(channelNumber, controller, value) {
|
|
@@ -979,9 +1008,20 @@ class MidyGM2 {
|
|
|
979
1008
|
this.channels[channelNumber].bankMSB = msb;
|
|
980
1009
|
}
|
|
981
1010
|
setModulation(channelNumber, modulation) {
|
|
1011
|
+
const now = this.audioContext.currentTime;
|
|
982
1012
|
const channel = this.channels[channelNumber];
|
|
983
1013
|
channel.modulation = (modulation / 127) *
|
|
984
1014
|
(channel.modulationDepthRange * 100);
|
|
1015
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
1016
|
+
activeNotes.forEach((activeNote) => {
|
|
1017
|
+
if (activeNote.modLFO) {
|
|
1018
|
+
activeNote.gainNode.gain.setValueAtTime(this.cbToRatio(activeNote.instrumentKey.modLfoToVolume) *
|
|
1019
|
+
channel.modulation, now);
|
|
1020
|
+
}
|
|
1021
|
+
else {
|
|
1022
|
+
this.startModulation(channel, activeNote, now);
|
|
1023
|
+
}
|
|
1024
|
+
});
|
|
985
1025
|
}
|
|
986
1026
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
987
1027
|
this.channels[channelNumber].portamentoTime = portamentoTime / 127;
|
|
@@ -991,12 +1031,17 @@ class MidyGM2 {
|
|
|
991
1031
|
channel.volume = volume / 127;
|
|
992
1032
|
this.updateChannelGain(channel);
|
|
993
1033
|
}
|
|
1034
|
+
panToGain(pan) {
|
|
1035
|
+
const theta = Math.PI / 2 * Math.max(0, pan - 1) / 126;
|
|
1036
|
+
return {
|
|
1037
|
+
gainLeft: Math.cos(theta),
|
|
1038
|
+
gainRight: Math.sin(theta),
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
994
1041
|
setPan(channelNumber, pan) {
|
|
995
|
-
const now = this.audioContext.currentTime;
|
|
996
1042
|
const channel = this.channels[channelNumber];
|
|
997
|
-
channel.pan = pan
|
|
998
|
-
|
|
999
|
-
channel.pannerNode.pan.setValueAtTime(channel.pan, now);
|
|
1043
|
+
channel.pan = pan;
|
|
1044
|
+
this.updateChannelGain(channel);
|
|
1000
1045
|
}
|
|
1001
1046
|
setExpression(channelNumber, expression) {
|
|
1002
1047
|
const channel = this.channels[channelNumber];
|
|
@@ -1009,8 +1054,13 @@ class MidyGM2 {
|
|
|
1009
1054
|
updateChannelGain(channel) {
|
|
1010
1055
|
const now = this.audioContext.currentTime;
|
|
1011
1056
|
const volume = channel.volume * channel.expression;
|
|
1012
|
-
channel.
|
|
1013
|
-
channel.
|
|
1057
|
+
const { gainLeft, gainRight } = this.panToGain(channel.pan);
|
|
1058
|
+
channel.gainL.gain
|
|
1059
|
+
.cancelScheduledValues(now)
|
|
1060
|
+
.setValueAtTime(volume * gainLeft, now);
|
|
1061
|
+
channel.gainR.gain
|
|
1062
|
+
.cancelScheduledValues(now)
|
|
1063
|
+
.setValueAtTime(volume * gainRight, now);
|
|
1014
1064
|
}
|
|
1015
1065
|
setSustainPedal(channelNumber, value) {
|
|
1016
1066
|
const isOn = value >= 64;
|
|
@@ -1042,7 +1092,8 @@ class MidyGM2 {
|
|
|
1042
1092
|
const channel = this.channels[channelNumber];
|
|
1043
1093
|
channel.sostenutoPedal = isOn;
|
|
1044
1094
|
if (isOn) {
|
|
1045
|
-
const
|
|
1095
|
+
const now = this.audioContext.currentTime;
|
|
1096
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
1046
1097
|
channel.sostenutoNotes = new Map(activeNotes);
|
|
1047
1098
|
}
|
|
1048
1099
|
else {
|
|
@@ -1066,8 +1117,7 @@ class MidyGM2 {
|
|
|
1066
1117
|
const { dataMSB, dataLSB } = channel;
|
|
1067
1118
|
switch (rpn) {
|
|
1068
1119
|
case 0:
|
|
1069
|
-
|
|
1070
|
-
break;
|
|
1120
|
+
return this.handlePitchBendRangeMessage(channelNumber, dataMSB, dataLSB);
|
|
1071
1121
|
case 1:
|
|
1072
1122
|
channel.fineTuning = (dataMSB * 128 + dataLSB - 8192) / 8192;
|
|
1073
1123
|
break;
|
|
@@ -1081,14 +1131,34 @@ class MidyGM2 {
|
|
|
1081
1131
|
console.warn(`Channel ${channelNumber}: Unsupported RPN MSB=${channel.rpnMSB} LSB=${channel.rpnLSB}`);
|
|
1082
1132
|
}
|
|
1083
1133
|
}
|
|
1134
|
+
handlePitchBendRangeMessage(channelNumber, dataMSB, dataLSB) {
|
|
1135
|
+
const pitchBendRange = dataMSB + dataLSB / 100;
|
|
1136
|
+
this.setPitchBendRange(channelNumber, pitchBendRange);
|
|
1137
|
+
}
|
|
1138
|
+
setPitchBendRange(channelNumber, pitchBendRange) {
|
|
1139
|
+
const now = this.audioContext.currentTime;
|
|
1140
|
+
const channel = this.channels[channelNumber];
|
|
1141
|
+
const prevPitchBendRange = channel.pitchBendRange;
|
|
1142
|
+
channel.pitchBendRange = pitchBendRange;
|
|
1143
|
+
const detuneChange = (channel.pitchBendRange - prevPitchBendRange) *
|
|
1144
|
+
channel.pitchBend * 100;
|
|
1145
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
1146
|
+
activeNotes.forEach((activeNote) => {
|
|
1147
|
+
const { bufferSource } = activeNote;
|
|
1148
|
+
const detune = bufferSource.detune.value + detuneChange;
|
|
1149
|
+
bufferSource.detune
|
|
1150
|
+
.cancelScheduledValues(now)
|
|
1151
|
+
.setValueAtTime(detune, now);
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1084
1154
|
allSoundOff(channelNumber) {
|
|
1085
1155
|
const now = this.audioContext.currentTime;
|
|
1086
1156
|
const channel = this.channels[channelNumber];
|
|
1087
1157
|
const velocity = 0;
|
|
1088
1158
|
const stopPedal = true;
|
|
1089
1159
|
const promises = [];
|
|
1090
|
-
channel.scheduledNotes.forEach((
|
|
1091
|
-
const activeNote = this.
|
|
1160
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1161
|
+
const activeNote = this.getActiveNote(noteList, now);
|
|
1092
1162
|
if (activeNote) {
|
|
1093
1163
|
const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
|
|
1094
1164
|
promises.push(notePromise);
|
|
@@ -1105,8 +1175,8 @@ class MidyGM2 {
|
|
|
1105
1175
|
const velocity = 0;
|
|
1106
1176
|
const stopPedal = false;
|
|
1107
1177
|
const promises = [];
|
|
1108
|
-
channel.scheduledNotes.forEach((
|
|
1109
|
-
const activeNote = this.
|
|
1178
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1179
|
+
const activeNote = this.getActiveNote(noteList, now);
|
|
1110
1180
|
if (activeNote) {
|
|
1111
1181
|
const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
|
|
1112
1182
|
promises.push(notePromise);
|
|
@@ -1171,9 +1241,9 @@ class MidyGM2 {
|
|
|
1171
1241
|
case 1:
|
|
1172
1242
|
return this.handleMasterVolumeSysEx(data);
|
|
1173
1243
|
case 3:
|
|
1174
|
-
return this.
|
|
1244
|
+
return this.handleMasterFineTuningSysEx(data);
|
|
1175
1245
|
case 4:
|
|
1176
|
-
return this.
|
|
1246
|
+
return this.handleMasterCoarseTuningSysEx(data);
|
|
1177
1247
|
// case 5: // TODO: Global Parameter Control
|
|
1178
1248
|
default:
|
|
1179
1249
|
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
@@ -1215,9 +1285,9 @@ class MidyGM2 {
|
|
|
1215
1285
|
}
|
|
1216
1286
|
handleMasterVolumeSysEx(data) {
|
|
1217
1287
|
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
1218
|
-
this.
|
|
1288
|
+
this.setMasterVolume(volume);
|
|
1219
1289
|
}
|
|
1220
|
-
|
|
1290
|
+
setMasterVolume(volume) {
|
|
1221
1291
|
if (volume < 0 && 1 < volume) {
|
|
1222
1292
|
console.error("Master Volume is out of range");
|
|
1223
1293
|
}
|
|
@@ -1229,9 +1299,9 @@ class MidyGM2 {
|
|
|
1229
1299
|
}
|
|
1230
1300
|
handleMasterFineTuningSysEx(data) {
|
|
1231
1301
|
const fineTuning = (data[5] * 128 + data[4] - 8192) / 8192;
|
|
1232
|
-
this.
|
|
1302
|
+
this.setMasterFineTuning(fineTuning);
|
|
1233
1303
|
}
|
|
1234
|
-
|
|
1304
|
+
setMasterFineTuning(fineTuning) {
|
|
1235
1305
|
if (fineTuning < -1 && 1 < fineTuning) {
|
|
1236
1306
|
console.error("Master Fine Tuning value is out of range");
|
|
1237
1307
|
}
|
|
@@ -1241,9 +1311,9 @@ class MidyGM2 {
|
|
|
1241
1311
|
}
|
|
1242
1312
|
handleMasterCoarseTuningSysEx(data) {
|
|
1243
1313
|
const coarseTuning = data[4];
|
|
1244
|
-
this.
|
|
1314
|
+
this.setMasterCoarseTuning(coarseTuning);
|
|
1245
1315
|
}
|
|
1246
|
-
|
|
1316
|
+
setMasterCoarseTuning(coarseTuning) {
|
|
1247
1317
|
if (coarseTuning < 0 && 127 < coarseTuning) {
|
|
1248
1318
|
console.error("Master Coarse Tuning value is out of range");
|
|
1249
1319
|
}
|
|
@@ -1288,9 +1358,6 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
1288
1358
|
portamentoTime: 0,
|
|
1289
1359
|
reverb: 0,
|
|
1290
1360
|
chorus: 0,
|
|
1291
|
-
vibratoRate: 5,
|
|
1292
|
-
vibratoDepth: 0.5,
|
|
1293
|
-
vibratoDelay: 2.5,
|
|
1294
1361
|
bank: 121 * 128,
|
|
1295
1362
|
bankMSB: 121,
|
|
1296
1363
|
bankLSB: 0,
|