@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/esm/midy-GM2.js
CHANGED
|
@@ -1,5 +1,55 @@
|
|
|
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.2/+esm.js";
|
|
3
|
+
class Note {
|
|
4
|
+
constructor(noteNumber, velocity, startTime, instrumentKey) {
|
|
5
|
+
Object.defineProperty(this, "bufferSource", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
configurable: true,
|
|
8
|
+
writable: true,
|
|
9
|
+
value: void 0
|
|
10
|
+
});
|
|
11
|
+
Object.defineProperty(this, "gainNode", {
|
|
12
|
+
enumerable: true,
|
|
13
|
+
configurable: true,
|
|
14
|
+
writable: true,
|
|
15
|
+
value: void 0
|
|
16
|
+
});
|
|
17
|
+
Object.defineProperty(this, "filterNode", {
|
|
18
|
+
enumerable: true,
|
|
19
|
+
configurable: true,
|
|
20
|
+
writable: true,
|
|
21
|
+
value: void 0
|
|
22
|
+
});
|
|
23
|
+
Object.defineProperty(this, "modLFO", {
|
|
24
|
+
enumerable: true,
|
|
25
|
+
configurable: true,
|
|
26
|
+
writable: true,
|
|
27
|
+
value: void 0
|
|
28
|
+
});
|
|
29
|
+
Object.defineProperty(this, "modLFOGain", {
|
|
30
|
+
enumerable: true,
|
|
31
|
+
configurable: true,
|
|
32
|
+
writable: true,
|
|
33
|
+
value: void 0
|
|
34
|
+
});
|
|
35
|
+
Object.defineProperty(this, "vibLFO", {
|
|
36
|
+
enumerable: true,
|
|
37
|
+
configurable: true,
|
|
38
|
+
writable: true,
|
|
39
|
+
value: void 0
|
|
40
|
+
});
|
|
41
|
+
Object.defineProperty(this, "vibLFOGain", {
|
|
42
|
+
enumerable: true,
|
|
43
|
+
configurable: true,
|
|
44
|
+
writable: true,
|
|
45
|
+
value: void 0
|
|
46
|
+
});
|
|
47
|
+
this.noteNumber = noteNumber;
|
|
48
|
+
this.velocity = velocity;
|
|
49
|
+
this.startTime = startTime;
|
|
50
|
+
this.instrumentKey = instrumentKey;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
3
53
|
export class MidyGM2 {
|
|
4
54
|
constructor(audioContext) {
|
|
5
55
|
Object.defineProperty(this, "ticksPerBeat", {
|
|
@@ -181,10 +231,8 @@ export class MidyGM2 {
|
|
|
181
231
|
const pannerNode = new StereoPannerNode(audioContext, {
|
|
182
232
|
pan: MidyGM2.channelSettings.pan,
|
|
183
233
|
});
|
|
184
|
-
const modulationEffect = this.createModulationEffect(audioContext);
|
|
185
234
|
const reverbEffect = this.createReverbEffect(audioContext);
|
|
186
235
|
const chorusEffect = this.createChorusEffect(audioContext);
|
|
187
|
-
modulationEffect.lfo.start();
|
|
188
236
|
chorusEffect.lfo.start();
|
|
189
237
|
reverbEffect.dryGain.connect(pannerNode);
|
|
190
238
|
reverbEffect.wetGain.connect(pannerNode);
|
|
@@ -193,7 +241,6 @@ export class MidyGM2 {
|
|
|
193
241
|
return {
|
|
194
242
|
gainNode,
|
|
195
243
|
pannerNode,
|
|
196
|
-
modulationEffect,
|
|
197
244
|
reverbEffect,
|
|
198
245
|
chorusEffect,
|
|
199
246
|
};
|
|
@@ -201,23 +248,23 @@ export class MidyGM2 {
|
|
|
201
248
|
createChannels(audioContext) {
|
|
202
249
|
const channels = Array.from({ length: 16 }, () => {
|
|
203
250
|
return {
|
|
204
|
-
...
|
|
205
|
-
...
|
|
251
|
+
...MidyGM2.channelSettings,
|
|
252
|
+
...MidyGM2.effectSettings,
|
|
206
253
|
...this.setChannelAudioNodes(audioContext),
|
|
207
254
|
scheduledNotes: new Map(),
|
|
208
255
|
sostenutoNotes: new Map(),
|
|
209
256
|
channelPressure: {
|
|
210
|
-
...
|
|
257
|
+
...MidyGM2.controllerDestinationSettings,
|
|
211
258
|
},
|
|
212
259
|
};
|
|
213
260
|
});
|
|
214
261
|
return channels;
|
|
215
262
|
}
|
|
216
|
-
async createNoteBuffer(
|
|
217
|
-
const sampleEnd =
|
|
263
|
+
async createNoteBuffer(instrumentKey, isSF3) {
|
|
264
|
+
const sampleEnd = instrumentKey.sample.length + instrumentKey.end;
|
|
218
265
|
if (isSF3) {
|
|
219
|
-
const sample = new Uint8Array(
|
|
220
|
-
sample.set(
|
|
266
|
+
const sample = new Uint8Array(instrumentKey.sample.length);
|
|
267
|
+
sample.set(instrumentKey.sample);
|
|
221
268
|
const audioBuffer = await this.audioContext.decodeAudioData(sample.buffer);
|
|
222
269
|
for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {
|
|
223
270
|
const channelData = audioBuffer.getChannelData(channel);
|
|
@@ -226,26 +273,27 @@ export class MidyGM2 {
|
|
|
226
273
|
return audioBuffer;
|
|
227
274
|
}
|
|
228
275
|
else {
|
|
229
|
-
const sample =
|
|
276
|
+
const sample = instrumentKey.sample.subarray(0, sampleEnd);
|
|
230
277
|
const floatSample = this.convertToFloat32Array(sample);
|
|
231
278
|
const audioBuffer = new AudioBuffer({
|
|
232
279
|
numberOfChannels: 1,
|
|
233
280
|
length: sample.length,
|
|
234
|
-
sampleRate:
|
|
281
|
+
sampleRate: instrumentKey.sampleRate,
|
|
235
282
|
});
|
|
236
283
|
const channelData = audioBuffer.getChannelData(0);
|
|
237
284
|
channelData.set(floatSample);
|
|
238
285
|
return audioBuffer;
|
|
239
286
|
}
|
|
240
287
|
}
|
|
241
|
-
async createNoteBufferNode(
|
|
288
|
+
async createNoteBufferNode(instrumentKey, isSF3) {
|
|
242
289
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
243
|
-
const audioBuffer = await this.createNoteBuffer(
|
|
290
|
+
const audioBuffer = await this.createNoteBuffer(instrumentKey, isSF3);
|
|
244
291
|
bufferSource.buffer = audioBuffer;
|
|
245
|
-
bufferSource.loop =
|
|
292
|
+
bufferSource.loop = instrumentKey.sampleModes % 2 !== 0;
|
|
246
293
|
if (bufferSource.loop) {
|
|
247
|
-
bufferSource.loopStart =
|
|
248
|
-
|
|
294
|
+
bufferSource.loopStart = instrumentKey.loopStart /
|
|
295
|
+
instrumentKey.sampleRate;
|
|
296
|
+
bufferSource.loopEnd = instrumentKey.loopEnd / instrumentKey.sampleRate;
|
|
249
297
|
}
|
|
250
298
|
return bufferSource;
|
|
251
299
|
}
|
|
@@ -286,7 +334,7 @@ export class MidyGM2 {
|
|
|
286
334
|
this.handleChannelPressure(event.channel, event.amount);
|
|
287
335
|
break;
|
|
288
336
|
case "pitchBend":
|
|
289
|
-
this.
|
|
337
|
+
this.setPitchBend(event.channel, event.value);
|
|
290
338
|
break;
|
|
291
339
|
case "sysEx":
|
|
292
340
|
this.handleSysEx(event.data);
|
|
@@ -366,7 +414,6 @@ export class MidyGM2 {
|
|
|
366
414
|
const tmpChannels = new Array(16);
|
|
367
415
|
for (let i = 0; i < tmpChannels.length; i++) {
|
|
368
416
|
tmpChannels[i] = {
|
|
369
|
-
durationTicks: new Map(),
|
|
370
417
|
programNumber: -1,
|
|
371
418
|
bankMSB: this.channels[i].bankMSB,
|
|
372
419
|
bankLSB: this.channels[i].bankLSB,
|
|
@@ -396,16 +443,6 @@ export class MidyGM2 {
|
|
|
396
443
|
}
|
|
397
444
|
channel.programNumber = 0;
|
|
398
445
|
}
|
|
399
|
-
channel.durationTicks.set(event.noteNumber, {
|
|
400
|
-
ticks: event.ticks,
|
|
401
|
-
noteOn: event,
|
|
402
|
-
});
|
|
403
|
-
break;
|
|
404
|
-
}
|
|
405
|
-
case "noteOff": {
|
|
406
|
-
const { ticks, noteOn } = tmpChannels[event.channel].durationTicks
|
|
407
|
-
.get(event.noteNumber);
|
|
408
|
-
noteOn.durationTicks = event.ticks - ticks;
|
|
409
446
|
break;
|
|
410
447
|
}
|
|
411
448
|
case "controller":
|
|
@@ -440,8 +477,8 @@ export class MidyGM2 {
|
|
|
440
477
|
});
|
|
441
478
|
});
|
|
442
479
|
const priority = {
|
|
443
|
-
|
|
444
|
-
|
|
480
|
+
controller: 0,
|
|
481
|
+
sysEx: 1,
|
|
445
482
|
};
|
|
446
483
|
timeline.sort((a, b) => {
|
|
447
484
|
if (a.ticks !== b.ticks)
|
|
@@ -524,30 +561,26 @@ export class MidyGM2 {
|
|
|
524
561
|
const now = this.audioContext.currentTime;
|
|
525
562
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
526
563
|
}
|
|
527
|
-
getActiveNotes(channel) {
|
|
564
|
+
getActiveNotes(channel, time) {
|
|
528
565
|
const activeNotes = new Map();
|
|
529
|
-
channel.scheduledNotes.forEach((
|
|
530
|
-
const activeNote = this.
|
|
566
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
567
|
+
const activeNote = this.getActiveNote(noteList, time);
|
|
531
568
|
if (activeNote) {
|
|
532
569
|
activeNotes.set(activeNote.noteNumber, activeNote);
|
|
533
570
|
}
|
|
534
571
|
});
|
|
535
572
|
return activeNotes;
|
|
536
573
|
}
|
|
537
|
-
|
|
538
|
-
for (let i =
|
|
539
|
-
const
|
|
540
|
-
if (
|
|
541
|
-
return
|
|
574
|
+
getActiveNote(noteList, time) {
|
|
575
|
+
for (let i = noteList.length - 1; i >= 0; i--) {
|
|
576
|
+
const note = noteList[i];
|
|
577
|
+
if (!note)
|
|
578
|
+
return;
|
|
579
|
+
if (time < note.startTime)
|
|
580
|
+
continue;
|
|
581
|
+
return (note.ending) ? null : note;
|
|
542
582
|
}
|
|
543
|
-
|
|
544
|
-
createModulationEffect(audioContext) {
|
|
545
|
-
const lfo = new OscillatorNode(audioContext, {
|
|
546
|
-
frequency: 5,
|
|
547
|
-
});
|
|
548
|
-
return {
|
|
549
|
-
lfo,
|
|
550
|
-
};
|
|
583
|
+
return noteList[0];
|
|
551
584
|
}
|
|
552
585
|
createReverbEffect(audioContext, options = {}) {
|
|
553
586
|
const { decay = 0.8, preDecay = 0, } = options;
|
|
@@ -583,12 +616,8 @@ export class MidyGM2 {
|
|
|
583
616
|
}
|
|
584
617
|
createChorusEffect(audioContext, options = {}) {
|
|
585
618
|
const { chorusCount = 2, chorusRate = 0.6, chorusDepth = 0.15, delay = 0.01, variance = delay * 0.1, } = options;
|
|
586
|
-
const lfo = new OscillatorNode(audioContext, {
|
|
587
|
-
|
|
588
|
-
});
|
|
589
|
-
const lfoGain = new GainNode(audioContext, {
|
|
590
|
-
gain: chorusDepth,
|
|
591
|
-
});
|
|
619
|
+
const lfo = new OscillatorNode(audioContext, { frequency: chorusRate });
|
|
620
|
+
const lfoGain = new GainNode(audioContext, { gain: chorusDepth });
|
|
592
621
|
const chorusGains = [];
|
|
593
622
|
const delayNodes = [];
|
|
594
623
|
const baseGain = 1 / chorusCount;
|
|
@@ -599,9 +628,7 @@ export class MidyGM2 {
|
|
|
599
628
|
maxDelayTime: delayTime,
|
|
600
629
|
});
|
|
601
630
|
delayNodes.push(delayNode);
|
|
602
|
-
const chorusGain = new GainNode(audioContext, {
|
|
603
|
-
gain: baseGain,
|
|
604
|
-
});
|
|
631
|
+
const chorusGain = new GainNode(audioContext, { gain: baseGain });
|
|
605
632
|
chorusGains.push(chorusGain);
|
|
606
633
|
lfo.connect(lfoGain);
|
|
607
634
|
lfoGain.connect(delayNode.delayTime);
|
|
@@ -657,77 +684,91 @@ export class MidyGM2 {
|
|
|
657
684
|
const tuning = masterTuning + channelTuning;
|
|
658
685
|
return channel.pitchBend * channel.pitchBendRange + tuning;
|
|
659
686
|
}
|
|
660
|
-
calcPlaybackRate(
|
|
661
|
-
return
|
|
687
|
+
calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset) {
|
|
688
|
+
return instrumentKey.playbackRate(noteNumber) *
|
|
689
|
+
Math.pow(2, semitoneOffset / 12);
|
|
662
690
|
}
|
|
663
|
-
|
|
664
|
-
const
|
|
665
|
-
|
|
666
|
-
bufferSource.playbackRate.value = this.calcPlaybackRate(noteInfo, noteNumber, semitoneOffset);
|
|
667
|
-
// volume envelope
|
|
668
|
-
const gainNode = new GainNode(this.audioContext, {
|
|
669
|
-
gain: 0,
|
|
670
|
-
});
|
|
691
|
+
setVolumeEnvelope(channel, note) {
|
|
692
|
+
const { instrumentKey, startTime, velocity } = note;
|
|
693
|
+
note.gainNode = new GainNode(this.audioContext, { gain: 0 });
|
|
671
694
|
let volume = (velocity / 127) * channel.volume * channel.expression;
|
|
672
695
|
if (volume === 0)
|
|
673
696
|
volume = 1e-6; // exponentialRampToValueAtTime() requires a non-zero value
|
|
674
|
-
const attackVolume = this.cbToRatio(-
|
|
675
|
-
|
|
676
|
-
const
|
|
677
|
-
const
|
|
678
|
-
const
|
|
679
|
-
const
|
|
680
|
-
|
|
697
|
+
const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation) *
|
|
698
|
+
volume;
|
|
699
|
+
const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
|
|
700
|
+
const volDelay = startTime + instrumentKey.volDelay;
|
|
701
|
+
const volAttack = volDelay + instrumentKey.volAttack;
|
|
702
|
+
const volHold = volAttack + instrumentKey.volHold;
|
|
703
|
+
const volDecay = volHold + instrumentKey.volDecay;
|
|
704
|
+
note.gainNode.gain
|
|
681
705
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
682
706
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
683
707
|
.setValueAtTime(attackVolume, volHold)
|
|
684
708
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
685
|
-
|
|
709
|
+
}
|
|
710
|
+
setFilterEnvelope(channel, note) {
|
|
711
|
+
const { instrumentKey, startTime, noteNumber } = note;
|
|
686
712
|
const softPedalFactor = 1 -
|
|
687
713
|
(0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
|
|
688
714
|
const maxFreq = this.audioContext.sampleRate / 2;
|
|
689
|
-
const baseFreq = this.centToHz(
|
|
690
|
-
|
|
715
|
+
const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
|
|
716
|
+
softPedalFactor;
|
|
717
|
+
const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
|
|
691
718
|
const sustainFreq = (baseFreq +
|
|
692
|
-
(peekFreq - baseFreq) * (1 -
|
|
719
|
+
(peekFreq - baseFreq) * (1 - instrumentKey.modSustain)) * softPedalFactor;
|
|
720
|
+
const modDelay = startTime + instrumentKey.modDelay;
|
|
721
|
+
const modAttack = modDelay + instrumentKey.modAttack;
|
|
722
|
+
const modHold = modAttack + instrumentKey.modHold;
|
|
723
|
+
const modDecay = modHold + instrumentKey.modDecay;
|
|
693
724
|
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
694
725
|
const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
|
|
695
726
|
const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
|
|
696
|
-
|
|
727
|
+
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
697
728
|
type: "lowpass",
|
|
698
|
-
Q:
|
|
729
|
+
Q: instrumentKey.initialFilterQ / 10, // dB
|
|
699
730
|
frequency: adjustedBaseFreq,
|
|
700
731
|
});
|
|
701
|
-
|
|
702
|
-
const modAttack = modDelay + noteInfo.modAttack;
|
|
703
|
-
const modHold = modAttack + noteInfo.modHold;
|
|
704
|
-
const modDecay = modHold + noteInfo.modDecay;
|
|
705
|
-
filterNode.frequency
|
|
732
|
+
note.filterNode.frequency
|
|
706
733
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
707
734
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
708
735
|
.setValueAtTime(adjustedPeekFreq, modHold)
|
|
709
736
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
710
|
-
|
|
737
|
+
note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modEnvToPitch, modDelay);
|
|
738
|
+
}
|
|
739
|
+
startModulation(channel, note, time) {
|
|
740
|
+
const { instrumentKey } = note;
|
|
741
|
+
note.modLFOGain = new GainNode(this.audioContext, {
|
|
742
|
+
gain: this.cbToRatio(instrumentKey.modLfoToVolume) * channel.modulation,
|
|
743
|
+
});
|
|
744
|
+
note.modLFO = new OscillatorNode(this.audioContext, {
|
|
745
|
+
frequency: this.centToHz(instrumentKey.freqModLFO),
|
|
746
|
+
});
|
|
747
|
+
note.modLFO.start(time);
|
|
748
|
+
note.filterNode.frequency.setValueAtTime(note.filterNode.frequency.value + instrumentKey.modLfoToFilterFc, time);
|
|
749
|
+
note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modLfoToPitch, time);
|
|
750
|
+
note.modLFO.connect(note.modLFOGain);
|
|
751
|
+
note.modLFOGain.connect(note.bufferSource.detune);
|
|
752
|
+
}
|
|
753
|
+
async createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3) {
|
|
754
|
+
const semitoneOffset = this.calcSemitoneOffset(channel);
|
|
755
|
+
const note = new Note(noteNumber, velocity, startTime, instrumentKey);
|
|
756
|
+
note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
|
|
757
|
+
note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
|
|
758
|
+
this.setVolumeEnvelope(channel, note);
|
|
759
|
+
this.setFilterEnvelope(channel, note);
|
|
711
760
|
if (channel.modulation > 0) {
|
|
712
|
-
const
|
|
713
|
-
|
|
714
|
-
lfoGain = new GainNode(this.audioContext, {
|
|
715
|
-
gain: 0,
|
|
716
|
-
});
|
|
717
|
-
lfoGain.gain
|
|
718
|
-
.setValueAtTime(1e-6, vibratoDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
719
|
-
.exponentialRampToValueAtTime(channel.modulation, vibratoAttack);
|
|
720
|
-
channel.modulationEffect.lfo.connect(lfoGain);
|
|
721
|
-
lfoGain.connect(bufferSource.detune);
|
|
761
|
+
const delayModLFO = startTime + instrumentKey.delayModLFO;
|
|
762
|
+
this.startModulation(channel, note, delayModLFO);
|
|
722
763
|
}
|
|
723
|
-
bufferSource.connect(filterNode);
|
|
724
|
-
filterNode.connect(gainNode);
|
|
725
764
|
if (this.mono && channel.currentBufferSource) {
|
|
726
765
|
channel.currentBufferSource.stop(startTime);
|
|
727
|
-
channel.currentBufferSource = bufferSource;
|
|
766
|
+
channel.currentBufferSource = note.bufferSource;
|
|
728
767
|
}
|
|
729
|
-
bufferSource.
|
|
730
|
-
|
|
768
|
+
note.bufferSource.connect(note.filterNode);
|
|
769
|
+
note.filterNode.connect(note.gainNode);
|
|
770
|
+
note.bufferSource.start(startTime, instrumentKey.start / instrumentKey.sampleRate);
|
|
771
|
+
return note;
|
|
731
772
|
}
|
|
732
773
|
calcBank(channel, channelNumber) {
|
|
733
774
|
if (channel.bankMSB === 121) {
|
|
@@ -746,36 +787,20 @@ export class MidyGM2 {
|
|
|
746
787
|
return;
|
|
747
788
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
748
789
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
749
|
-
const
|
|
750
|
-
if (!
|
|
790
|
+
const instrumentKey = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
|
|
791
|
+
if (!instrumentKey)
|
|
751
792
|
return;
|
|
752
|
-
const
|
|
753
|
-
|
|
754
|
-
this.connectNoteEffects(channel, gainNode);
|
|
793
|
+
const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
|
|
794
|
+
this.connectNoteEffects(channel, note.gainNode);
|
|
755
795
|
if (channel.sostenutoPedal) {
|
|
756
|
-
channel.sostenutoNotes.set(noteNumber,
|
|
757
|
-
gainNode,
|
|
758
|
-
filterNode,
|
|
759
|
-
bufferSource,
|
|
760
|
-
noteNumber,
|
|
761
|
-
noteInfo,
|
|
762
|
-
});
|
|
796
|
+
channel.sostenutoNotes.set(noteNumber, note);
|
|
763
797
|
}
|
|
764
798
|
const scheduledNotes = channel.scheduledNotes;
|
|
765
|
-
const scheduledNote = {
|
|
766
|
-
bufferSource,
|
|
767
|
-
filterNode,
|
|
768
|
-
gainNode,
|
|
769
|
-
lfoGain,
|
|
770
|
-
noteInfo,
|
|
771
|
-
noteNumber,
|
|
772
|
-
startTime,
|
|
773
|
-
};
|
|
774
799
|
if (scheduledNotes.has(noteNumber)) {
|
|
775
|
-
scheduledNotes.get(noteNumber).push(
|
|
800
|
+
scheduledNotes.get(noteNumber).push(note);
|
|
776
801
|
}
|
|
777
802
|
else {
|
|
778
|
-
scheduledNotes.set(noteNumber, [
|
|
803
|
+
scheduledNotes.set(noteNumber, [note]);
|
|
779
804
|
}
|
|
780
805
|
}
|
|
781
806
|
noteOn(channelNumber, noteNumber, velocity) {
|
|
@@ -797,15 +822,15 @@ export class MidyGM2 {
|
|
|
797
822
|
continue;
|
|
798
823
|
if (targetNote.ending)
|
|
799
824
|
continue;
|
|
800
|
-
const { bufferSource, filterNode, gainNode,
|
|
825
|
+
const { bufferSource, filterNode, gainNode, modLFO, modLFOGain, instrumentKey, } = targetNote;
|
|
801
826
|
const velocityRate = (velocity + 127) / 127;
|
|
802
|
-
const volEndTime = stopTime +
|
|
827
|
+
const volEndTime = stopTime + instrumentKey.volRelease * velocityRate;
|
|
803
828
|
gainNode.gain.cancelScheduledValues(stopTime);
|
|
804
829
|
gainNode.gain.linearRampToValueAtTime(0, volEndTime);
|
|
805
830
|
const maxFreq = this.audioContext.sampleRate / 2;
|
|
806
|
-
const baseFreq = this.centToHz(
|
|
831
|
+
const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
|
|
807
832
|
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
808
|
-
const modEndTime = stopTime +
|
|
833
|
+
const modEndTime = stopTime + instrumentKey.modRelease * velocityRate;
|
|
809
834
|
filterNode.frequency
|
|
810
835
|
.cancelScheduledValues(stopTime)
|
|
811
836
|
.linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
|
|
@@ -819,8 +844,10 @@ export class MidyGM2 {
|
|
|
819
844
|
bufferSource.disconnect(0);
|
|
820
845
|
filterNode.disconnect(0);
|
|
821
846
|
gainNode.disconnect(0);
|
|
822
|
-
if (
|
|
823
|
-
|
|
847
|
+
if (modLFOGain)
|
|
848
|
+
modLFOGain.disconnect(0);
|
|
849
|
+
if (modLFO)
|
|
850
|
+
modLFO.stop();
|
|
824
851
|
resolve();
|
|
825
852
|
};
|
|
826
853
|
bufferSource.stop(volEndTime);
|
|
@@ -890,7 +917,7 @@ export class MidyGM2 {
|
|
|
890
917
|
const channel = this.channels[channelNumber];
|
|
891
918
|
pressure /= 64;
|
|
892
919
|
channel.channelPressure = pressure;
|
|
893
|
-
const activeNotes = this.getActiveNotes(channel);
|
|
920
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
894
921
|
if (channel.channelPressure.amplitudeControl !== 1) {
|
|
895
922
|
activeNotes.forEach((activeNote) => {
|
|
896
923
|
const gain = activeNote.gainNode.gain.value;
|
|
@@ -902,20 +929,22 @@ export class MidyGM2 {
|
|
|
902
929
|
}
|
|
903
930
|
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
904
931
|
const pitchBend = msb * 128 + lsb;
|
|
905
|
-
this.
|
|
932
|
+
this.setPitchBend(channelNumber, pitchBend);
|
|
906
933
|
}
|
|
907
|
-
|
|
934
|
+
setPitchBend(channelNumber, pitchBend) {
|
|
908
935
|
const now = this.audioContext.currentTime;
|
|
909
936
|
const channel = this.channels[channelNumber];
|
|
937
|
+
const prevPitchBend = channel.pitchBend;
|
|
910
938
|
channel.pitchBend = (pitchBend - 8192) / 8192;
|
|
911
|
-
const
|
|
912
|
-
|
|
939
|
+
const detuneChange = (channel.pitchBend - prevPitchBend) *
|
|
940
|
+
channel.pitchBendRange * 100;
|
|
941
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
913
942
|
activeNotes.forEach((activeNote) => {
|
|
914
|
-
const { bufferSource
|
|
915
|
-
const
|
|
916
|
-
bufferSource.
|
|
943
|
+
const { bufferSource } = activeNote;
|
|
944
|
+
const detune = bufferSource.detune.value + detuneChange;
|
|
945
|
+
bufferSource.detune
|
|
917
946
|
.cancelScheduledValues(now)
|
|
918
|
-
.setValueAtTime(
|
|
947
|
+
.setValueAtTime(detune, now);
|
|
919
948
|
});
|
|
920
949
|
}
|
|
921
950
|
handleControlChange(channelNumber, controller, value) {
|
|
@@ -976,9 +1005,20 @@ export class MidyGM2 {
|
|
|
976
1005
|
this.channels[channelNumber].bankMSB = msb;
|
|
977
1006
|
}
|
|
978
1007
|
setModulation(channelNumber, modulation) {
|
|
1008
|
+
const now = this.audioContext.currentTime;
|
|
979
1009
|
const channel = this.channels[channelNumber];
|
|
980
1010
|
channel.modulation = (modulation / 127) *
|
|
981
1011
|
(channel.modulationDepthRange * 100);
|
|
1012
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
1013
|
+
activeNotes.forEach((activeNote) => {
|
|
1014
|
+
if (activeNote.modLFO) {
|
|
1015
|
+
activeNote.gainNode.gain.setValueAtTime(this.cbToRatio(activeNote.instrumentKey.modLfoToVolume) *
|
|
1016
|
+
channel.modulation, now);
|
|
1017
|
+
}
|
|
1018
|
+
else {
|
|
1019
|
+
this.startModulation(channel, activeNote, now);
|
|
1020
|
+
}
|
|
1021
|
+
});
|
|
982
1022
|
}
|
|
983
1023
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
984
1024
|
this.channels[channelNumber].portamentoTime = portamentoTime / 127;
|
|
@@ -988,12 +1028,17 @@ export class MidyGM2 {
|
|
|
988
1028
|
channel.volume = volume / 127;
|
|
989
1029
|
this.updateChannelGain(channel);
|
|
990
1030
|
}
|
|
1031
|
+
panToGain(pan) {
|
|
1032
|
+
const theta = Math.PI / 2 * Math.max(0, pan - 1) / 126;
|
|
1033
|
+
return {
|
|
1034
|
+
gainLeft: Math.cos(theta),
|
|
1035
|
+
gainRight: Math.sin(theta),
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
991
1038
|
setPan(channelNumber, pan) {
|
|
992
|
-
const now = this.audioContext.currentTime;
|
|
993
1039
|
const channel = this.channels[channelNumber];
|
|
994
|
-
channel.pan = pan
|
|
995
|
-
|
|
996
|
-
channel.pannerNode.pan.setValueAtTime(channel.pan, now);
|
|
1040
|
+
channel.pan = pan;
|
|
1041
|
+
this.updateChannelGain(channel);
|
|
997
1042
|
}
|
|
998
1043
|
setExpression(channelNumber, expression) {
|
|
999
1044
|
const channel = this.channels[channelNumber];
|
|
@@ -1006,8 +1051,13 @@ export class MidyGM2 {
|
|
|
1006
1051
|
updateChannelGain(channel) {
|
|
1007
1052
|
const now = this.audioContext.currentTime;
|
|
1008
1053
|
const volume = channel.volume * channel.expression;
|
|
1009
|
-
channel.
|
|
1010
|
-
channel.
|
|
1054
|
+
const { gainLeft, gainRight } = this.panToGain(channel.pan);
|
|
1055
|
+
channel.gainL.gain
|
|
1056
|
+
.cancelScheduledValues(now)
|
|
1057
|
+
.setValueAtTime(volume * gainLeft, now);
|
|
1058
|
+
channel.gainR.gain
|
|
1059
|
+
.cancelScheduledValues(now)
|
|
1060
|
+
.setValueAtTime(volume * gainRight, now);
|
|
1011
1061
|
}
|
|
1012
1062
|
setSustainPedal(channelNumber, value) {
|
|
1013
1063
|
const isOn = value >= 64;
|
|
@@ -1039,7 +1089,8 @@ export class MidyGM2 {
|
|
|
1039
1089
|
const channel = this.channels[channelNumber];
|
|
1040
1090
|
channel.sostenutoPedal = isOn;
|
|
1041
1091
|
if (isOn) {
|
|
1042
|
-
const
|
|
1092
|
+
const now = this.audioContext.currentTime;
|
|
1093
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
1043
1094
|
channel.sostenutoNotes = new Map(activeNotes);
|
|
1044
1095
|
}
|
|
1045
1096
|
else {
|
|
@@ -1063,8 +1114,7 @@ export class MidyGM2 {
|
|
|
1063
1114
|
const { dataMSB, dataLSB } = channel;
|
|
1064
1115
|
switch (rpn) {
|
|
1065
1116
|
case 0:
|
|
1066
|
-
|
|
1067
|
-
break;
|
|
1117
|
+
return this.handlePitchBendRangeMessage(channelNumber, dataMSB, dataLSB);
|
|
1068
1118
|
case 1:
|
|
1069
1119
|
channel.fineTuning = (dataMSB * 128 + dataLSB - 8192) / 8192;
|
|
1070
1120
|
break;
|
|
@@ -1078,14 +1128,34 @@ export class MidyGM2 {
|
|
|
1078
1128
|
console.warn(`Channel ${channelNumber}: Unsupported RPN MSB=${channel.rpnMSB} LSB=${channel.rpnLSB}`);
|
|
1079
1129
|
}
|
|
1080
1130
|
}
|
|
1131
|
+
handlePitchBendRangeMessage(channelNumber, dataMSB, dataLSB) {
|
|
1132
|
+
const pitchBendRange = dataMSB + dataLSB / 100;
|
|
1133
|
+
this.setPitchBendRange(channelNumber, pitchBendRange);
|
|
1134
|
+
}
|
|
1135
|
+
setPitchBendRange(channelNumber, pitchBendRange) {
|
|
1136
|
+
const now = this.audioContext.currentTime;
|
|
1137
|
+
const channel = this.channels[channelNumber];
|
|
1138
|
+
const prevPitchBendRange = channel.pitchBendRange;
|
|
1139
|
+
channel.pitchBendRange = pitchBendRange;
|
|
1140
|
+
const detuneChange = (channel.pitchBendRange - prevPitchBendRange) *
|
|
1141
|
+
channel.pitchBend * 100;
|
|
1142
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
1143
|
+
activeNotes.forEach((activeNote) => {
|
|
1144
|
+
const { bufferSource } = activeNote;
|
|
1145
|
+
const detune = bufferSource.detune.value + detuneChange;
|
|
1146
|
+
bufferSource.detune
|
|
1147
|
+
.cancelScheduledValues(now)
|
|
1148
|
+
.setValueAtTime(detune, now);
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1081
1151
|
allSoundOff(channelNumber) {
|
|
1082
1152
|
const now = this.audioContext.currentTime;
|
|
1083
1153
|
const channel = this.channels[channelNumber];
|
|
1084
1154
|
const velocity = 0;
|
|
1085
1155
|
const stopPedal = true;
|
|
1086
1156
|
const promises = [];
|
|
1087
|
-
channel.scheduledNotes.forEach((
|
|
1088
|
-
const activeNote = this.
|
|
1157
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1158
|
+
const activeNote = this.getActiveNote(noteList, now);
|
|
1089
1159
|
if (activeNote) {
|
|
1090
1160
|
const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
|
|
1091
1161
|
promises.push(notePromise);
|
|
@@ -1102,8 +1172,8 @@ export class MidyGM2 {
|
|
|
1102
1172
|
const velocity = 0;
|
|
1103
1173
|
const stopPedal = false;
|
|
1104
1174
|
const promises = [];
|
|
1105
|
-
channel.scheduledNotes.forEach((
|
|
1106
|
-
const activeNote = this.
|
|
1175
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1176
|
+
const activeNote = this.getActiveNote(noteList, now);
|
|
1107
1177
|
if (activeNote) {
|
|
1108
1178
|
const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
|
|
1109
1179
|
promises.push(notePromise);
|
|
@@ -1168,9 +1238,9 @@ export class MidyGM2 {
|
|
|
1168
1238
|
case 1:
|
|
1169
1239
|
return this.handleMasterVolumeSysEx(data);
|
|
1170
1240
|
case 3:
|
|
1171
|
-
return this.
|
|
1241
|
+
return this.handleMasterFineTuningSysEx(data);
|
|
1172
1242
|
case 4:
|
|
1173
|
-
return this.
|
|
1243
|
+
return this.handleMasterCoarseTuningSysEx(data);
|
|
1174
1244
|
// case 5: // TODO: Global Parameter Control
|
|
1175
1245
|
default:
|
|
1176
1246
|
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
@@ -1212,9 +1282,9 @@ export class MidyGM2 {
|
|
|
1212
1282
|
}
|
|
1213
1283
|
handleMasterVolumeSysEx(data) {
|
|
1214
1284
|
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
1215
|
-
this.
|
|
1285
|
+
this.setMasterVolume(volume);
|
|
1216
1286
|
}
|
|
1217
|
-
|
|
1287
|
+
setMasterVolume(volume) {
|
|
1218
1288
|
if (volume < 0 && 1 < volume) {
|
|
1219
1289
|
console.error("Master Volume is out of range");
|
|
1220
1290
|
}
|
|
@@ -1226,9 +1296,9 @@ export class MidyGM2 {
|
|
|
1226
1296
|
}
|
|
1227
1297
|
handleMasterFineTuningSysEx(data) {
|
|
1228
1298
|
const fineTuning = (data[5] * 128 + data[4] - 8192) / 8192;
|
|
1229
|
-
this.
|
|
1299
|
+
this.setMasterFineTuning(fineTuning);
|
|
1230
1300
|
}
|
|
1231
|
-
|
|
1301
|
+
setMasterFineTuning(fineTuning) {
|
|
1232
1302
|
if (fineTuning < -1 && 1 < fineTuning) {
|
|
1233
1303
|
console.error("Master Fine Tuning value is out of range");
|
|
1234
1304
|
}
|
|
@@ -1238,9 +1308,9 @@ export class MidyGM2 {
|
|
|
1238
1308
|
}
|
|
1239
1309
|
handleMasterCoarseTuningSysEx(data) {
|
|
1240
1310
|
const coarseTuning = data[4];
|
|
1241
|
-
this.
|
|
1311
|
+
this.setMasterCoarseTuning(coarseTuning);
|
|
1242
1312
|
}
|
|
1243
|
-
|
|
1313
|
+
setMasterCoarseTuning(coarseTuning) {
|
|
1244
1314
|
if (coarseTuning < 0 && 127 < coarseTuning) {
|
|
1245
1315
|
console.error("Master Coarse Tuning value is out of range");
|
|
1246
1316
|
}
|
|
@@ -1284,9 +1354,6 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
1284
1354
|
portamentoTime: 0,
|
|
1285
1355
|
reverb: 0,
|
|
1286
1356
|
chorus: 0,
|
|
1287
|
-
vibratoRate: 5,
|
|
1288
|
-
vibratoDepth: 0.5,
|
|
1289
|
-
vibratoDelay: 2.5,
|
|
1290
1357
|
bank: 121 * 128,
|
|
1291
1358
|
bankMSB: 121,
|
|
1292
1359
|
bankLSB: 0,
|