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