@marmooo/midy 0.1.7 → 0.2.0
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/midy-GM1.d.ts +48 -23
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +358 -135
- package/esm/midy-GM2.d.ts +50 -33
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +512 -220
- package/esm/midy-GMLite.d.ts +46 -22
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +339 -137
- package/esm/midy.d.ts +50 -38
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +526 -237
- package/package.json +2 -2
- package/script/midy-GM1.d.ts +48 -23
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +358 -135
- package/script/midy-GM2.d.ts +50 -33
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +512 -220
- package/script/midy-GMLite.d.ts +46 -22
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +339 -137
- package/script/midy.d.ts +50 -38
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +526 -237
package/script/midy-GM1.js
CHANGED
|
@@ -4,7 +4,7 @@ exports.MidyGM1 = void 0;
|
|
|
4
4
|
const midi_file_1 = require("midi-file");
|
|
5
5
|
const soundfont_parser_1 = require("@marmooo/soundfont-parser");
|
|
6
6
|
class Note {
|
|
7
|
-
constructor(noteNumber, velocity, startTime,
|
|
7
|
+
constructor(noteNumber, velocity, startTime, voice, voiceParams) {
|
|
8
8
|
Object.defineProperty(this, "bufferSource", {
|
|
9
9
|
enumerable: true,
|
|
10
10
|
configurable: true,
|
|
@@ -56,9 +56,75 @@ class Note {
|
|
|
56
56
|
this.noteNumber = noteNumber;
|
|
57
57
|
this.velocity = velocity;
|
|
58
58
|
this.startTime = startTime;
|
|
59
|
-
this.
|
|
59
|
+
this.voice = voice;
|
|
60
|
+
this.voiceParams = voiceParams;
|
|
60
61
|
}
|
|
61
62
|
}
|
|
63
|
+
// normalized to 0-1 for use with the SF2 modulator model
|
|
64
|
+
const defaultControllerState = {
|
|
65
|
+
noteOnVelocity: { type: 2, defaultValue: 0 },
|
|
66
|
+
noteOnKeyNumber: { type: 3, defaultValue: 0 },
|
|
67
|
+
pitchWheel: { type: 14, defaultValue: 8192 / 16383 },
|
|
68
|
+
pitchWheelSensitivity: { type: 16, defaultValue: 2 / 128 },
|
|
69
|
+
link: { type: 127, defaultValue: 0 },
|
|
70
|
+
// bankMSB: { type: 128 + 0, defaultValue: 121, },
|
|
71
|
+
modulationDepth: { type: 128 + 1, defaultValue: 0 },
|
|
72
|
+
// dataMSB: { type: 128 + 6, defaultValue: 0, },
|
|
73
|
+
volume: { type: 128 + 7, defaultValue: 100 / 127 },
|
|
74
|
+
pan: { type: 128 + 10, defaultValue: 0.5 },
|
|
75
|
+
expression: { type: 128 + 11, defaultValue: 1 },
|
|
76
|
+
// bankLSB: { type: 128 + 32, defaultValue: 0, },
|
|
77
|
+
// dataLSB: { type: 128 + 38, defaultValue: 0, },
|
|
78
|
+
sustainPedal: { type: 128 + 64, defaultValue: 0 },
|
|
79
|
+
// rpnLSB: { type: 128 + 100, defaultValue: 127 },
|
|
80
|
+
// rpnMSB: { type: 128 + 101, defaultValue: 127 },
|
|
81
|
+
// allSoundOff: { type: 128 + 120, defaultValue: 0 },
|
|
82
|
+
// resetAllControllers: { type: 128 + 121, defaultValue: 0 },
|
|
83
|
+
// allNotesOff: { type: 128 + 123, defaultValue: 0 },
|
|
84
|
+
};
|
|
85
|
+
class ControllerState {
|
|
86
|
+
constructor() {
|
|
87
|
+
Object.defineProperty(this, "array", {
|
|
88
|
+
enumerable: true,
|
|
89
|
+
configurable: true,
|
|
90
|
+
writable: true,
|
|
91
|
+
value: new Float32Array(256)
|
|
92
|
+
});
|
|
93
|
+
const entries = Object.entries(defaultControllerState);
|
|
94
|
+
for (const [name, { type, defaultValue }] of entries) {
|
|
95
|
+
this.array[type] = defaultValue;
|
|
96
|
+
Object.defineProperty(this, name, {
|
|
97
|
+
get: () => this.array[type],
|
|
98
|
+
set: (value) => this.array[type] = value,
|
|
99
|
+
enumerable: true,
|
|
100
|
+
configurable: true,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const filterEnvelopeKeys = [
|
|
106
|
+
"modEnvToPitch",
|
|
107
|
+
"initialFilterFc",
|
|
108
|
+
"modEnvToFilterFc",
|
|
109
|
+
"modDelay",
|
|
110
|
+
"modAttack",
|
|
111
|
+
"modHold",
|
|
112
|
+
"modDecay",
|
|
113
|
+
"modSustain",
|
|
114
|
+
"modRelease",
|
|
115
|
+
"playbackRate",
|
|
116
|
+
];
|
|
117
|
+
const filterEnvelopeKeySet = new Set(filterEnvelopeKeys);
|
|
118
|
+
const volumeEnvelopeKeys = [
|
|
119
|
+
"volDelay",
|
|
120
|
+
"volAttack",
|
|
121
|
+
"volHold",
|
|
122
|
+
"volDecay",
|
|
123
|
+
"volSustain",
|
|
124
|
+
"volRelease",
|
|
125
|
+
"initialAttenuation",
|
|
126
|
+
];
|
|
127
|
+
const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
|
|
62
128
|
class MidyGM1 {
|
|
63
129
|
constructor(audioContext) {
|
|
64
130
|
Object.defineProperty(this, "ticksPerBeat", {
|
|
@@ -171,6 +237,7 @@ class MidyGM1 {
|
|
|
171
237
|
});
|
|
172
238
|
this.audioContext = audioContext;
|
|
173
239
|
this.masterGain = new GainNode(audioContext);
|
|
240
|
+
this.voiceParamsHandlers = this.createVoiceParamsHandlers();
|
|
174
241
|
this.controlChangeHandlers = this.createControlChangeHandlers();
|
|
175
242
|
this.channels = this.createChannels(audioContext);
|
|
176
243
|
this.masterGain.connect(audioContext.destination);
|
|
@@ -213,7 +280,7 @@ class MidyGM1 {
|
|
|
213
280
|
this.totalTime = this.calcTotalTime();
|
|
214
281
|
}
|
|
215
282
|
setChannelAudioNodes(audioContext) {
|
|
216
|
-
const { gainLeft, gainRight } = this.panToGain(
|
|
283
|
+
const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
|
|
217
284
|
const gainL = new GainNode(audioContext, { gain: gainLeft });
|
|
218
285
|
const gainR = new GainNode(audioContext, { gain: gainRight });
|
|
219
286
|
const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
|
|
@@ -230,18 +297,18 @@ class MidyGM1 {
|
|
|
230
297
|
const channels = Array.from({ length: 16 }, () => {
|
|
231
298
|
return {
|
|
232
299
|
...this.constructor.channelSettings,
|
|
233
|
-
|
|
300
|
+
state: new ControllerState(),
|
|
234
301
|
...this.setChannelAudioNodes(audioContext),
|
|
235
302
|
scheduledNotes: new Map(),
|
|
236
303
|
};
|
|
237
304
|
});
|
|
238
305
|
return channels;
|
|
239
306
|
}
|
|
240
|
-
async createNoteBuffer(
|
|
241
|
-
const sampleStart =
|
|
242
|
-
const sampleEnd =
|
|
307
|
+
async createNoteBuffer(voiceParams, isSF3) {
|
|
308
|
+
const sampleStart = voiceParams.start;
|
|
309
|
+
const sampleEnd = voiceParams.sample.length + voiceParams.end;
|
|
243
310
|
if (isSF3) {
|
|
244
|
-
const sample =
|
|
311
|
+
const sample = voiceParams.sample;
|
|
245
312
|
const start = sample.byteOffset + sampleStart;
|
|
246
313
|
const end = sample.byteOffset + sampleEnd;
|
|
247
314
|
const buffer = sample.buffer.slice(start, end);
|
|
@@ -249,14 +316,14 @@ class MidyGM1 {
|
|
|
249
316
|
return audioBuffer;
|
|
250
317
|
}
|
|
251
318
|
else {
|
|
252
|
-
const sample =
|
|
319
|
+
const sample = voiceParams.sample;
|
|
253
320
|
const start = sample.byteOffset + sampleStart;
|
|
254
321
|
const end = sample.byteOffset + sampleEnd;
|
|
255
322
|
const buffer = sample.buffer.slice(start, end);
|
|
256
323
|
const audioBuffer = new AudioBuffer({
|
|
257
324
|
numberOfChannels: 1,
|
|
258
325
|
length: sample.length,
|
|
259
|
-
sampleRate:
|
|
326
|
+
sampleRate: voiceParams.sampleRate,
|
|
260
327
|
});
|
|
261
328
|
const channelData = audioBuffer.getChannelData(0);
|
|
262
329
|
const int16Array = new Int16Array(buffer);
|
|
@@ -266,15 +333,14 @@ class MidyGM1 {
|
|
|
266
333
|
return audioBuffer;
|
|
267
334
|
}
|
|
268
335
|
}
|
|
269
|
-
async createNoteBufferNode(
|
|
336
|
+
async createNoteBufferNode(voiceParams, isSF3) {
|
|
270
337
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
271
|
-
const audioBuffer = await this.createNoteBuffer(
|
|
338
|
+
const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
|
|
272
339
|
bufferSource.buffer = audioBuffer;
|
|
273
|
-
bufferSource.loop =
|
|
340
|
+
bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
|
|
274
341
|
if (bufferSource.loop) {
|
|
275
|
-
bufferSource.loopStart =
|
|
276
|
-
|
|
277
|
-
bufferSource.loopEnd = instrumentKey.loopEnd / instrumentKey.sampleRate;
|
|
342
|
+
bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
|
|
343
|
+
bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
|
|
278
344
|
}
|
|
279
345
|
return bufferSource;
|
|
280
346
|
}
|
|
@@ -304,7 +370,7 @@ class MidyGM1 {
|
|
|
304
370
|
this.handleProgramChange(event.channel, event.programNumber);
|
|
305
371
|
break;
|
|
306
372
|
case "pitchBend":
|
|
307
|
-
this.setPitchBend(event.channel, event.value);
|
|
373
|
+
this.setPitchBend(event.channel, event.value + 8192);
|
|
308
374
|
break;
|
|
309
375
|
case "sysEx":
|
|
310
376
|
this.handleSysEx(event.data);
|
|
@@ -535,41 +601,50 @@ class MidyGM1 {
|
|
|
535
601
|
}
|
|
536
602
|
calcSemitoneOffset(channel) {
|
|
537
603
|
const tuning = channel.coarseTuning + channel.fineTuning;
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
return
|
|
542
|
-
Math.pow(2, semitoneOffset / 12);
|
|
604
|
+
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
605
|
+
const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 128;
|
|
606
|
+
const pitch = pitchWheel * pitchWheelSensitivity;
|
|
607
|
+
return tuning + pitch;
|
|
543
608
|
}
|
|
544
609
|
setVolumeEnvelope(note) {
|
|
545
|
-
const
|
|
546
|
-
const
|
|
547
|
-
const
|
|
548
|
-
const
|
|
549
|
-
const
|
|
550
|
-
const
|
|
551
|
-
const
|
|
610
|
+
const now = this.audioContext.currentTime;
|
|
611
|
+
const { voiceParams, startTime } = note;
|
|
612
|
+
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
613
|
+
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
614
|
+
const volDelay = startTime + voiceParams.volDelay;
|
|
615
|
+
const volAttack = volDelay + voiceParams.volAttack;
|
|
616
|
+
const volHold = volAttack + voiceParams.volHold;
|
|
617
|
+
const volDecay = volHold + voiceParams.volDecay;
|
|
552
618
|
note.volumeNode.gain
|
|
553
|
-
.cancelScheduledValues(
|
|
619
|
+
.cancelScheduledValues(now)
|
|
554
620
|
.setValueAtTime(0, startTime)
|
|
555
621
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
556
622
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
557
623
|
.setValueAtTime(attackVolume, volHold)
|
|
558
624
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
559
625
|
}
|
|
560
|
-
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
|
|
626
|
+
setPlaybackRate(note) {
|
|
627
|
+
const now = this.audioContext.currentTime;
|
|
628
|
+
note.bufferSource.playbackRate
|
|
629
|
+
.cancelScheduledValues(now)
|
|
630
|
+
.setValueAtTime(note.voiceParams.playbackRate, now);
|
|
631
|
+
}
|
|
632
|
+
setPitch(channel, note) {
|
|
633
|
+
const now = this.audioContext.currentTime;
|
|
634
|
+
const { startTime } = note;
|
|
635
|
+
const basePitch = this.calcSemitoneOffset(channel) * 100;
|
|
636
|
+
note.bufferSource.detune
|
|
637
|
+
.cancelScheduledValues(now)
|
|
638
|
+
.setValueAtTime(basePitch, startTime);
|
|
639
|
+
const modEnvToPitch = note.voiceParams.modEnvToPitch;
|
|
564
640
|
if (modEnvToPitch === 0)
|
|
565
641
|
return;
|
|
566
|
-
const
|
|
567
|
-
const
|
|
568
|
-
const
|
|
569
|
-
const
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
note.bufferSource.playbackRate.value
|
|
642
|
+
const peekPitch = basePitch + modEnvToPitch;
|
|
643
|
+
const modDelay = startTime + voiceParams.modDelay;
|
|
644
|
+
const modAttack = modDelay + voiceParams.modAttack;
|
|
645
|
+
const modHold = modAttack + voiceParams.modHold;
|
|
646
|
+
const modDecay = modHold + voiceParams.modDecay;
|
|
647
|
+
note.bufferSource.detune
|
|
573
648
|
.setValueAtTime(basePitch, modDelay)
|
|
574
649
|
.exponentialRampToValueAtTime(peekPitch, modAttack)
|
|
575
650
|
.setValueAtTime(peekPitch, modHold)
|
|
@@ -581,20 +656,21 @@ class MidyGM1 {
|
|
|
581
656
|
return Math.max(minFrequency, Math.min(frequency, maxFrequency));
|
|
582
657
|
}
|
|
583
658
|
setFilterEnvelope(note) {
|
|
584
|
-
const
|
|
585
|
-
const
|
|
586
|
-
const
|
|
659
|
+
const now = this.audioContext.currentTime;
|
|
660
|
+
const { voiceParams, startTime } = note;
|
|
661
|
+
const baseFreq = this.centToHz(voiceParams.initialFilterFc);
|
|
662
|
+
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc);
|
|
587
663
|
const sustainFreq = baseFreq +
|
|
588
|
-
(peekFreq - baseFreq) * (1 -
|
|
664
|
+
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
589
665
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
590
666
|
const adjustedPeekFreq = this.clampCutoffFrequency(peekFreq);
|
|
591
667
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
592
|
-
const modDelay = startTime +
|
|
593
|
-
const modAttack = modDelay +
|
|
594
|
-
const modHold = modAttack +
|
|
595
|
-
const modDecay = modHold +
|
|
668
|
+
const modDelay = startTime + voiceParams.modDelay;
|
|
669
|
+
const modAttack = modDelay + voiceParams.modAttack;
|
|
670
|
+
const modHold = modAttack + voiceParams.modHold;
|
|
671
|
+
const modDecay = modHold + voiceParams.modDecay;
|
|
596
672
|
note.filterNode.frequency
|
|
597
|
-
.cancelScheduledValues(
|
|
673
|
+
.cancelScheduledValues(now)
|
|
598
674
|
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
599
675
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
600
676
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
@@ -602,25 +678,18 @@ class MidyGM1 {
|
|
|
602
678
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
603
679
|
}
|
|
604
680
|
startModulation(channel, note, startTime) {
|
|
605
|
-
const {
|
|
606
|
-
const { modLfoToPitch, modLfoToVolume } = instrumentKey;
|
|
681
|
+
const { voiceParams } = note;
|
|
607
682
|
note.modulationLFO = new OscillatorNode(this.audioContext, {
|
|
608
|
-
frequency: this.centToHz(
|
|
683
|
+
frequency: this.centToHz(voiceParams.freqModLFO),
|
|
609
684
|
});
|
|
610
685
|
note.filterDepth = new GainNode(this.audioContext, {
|
|
611
|
-
gain:
|
|
612
|
-
});
|
|
613
|
-
const modulationDepth = Math.abs(modLfoToPitch) + channel.modulationDepth;
|
|
614
|
-
const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
|
|
615
|
-
note.modulationDepth = new GainNode(this.audioContext, {
|
|
616
|
-
gain: modulationDepth * modulationDepthSign,
|
|
617
|
-
});
|
|
618
|
-
const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
619
|
-
const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
|
|
620
|
-
note.volumeDepth = new GainNode(this.audioContext, {
|
|
621
|
-
gain: volumeDepth * volumeDepthSign,
|
|
686
|
+
gain: voiceParams.modLfoToFilterFc,
|
|
622
687
|
});
|
|
623
|
-
note.
|
|
688
|
+
note.modulationDepth = new GainNode(this.audioContext);
|
|
689
|
+
this.setModLfoToPitch(channel, note);
|
|
690
|
+
note.volumeDepth = new GainNode(this.audioContext);
|
|
691
|
+
this.setModLfoToVolume(note);
|
|
692
|
+
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
624
693
|
note.modulationLFO.connect(note.filterDepth);
|
|
625
694
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
626
695
|
note.modulationLFO.connect(note.modulationDepth);
|
|
@@ -628,24 +697,23 @@ class MidyGM1 {
|
|
|
628
697
|
note.modulationLFO.connect(note.volumeDepth);
|
|
629
698
|
note.volumeDepth.connect(note.volumeNode.gain);
|
|
630
699
|
}
|
|
631
|
-
async createNote(channel,
|
|
632
|
-
const
|
|
633
|
-
const
|
|
634
|
-
note
|
|
700
|
+
async createNote(channel, voice, noteNumber, velocity, startTime, isSF3) {
|
|
701
|
+
const state = channel.state;
|
|
702
|
+
const voiceParams = voice.getAllParams(state.array);
|
|
703
|
+
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
704
|
+
note.bufferSource = await this.createNoteBufferNode(voiceParams, isSF3);
|
|
635
705
|
note.volumeNode = new GainNode(this.audioContext);
|
|
636
706
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
637
707
|
type: "lowpass",
|
|
638
|
-
Q:
|
|
708
|
+
Q: voiceParams.initialFilterQ / 10, // dB
|
|
639
709
|
});
|
|
640
710
|
this.setVolumeEnvelope(note);
|
|
641
711
|
this.setFilterEnvelope(note);
|
|
642
|
-
|
|
643
|
-
|
|
712
|
+
this.setPlaybackRate(note);
|
|
713
|
+
if (0 < state.modulationDepth) {
|
|
714
|
+
this.setPitch(channel, note);
|
|
644
715
|
this.startModulation(channel, note, startTime);
|
|
645
716
|
}
|
|
646
|
-
else {
|
|
647
|
-
note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
|
|
648
|
-
}
|
|
649
717
|
note.bufferSource.connect(note.filterNode);
|
|
650
718
|
note.filterNode.connect(note.volumeNode);
|
|
651
719
|
note.bufferSource.start(startTime);
|
|
@@ -653,19 +721,19 @@ class MidyGM1 {
|
|
|
653
721
|
}
|
|
654
722
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
655
723
|
const channel = this.channels[channelNumber];
|
|
656
|
-
const bankNumber =
|
|
724
|
+
const bankNumber = channel.bank;
|
|
657
725
|
const soundFontIndex = this.soundFontTable[channel.program].get(bankNumber);
|
|
658
726
|
if (soundFontIndex === undefined)
|
|
659
727
|
return;
|
|
660
728
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
661
729
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
662
|
-
const
|
|
663
|
-
if (!
|
|
730
|
+
const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
|
|
731
|
+
if (!voice)
|
|
664
732
|
return;
|
|
665
|
-
const note = await this.createNote(channel,
|
|
733
|
+
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
|
|
666
734
|
note.volumeNode.connect(channel.gainL);
|
|
667
735
|
note.volumeNode.connect(channel.gainR);
|
|
668
|
-
const exclusiveClass =
|
|
736
|
+
const exclusiveClass = note.voiceParams.exclusiveClass;
|
|
669
737
|
if (exclusiveClass !== 0) {
|
|
670
738
|
if (this.exclusiveClassMap.has(exclusiveClass)) {
|
|
671
739
|
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
@@ -721,7 +789,7 @@ class MidyGM1 {
|
|
|
721
789
|
}
|
|
722
790
|
scheduleNoteRelease(channelNumber, noteNumber, _velocity, endTime, force) {
|
|
723
791
|
const channel = this.channels[channelNumber];
|
|
724
|
-
if (!force && channel.sustainPedal)
|
|
792
|
+
if (!force && 0.5 < channel.state.sustainPedal)
|
|
725
793
|
return;
|
|
726
794
|
if (!channel.scheduledNotes.has(noteNumber))
|
|
727
795
|
return;
|
|
@@ -732,8 +800,8 @@ class MidyGM1 {
|
|
|
732
800
|
continue;
|
|
733
801
|
if (note.ending)
|
|
734
802
|
continue;
|
|
735
|
-
const volRelease = endTime + note.
|
|
736
|
-
const modRelease = endTime + note.
|
|
803
|
+
const volRelease = endTime + note.voiceParams.volRelease;
|
|
804
|
+
const modRelease = endTime + note.voiceParams.modRelease;
|
|
737
805
|
note.filterNode.frequency
|
|
738
806
|
.cancelScheduledValues(endTime)
|
|
739
807
|
.linearRampToValueAtTime(0, modRelease);
|
|
@@ -749,7 +817,7 @@ class MidyGM1 {
|
|
|
749
817
|
const velocity = halfVelocity * 2;
|
|
750
818
|
const channel = this.channels[channelNumber];
|
|
751
819
|
const promises = [];
|
|
752
|
-
channel.sustainPedal =
|
|
820
|
+
channel.state.sustainPedal = halfVelocity;
|
|
753
821
|
channel.scheduledNotes.forEach((noteList) => {
|
|
754
822
|
for (let i = 0; i < noteList.length; i++) {
|
|
755
823
|
const note = noteList[i];
|
|
@@ -785,17 +853,170 @@ class MidyGM1 {
|
|
|
785
853
|
channel.program = program;
|
|
786
854
|
}
|
|
787
855
|
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
788
|
-
const pitchBend = msb * 128 + lsb
|
|
856
|
+
const pitchBend = msb * 128 + lsb;
|
|
789
857
|
this.setPitchBend(channelNumber, pitchBend);
|
|
790
858
|
}
|
|
791
|
-
setPitchBend(channelNumber,
|
|
859
|
+
setPitchBend(channelNumber, value) {
|
|
792
860
|
const channel = this.channels[channelNumber];
|
|
793
|
-
const
|
|
794
|
-
|
|
795
|
-
const
|
|
796
|
-
|
|
861
|
+
const state = channel.state;
|
|
862
|
+
state.pitchWheel = value / 16383;
|
|
863
|
+
const pitchWheel = (value - 8192) / 8192;
|
|
864
|
+
const detuneChange = pitchWheel * state.pitchWheelSensitivity * 12800;
|
|
797
865
|
this.updateDetune(channel, detuneChange);
|
|
798
866
|
}
|
|
867
|
+
setModLfoToPitch(channel, note) {
|
|
868
|
+
const now = this.audioContext.currentTime;
|
|
869
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch;
|
|
870
|
+
const modulationDepth = Math.abs(modLfoToPitch) +
|
|
871
|
+
channel.state.modulationDepth;
|
|
872
|
+
const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
|
|
873
|
+
note.modulationDepth.gain
|
|
874
|
+
.cancelScheduledValues(now)
|
|
875
|
+
.setValueAtTime(modulationDepth * modulationDepthSign, now);
|
|
876
|
+
}
|
|
877
|
+
setModLfoToVolume(note) {
|
|
878
|
+
const now = this.audioContext.currentTime;
|
|
879
|
+
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
880
|
+
const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
881
|
+
const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
|
|
882
|
+
note.volumeDepth.gain
|
|
883
|
+
.cancelScheduledValues(now)
|
|
884
|
+
.setValueAtTime(volumeDepth * volumeDepthSign, now);
|
|
885
|
+
}
|
|
886
|
+
setVibLfoToPitch(channel, note) {
|
|
887
|
+
const now = this.audioContext.currentTime;
|
|
888
|
+
const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
|
|
889
|
+
const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
|
|
890
|
+
2;
|
|
891
|
+
const vibratoDepthSign = 0 < vibLfoToPitch;
|
|
892
|
+
note.vibratoDepth.gain
|
|
893
|
+
.cancelScheduledValues(now)
|
|
894
|
+
.setValueAtTime(vibratoDepth * vibratoDepthSign, now);
|
|
895
|
+
}
|
|
896
|
+
setModLfoToFilterFc(note) {
|
|
897
|
+
const now = this.audioContext.currentTime;
|
|
898
|
+
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
|
|
899
|
+
note.filterDepth.gain
|
|
900
|
+
.cancelScheduledValues(now)
|
|
901
|
+
.setValueAtTime(modLfoToFilterFc, now);
|
|
902
|
+
}
|
|
903
|
+
setDelayModLFO(note) {
|
|
904
|
+
const now = this.audioContext.currentTime;
|
|
905
|
+
const startTime = note.startTime;
|
|
906
|
+
if (startTime < now)
|
|
907
|
+
return;
|
|
908
|
+
note.modulationLFO.stop(now);
|
|
909
|
+
note.modulationLFO.start(startTime + note.voiceParams.delayModLFO);
|
|
910
|
+
note.modulationLFO.connect(note.filterDepth);
|
|
911
|
+
}
|
|
912
|
+
setFreqModLFO(note) {
|
|
913
|
+
const now = this.audioContext.currentTime;
|
|
914
|
+
const freqModLFO = note.voiceParams.freqModLFO;
|
|
915
|
+
note.modulationLFO.frequency
|
|
916
|
+
.cancelScheduledValues(now)
|
|
917
|
+
.setValueAtTime(freqModLFO, now);
|
|
918
|
+
}
|
|
919
|
+
createVoiceParamsHandlers() {
|
|
920
|
+
return {
|
|
921
|
+
modLfoToPitch: (channel, note, _prevValue) => {
|
|
922
|
+
if (0 < channel.state.modulationDepth) {
|
|
923
|
+
this.setModLfoToPitch(channel, note);
|
|
924
|
+
}
|
|
925
|
+
},
|
|
926
|
+
vibLfoToPitch: (channel, note, _prevValue) => {
|
|
927
|
+
if (0 < channel.state.vibratoDepth) {
|
|
928
|
+
this.setVibLfoToPitch(channel, note);
|
|
929
|
+
}
|
|
930
|
+
},
|
|
931
|
+
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
932
|
+
if (0 < channel.state.modulationDepth)
|
|
933
|
+
this.setModLfoToFilterFc(note);
|
|
934
|
+
},
|
|
935
|
+
modLfoToVolume: (channel, note) => {
|
|
936
|
+
if (0 < channel.state.modulationDepth)
|
|
937
|
+
this.setModLfoToVolume(note);
|
|
938
|
+
},
|
|
939
|
+
chorusEffectsSend: (_channel, _note, _prevValue) => { },
|
|
940
|
+
reverbEffectsSend: (_channel, _note, _prevValue) => { },
|
|
941
|
+
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
942
|
+
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
943
|
+
delayVibLFO: (channel, note, prevValue) => {
|
|
944
|
+
if (0 < channel.state.vibratoDepth) {
|
|
945
|
+
const now = this.audioContext.currentTime;
|
|
946
|
+
const prevStartTime = note.startTime +
|
|
947
|
+
prevValue * channel.state.vibratoDelay * 2;
|
|
948
|
+
if (now < prevStartTime)
|
|
949
|
+
return;
|
|
950
|
+
const startTime = note.startTime +
|
|
951
|
+
value * channel.state.vibratoDelay * 2;
|
|
952
|
+
note.vibratoLFO.stop(now);
|
|
953
|
+
note.vibratoLFO.start(startTime);
|
|
954
|
+
}
|
|
955
|
+
},
|
|
956
|
+
freqVibLFO: (channel, note, _prevValue) => {
|
|
957
|
+
if (0 < channel.state.vibratoDepth) {
|
|
958
|
+
const now = this.audioContext.currentTime;
|
|
959
|
+
note.vibratoLFO.frequency
|
|
960
|
+
.cancelScheduledValues(now)
|
|
961
|
+
.setValueAtTime(value * sate.vibratoRate, now);
|
|
962
|
+
}
|
|
963
|
+
},
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
getControllerState(channel, noteNumber, velocity) {
|
|
967
|
+
const state = new Float32Array(channel.state.array.length);
|
|
968
|
+
state.set(channel.state.array);
|
|
969
|
+
state[2] = velocity / 127;
|
|
970
|
+
state[3] = noteNumber / 127;
|
|
971
|
+
return state;
|
|
972
|
+
}
|
|
973
|
+
applyVoiceParams(channel, controllerType) {
|
|
974
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
975
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
976
|
+
const note = noteList[i];
|
|
977
|
+
if (!note)
|
|
978
|
+
continue;
|
|
979
|
+
const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
|
|
980
|
+
const voiceParams = note.voice.getParams(controllerType, controllerState);
|
|
981
|
+
let appliedFilterEnvelope = false;
|
|
982
|
+
let appliedVolumeEnvelope = false;
|
|
983
|
+
for (const [key, value] of Object.entries(voiceParams)) {
|
|
984
|
+
const prevValue = note.voiceParams[key];
|
|
985
|
+
if (value === prevValue)
|
|
986
|
+
continue;
|
|
987
|
+
note.voiceParams[key] = value;
|
|
988
|
+
if (key in this.voiceParamsHandlers) {
|
|
989
|
+
this.voiceParamsHandlers[key](channel, note, prevValue);
|
|
990
|
+
}
|
|
991
|
+
else if (filterEnvelopeKeySet.has(key)) {
|
|
992
|
+
if (appliedFilterEnvelope)
|
|
993
|
+
continue;
|
|
994
|
+
appliedFilterEnvelope = true;
|
|
995
|
+
const noteVoiceParams = note.voiceParams;
|
|
996
|
+
for (let i = 0; i < filterEnvelopeKeys.length; i++) {
|
|
997
|
+
const key = filterEnvelopeKeys[i];
|
|
998
|
+
if (key in voiceParams)
|
|
999
|
+
noteVoiceParams[key] = voiceParams[key];
|
|
1000
|
+
}
|
|
1001
|
+
this.setFilterEnvelope(channel, note);
|
|
1002
|
+
this.setPitch(channel, note);
|
|
1003
|
+
}
|
|
1004
|
+
else if (volumeEnvelopeKeySet.has(key)) {
|
|
1005
|
+
if (appliedVolumeEnvelope)
|
|
1006
|
+
continue;
|
|
1007
|
+
appliedVolumeEnvelope = true;
|
|
1008
|
+
const noteVoiceParams = note.voiceParams;
|
|
1009
|
+
for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
|
|
1010
|
+
const key = volumeEnvelopeKeys[i];
|
|
1011
|
+
if (key in voiceParams)
|
|
1012
|
+
noteVoiceParams[key] = voiceParams[key];
|
|
1013
|
+
}
|
|
1014
|
+
this.setVolumeEnvelope(channel, note);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
799
1020
|
createControlChangeHandlers() {
|
|
800
1021
|
return {
|
|
801
1022
|
1: this.setModulationDepth,
|
|
@@ -812,13 +1033,13 @@ class MidyGM1 {
|
|
|
812
1033
|
123: this.allNotesOff,
|
|
813
1034
|
};
|
|
814
1035
|
}
|
|
815
|
-
handleControlChange(channelNumber,
|
|
816
|
-
const handler = this.controlChangeHandlers[
|
|
1036
|
+
handleControlChange(channelNumber, controllerType, value) {
|
|
1037
|
+
const handler = this.controlChangeHandlers[controllerType];
|
|
817
1038
|
if (handler) {
|
|
818
1039
|
handler.call(this, channelNumber, value);
|
|
819
1040
|
}
|
|
820
1041
|
else {
|
|
821
|
-
console.warn(`Unsupported Control change:
|
|
1042
|
+
console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
|
|
822
1043
|
}
|
|
823
1044
|
}
|
|
824
1045
|
updateModulation(channel) {
|
|
@@ -829,11 +1050,10 @@ class MidyGM1 {
|
|
|
829
1050
|
if (!note)
|
|
830
1051
|
continue;
|
|
831
1052
|
if (note.modulationDepth) {
|
|
832
|
-
note.modulationDepth.gain.setValueAtTime(channel.modulationDepth, now);
|
|
1053
|
+
note.modulationDepth.gain.setValueAtTime(channel.state.modulationDepth, now);
|
|
833
1054
|
}
|
|
834
1055
|
else {
|
|
835
|
-
|
|
836
|
-
this.setPitch(note, semitoneOffset);
|
|
1056
|
+
this.setPitch(channel, note);
|
|
837
1057
|
this.startModulation(channel, note, now);
|
|
838
1058
|
}
|
|
839
1059
|
}
|
|
@@ -841,16 +1061,17 @@ class MidyGM1 {
|
|
|
841
1061
|
}
|
|
842
1062
|
setModulationDepth(channelNumber, modulation) {
|
|
843
1063
|
const channel = this.channels[channelNumber];
|
|
844
|
-
channel.modulationDepth = (modulation / 127) *
|
|
1064
|
+
channel.state.modulationDepth = (modulation / 127) *
|
|
1065
|
+
channel.modulationDepthRange;
|
|
845
1066
|
this.updateModulation(channel);
|
|
846
1067
|
}
|
|
847
1068
|
setVolume(channelNumber, volume) {
|
|
848
1069
|
const channel = this.channels[channelNumber];
|
|
849
|
-
channel.volume = volume / 127;
|
|
1070
|
+
channel.state.volume = volume / 127;
|
|
850
1071
|
this.updateChannelVolume(channel);
|
|
851
1072
|
}
|
|
852
1073
|
panToGain(pan) {
|
|
853
|
-
const theta = Math.PI / 2 * Math.max(0, pan - 1) / 126;
|
|
1074
|
+
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
854
1075
|
return {
|
|
855
1076
|
gainLeft: Math.cos(theta),
|
|
856
1077
|
gainRight: Math.sin(theta),
|
|
@@ -858,12 +1079,12 @@ class MidyGM1 {
|
|
|
858
1079
|
}
|
|
859
1080
|
setPan(channelNumber, pan) {
|
|
860
1081
|
const channel = this.channels[channelNumber];
|
|
861
|
-
channel.pan = pan;
|
|
1082
|
+
channel.state.pan = pan / 127;
|
|
862
1083
|
this.updateChannelVolume(channel);
|
|
863
1084
|
}
|
|
864
1085
|
setExpression(channelNumber, expression) {
|
|
865
1086
|
const channel = this.channels[channelNumber];
|
|
866
|
-
channel.expression = expression / 127;
|
|
1087
|
+
channel.state.expression = expression / 127;
|
|
867
1088
|
this.updateChannelVolume(channel);
|
|
868
1089
|
}
|
|
869
1090
|
dataEntryLSB(channelNumber, value) {
|
|
@@ -872,8 +1093,9 @@ class MidyGM1 {
|
|
|
872
1093
|
}
|
|
873
1094
|
updateChannelVolume(channel) {
|
|
874
1095
|
const now = this.audioContext.currentTime;
|
|
875
|
-
const
|
|
876
|
-
const
|
|
1096
|
+
const state = channel.state;
|
|
1097
|
+
const volume = state.volume * state.expression;
|
|
1098
|
+
const { gainLeft, gainRight } = this.panToGain(state.pan);
|
|
877
1099
|
channel.gainL.gain
|
|
878
1100
|
.cancelScheduledValues(now)
|
|
879
1101
|
.setValueAtTime(volume * gainLeft, now);
|
|
@@ -882,9 +1104,8 @@ class MidyGM1 {
|
|
|
882
1104
|
.setValueAtTime(volume * gainRight, now);
|
|
883
1105
|
}
|
|
884
1106
|
setSustainPedal(channelNumber, value) {
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
if (!isOn) {
|
|
1107
|
+
this.channels[channelNumber].state.sustainPedal = value / 127;
|
|
1108
|
+
if (value < 64) {
|
|
888
1109
|
this.releaseSustainPedal(channelNumber, value);
|
|
889
1110
|
}
|
|
890
1111
|
}
|
|
@@ -941,7 +1162,7 @@ class MidyGM1 {
|
|
|
941
1162
|
this.channels[channelNumber].dataMSB = value;
|
|
942
1163
|
this.handleRPN(channelNumber);
|
|
943
1164
|
}
|
|
944
|
-
updateDetune(channel,
|
|
1165
|
+
updateDetune(channel, detune) {
|
|
945
1166
|
const now = this.audioContext.currentTime;
|
|
946
1167
|
channel.scheduledNotes.forEach((noteList) => {
|
|
947
1168
|
for (let i = 0; i < noteList.length; i++) {
|
|
@@ -949,7 +1170,6 @@ class MidyGM1 {
|
|
|
949
1170
|
if (!note)
|
|
950
1171
|
continue;
|
|
951
1172
|
const { bufferSource } = note;
|
|
952
|
-
const detune = bufferSource.detune.value + detuneChange;
|
|
953
1173
|
bufferSource.detune
|
|
954
1174
|
.cancelScheduledValues(now)
|
|
955
1175
|
.setValueAtTime(detune, now);
|
|
@@ -962,13 +1182,13 @@ class MidyGM1 {
|
|
|
962
1182
|
const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
|
|
963
1183
|
this.setPitchBendRange(channelNumber, pitchBendRange);
|
|
964
1184
|
}
|
|
965
|
-
setPitchBendRange(channelNumber,
|
|
1185
|
+
setPitchBendRange(channelNumber, pitchWheelSensitivity) {
|
|
966
1186
|
const channel = this.channels[channelNumber];
|
|
967
|
-
const
|
|
968
|
-
|
|
969
|
-
const
|
|
970
|
-
|
|
971
|
-
this.
|
|
1187
|
+
const state = channel.state;
|
|
1188
|
+
state.pitchWheelSensitivity = pitchWheelSensitivity / 128;
|
|
1189
|
+
const detune = (state.pitchWheel * 2 - 1) * pitchWheelSensitivity * 100;
|
|
1190
|
+
this.updateDetune(channel, detune);
|
|
1191
|
+
this.applyVoiceParams(channel, 16);
|
|
972
1192
|
}
|
|
973
1193
|
handleFineTuningRPN(channelNumber) {
|
|
974
1194
|
const channel = this.channels[channelNumber];
|
|
@@ -1000,7 +1220,26 @@ class MidyGM1 {
|
|
|
1000
1220
|
return this.stopChannelNotes(channelNumber, 0, true);
|
|
1001
1221
|
}
|
|
1002
1222
|
resetAllControllers(channelNumber) {
|
|
1003
|
-
|
|
1223
|
+
const stateTypes = [
|
|
1224
|
+
"expression",
|
|
1225
|
+
"modulationDepth",
|
|
1226
|
+
"sustainPedal",
|
|
1227
|
+
"pitchWheelSensitivity",
|
|
1228
|
+
];
|
|
1229
|
+
const channel = this.channels[channelNumber];
|
|
1230
|
+
const state = channel.state;
|
|
1231
|
+
for (let i = 0; i < stateTypes.length; i++) {
|
|
1232
|
+
const type = stateTypes[i];
|
|
1233
|
+
state[type] = defaultControllerState[type];
|
|
1234
|
+
}
|
|
1235
|
+
const settingTypes = [
|
|
1236
|
+
"rpnMSB",
|
|
1237
|
+
"rpnLSB",
|
|
1238
|
+
];
|
|
1239
|
+
for (let i = 0; i < settingTypes.length; i++) {
|
|
1240
|
+
const type = settingTypes[i];
|
|
1241
|
+
channel[type] = this.constructor.channelSettings[type];
|
|
1242
|
+
}
|
|
1004
1243
|
}
|
|
1005
1244
|
allNotesOff(channelNumber) {
|
|
1006
1245
|
return this.stopChannelNotes(channelNumber, 0, false);
|
|
@@ -1025,11 +1264,8 @@ class MidyGM1 {
|
|
|
1025
1264
|
GM1SystemOn() {
|
|
1026
1265
|
for (let i = 0; i < this.channels.length; i++) {
|
|
1027
1266
|
const channel = this.channels[i];
|
|
1028
|
-
channel.bankMSB = 0;
|
|
1029
|
-
channel.bankLSB = 0;
|
|
1030
1267
|
channel.bank = 0;
|
|
1031
1268
|
}
|
|
1032
|
-
this.channels[9].bankMSB = 1;
|
|
1033
1269
|
this.channels[9].bank = 128;
|
|
1034
1270
|
}
|
|
1035
1271
|
handleUniversalRealTimeExclusiveMessage(data) {
|
|
@@ -1091,28 +1327,15 @@ Object.defineProperty(MidyGM1, "channelSettings", {
|
|
|
1091
1327
|
configurable: true,
|
|
1092
1328
|
writable: true,
|
|
1093
1329
|
value: {
|
|
1094
|
-
|
|
1095
|
-
|
|
1330
|
+
currentBufferSource: null,
|
|
1331
|
+
program: 0,
|
|
1096
1332
|
bank: 0,
|
|
1097
1333
|
dataMSB: 0,
|
|
1098
1334
|
dataLSB: 0,
|
|
1099
|
-
|
|
1100
|
-
|
|
1335
|
+
rpnMSB: 127,
|
|
1336
|
+
rpnLSB: 127,
|
|
1101
1337
|
fineTuning: 0, // cb
|
|
1102
1338
|
coarseTuning: 0, // cb
|
|
1103
1339
|
modulationDepthRange: 50, // cent
|
|
1104
1340
|
}
|
|
1105
1341
|
});
|
|
1106
|
-
Object.defineProperty(MidyGM1, "effectSettings", {
|
|
1107
|
-
enumerable: true,
|
|
1108
|
-
configurable: true,
|
|
1109
|
-
writable: true,
|
|
1110
|
-
value: {
|
|
1111
|
-
expression: 1,
|
|
1112
|
-
modulationDepth: 0,
|
|
1113
|
-
sustainPedal: false,
|
|
1114
|
-
rpnMSB: 127,
|
|
1115
|
-
rpnLSB: 127,
|
|
1116
|
-
pitchBendRange: 2,
|
|
1117
|
-
}
|
|
1118
|
-
});
|