@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/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
|
};
|
|
@@ -206,15 +253,18 @@ export class MidyGM2 {
|
|
|
206
253
|
...this.setChannelAudioNodes(audioContext),
|
|
207
254
|
scheduledNotes: new Map(),
|
|
208
255
|
sostenutoNotes: new Map(),
|
|
256
|
+
channelPressure: {
|
|
257
|
+
...MidyGM2.controllerDestinationSettings,
|
|
258
|
+
},
|
|
209
259
|
};
|
|
210
260
|
});
|
|
211
261
|
return channels;
|
|
212
262
|
}
|
|
213
|
-
async createNoteBuffer(
|
|
214
|
-
const sampleEnd =
|
|
263
|
+
async createNoteBuffer(instrumentKey, isSF3) {
|
|
264
|
+
const sampleEnd = instrumentKey.sample.length + instrumentKey.end;
|
|
215
265
|
if (isSF3) {
|
|
216
|
-
const sample = new Uint8Array(
|
|
217
|
-
sample.set(
|
|
266
|
+
const sample = new Uint8Array(instrumentKey.sample.length);
|
|
267
|
+
sample.set(instrumentKey.sample);
|
|
218
268
|
const audioBuffer = await this.audioContext.decodeAudioData(sample.buffer);
|
|
219
269
|
for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {
|
|
220
270
|
const channelData = audioBuffer.getChannelData(channel);
|
|
@@ -223,26 +273,27 @@ export class MidyGM2 {
|
|
|
223
273
|
return audioBuffer;
|
|
224
274
|
}
|
|
225
275
|
else {
|
|
226
|
-
const sample =
|
|
276
|
+
const sample = instrumentKey.sample.subarray(0, sampleEnd);
|
|
227
277
|
const floatSample = this.convertToFloat32Array(sample);
|
|
228
278
|
const audioBuffer = new AudioBuffer({
|
|
229
279
|
numberOfChannels: 1,
|
|
230
280
|
length: sample.length,
|
|
231
|
-
sampleRate:
|
|
281
|
+
sampleRate: instrumentKey.sampleRate,
|
|
232
282
|
});
|
|
233
283
|
const channelData = audioBuffer.getChannelData(0);
|
|
234
284
|
channelData.set(floatSample);
|
|
235
285
|
return audioBuffer;
|
|
236
286
|
}
|
|
237
287
|
}
|
|
238
|
-
async createNoteBufferNode(
|
|
288
|
+
async createNoteBufferNode(instrumentKey, isSF3) {
|
|
239
289
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
240
|
-
const audioBuffer = await this.createNoteBuffer(
|
|
290
|
+
const audioBuffer = await this.createNoteBuffer(instrumentKey, isSF3);
|
|
241
291
|
bufferSource.buffer = audioBuffer;
|
|
242
|
-
bufferSource.loop =
|
|
292
|
+
bufferSource.loop = instrumentKey.sampleModes % 2 !== 0;
|
|
243
293
|
if (bufferSource.loop) {
|
|
244
|
-
bufferSource.loopStart =
|
|
245
|
-
|
|
294
|
+
bufferSource.loopStart = instrumentKey.loopStart /
|
|
295
|
+
instrumentKey.sampleRate;
|
|
296
|
+
bufferSource.loopEnd = instrumentKey.loopEnd / instrumentKey.sampleRate;
|
|
246
297
|
}
|
|
247
298
|
return bufferSource;
|
|
248
299
|
}
|
|
@@ -260,9 +311,6 @@ export class MidyGM2 {
|
|
|
260
311
|
if (event.startTime > t + this.lookAhead)
|
|
261
312
|
break;
|
|
262
313
|
switch (event.type) {
|
|
263
|
-
case "controller":
|
|
264
|
-
this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
|
|
265
|
-
break;
|
|
266
314
|
case "noteOn":
|
|
267
315
|
if (event.velocity !== 0) {
|
|
268
316
|
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
|
|
@@ -276,9 +324,18 @@ export class MidyGM2 {
|
|
|
276
324
|
}
|
|
277
325
|
break;
|
|
278
326
|
}
|
|
327
|
+
case "controller":
|
|
328
|
+
this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
|
|
329
|
+
break;
|
|
279
330
|
case "programChange":
|
|
280
331
|
this.handleProgramChange(event.channel, event.programNumber);
|
|
281
332
|
break;
|
|
333
|
+
case "channelAftertouch":
|
|
334
|
+
this.handleChannelPressure(event.channel, event.amount);
|
|
335
|
+
break;
|
|
336
|
+
case "pitchBend":
|
|
337
|
+
this.handlePitchBend(event.channel, event.value);
|
|
338
|
+
break;
|
|
282
339
|
case "sysEx":
|
|
283
340
|
this.handleSysEx(event.data);
|
|
284
341
|
}
|
|
@@ -515,30 +572,26 @@ export class MidyGM2 {
|
|
|
515
572
|
const now = this.audioContext.currentTime;
|
|
516
573
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
517
574
|
}
|
|
518
|
-
getActiveNotes(channel) {
|
|
575
|
+
getActiveNotes(channel, time) {
|
|
519
576
|
const activeNotes = new Map();
|
|
520
|
-
channel.scheduledNotes.forEach((
|
|
521
|
-
const activeNote = this.
|
|
577
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
578
|
+
const activeNote = this.getActiveNote(noteList, time);
|
|
522
579
|
if (activeNote) {
|
|
523
580
|
activeNotes.set(activeNote.noteNumber, activeNote);
|
|
524
581
|
}
|
|
525
582
|
});
|
|
526
583
|
return activeNotes;
|
|
527
584
|
}
|
|
528
|
-
|
|
529
|
-
for (let i =
|
|
530
|
-
const
|
|
531
|
-
if (
|
|
532
|
-
return
|
|
585
|
+
getActiveNote(noteList, time) {
|
|
586
|
+
for (let i = noteList.length - 1; i >= 0; i--) {
|
|
587
|
+
const note = noteList[i];
|
|
588
|
+
if (!note)
|
|
589
|
+
return;
|
|
590
|
+
if (time < note.startTime)
|
|
591
|
+
continue;
|
|
592
|
+
return (note.ending) ? null : note;
|
|
533
593
|
}
|
|
534
|
-
|
|
535
|
-
createModulationEffect(audioContext) {
|
|
536
|
-
const lfo = new OscillatorNode(audioContext, {
|
|
537
|
-
frequency: 5,
|
|
538
|
-
});
|
|
539
|
-
return {
|
|
540
|
-
lfo,
|
|
541
|
-
};
|
|
594
|
+
return noteList[0];
|
|
542
595
|
}
|
|
543
596
|
createReverbEffect(audioContext, options = {}) {
|
|
544
597
|
const { decay = 0.8, preDecay = 0, } = options;
|
|
@@ -642,79 +695,99 @@ export class MidyGM2 {
|
|
|
642
695
|
centToHz(cent) {
|
|
643
696
|
return 8.176 * Math.pow(2, cent / 1200);
|
|
644
697
|
}
|
|
645
|
-
|
|
698
|
+
calcSemitoneOffset(channel) {
|
|
646
699
|
const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
|
|
647
700
|
const channelTuning = channel.coarseTuning + channel.fineTuning;
|
|
648
701
|
const tuning = masterTuning + channelTuning;
|
|
649
|
-
|
|
650
|
-
|
|
702
|
+
return channel.pitchBend * channel.pitchBendRange + tuning;
|
|
703
|
+
}
|
|
704
|
+
calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset) {
|
|
705
|
+
return instrumentKey.playbackRate(noteNumber) *
|
|
651
706
|
Math.pow(2, semitoneOffset / 12);
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
707
|
+
}
|
|
708
|
+
setVolumeEnvelope(channel, note) {
|
|
709
|
+
const { instrumentKey, startTime, velocity } = note;
|
|
710
|
+
note.gainNode = new GainNode(this.audioContext, {
|
|
656
711
|
gain: 0,
|
|
657
712
|
});
|
|
658
713
|
let volume = (velocity / 127) * channel.volume * channel.expression;
|
|
659
714
|
if (volume === 0)
|
|
660
715
|
volume = 1e-6; // exponentialRampToValueAtTime() requires a non-zero value
|
|
661
|
-
const attackVolume = this.cbToRatio(-
|
|
662
|
-
|
|
663
|
-
const
|
|
664
|
-
const
|
|
665
|
-
const
|
|
666
|
-
const
|
|
667
|
-
|
|
716
|
+
const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation) *
|
|
717
|
+
volume;
|
|
718
|
+
const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
|
|
719
|
+
const volDelay = startTime + instrumentKey.volDelay;
|
|
720
|
+
const volAttack = volDelay + instrumentKey.volAttack;
|
|
721
|
+
const volHold = volAttack + instrumentKey.volHold;
|
|
722
|
+
const volDecay = volHold + instrumentKey.volDecay;
|
|
723
|
+
note.gainNode.gain
|
|
668
724
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
669
725
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
670
726
|
.setValueAtTime(attackVolume, volHold)
|
|
671
727
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
672
|
-
|
|
728
|
+
}
|
|
729
|
+
setFilterEnvelope(channel, note) {
|
|
730
|
+
const { instrumentKey, startTime, noteNumber } = note;
|
|
673
731
|
const softPedalFactor = 1 -
|
|
674
732
|
(0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
|
|
675
733
|
const maxFreq = this.audioContext.sampleRate / 2;
|
|
676
|
-
const baseFreq = this.centToHz(
|
|
677
|
-
|
|
734
|
+
const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
|
|
735
|
+
softPedalFactor;
|
|
736
|
+
const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
|
|
678
737
|
const sustainFreq = (baseFreq +
|
|
679
|
-
(peekFreq - baseFreq) * (1 -
|
|
738
|
+
(peekFreq - baseFreq) * (1 - instrumentKey.modSustain)) * softPedalFactor;
|
|
739
|
+
const modDelay = startTime + instrumentKey.modDelay;
|
|
740
|
+
const modAttack = modDelay + instrumentKey.modAttack;
|
|
741
|
+
const modHold = modAttack + instrumentKey.modHold;
|
|
742
|
+
const modDecay = modHold + instrumentKey.modDecay;
|
|
680
743
|
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
681
744
|
const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
|
|
682
745
|
const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
|
|
683
|
-
|
|
746
|
+
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
684
747
|
type: "lowpass",
|
|
685
|
-
Q:
|
|
748
|
+
Q: instrumentKey.initialFilterQ / 10, // dB
|
|
686
749
|
frequency: adjustedBaseFreq,
|
|
687
750
|
});
|
|
688
|
-
|
|
689
|
-
const modAttack = modDelay + noteInfo.modAttack;
|
|
690
|
-
const modHold = modAttack + noteInfo.modHold;
|
|
691
|
-
const modDecay = modHold + noteInfo.modDecay;
|
|
692
|
-
filterNode.frequency
|
|
751
|
+
note.filterNode.frequency
|
|
693
752
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
694
753
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
695
754
|
.setValueAtTime(adjustedPeekFreq, modHold)
|
|
696
755
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
697
|
-
|
|
756
|
+
note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modEnvToPitch, modDelay);
|
|
757
|
+
}
|
|
758
|
+
startModulation(channel, note, time) {
|
|
759
|
+
const { instrumentKey } = note;
|
|
760
|
+
note.modLFOGain = new GainNode(this.audioContext, {
|
|
761
|
+
gain: this.cbToRatio(instrumentKey.modLfoToVolume) * channel.modulation,
|
|
762
|
+
});
|
|
763
|
+
note.modLFO = new OscillatorNode(this.audioContext, {
|
|
764
|
+
frequency: this.centToHz(instrumentKey.freqModLFO),
|
|
765
|
+
});
|
|
766
|
+
note.modLFO.start(time);
|
|
767
|
+
note.filterNode.frequency.setValueAtTime(note.filterNode.frequency.value + instrumentKey.modLfoToFilterFc, time);
|
|
768
|
+
note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modLfoToPitch, time);
|
|
769
|
+
note.modLFO.connect(note.modLFOGain);
|
|
770
|
+
note.modLFOGain.connect(note.bufferSource.detune);
|
|
771
|
+
}
|
|
772
|
+
async createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3) {
|
|
773
|
+
const semitoneOffset = this.calcSemitoneOffset(channel);
|
|
774
|
+
const note = new Note(noteNumber, velocity, startTime, instrumentKey);
|
|
775
|
+
note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
|
|
776
|
+
note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
|
|
777
|
+
this.setVolumeEnvelope(channel, note);
|
|
778
|
+
this.setFilterEnvelope(channel, note);
|
|
698
779
|
if (channel.modulation > 0) {
|
|
699
|
-
const
|
|
700
|
-
|
|
701
|
-
lfoGain = new GainNode(this.audioContext, {
|
|
702
|
-
gain: 0,
|
|
703
|
-
});
|
|
704
|
-
lfoGain.gain
|
|
705
|
-
.setValueAtTime(1e-6, vibratoDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
706
|
-
.exponentialRampToValueAtTime(channel.modulation, vibratoAttack);
|
|
707
|
-
channel.modulationEffect.lfo.connect(lfoGain);
|
|
708
|
-
lfoGain.connect(bufferSource.detune);
|
|
780
|
+
const delayModLFO = startTime + instrumentKey.delayModLFO;
|
|
781
|
+
this.startModulation(channel, note, delayModLFO);
|
|
709
782
|
}
|
|
710
|
-
bufferSource.connect(filterNode);
|
|
711
|
-
filterNode.connect(gainNode);
|
|
712
783
|
if (this.mono && channel.currentBufferSource) {
|
|
713
784
|
channel.currentBufferSource.stop(startTime);
|
|
714
|
-
channel.currentBufferSource = bufferSource;
|
|
785
|
+
channel.currentBufferSource = note.bufferSource;
|
|
715
786
|
}
|
|
716
|
-
bufferSource.
|
|
717
|
-
|
|
787
|
+
note.bufferSource.connect(note.filterNode);
|
|
788
|
+
note.filterNode.connect(note.gainNode);
|
|
789
|
+
note.bufferSource.start(startTime, instrumentKey.start / instrumentKey.sampleRate);
|
|
790
|
+
return note;
|
|
718
791
|
}
|
|
719
792
|
calcBank(channel, channelNumber) {
|
|
720
793
|
if (channel.bankMSB === 121) {
|
|
@@ -733,36 +806,20 @@ export class MidyGM2 {
|
|
|
733
806
|
return;
|
|
734
807
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
735
808
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
736
|
-
const
|
|
737
|
-
if (!
|
|
809
|
+
const instrumentKey = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
|
|
810
|
+
if (!instrumentKey)
|
|
738
811
|
return;
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
this.connectNoteEffects(channel, gainNode);
|
|
812
|
+
const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
|
|
813
|
+
this.connectNoteEffects(channel, note.gainNode);
|
|
742
814
|
if (channel.sostenutoPedal) {
|
|
743
|
-
channel.sostenutoNotes.set(noteNumber,
|
|
744
|
-
gainNode,
|
|
745
|
-
filterNode,
|
|
746
|
-
bufferSource,
|
|
747
|
-
noteNumber,
|
|
748
|
-
noteInfo,
|
|
749
|
-
});
|
|
815
|
+
channel.sostenutoNotes.set(noteNumber, note);
|
|
750
816
|
}
|
|
751
817
|
const scheduledNotes = channel.scheduledNotes;
|
|
752
|
-
const scheduledNote = {
|
|
753
|
-
bufferSource,
|
|
754
|
-
filterNode,
|
|
755
|
-
gainNode,
|
|
756
|
-
lfoGain,
|
|
757
|
-
noteInfo,
|
|
758
|
-
noteNumber,
|
|
759
|
-
startTime,
|
|
760
|
-
};
|
|
761
818
|
if (scheduledNotes.has(noteNumber)) {
|
|
762
|
-
scheduledNotes.get(noteNumber).push(
|
|
819
|
+
scheduledNotes.get(noteNumber).push(note);
|
|
763
820
|
}
|
|
764
821
|
else {
|
|
765
|
-
scheduledNotes.set(noteNumber, [
|
|
822
|
+
scheduledNotes.set(noteNumber, [note]);
|
|
766
823
|
}
|
|
767
824
|
}
|
|
768
825
|
noteOn(channelNumber, noteNumber, velocity) {
|
|
@@ -784,15 +841,15 @@ export class MidyGM2 {
|
|
|
784
841
|
continue;
|
|
785
842
|
if (targetNote.ending)
|
|
786
843
|
continue;
|
|
787
|
-
const { bufferSource, filterNode, gainNode,
|
|
844
|
+
const { bufferSource, filterNode, gainNode, modLFO, modLFOGain, instrumentKey, } = targetNote;
|
|
788
845
|
const velocityRate = (velocity + 127) / 127;
|
|
789
|
-
const volEndTime = stopTime +
|
|
846
|
+
const volEndTime = stopTime + instrumentKey.volRelease * velocityRate;
|
|
790
847
|
gainNode.gain.cancelScheduledValues(stopTime);
|
|
791
848
|
gainNode.gain.linearRampToValueAtTime(0, volEndTime);
|
|
792
849
|
const maxFreq = this.audioContext.sampleRate / 2;
|
|
793
|
-
const baseFreq = this.centToHz(
|
|
850
|
+
const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
|
|
794
851
|
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
795
|
-
const modEndTime = stopTime +
|
|
852
|
+
const modEndTime = stopTime + instrumentKey.modRelease * velocityRate;
|
|
796
853
|
filterNode.frequency
|
|
797
854
|
.cancelScheduledValues(stopTime)
|
|
798
855
|
.linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
|
|
@@ -806,8 +863,10 @@ export class MidyGM2 {
|
|
|
806
863
|
bufferSource.disconnect(0);
|
|
807
864
|
filterNode.disconnect(0);
|
|
808
865
|
gainNode.disconnect(0);
|
|
809
|
-
if (
|
|
810
|
-
|
|
866
|
+
if (modLFOGain)
|
|
867
|
+
modLFOGain.disconnect(0);
|
|
868
|
+
if (modLFO)
|
|
869
|
+
modLFO.stop();
|
|
811
870
|
resolve();
|
|
812
871
|
};
|
|
813
872
|
bufferSource.stop(volEndTime);
|
|
@@ -855,8 +914,6 @@ export class MidyGM2 {
|
|
|
855
914
|
return this.releaseNote(channelNumber, data1, data2);
|
|
856
915
|
case 0x90:
|
|
857
916
|
return this.noteOn(channelNumber, data1, data2);
|
|
858
|
-
case 0xA0:
|
|
859
|
-
return this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
|
|
860
917
|
case 0xB0:
|
|
861
918
|
return this.handleControlChange(channelNumber, data1, data2);
|
|
862
919
|
case 0xC0:
|
|
@@ -864,38 +921,48 @@ export class MidyGM2 {
|
|
|
864
921
|
case 0xD0:
|
|
865
922
|
return this.handleChannelPressure(channelNumber, data1);
|
|
866
923
|
case 0xE0:
|
|
867
|
-
return this.
|
|
924
|
+
return this.handlePitchBendMessage(channelNumber, data1, data2);
|
|
868
925
|
default:
|
|
869
926
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
870
927
|
}
|
|
871
928
|
}
|
|
872
|
-
handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure) {
|
|
873
|
-
const now = this.audioContext.currentTime;
|
|
874
|
-
const channel = this.channels[channelNumber];
|
|
875
|
-
const scheduledNotes = channel.scheduledNotes.get(noteNumber);
|
|
876
|
-
pressure /= 127;
|
|
877
|
-
if (scheduledNotes) {
|
|
878
|
-
scheduledNotes.forEach((scheduledNote) => {
|
|
879
|
-
if (scheduledNote) {
|
|
880
|
-
const { initialAttenuation } = scheduledNote.noteInfo;
|
|
881
|
-
const gain = this.cbToRatio(-initialAttenuation) * pressure;
|
|
882
|
-
scheduledNote.gainNode.gain.cancelScheduledValues(now);
|
|
883
|
-
scheduledNote.gainNode.gain.setValueAtTime(gain, now);
|
|
884
|
-
}
|
|
885
|
-
});
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
929
|
handleProgramChange(channelNumber, program) {
|
|
889
930
|
const channel = this.channels[channelNumber];
|
|
890
931
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
891
932
|
channel.program = program;
|
|
892
933
|
}
|
|
893
934
|
handleChannelPressure(channelNumber, pressure) {
|
|
894
|
-
this.
|
|
935
|
+
const now = this.audioContext.currentTime;
|
|
936
|
+
const channel = this.channels[channelNumber];
|
|
937
|
+
pressure /= 64;
|
|
938
|
+
channel.channelPressure = pressure;
|
|
939
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
940
|
+
if (channel.channelPressure.amplitudeControl !== 1) {
|
|
941
|
+
activeNotes.forEach((activeNote) => {
|
|
942
|
+
const gain = activeNote.gainNode.gain.value;
|
|
943
|
+
activeNote.gainNode.gain
|
|
944
|
+
.cancelScheduledValues(now)
|
|
945
|
+
.setValueAtTime(gain * pressure, now);
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
950
|
+
const pitchBend = msb * 128 + lsb;
|
|
951
|
+
this.handlePitchBend(channelNumber, pitchBend);
|
|
895
952
|
}
|
|
896
|
-
handlePitchBend(channelNumber,
|
|
897
|
-
const
|
|
898
|
-
this.channels[channelNumber]
|
|
953
|
+
handlePitchBend(channelNumber, pitchBend) {
|
|
954
|
+
const now = this.audioContext.currentTime;
|
|
955
|
+
const channel = this.channels[channelNumber];
|
|
956
|
+
channel.pitchBend = (pitchBend - 8192) / 8192;
|
|
957
|
+
const semitoneOffset = this.calcSemitoneOffset(channel);
|
|
958
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
959
|
+
activeNotes.forEach((activeNote) => {
|
|
960
|
+
const { bufferSource, instrumentKey, noteNumber } = activeNote;
|
|
961
|
+
const playbackRate = calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
|
|
962
|
+
bufferSource.playbackRate
|
|
963
|
+
.cancelScheduledValues(now)
|
|
964
|
+
.setValueAtTime(playbackRate * pressure, now);
|
|
965
|
+
});
|
|
899
966
|
}
|
|
900
967
|
handleControlChange(channelNumber, controller, value) {
|
|
901
968
|
switch (controller) {
|
|
@@ -955,9 +1022,20 @@ export class MidyGM2 {
|
|
|
955
1022
|
this.channels[channelNumber].bankMSB = msb;
|
|
956
1023
|
}
|
|
957
1024
|
setModulation(channelNumber, modulation) {
|
|
1025
|
+
const now = this.audioContext.currentTime;
|
|
958
1026
|
const channel = this.channels[channelNumber];
|
|
959
1027
|
channel.modulation = (modulation / 127) *
|
|
960
1028
|
(channel.modulationDepthRange * 100);
|
|
1029
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
1030
|
+
activeNotes.forEach((activeNote) => {
|
|
1031
|
+
if (activeNote.modLFO) {
|
|
1032
|
+
activeNote.gainNode.gain.setValueAtTime(this.cbToRatio(activeNote.instrumentKey.modLfoToVolume) *
|
|
1033
|
+
channel.modulation, now);
|
|
1034
|
+
}
|
|
1035
|
+
else {
|
|
1036
|
+
this.startModulation(channel, activeNote, now);
|
|
1037
|
+
}
|
|
1038
|
+
});
|
|
961
1039
|
}
|
|
962
1040
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
963
1041
|
this.channels[channelNumber].portamentoTime = portamentoTime / 127;
|
|
@@ -1063,8 +1141,8 @@ export class MidyGM2 {
|
|
|
1063
1141
|
const velocity = 0;
|
|
1064
1142
|
const stopPedal = true;
|
|
1065
1143
|
const promises = [];
|
|
1066
|
-
channel.scheduledNotes.forEach((
|
|
1067
|
-
const activeNote = this.
|
|
1144
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1145
|
+
const activeNote = this.getActiveNote(noteList, now);
|
|
1068
1146
|
if (activeNote) {
|
|
1069
1147
|
const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
|
|
1070
1148
|
promises.push(notePromise);
|
|
@@ -1081,8 +1159,8 @@ export class MidyGM2 {
|
|
|
1081
1159
|
const velocity = 0;
|
|
1082
1160
|
const stopPedal = false;
|
|
1083
1161
|
const promises = [];
|
|
1084
|
-
channel.scheduledNotes.forEach((
|
|
1085
|
-
const activeNote = this.
|
|
1162
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1163
|
+
const activeNote = this.getActiveNote(noteList, now);
|
|
1086
1164
|
if (activeNote) {
|
|
1087
1165
|
const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
|
|
1088
1166
|
promises.push(notePromise);
|
|
@@ -1168,10 +1246,10 @@ export class MidyGM2 {
|
|
|
1168
1246
|
switch (data[3]) {
|
|
1169
1247
|
// case 1:
|
|
1170
1248
|
// // TODO
|
|
1171
|
-
// return this.
|
|
1249
|
+
// return this.setChannelPressure();
|
|
1172
1250
|
// case 3:
|
|
1173
1251
|
// // TODO
|
|
1174
|
-
// return this.
|
|
1252
|
+
// return this.setControlChange();
|
|
1175
1253
|
default:
|
|
1176
1254
|
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
1177
1255
|
}
|
|
@@ -1190,20 +1268,25 @@ export class MidyGM2 {
|
|
|
1190
1268
|
}
|
|
1191
1269
|
}
|
|
1192
1270
|
handleMasterVolumeSysEx(data) {
|
|
1193
|
-
const volume = (data[5] * 128 + data[4]
|
|
1271
|
+
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
1194
1272
|
this.handleMasterVolume(volume);
|
|
1195
1273
|
}
|
|
1196
1274
|
handleMasterVolume(volume) {
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1275
|
+
if (volume < 0 && 1 < volume) {
|
|
1276
|
+
console.error("Master Volume is out of range");
|
|
1277
|
+
}
|
|
1278
|
+
else {
|
|
1279
|
+
const now = this.audioContext.currentTime;
|
|
1280
|
+
this.masterGain.gain.cancelScheduledValues(now);
|
|
1281
|
+
this.masterGain.gain.setValueAtTime(volume * volume, now);
|
|
1282
|
+
}
|
|
1200
1283
|
}
|
|
1201
1284
|
handleMasterFineTuningSysEx(data) {
|
|
1202
1285
|
const fineTuning = (data[5] * 128 + data[4] - 8192) / 8192;
|
|
1203
1286
|
this.handleMasterFineTuning(fineTuning);
|
|
1204
1287
|
}
|
|
1205
1288
|
handleMasterFineTuning(fineTuning) {
|
|
1206
|
-
if (fineTuning <
|
|
1289
|
+
if (fineTuning < -1 && 1 < fineTuning) {
|
|
1207
1290
|
console.error("Master Fine Tuning value is out of range");
|
|
1208
1291
|
}
|
|
1209
1292
|
else {
|
|
@@ -1258,9 +1341,6 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
1258
1341
|
portamentoTime: 0,
|
|
1259
1342
|
reverb: 0,
|
|
1260
1343
|
chorus: 0,
|
|
1261
|
-
vibratoRate: 5,
|
|
1262
|
-
vibratoDepth: 0.5,
|
|
1263
|
-
vibratoDelay: 2.5,
|
|
1264
1344
|
bank: 121 * 128,
|
|
1265
1345
|
bankMSB: 121,
|
|
1266
1346
|
bankLSB: 0,
|
|
@@ -1290,3 +1370,16 @@ Object.defineProperty(MidyGM2, "effectSettings", {
|
|
|
1290
1370
|
pitchBendRange: 2,
|
|
1291
1371
|
}
|
|
1292
1372
|
});
|
|
1373
|
+
Object.defineProperty(MidyGM2, "controllerDestinationSettings", {
|
|
1374
|
+
enumerable: true,
|
|
1375
|
+
configurable: true,
|
|
1376
|
+
writable: true,
|
|
1377
|
+
value: {
|
|
1378
|
+
pitchControl: 0,
|
|
1379
|
+
filterCutoffControl: 0,
|
|
1380
|
+
amplitudeControl: 1,
|
|
1381
|
+
lfoPitchDepth: 0,
|
|
1382
|
+
lfoFilterDepth: 0,
|
|
1383
|
+
lfoAmplitudeDepth: 0,
|
|
1384
|
+
}
|
|
1385
|
+
});
|