@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.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 Midy {
|
|
4
54
|
constructor(audioContext) {
|
|
5
55
|
Object.defineProperty(this, "ticksPerBeat", {
|
|
@@ -181,10 +231,8 @@ export class Midy {
|
|
|
181
231
|
const pannerNode = new StereoPannerNode(audioContext, {
|
|
182
232
|
pan: Midy.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 Midy {
|
|
|
193
241
|
return {
|
|
194
242
|
gainNode,
|
|
195
243
|
pannerNode,
|
|
196
|
-
modulationEffect,
|
|
197
244
|
reverbEffect,
|
|
198
245
|
chorusEffect,
|
|
199
246
|
};
|
|
@@ -206,15 +253,21 @@ export class Midy {
|
|
|
206
253
|
...this.setChannelAudioNodes(audioContext),
|
|
207
254
|
scheduledNotes: new Map(),
|
|
208
255
|
sostenutoNotes: new Map(),
|
|
256
|
+
polyphonicKeyPressure: {
|
|
257
|
+
...Midy.controllerDestinationSettings,
|
|
258
|
+
},
|
|
259
|
+
channelPressure: {
|
|
260
|
+
...Midy.controllerDestinationSettings,
|
|
261
|
+
},
|
|
209
262
|
};
|
|
210
263
|
});
|
|
211
264
|
return channels;
|
|
212
265
|
}
|
|
213
|
-
async createNoteBuffer(
|
|
214
|
-
const sampleEnd =
|
|
266
|
+
async createNoteBuffer(instrumentKey, isSF3) {
|
|
267
|
+
const sampleEnd = instrumentKey.sample.length + instrumentKey.end;
|
|
215
268
|
if (isSF3) {
|
|
216
|
-
const sample = new Uint8Array(
|
|
217
|
-
sample.set(
|
|
269
|
+
const sample = new Uint8Array(instrumentKey.sample.length);
|
|
270
|
+
sample.set(instrumentKey.sample);
|
|
218
271
|
const audioBuffer = await this.audioContext.decodeAudioData(sample.buffer);
|
|
219
272
|
for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {
|
|
220
273
|
const channelData = audioBuffer.getChannelData(channel);
|
|
@@ -223,26 +276,27 @@ export class Midy {
|
|
|
223
276
|
return audioBuffer;
|
|
224
277
|
}
|
|
225
278
|
else {
|
|
226
|
-
const sample =
|
|
279
|
+
const sample = instrumentKey.sample.subarray(0, sampleEnd);
|
|
227
280
|
const floatSample = this.convertToFloat32Array(sample);
|
|
228
281
|
const audioBuffer = new AudioBuffer({
|
|
229
282
|
numberOfChannels: 1,
|
|
230
283
|
length: sample.length,
|
|
231
|
-
sampleRate:
|
|
284
|
+
sampleRate: instrumentKey.sampleRate,
|
|
232
285
|
});
|
|
233
286
|
const channelData = audioBuffer.getChannelData(0);
|
|
234
287
|
channelData.set(floatSample);
|
|
235
288
|
return audioBuffer;
|
|
236
289
|
}
|
|
237
290
|
}
|
|
238
|
-
async createNoteBufferNode(
|
|
291
|
+
async createNoteBufferNode(instrumentKey, isSF3) {
|
|
239
292
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
240
|
-
const audioBuffer = await this.createNoteBuffer(
|
|
293
|
+
const audioBuffer = await this.createNoteBuffer(instrumentKey, isSF3);
|
|
241
294
|
bufferSource.buffer = audioBuffer;
|
|
242
|
-
bufferSource.loop =
|
|
295
|
+
bufferSource.loop = instrumentKey.sampleModes % 2 !== 0;
|
|
243
296
|
if (bufferSource.loop) {
|
|
244
|
-
bufferSource.loopStart =
|
|
245
|
-
|
|
297
|
+
bufferSource.loopStart = instrumentKey.loopStart /
|
|
298
|
+
instrumentKey.sampleRate;
|
|
299
|
+
bufferSource.loopEnd = instrumentKey.loopEnd / instrumentKey.sampleRate;
|
|
246
300
|
}
|
|
247
301
|
return bufferSource;
|
|
248
302
|
}
|
|
@@ -260,9 +314,6 @@ export class Midy {
|
|
|
260
314
|
if (event.startTime > t + this.lookAhead)
|
|
261
315
|
break;
|
|
262
316
|
switch (event.type) {
|
|
263
|
-
case "controller":
|
|
264
|
-
this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
|
|
265
|
-
break;
|
|
266
317
|
case "noteOn":
|
|
267
318
|
if (event.velocity !== 0) {
|
|
268
319
|
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
|
|
@@ -276,9 +327,21 @@ export class Midy {
|
|
|
276
327
|
}
|
|
277
328
|
break;
|
|
278
329
|
}
|
|
330
|
+
case "noteAftertouch":
|
|
331
|
+
this.handlePolyphonicKeyPressure(event.channel, event.noteNumber, event.amount);
|
|
332
|
+
break;
|
|
333
|
+
case "controller":
|
|
334
|
+
this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
|
|
335
|
+
break;
|
|
279
336
|
case "programChange":
|
|
280
337
|
this.handleProgramChange(event.channel, event.programNumber);
|
|
281
338
|
break;
|
|
339
|
+
case "channelAftertouch":
|
|
340
|
+
this.handleChannelPressure(event.channel, event.amount);
|
|
341
|
+
break;
|
|
342
|
+
case "pitchBend":
|
|
343
|
+
this.handlePitchBend(event.channel, event.value);
|
|
344
|
+
break;
|
|
282
345
|
case "sysEx":
|
|
283
346
|
this.handleSysEx(event.data);
|
|
284
347
|
}
|
|
@@ -515,30 +578,26 @@ export class Midy {
|
|
|
515
578
|
const now = this.audioContext.currentTime;
|
|
516
579
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
517
580
|
}
|
|
518
|
-
getActiveNotes(channel) {
|
|
581
|
+
getActiveNotes(channel, time) {
|
|
519
582
|
const activeNotes = new Map();
|
|
520
|
-
channel.scheduledNotes.forEach((
|
|
521
|
-
const activeNote = this.
|
|
583
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
584
|
+
const activeNote = this.getActiveNote(noteList, time);
|
|
522
585
|
if (activeNote) {
|
|
523
586
|
activeNotes.set(activeNote.noteNumber, activeNote);
|
|
524
587
|
}
|
|
525
588
|
});
|
|
526
589
|
return activeNotes;
|
|
527
590
|
}
|
|
528
|
-
|
|
529
|
-
for (let i =
|
|
530
|
-
const
|
|
531
|
-
if (
|
|
532
|
-
return
|
|
591
|
+
getActiveNote(noteList, time) {
|
|
592
|
+
for (let i = noteList.length - 1; i >= 0; i--) {
|
|
593
|
+
const note = noteList[i];
|
|
594
|
+
if (!note)
|
|
595
|
+
return;
|
|
596
|
+
if (time < note.startTime)
|
|
597
|
+
continue;
|
|
598
|
+
return (note.ending) ? null : note;
|
|
533
599
|
}
|
|
534
|
-
|
|
535
|
-
createModulationEffect(audioContext) {
|
|
536
|
-
const lfo = new OscillatorNode(audioContext, {
|
|
537
|
-
frequency: 5,
|
|
538
|
-
});
|
|
539
|
-
return {
|
|
540
|
-
lfo,
|
|
541
|
-
};
|
|
600
|
+
return noteList[0];
|
|
542
601
|
}
|
|
543
602
|
createReverbEffect(audioContext, options = {}) {
|
|
544
603
|
const { decay = 0.8, preDecay = 0, } = options;
|
|
@@ -642,79 +701,116 @@ export class Midy {
|
|
|
642
701
|
centToHz(cent) {
|
|
643
702
|
return 8.176 * Math.pow(2, cent / 1200);
|
|
644
703
|
}
|
|
645
|
-
|
|
704
|
+
calcSemitoneOffset(channel) {
|
|
646
705
|
const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
|
|
647
706
|
const channelTuning = channel.coarseTuning + channel.fineTuning;
|
|
648
707
|
const tuning = masterTuning + channelTuning;
|
|
649
|
-
|
|
650
|
-
|
|
708
|
+
return channel.pitchBend * channel.pitchBendRange + tuning;
|
|
709
|
+
}
|
|
710
|
+
calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset) {
|
|
711
|
+
return instrumentKey.playbackRate(noteNumber) *
|
|
651
712
|
Math.pow(2, semitoneOffset / 12);
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
713
|
+
}
|
|
714
|
+
setVolumeEnvelope(channel, note) {
|
|
715
|
+
const { instrumentKey, startTime, velocity } = note;
|
|
716
|
+
note.gainNode = new GainNode(this.audioContext, {
|
|
656
717
|
gain: 0,
|
|
657
718
|
});
|
|
658
719
|
let volume = (velocity / 127) * channel.volume * channel.expression;
|
|
659
720
|
if (volume === 0)
|
|
660
721
|
volume = 1e-6; // exponentialRampToValueAtTime() requires a non-zero value
|
|
661
|
-
const attackVolume = this.cbToRatio(-
|
|
662
|
-
|
|
663
|
-
const
|
|
664
|
-
const
|
|
665
|
-
const
|
|
666
|
-
const
|
|
667
|
-
|
|
722
|
+
const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation) *
|
|
723
|
+
volume;
|
|
724
|
+
const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
|
|
725
|
+
const volDelay = startTime + instrumentKey.volDelay;
|
|
726
|
+
const volAttack = volDelay + instrumentKey.volAttack;
|
|
727
|
+
const volHold = volAttack + instrumentKey.volHold;
|
|
728
|
+
const volDecay = volHold + instrumentKey.volDecay;
|
|
729
|
+
note.gainNode.gain
|
|
668
730
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
669
731
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
670
732
|
.setValueAtTime(attackVolume, volHold)
|
|
671
733
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
672
|
-
|
|
734
|
+
}
|
|
735
|
+
setFilterEnvelope(channel, note) {
|
|
736
|
+
const { instrumentKey, startTime, noteNumber } = note;
|
|
673
737
|
const softPedalFactor = 1 -
|
|
674
738
|
(0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
|
|
675
739
|
const maxFreq = this.audioContext.sampleRate / 2;
|
|
676
|
-
const baseFreq = this.centToHz(
|
|
677
|
-
|
|
740
|
+
const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
|
|
741
|
+
softPedalFactor;
|
|
742
|
+
const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
|
|
678
743
|
const sustainFreq = (baseFreq +
|
|
679
|
-
(peekFreq - baseFreq) * (1 -
|
|
744
|
+
(peekFreq - baseFreq) * (1 - instrumentKey.modSustain)) * softPedalFactor;
|
|
745
|
+
const modDelay = startTime + instrumentKey.modDelay;
|
|
746
|
+
const modAttack = modDelay + instrumentKey.modAttack;
|
|
747
|
+
const modHold = modAttack + instrumentKey.modHold;
|
|
748
|
+
const modDecay = modHold + instrumentKey.modDecay;
|
|
680
749
|
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
681
750
|
const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
|
|
682
751
|
const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
|
|
683
|
-
|
|
752
|
+
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
684
753
|
type: "lowpass",
|
|
685
|
-
Q:
|
|
754
|
+
Q: instrumentKey.initialFilterQ / 10, // dB
|
|
686
755
|
frequency: adjustedBaseFreq,
|
|
687
756
|
});
|
|
688
|
-
|
|
689
|
-
const modAttack = modDelay + noteInfo.modAttack;
|
|
690
|
-
const modHold = modAttack + noteInfo.modHold;
|
|
691
|
-
const modDecay = modHold + noteInfo.modDecay;
|
|
692
|
-
filterNode.frequency
|
|
757
|
+
note.filterNode.frequency
|
|
693
758
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
694
759
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
695
760
|
.setValueAtTime(adjustedPeekFreq, modHold)
|
|
696
761
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
697
|
-
|
|
762
|
+
note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modEnvToPitch, modDelay);
|
|
763
|
+
}
|
|
764
|
+
startModulation(channel, note, time) {
|
|
765
|
+
const { instrumentKey } = note;
|
|
766
|
+
note.modLFOGain = new GainNode(this.audioContext, {
|
|
767
|
+
gain: this.cbToRatio(instrumentKey.modLfoToVolume) * channel.modulation,
|
|
768
|
+
});
|
|
769
|
+
note.modLFO = new OscillatorNode(this.audioContext, {
|
|
770
|
+
frequency: this.centToHz(instrumentKey.freqModLFO),
|
|
771
|
+
});
|
|
772
|
+
note.modLFO.start(time);
|
|
773
|
+
note.filterNode.frequency.setValueAtTime(note.filterNode.frequency.value + instrumentKey.modLfoToFilterFc, time);
|
|
774
|
+
note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modLfoToPitch, time);
|
|
775
|
+
note.modLFO.connect(note.modLFOGain);
|
|
776
|
+
note.modLFOGain.connect(note.bufferSource.detune);
|
|
777
|
+
}
|
|
778
|
+
startVibrato(channel, note, time) {
|
|
779
|
+
const { instrumentKey } = note;
|
|
780
|
+
note.vibLFOGain = new GainNode(this.audioContext, {
|
|
781
|
+
gain: channel.vibratoDepth,
|
|
782
|
+
});
|
|
783
|
+
note.vibLFO = new OscillatorNode(this.audioContext, {
|
|
784
|
+
frequency: this.centToHz(instrumentKey.freqModLFO) +
|
|
785
|
+
channel.vibratoRate,
|
|
786
|
+
});
|
|
787
|
+
note.vibLFO.start(time + channel.vibratoDelay);
|
|
788
|
+
note.vibLFO.connect(note.vibLFOGain);
|
|
789
|
+
note.vibLFOGain.connect(note.bufferSource.detune);
|
|
790
|
+
}
|
|
791
|
+
async createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3) {
|
|
792
|
+
const semitoneOffset = this.calcSemitoneOffset(channel);
|
|
793
|
+
const note = new Note(noteNumber, velocity, startTime, instrumentKey);
|
|
794
|
+
note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
|
|
795
|
+
note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
|
|
796
|
+
this.setVolumeEnvelope(channel, note);
|
|
797
|
+
this.setFilterEnvelope(channel, note);
|
|
698
798
|
if (channel.modulation > 0) {
|
|
699
|
-
const
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
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);
|
|
799
|
+
const delayModLFO = startTime + instrumentKey.delayModLFO;
|
|
800
|
+
this.startModulation(channel, note, delayModLFO);
|
|
801
|
+
}
|
|
802
|
+
if (channel.vibratoDepth > 0) {
|
|
803
|
+
const delayVibLFO = startTime + instrumentKey.delayVibLFO;
|
|
804
|
+
this.startVibrato(channel, note, delayVibLFO);
|
|
709
805
|
}
|
|
710
|
-
bufferSource.connect(filterNode);
|
|
711
|
-
filterNode.connect(gainNode);
|
|
712
806
|
if (this.mono && channel.currentBufferSource) {
|
|
713
807
|
channel.currentBufferSource.stop(startTime);
|
|
714
|
-
channel.currentBufferSource = bufferSource;
|
|
808
|
+
channel.currentBufferSource = note.bufferSource;
|
|
715
809
|
}
|
|
716
|
-
bufferSource.
|
|
717
|
-
|
|
810
|
+
note.bufferSource.connect(note.filterNode);
|
|
811
|
+
note.filterNode.connect(note.gainNode);
|
|
812
|
+
note.bufferSource.start(startTime, instrumentKey.start / instrumentKey.sampleRate);
|
|
813
|
+
return note;
|
|
718
814
|
}
|
|
719
815
|
calcBank(channel, channelNumber) {
|
|
720
816
|
if (channel.bankMSB === 121) {
|
|
@@ -733,36 +829,20 @@ export class Midy {
|
|
|
733
829
|
return;
|
|
734
830
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
735
831
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
736
|
-
const
|
|
737
|
-
if (!
|
|
832
|
+
const instrumentKey = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
|
|
833
|
+
if (!instrumentKey)
|
|
738
834
|
return;
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
this.connectNoteEffects(channel, gainNode);
|
|
835
|
+
const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
|
|
836
|
+
this.connectNoteEffects(channel, note.gainNode);
|
|
742
837
|
if (channel.sostenutoPedal) {
|
|
743
|
-
channel.sostenutoNotes.set(noteNumber,
|
|
744
|
-
gainNode,
|
|
745
|
-
filterNode,
|
|
746
|
-
bufferSource,
|
|
747
|
-
noteNumber,
|
|
748
|
-
noteInfo,
|
|
749
|
-
});
|
|
838
|
+
channel.sostenutoNotes.set(noteNumber, note);
|
|
750
839
|
}
|
|
751
840
|
const scheduledNotes = channel.scheduledNotes;
|
|
752
|
-
const scheduledNote = {
|
|
753
|
-
bufferSource,
|
|
754
|
-
filterNode,
|
|
755
|
-
gainNode,
|
|
756
|
-
lfoGain,
|
|
757
|
-
noteInfo,
|
|
758
|
-
noteNumber,
|
|
759
|
-
startTime,
|
|
760
|
-
};
|
|
761
841
|
if (scheduledNotes.has(noteNumber)) {
|
|
762
|
-
scheduledNotes.get(noteNumber).push(
|
|
842
|
+
scheduledNotes.get(noteNumber).push(note);
|
|
763
843
|
}
|
|
764
844
|
else {
|
|
765
|
-
scheduledNotes.set(noteNumber, [
|
|
845
|
+
scheduledNotes.set(noteNumber, [note]);
|
|
766
846
|
}
|
|
767
847
|
}
|
|
768
848
|
noteOn(channelNumber, noteNumber, velocity) {
|
|
@@ -784,15 +864,15 @@ export class Midy {
|
|
|
784
864
|
continue;
|
|
785
865
|
if (targetNote.ending)
|
|
786
866
|
continue;
|
|
787
|
-
const { bufferSource, filterNode, gainNode,
|
|
867
|
+
const { bufferSource, filterNode, gainNode, modLFO, modLFOGain, vibLFO, vibLFOGain, instrumentKey, } = targetNote;
|
|
788
868
|
const velocityRate = (velocity + 127) / 127;
|
|
789
|
-
const volEndTime = stopTime +
|
|
869
|
+
const volEndTime = stopTime + instrumentKey.volRelease * velocityRate;
|
|
790
870
|
gainNode.gain.cancelScheduledValues(stopTime);
|
|
791
871
|
gainNode.gain.linearRampToValueAtTime(0, volEndTime);
|
|
792
872
|
const maxFreq = this.audioContext.sampleRate / 2;
|
|
793
|
-
const baseFreq = this.centToHz(
|
|
873
|
+
const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
|
|
794
874
|
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
795
|
-
const modEndTime = stopTime +
|
|
875
|
+
const modEndTime = stopTime + instrumentKey.modRelease * velocityRate;
|
|
796
876
|
filterNode.frequency
|
|
797
877
|
.cancelScheduledValues(stopTime)
|
|
798
878
|
.linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
|
|
@@ -806,8 +886,14 @@ export class Midy {
|
|
|
806
886
|
bufferSource.disconnect(0);
|
|
807
887
|
filterNode.disconnect(0);
|
|
808
888
|
gainNode.disconnect(0);
|
|
809
|
-
if (
|
|
810
|
-
|
|
889
|
+
if (modLFOGain)
|
|
890
|
+
modLFOGain.disconnect(0);
|
|
891
|
+
if (vibLFOGain)
|
|
892
|
+
vibLFOGain.disconnect(0);
|
|
893
|
+
if (modLFO)
|
|
894
|
+
modLFO.stop();
|
|
895
|
+
if (vibLFO)
|
|
896
|
+
vibLFO.stop();
|
|
811
897
|
resolve();
|
|
812
898
|
};
|
|
813
899
|
bufferSource.stop(volEndTime);
|
|
@@ -856,7 +942,7 @@ export class Midy {
|
|
|
856
942
|
case 0x90:
|
|
857
943
|
return this.noteOn(channelNumber, data1, data2);
|
|
858
944
|
case 0xA0:
|
|
859
|
-
return this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
|
|
945
|
+
return; // this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
|
|
860
946
|
case 0xB0:
|
|
861
947
|
return this.handleControlChange(channelNumber, data1, data2);
|
|
862
948
|
case 0xC0:
|
|
@@ -864,7 +950,7 @@ export class Midy {
|
|
|
864
950
|
case 0xD0:
|
|
865
951
|
return this.handleChannelPressure(channelNumber, data1);
|
|
866
952
|
case 0xE0:
|
|
867
|
-
return this.
|
|
953
|
+
return this.handlePitchBendMessage(channelNumber, data1, data2);
|
|
868
954
|
default:
|
|
869
955
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
870
956
|
}
|
|
@@ -872,17 +958,16 @@ export class Midy {
|
|
|
872
958
|
handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure) {
|
|
873
959
|
const now = this.audioContext.currentTime;
|
|
874
960
|
const channel = this.channels[channelNumber];
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
if (
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
});
|
|
961
|
+
pressure /= 64;
|
|
962
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
963
|
+
if (channel.polyphonicKeyPressure.amplitudeControl !== 1) {
|
|
964
|
+
if (activeNotes.has(noteNumber)) {
|
|
965
|
+
const activeNote = activeNotes.get(noteNumber);
|
|
966
|
+
const gain = activeNote.gainNode.gain.value;
|
|
967
|
+
activeNote.gainNode.gain
|
|
968
|
+
.cancelScheduledValues(now)
|
|
969
|
+
.setValueAtTime(gain * pressure, now);
|
|
970
|
+
}
|
|
886
971
|
}
|
|
887
972
|
}
|
|
888
973
|
handleProgramChange(channelNumber, program) {
|
|
@@ -891,11 +976,37 @@ export class Midy {
|
|
|
891
976
|
channel.program = program;
|
|
892
977
|
}
|
|
893
978
|
handleChannelPressure(channelNumber, pressure) {
|
|
894
|
-
this.
|
|
979
|
+
const now = this.audioContext.currentTime;
|
|
980
|
+
const channel = this.channels[channelNumber];
|
|
981
|
+
pressure /= 64;
|
|
982
|
+
channel.channelPressure = pressure;
|
|
983
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
984
|
+
if (channel.channelPressure.amplitudeControl !== 1) {
|
|
985
|
+
activeNotes.forEach((activeNote) => {
|
|
986
|
+
const gain = activeNote.gainNode.gain.value;
|
|
987
|
+
activeNote.gainNode.gain
|
|
988
|
+
.cancelScheduledValues(now)
|
|
989
|
+
.setValueAtTime(gain * pressure, now);
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
994
|
+
const pitchBend = msb * 128 + lsb;
|
|
995
|
+
this.handlePitchBend(channelNumber, pitchBend);
|
|
895
996
|
}
|
|
896
|
-
handlePitchBend(channelNumber,
|
|
897
|
-
const
|
|
898
|
-
this.channels[channelNumber]
|
|
997
|
+
handlePitchBend(channelNumber, pitchBend) {
|
|
998
|
+
const now = this.audioContext.currentTime;
|
|
999
|
+
const channel = this.channels[channelNumber];
|
|
1000
|
+
channel.pitchBend = (pitchBend - 8192) / 8192;
|
|
1001
|
+
const semitoneOffset = this.calcSemitoneOffset(channel);
|
|
1002
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
1003
|
+
activeNotes.forEach((activeNote) => {
|
|
1004
|
+
const { bufferSource, instrumentKey, noteNumber } = activeNote;
|
|
1005
|
+
const playbackRate = calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
|
|
1006
|
+
bufferSource.playbackRate
|
|
1007
|
+
.cancelScheduledValues(now)
|
|
1008
|
+
.setValueAtTime(playbackRate * pressure, now);
|
|
1009
|
+
});
|
|
899
1010
|
}
|
|
900
1011
|
handleControlChange(channelNumber, controller, value) {
|
|
901
1012
|
switch (controller) {
|
|
@@ -966,9 +1077,20 @@ export class Midy {
|
|
|
966
1077
|
this.channels[channelNumber].bankMSB = msb;
|
|
967
1078
|
}
|
|
968
1079
|
setModulation(channelNumber, modulation) {
|
|
1080
|
+
const now = this.audioContext.currentTime;
|
|
969
1081
|
const channel = this.channels[channelNumber];
|
|
970
1082
|
channel.modulation = (modulation / 127) *
|
|
971
1083
|
(channel.modulationDepthRange * 100);
|
|
1084
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
1085
|
+
activeNotes.forEach((activeNote) => {
|
|
1086
|
+
if (activeNote.modLFO) {
|
|
1087
|
+
activeNote.gainNode.gain.setValueAtTime(this.cbToRatio(activeNote.instrumentKey.modLfoToVolume) *
|
|
1088
|
+
channel.modulation, now);
|
|
1089
|
+
}
|
|
1090
|
+
else {
|
|
1091
|
+
this.startModulation(channel, activeNote, now);
|
|
1092
|
+
}
|
|
1093
|
+
});
|
|
972
1094
|
}
|
|
973
1095
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
974
1096
|
this.channels[channelNumber].portamentoTime = portamentoTime / 127;
|
|
@@ -1029,7 +1151,8 @@ export class Midy {
|
|
|
1029
1151
|
const channel = this.channels[channelNumber];
|
|
1030
1152
|
channel.sostenutoPedal = isOn;
|
|
1031
1153
|
if (isOn) {
|
|
1032
|
-
const
|
|
1154
|
+
const now = this.audioContext.currentTime;
|
|
1155
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
1033
1156
|
channel.sostenutoNotes = new Map(activeNotes);
|
|
1034
1157
|
}
|
|
1035
1158
|
else {
|
|
@@ -1041,20 +1164,12 @@ export class Midy {
|
|
|
1041
1164
|
channel.softPedal = softPedal / 127;
|
|
1042
1165
|
}
|
|
1043
1166
|
setVibratoRate(channelNumber, vibratoRate) {
|
|
1044
|
-
const now = this.audioContext.currentTime;
|
|
1045
1167
|
const channel = this.channels[channelNumber];
|
|
1046
1168
|
channel.vibratoRate = vibratoRate / 127 * 4 + 3; // 3-7Hz
|
|
1047
|
-
channel.modulationEffect.lfo.frequency
|
|
1048
|
-
.cancelScheduledValues(now)
|
|
1049
|
-
.setValueAtTime(channel.vibratoRate, now);
|
|
1050
1169
|
}
|
|
1051
1170
|
setVibratoDepth(channelNumber, vibratoDepth) {
|
|
1052
|
-
const now = this.audioContext.currentTime;
|
|
1053
1171
|
const channel = this.channels[channelNumber];
|
|
1054
1172
|
channel.vibratoDepth = vibratoDepth / 127;
|
|
1055
|
-
channel.modulationEffect.lfoGain.gain
|
|
1056
|
-
.cancelScheduledValues(now)
|
|
1057
|
-
.setValueAtTime(channel.vibratoDepth, now);
|
|
1058
1173
|
}
|
|
1059
1174
|
setVibratoDelay(channelNumber, vibratoDelay) {
|
|
1060
1175
|
// Access Virus: 0-10sec
|
|
@@ -1135,8 +1250,8 @@ export class Midy {
|
|
|
1135
1250
|
const velocity = 0;
|
|
1136
1251
|
const stopPedal = true;
|
|
1137
1252
|
const promises = [];
|
|
1138
|
-
channel.scheduledNotes.forEach((
|
|
1139
|
-
const activeNote = this.
|
|
1253
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1254
|
+
const activeNote = this.getActiveNote(noteList, now);
|
|
1140
1255
|
if (activeNote) {
|
|
1141
1256
|
const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
|
|
1142
1257
|
promises.push(notePromise);
|
|
@@ -1153,8 +1268,8 @@ export class Midy {
|
|
|
1153
1268
|
const velocity = 0;
|
|
1154
1269
|
const stopPedal = false;
|
|
1155
1270
|
const promises = [];
|
|
1156
|
-
channel.scheduledNotes.forEach((
|
|
1157
|
-
const activeNote = this.
|
|
1271
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1272
|
+
const activeNote = this.getActiveNote(noteList, now);
|
|
1158
1273
|
if (activeNote) {
|
|
1159
1274
|
const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
|
|
1160
1275
|
promises.push(notePromise);
|
|
@@ -1240,10 +1355,10 @@ export class Midy {
|
|
|
1240
1355
|
switch (data[3]) {
|
|
1241
1356
|
// case 1:
|
|
1242
1357
|
// // TODO
|
|
1243
|
-
// return this.
|
|
1358
|
+
// return this.setChannelPressure();
|
|
1244
1359
|
// case 3:
|
|
1245
1360
|
// // TODO
|
|
1246
|
-
// return this.
|
|
1361
|
+
// return this.setControlChange();
|
|
1247
1362
|
default:
|
|
1248
1363
|
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
1249
1364
|
}
|
|
@@ -1262,20 +1377,25 @@ export class Midy {
|
|
|
1262
1377
|
}
|
|
1263
1378
|
}
|
|
1264
1379
|
handleMasterVolumeSysEx(data) {
|
|
1265
|
-
const volume = (data[5] * 128 + data[4]
|
|
1380
|
+
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
1266
1381
|
this.handleMasterVolume(volume);
|
|
1267
1382
|
}
|
|
1268
1383
|
handleMasterVolume(volume) {
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1384
|
+
if (volume < 0 && 1 < volume) {
|
|
1385
|
+
console.error("Master Volume is out of range");
|
|
1386
|
+
}
|
|
1387
|
+
else {
|
|
1388
|
+
const now = this.audioContext.currentTime;
|
|
1389
|
+
this.masterGain.gain.cancelScheduledValues(now);
|
|
1390
|
+
this.masterGain.gain.setValueAtTime(volume * volume, now);
|
|
1391
|
+
}
|
|
1272
1392
|
}
|
|
1273
1393
|
handleMasterFineTuningSysEx(data) {
|
|
1274
1394
|
const fineTuning = (data[5] * 128 + data[4] - 8192) / 8192;
|
|
1275
1395
|
this.handleMasterFineTuning(fineTuning);
|
|
1276
1396
|
}
|
|
1277
1397
|
handleMasterFineTuning(fineTuning) {
|
|
1278
|
-
if (fineTuning <
|
|
1398
|
+
if (fineTuning < -1 && 1 < fineTuning) {
|
|
1279
1399
|
console.error("Master Fine Tuning value is out of range");
|
|
1280
1400
|
}
|
|
1281
1401
|
else {
|
|
@@ -1362,3 +1482,16 @@ Object.defineProperty(Midy, "effectSettings", {
|
|
|
1362
1482
|
pitchBendRange: 2,
|
|
1363
1483
|
}
|
|
1364
1484
|
});
|
|
1485
|
+
Object.defineProperty(Midy, "controllerDestinationSettings", {
|
|
1486
|
+
enumerable: true,
|
|
1487
|
+
configurable: true,
|
|
1488
|
+
writable: true,
|
|
1489
|
+
value: {
|
|
1490
|
+
pitchControl: 0,
|
|
1491
|
+
filterCutoffControl: 0,
|
|
1492
|
+
amplitudeControl: 1,
|
|
1493
|
+
lfoPitchDepth: 0,
|
|
1494
|
+
lfoFilterDepth: 0,
|
|
1495
|
+
lfoAmplitudeDepth: 0,
|
|
1496
|
+
}
|
|
1497
|
+
});
|