@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/script/midy-GM1.js
CHANGED
|
@@ -2,7 +2,57 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MidyGM1 = void 0;
|
|
4
4
|
const _esm_js_1 = require("./deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.js");
|
|
5
|
-
const _esm_js_2 = require("./deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.
|
|
5
|
+
const _esm_js_2 = require("./deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.2/+esm.js");
|
|
6
|
+
class Note {
|
|
7
|
+
constructor(noteNumber, velocity, startTime, instrumentKey) {
|
|
8
|
+
Object.defineProperty(this, "bufferSource", {
|
|
9
|
+
enumerable: true,
|
|
10
|
+
configurable: true,
|
|
11
|
+
writable: true,
|
|
12
|
+
value: void 0
|
|
13
|
+
});
|
|
14
|
+
Object.defineProperty(this, "gainNode", {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
configurable: true,
|
|
17
|
+
writable: true,
|
|
18
|
+
value: void 0
|
|
19
|
+
});
|
|
20
|
+
Object.defineProperty(this, "filterNode", {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
configurable: true,
|
|
23
|
+
writable: true,
|
|
24
|
+
value: void 0
|
|
25
|
+
});
|
|
26
|
+
Object.defineProperty(this, "modLFO", {
|
|
27
|
+
enumerable: true,
|
|
28
|
+
configurable: true,
|
|
29
|
+
writable: true,
|
|
30
|
+
value: void 0
|
|
31
|
+
});
|
|
32
|
+
Object.defineProperty(this, "modLFOGain", {
|
|
33
|
+
enumerable: true,
|
|
34
|
+
configurable: true,
|
|
35
|
+
writable: true,
|
|
36
|
+
value: void 0
|
|
37
|
+
});
|
|
38
|
+
Object.defineProperty(this, "vibLFO", {
|
|
39
|
+
enumerable: true,
|
|
40
|
+
configurable: true,
|
|
41
|
+
writable: true,
|
|
42
|
+
value: void 0
|
|
43
|
+
});
|
|
44
|
+
Object.defineProperty(this, "vibLFOGain", {
|
|
45
|
+
enumerable: true,
|
|
46
|
+
configurable: true,
|
|
47
|
+
writable: true,
|
|
48
|
+
value: void 0
|
|
49
|
+
});
|
|
50
|
+
this.noteNumber = noteNumber;
|
|
51
|
+
this.velocity = velocity;
|
|
52
|
+
this.startTime = startTime;
|
|
53
|
+
this.instrumentKey = instrumentKey;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
6
56
|
class MidyGM1 {
|
|
7
57
|
constructor(audioContext) {
|
|
8
58
|
Object.defineProperty(this, "ticksPerBeat", {
|
|
@@ -154,14 +204,11 @@ class MidyGM1 {
|
|
|
154
204
|
const pannerNode = new StereoPannerNode(audioContext, {
|
|
155
205
|
pan: MidyGM1.channelSettings.pan,
|
|
156
206
|
});
|
|
157
|
-
const modulationEffect = this.createModulationEffect(audioContext);
|
|
158
|
-
modulationEffect.lfo.start();
|
|
159
207
|
pannerNode.connect(gainNode);
|
|
160
208
|
gainNode.connect(this.masterGain);
|
|
161
209
|
return {
|
|
162
210
|
gainNode,
|
|
163
211
|
pannerNode,
|
|
164
|
-
modulationEffect,
|
|
165
212
|
};
|
|
166
213
|
}
|
|
167
214
|
createChannels(audioContext) {
|
|
@@ -171,16 +218,15 @@ class MidyGM1 {
|
|
|
171
218
|
...MidyGM1.effectSettings,
|
|
172
219
|
...this.setChannelAudioNodes(audioContext),
|
|
173
220
|
scheduledNotes: new Map(),
|
|
174
|
-
sostenutoNotes: new Map(),
|
|
175
221
|
};
|
|
176
222
|
});
|
|
177
223
|
return channels;
|
|
178
224
|
}
|
|
179
|
-
async createNoteBuffer(
|
|
180
|
-
const sampleEnd =
|
|
225
|
+
async createNoteBuffer(instrumentKey, isSF3) {
|
|
226
|
+
const sampleEnd = instrumentKey.sample.length + instrumentKey.end;
|
|
181
227
|
if (isSF3) {
|
|
182
|
-
const sample = new Uint8Array(
|
|
183
|
-
sample.set(
|
|
228
|
+
const sample = new Uint8Array(instrumentKey.sample.length);
|
|
229
|
+
sample.set(instrumentKey.sample);
|
|
184
230
|
const audioBuffer = await this.audioContext.decodeAudioData(sample.buffer);
|
|
185
231
|
for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {
|
|
186
232
|
const channelData = audioBuffer.getChannelData(channel);
|
|
@@ -189,26 +235,27 @@ class MidyGM1 {
|
|
|
189
235
|
return audioBuffer;
|
|
190
236
|
}
|
|
191
237
|
else {
|
|
192
|
-
const sample =
|
|
238
|
+
const sample = instrumentKey.sample.subarray(0, sampleEnd);
|
|
193
239
|
const floatSample = this.convertToFloat32Array(sample);
|
|
194
240
|
const audioBuffer = new AudioBuffer({
|
|
195
241
|
numberOfChannels: 1,
|
|
196
242
|
length: sample.length,
|
|
197
|
-
sampleRate:
|
|
243
|
+
sampleRate: instrumentKey.sampleRate,
|
|
198
244
|
});
|
|
199
245
|
const channelData = audioBuffer.getChannelData(0);
|
|
200
246
|
channelData.set(floatSample);
|
|
201
247
|
return audioBuffer;
|
|
202
248
|
}
|
|
203
249
|
}
|
|
204
|
-
async createNoteBufferNode(
|
|
250
|
+
async createNoteBufferNode(instrumentKey, isSF3) {
|
|
205
251
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
206
|
-
const audioBuffer = await this.createNoteBuffer(
|
|
252
|
+
const audioBuffer = await this.createNoteBuffer(instrumentKey, isSF3);
|
|
207
253
|
bufferSource.buffer = audioBuffer;
|
|
208
|
-
bufferSource.loop =
|
|
254
|
+
bufferSource.loop = instrumentKey.sampleModes % 2 !== 0;
|
|
209
255
|
if (bufferSource.loop) {
|
|
210
|
-
bufferSource.loopStart =
|
|
211
|
-
|
|
256
|
+
bufferSource.loopStart = instrumentKey.loopStart /
|
|
257
|
+
instrumentKey.sampleRate;
|
|
258
|
+
bufferSource.loopEnd = instrumentKey.loopEnd / instrumentKey.sampleRate;
|
|
212
259
|
}
|
|
213
260
|
return bufferSource;
|
|
214
261
|
}
|
|
@@ -226,9 +273,6 @@ class MidyGM1 {
|
|
|
226
273
|
if (event.startTime > t + this.lookAhead)
|
|
227
274
|
break;
|
|
228
275
|
switch (event.type) {
|
|
229
|
-
case "controller":
|
|
230
|
-
this.handleControlChange(event.channel, event.controllerType, event.value);
|
|
231
|
-
break;
|
|
232
276
|
case "noteOn":
|
|
233
277
|
if (event.velocity !== 0) {
|
|
234
278
|
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
|
|
@@ -242,9 +286,15 @@ class MidyGM1 {
|
|
|
242
286
|
}
|
|
243
287
|
break;
|
|
244
288
|
}
|
|
289
|
+
case "controller":
|
|
290
|
+
this.handleControlChange(event.channel, event.controllerType, event.value);
|
|
291
|
+
break;
|
|
245
292
|
case "programChange":
|
|
246
293
|
this.handleProgramChange(event.channel, event.programNumber);
|
|
247
294
|
break;
|
|
295
|
+
case "pitchBend":
|
|
296
|
+
this.handlePitchBend(event.channel, event.value);
|
|
297
|
+
break;
|
|
248
298
|
case "sysEx":
|
|
249
299
|
this.handleSysEx(event.data);
|
|
250
300
|
}
|
|
@@ -447,30 +497,26 @@ class MidyGM1 {
|
|
|
447
497
|
const now = this.audioContext.currentTime;
|
|
448
498
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
449
499
|
}
|
|
450
|
-
getActiveNotes(channel) {
|
|
500
|
+
getActiveNotes(channel, time) {
|
|
451
501
|
const activeNotes = new Map();
|
|
452
|
-
channel.scheduledNotes.forEach((
|
|
453
|
-
const activeNote = this.
|
|
502
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
503
|
+
const activeNote = this.getActiveNote(noteList, time);
|
|
454
504
|
if (activeNote) {
|
|
455
505
|
activeNotes.set(activeNote.noteNumber, activeNote);
|
|
456
506
|
}
|
|
457
507
|
});
|
|
458
508
|
return activeNotes;
|
|
459
509
|
}
|
|
460
|
-
|
|
461
|
-
for (let i =
|
|
462
|
-
const
|
|
463
|
-
if (
|
|
464
|
-
return
|
|
510
|
+
getActiveNote(noteList, time) {
|
|
511
|
+
for (let i = noteList.length - 1; i >= 0; i--) {
|
|
512
|
+
const note = noteList[i];
|
|
513
|
+
if (!note)
|
|
514
|
+
return;
|
|
515
|
+
if (time < note.startTime)
|
|
516
|
+
continue;
|
|
517
|
+
return (note.ending) ? null : note;
|
|
465
518
|
}
|
|
466
|
-
|
|
467
|
-
createModulationEffect(audioContext) {
|
|
468
|
-
const lfo = new OscillatorNode(audioContext, {
|
|
469
|
-
frequency: 5,
|
|
470
|
-
});
|
|
471
|
-
return {
|
|
472
|
-
lfo,
|
|
473
|
-
};
|
|
519
|
+
return noteList[0];
|
|
474
520
|
}
|
|
475
521
|
connectNoteEffects(channel, gainNode) {
|
|
476
522
|
gainNode.connect(channel.pannerNode);
|
|
@@ -481,71 +527,93 @@ class MidyGM1 {
|
|
|
481
527
|
centToHz(cent) {
|
|
482
528
|
return 8.176 * Math.pow(2, cent / 1200);
|
|
483
529
|
}
|
|
484
|
-
|
|
530
|
+
calcSemitoneOffset(channel) {
|
|
485
531
|
const tuning = channel.coarseTuning + channel.fineTuning;
|
|
486
|
-
|
|
487
|
-
|
|
532
|
+
return channel.pitchBend * channel.pitchBendRange + tuning;
|
|
533
|
+
}
|
|
534
|
+
calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset) {
|
|
535
|
+
return instrumentKey.playbackRate(noteNumber) *
|
|
488
536
|
Math.pow(2, semitoneOffset / 12);
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
537
|
+
}
|
|
538
|
+
setVolumeEnvelope(channel, note) {
|
|
539
|
+
const { instrumentKey, startTime, velocity } = note;
|
|
540
|
+
note.gainNode = new GainNode(this.audioContext, {
|
|
493
541
|
gain: 0,
|
|
494
542
|
});
|
|
495
543
|
let volume = (velocity / 127) * channel.volume * channel.expression;
|
|
496
544
|
if (volume === 0)
|
|
497
545
|
volume = 1e-6; // exponentialRampToValueAtTime() requires a non-zero value
|
|
498
|
-
const attackVolume = this.cbToRatio(-
|
|
499
|
-
|
|
500
|
-
const
|
|
501
|
-
const
|
|
502
|
-
const
|
|
503
|
-
const
|
|
504
|
-
|
|
546
|
+
const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation) *
|
|
547
|
+
volume;
|
|
548
|
+
const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
|
|
549
|
+
const volDelay = startTime + instrumentKey.volDelay;
|
|
550
|
+
const volAttack = volDelay + instrumentKey.volAttack;
|
|
551
|
+
const volHold = volAttack + instrumentKey.volHold;
|
|
552
|
+
const volDecay = volHold + instrumentKey.volDecay;
|
|
553
|
+
note.gainNode.gain
|
|
505
554
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
506
555
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
507
556
|
.setValueAtTime(attackVolume, volHold)
|
|
508
557
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
509
|
-
|
|
558
|
+
}
|
|
559
|
+
setFilterEnvelope(channel, note) {
|
|
560
|
+
const { instrumentKey, startTime, noteNumber } = note;
|
|
561
|
+
const softPedalFactor = 1 -
|
|
562
|
+
(0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
|
|
510
563
|
const maxFreq = this.audioContext.sampleRate / 2;
|
|
511
|
-
const baseFreq = this.centToHz(
|
|
512
|
-
|
|
513
|
-
const
|
|
514
|
-
|
|
564
|
+
const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
|
|
565
|
+
softPedalFactor;
|
|
566
|
+
const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
|
|
567
|
+
const sustainFreq = (baseFreq +
|
|
568
|
+
(peekFreq - baseFreq) * (1 - instrumentKey.modSustain)) * softPedalFactor;
|
|
569
|
+
const modDelay = startTime + instrumentKey.modDelay;
|
|
570
|
+
const modAttack = modDelay + instrumentKey.modAttack;
|
|
571
|
+
const modHold = modAttack + instrumentKey.modHold;
|
|
572
|
+
const modDecay = modHold + instrumentKey.modDecay;
|
|
515
573
|
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
516
574
|
const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
|
|
517
575
|
const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
|
|
518
|
-
|
|
576
|
+
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
519
577
|
type: "lowpass",
|
|
520
|
-
Q:
|
|
578
|
+
Q: instrumentKey.initialFilterQ / 10, // dB
|
|
521
579
|
frequency: adjustedBaseFreq,
|
|
522
580
|
});
|
|
523
|
-
|
|
524
|
-
const modAttack = modDelay + noteInfo.modAttack;
|
|
525
|
-
const modHold = modAttack + noteInfo.modHold;
|
|
526
|
-
const modDecay = modHold + noteInfo.modDecay;
|
|
527
|
-
filterNode.frequency
|
|
581
|
+
note.filterNode.frequency
|
|
528
582
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
529
583
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
530
584
|
.setValueAtTime(adjustedPeekFreq, modHold)
|
|
531
585
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
532
|
-
|
|
586
|
+
note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modEnvToPitch, modDelay);
|
|
587
|
+
}
|
|
588
|
+
startModulation(channel, note, time) {
|
|
589
|
+
const { instrumentKey } = note;
|
|
590
|
+
note.modLFOGain = new GainNode(this.audioContext, {
|
|
591
|
+
gain: this.cbToRatio(instrumentKey.modLfoToVolume) * channel.modulation,
|
|
592
|
+
});
|
|
593
|
+
note.modLFO = new OscillatorNode(this.audioContext, {
|
|
594
|
+
frequency: this.centToHz(instrumentKey.freqModLFO),
|
|
595
|
+
});
|
|
596
|
+
note.modLFO.start(time);
|
|
597
|
+
note.filterNode.frequency.setValueAtTime(note.filterNode.frequency.value + instrumentKey.modLfoToFilterFc, time);
|
|
598
|
+
note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modLfoToPitch, time);
|
|
599
|
+
note.modLFO.connect(note.modLFOGain);
|
|
600
|
+
note.modLFOGain.connect(note.bufferSource.detune);
|
|
601
|
+
}
|
|
602
|
+
async createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3) {
|
|
603
|
+
const semitoneOffset = this.calcSemitoneOffset(channel);
|
|
604
|
+
const note = new Note(noteNumber, velocity, startTime, instrumentKey);
|
|
605
|
+
note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
|
|
606
|
+
note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
|
|
607
|
+
this.setVolumeEnvelope(channel, note);
|
|
608
|
+
this.setFilterEnvelope(channel, note);
|
|
533
609
|
if (channel.modulation > 0) {
|
|
534
|
-
const
|
|
535
|
-
|
|
536
|
-
lfoGain = new GainNode(this.audioContext, {
|
|
537
|
-
gain: 0,
|
|
538
|
-
});
|
|
539
|
-
lfoGain.gain
|
|
540
|
-
.setValueAtTime(1e-6, vibratoDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
541
|
-
.exponentialRampToValueAtTime(channel.modulation, vibratoAttack);
|
|
542
|
-
channel.modulationEffect.lfo.connect(lfoGain);
|
|
543
|
-
lfoGain.connect(bufferSource.detune);
|
|
610
|
+
const delayModLFO = startTime + instrumentKey.delayModLFO;
|
|
611
|
+
this.startModulation(channel, note, delayModLFO);
|
|
544
612
|
}
|
|
545
|
-
bufferSource.connect(filterNode);
|
|
546
|
-
filterNode.connect(gainNode);
|
|
547
|
-
bufferSource.start(startTime,
|
|
548
|
-
return
|
|
613
|
+
note.bufferSource.connect(note.filterNode);
|
|
614
|
+
note.filterNode.connect(note.gainNode);
|
|
615
|
+
note.bufferSource.start(startTime, instrumentKey.start / instrumentKey.sampleRate);
|
|
616
|
+
return note;
|
|
549
617
|
}
|
|
550
618
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
551
619
|
const channel = this.channels[channelNumber];
|
|
@@ -555,27 +623,17 @@ class MidyGM1 {
|
|
|
555
623
|
return;
|
|
556
624
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
557
625
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
558
|
-
const
|
|
559
|
-
if (!
|
|
626
|
+
const instrumentKey = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
|
|
627
|
+
if (!instrumentKey)
|
|
560
628
|
return;
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
this.connectNoteEffects(channel, gainNode);
|
|
629
|
+
const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
|
|
630
|
+
this.connectNoteEffects(channel, note.gainNode);
|
|
564
631
|
const scheduledNotes = channel.scheduledNotes;
|
|
565
|
-
const scheduledNote = {
|
|
566
|
-
bufferSource,
|
|
567
|
-
filterNode,
|
|
568
|
-
gainNode,
|
|
569
|
-
lfoGain,
|
|
570
|
-
noteInfo,
|
|
571
|
-
noteNumber,
|
|
572
|
-
startTime,
|
|
573
|
-
};
|
|
574
632
|
if (scheduledNotes.has(noteNumber)) {
|
|
575
|
-
scheduledNotes.get(noteNumber).push(
|
|
633
|
+
scheduledNotes.get(noteNumber).push(note);
|
|
576
634
|
}
|
|
577
635
|
else {
|
|
578
|
-
scheduledNotes.set(noteNumber, [
|
|
636
|
+
scheduledNotes.set(noteNumber, [note]);
|
|
579
637
|
}
|
|
580
638
|
}
|
|
581
639
|
noteOn(channelNumber, noteNumber, velocity) {
|
|
@@ -595,15 +653,15 @@ class MidyGM1 {
|
|
|
595
653
|
continue;
|
|
596
654
|
if (targetNote.ending)
|
|
597
655
|
continue;
|
|
598
|
-
const { bufferSource, filterNode, gainNode,
|
|
656
|
+
const { bufferSource, filterNode, gainNode, modLFO, modLFOGain, instrumentKey, } = targetNote;
|
|
599
657
|
const velocityRate = (velocity + 127) / 127;
|
|
600
|
-
const volEndTime = stopTime +
|
|
658
|
+
const volEndTime = stopTime + instrumentKey.volRelease * velocityRate;
|
|
601
659
|
gainNode.gain.cancelScheduledValues(stopTime);
|
|
602
660
|
gainNode.gain.linearRampToValueAtTime(0, volEndTime);
|
|
603
661
|
const maxFreq = this.audioContext.sampleRate / 2;
|
|
604
|
-
const baseFreq = this.centToHz(
|
|
662
|
+
const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
|
|
605
663
|
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
606
|
-
const modEndTime = stopTime +
|
|
664
|
+
const modEndTime = stopTime + instrumentKey.modRelease * velocityRate;
|
|
607
665
|
filterNode.frequency
|
|
608
666
|
.cancelScheduledValues(stopTime)
|
|
609
667
|
.linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
|
|
@@ -617,8 +675,10 @@ class MidyGM1 {
|
|
|
617
675
|
bufferSource.disconnect(0);
|
|
618
676
|
filterNode.disconnect(0);
|
|
619
677
|
gainNode.disconnect(0);
|
|
620
|
-
if (
|
|
621
|
-
|
|
678
|
+
if (modLFOGain)
|
|
679
|
+
modLFOGain.disconnect(0);
|
|
680
|
+
if (modLFO)
|
|
681
|
+
modLFO.stop();
|
|
622
682
|
resolve();
|
|
623
683
|
};
|
|
624
684
|
bufferSource.stop(volEndTime);
|
|
@@ -653,46 +713,37 @@ class MidyGM1 {
|
|
|
653
713
|
return this.releaseNote(channelNumber, data1, data2);
|
|
654
714
|
case 0x90:
|
|
655
715
|
return this.noteOn(channelNumber, data1, data2);
|
|
656
|
-
case 0xA0:
|
|
657
|
-
return this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
|
|
658
716
|
case 0xB0:
|
|
659
717
|
return this.handleControlChange(channelNumber, data1, data2);
|
|
660
718
|
case 0xC0:
|
|
661
719
|
return this.handleProgramChange(channelNumber, data1);
|
|
662
|
-
case 0xD0:
|
|
663
|
-
return this.handleChannelPressure(channelNumber, data1);
|
|
664
720
|
case 0xE0:
|
|
665
|
-
return this.
|
|
721
|
+
return this.handlePitchBendMessage(channelNumber, data1, data2);
|
|
666
722
|
default:
|
|
667
723
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
668
724
|
}
|
|
669
725
|
}
|
|
670
|
-
handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure) {
|
|
671
|
-
const now = this.audioContext.currentTime;
|
|
672
|
-
const channel = this.channels[channelNumber];
|
|
673
|
-
const scheduledNotes = channel.scheduledNotes.get(noteNumber);
|
|
674
|
-
pressure /= 127;
|
|
675
|
-
if (scheduledNotes) {
|
|
676
|
-
scheduledNotes.forEach((scheduledNote) => {
|
|
677
|
-
if (scheduledNote) {
|
|
678
|
-
const { initialAttenuation } = scheduledNote.noteInfo;
|
|
679
|
-
const gain = this.cbToRatio(-initialAttenuation) * pressure;
|
|
680
|
-
scheduledNote.gainNode.gain.cancelScheduledValues(now);
|
|
681
|
-
scheduledNote.gainNode.gain.setValueAtTime(gain, now);
|
|
682
|
-
}
|
|
683
|
-
});
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
726
|
handleProgramChange(channelNumber, program) {
|
|
687
727
|
const channel = this.channels[channelNumber];
|
|
688
728
|
channel.program = program;
|
|
689
729
|
}
|
|
690
|
-
|
|
691
|
-
|
|
730
|
+
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
731
|
+
const pitchBend = msb * 128 + lsb;
|
|
732
|
+
this.handlePitchBend(channelNumber, pitchBend);
|
|
692
733
|
}
|
|
693
|
-
handlePitchBend(channelNumber,
|
|
694
|
-
const
|
|
695
|
-
this.channels[channelNumber]
|
|
734
|
+
handlePitchBend(channelNumber, pitchBend) {
|
|
735
|
+
const now = this.audioContext.currentTime;
|
|
736
|
+
const channel = this.channels[channelNumber];
|
|
737
|
+
channel.pitchBend = (pitchBend - 8192) / 8192;
|
|
738
|
+
const semitoneOffset = this.calcSemitoneOffset(channel);
|
|
739
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
740
|
+
activeNotes.forEach((activeNote) => {
|
|
741
|
+
const { bufferSource, instrumentKey, noteNumber } = activeNote;
|
|
742
|
+
const playbackRate = calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
|
|
743
|
+
bufferSource.playbackRate
|
|
744
|
+
.cancelScheduledValues(now)
|
|
745
|
+
.setValueAtTime(playbackRate * pressure, now);
|
|
746
|
+
});
|
|
696
747
|
}
|
|
697
748
|
handleControlChange(channelNumber, controller, value) {
|
|
698
749
|
switch (controller) {
|
|
@@ -725,9 +776,20 @@ class MidyGM1 {
|
|
|
725
776
|
}
|
|
726
777
|
}
|
|
727
778
|
setModulation(channelNumber, modulation) {
|
|
779
|
+
const now = this.audioContext.currentTime;
|
|
728
780
|
const channel = this.channels[channelNumber];
|
|
729
781
|
channel.modulation = (modulation / 127) *
|
|
730
782
|
(channel.modulationDepthRange * 100);
|
|
783
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
784
|
+
activeNotes.forEach((activeNote) => {
|
|
785
|
+
if (activeNote.modLFO) {
|
|
786
|
+
activeNote.gainNode.gain.setValueAtTime(this.cbToRatio(activeNote.instrumentKey.modLfoToVolume) *
|
|
787
|
+
channel.modulation, now);
|
|
788
|
+
}
|
|
789
|
+
else {
|
|
790
|
+
this.startModulation(channel, activeNote, now);
|
|
791
|
+
}
|
|
792
|
+
});
|
|
731
793
|
}
|
|
732
794
|
setVolume(channelNumber, volume) {
|
|
733
795
|
const channel = this.channels[channelNumber];
|
|
@@ -790,8 +852,8 @@ class MidyGM1 {
|
|
|
790
852
|
const velocity = 0;
|
|
791
853
|
const stopPedal = true;
|
|
792
854
|
const promises = [];
|
|
793
|
-
channel.scheduledNotes.forEach((
|
|
794
|
-
const activeNote = this.
|
|
855
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
856
|
+
const activeNote = this.getActiveNote(noteList, now);
|
|
795
857
|
if (activeNote) {
|
|
796
858
|
const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
|
|
797
859
|
promises.push(notePromise);
|
|
@@ -808,8 +870,8 @@ class MidyGM1 {
|
|
|
808
870
|
const velocity = 0;
|
|
809
871
|
const stopPedal = false;
|
|
810
872
|
const promises = [];
|
|
811
|
-
channel.scheduledNotes.forEach((
|
|
812
|
-
const activeNote = this.
|
|
873
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
874
|
+
const activeNote = this.getActiveNote(noteList, now);
|
|
813
875
|
if (activeNote) {
|
|
814
876
|
const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
|
|
815
877
|
promises.push(notePromise);
|
|
@@ -858,13 +920,18 @@ class MidyGM1 {
|
|
|
858
920
|
}
|
|
859
921
|
}
|
|
860
922
|
handleMasterVolumeSysEx(data) {
|
|
861
|
-
const volume = (data[5] * 128 + data[4]
|
|
923
|
+
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
862
924
|
this.handleMasterVolume(volume);
|
|
863
925
|
}
|
|
864
926
|
handleMasterVolume(volume) {
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
927
|
+
if (volume < 0 && 1 < volume) {
|
|
928
|
+
console.error("Master Volume is out of range");
|
|
929
|
+
}
|
|
930
|
+
else {
|
|
931
|
+
const now = this.audioContext.currentTime;
|
|
932
|
+
this.masterGain.gain.cancelScheduledValues(now);
|
|
933
|
+
this.masterGain.gain.setValueAtTime(volume * volume, now);
|
|
934
|
+
}
|
|
868
935
|
}
|
|
869
936
|
handleExclusiveMessage(data) {
|
|
870
937
|
console.warn(`Unsupported Exclusive Message ${data}`);
|
|
@@ -899,9 +966,6 @@ Object.defineProperty(MidyGM1, "channelSettings", {
|
|
|
899
966
|
value: {
|
|
900
967
|
volume: 100 / 127,
|
|
901
968
|
pan: 0,
|
|
902
|
-
vibratoRate: 5,
|
|
903
|
-
vibratoDepth: 0.5,
|
|
904
|
-
vibratoDelay: 2.5,
|
|
905
969
|
bank: 0,
|
|
906
970
|
dataMSB: 0,
|
|
907
971
|
dataLSB: 0,
|