@marmooo/midy 0.0.4 → 0.0.6
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 +36 -42
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +214 -148
- package/esm/midy-GM2.d.ts +133 -25
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +230 -163
- package/esm/midy-GMLite.d.ts +37 -43
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +215 -149
- package/esm/midy.d.ts +41 -33
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +263 -179
- 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 +36 -42
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +214 -148
- package/script/midy-GM2.d.ts +133 -25
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +230 -163
- package/script/midy-GMLite.d.ts +37 -43
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +215 -149
- package/script/midy.d.ts +41 -33
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +263 -179
- 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-GMLite.js
CHANGED
|
@@ -2,7 +2,45 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MidyGMLite = 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
|
+
this.noteNumber = noteNumber;
|
|
39
|
+
this.velocity = velocity;
|
|
40
|
+
this.startTime = startTime;
|
|
41
|
+
this.instrumentKey = instrumentKey;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
6
44
|
class MidyGMLite {
|
|
7
45
|
constructor(audioContext) {
|
|
8
46
|
Object.defineProperty(this, "ticksPerBeat", {
|
|
@@ -148,20 +186,16 @@ class MidyGMLite {
|
|
|
148
186
|
this.totalTime = this.calcTotalTime();
|
|
149
187
|
}
|
|
150
188
|
setChannelAudioNodes(audioContext) {
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
});
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
modulationEffect.lfo.start();
|
|
159
|
-
pannerNode.connect(gainNode);
|
|
160
|
-
gainNode.connect(this.masterGain);
|
|
189
|
+
const { gainLeft, gainRight } = this.panToGain(MidyGMLite.channelSettings.pan);
|
|
190
|
+
const gainL = new GainNode(audioContext, { gain: gainLeft });
|
|
191
|
+
const gainR = new GainNode(audioContext, { gain: gainRight });
|
|
192
|
+
const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
|
|
193
|
+
gainL.connect(merger, 0, 0);
|
|
194
|
+
gainR.connect(merger, 0, 1);
|
|
195
|
+
merger.connect(this.masterGain);
|
|
161
196
|
return {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
modulationEffect,
|
|
197
|
+
gainL,
|
|
198
|
+
gainR,
|
|
165
199
|
};
|
|
166
200
|
}
|
|
167
201
|
createChannels(audioContext) {
|
|
@@ -171,16 +205,15 @@ class MidyGMLite {
|
|
|
171
205
|
...MidyGMLite.effectSettings,
|
|
172
206
|
...this.setChannelAudioNodes(audioContext),
|
|
173
207
|
scheduledNotes: new Map(),
|
|
174
|
-
sostenutoNotes: new Map(),
|
|
175
208
|
};
|
|
176
209
|
});
|
|
177
210
|
return channels;
|
|
178
211
|
}
|
|
179
|
-
async createNoteBuffer(
|
|
180
|
-
const sampleEnd =
|
|
212
|
+
async createNoteBuffer(instrumentKey, isSF3) {
|
|
213
|
+
const sampleEnd = instrumentKey.sample.length + instrumentKey.end;
|
|
181
214
|
if (isSF3) {
|
|
182
|
-
const sample = new Uint8Array(
|
|
183
|
-
sample.set(
|
|
215
|
+
const sample = new Uint8Array(instrumentKey.sample.length);
|
|
216
|
+
sample.set(instrumentKey.sample);
|
|
184
217
|
const audioBuffer = await this.audioContext.decodeAudioData(sample.buffer);
|
|
185
218
|
for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {
|
|
186
219
|
const channelData = audioBuffer.getChannelData(channel);
|
|
@@ -189,26 +222,27 @@ class MidyGMLite {
|
|
|
189
222
|
return audioBuffer;
|
|
190
223
|
}
|
|
191
224
|
else {
|
|
192
|
-
const sample =
|
|
225
|
+
const sample = instrumentKey.sample.subarray(0, sampleEnd);
|
|
193
226
|
const floatSample = this.convertToFloat32Array(sample);
|
|
194
227
|
const audioBuffer = new AudioBuffer({
|
|
195
228
|
numberOfChannels: 1,
|
|
196
229
|
length: sample.length,
|
|
197
|
-
sampleRate:
|
|
230
|
+
sampleRate: instrumentKey.sampleRate,
|
|
198
231
|
});
|
|
199
232
|
const channelData = audioBuffer.getChannelData(0);
|
|
200
233
|
channelData.set(floatSample);
|
|
201
234
|
return audioBuffer;
|
|
202
235
|
}
|
|
203
236
|
}
|
|
204
|
-
async createNoteBufferNode(
|
|
237
|
+
async createNoteBufferNode(instrumentKey, isSF3) {
|
|
205
238
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
206
|
-
const audioBuffer = await this.createNoteBuffer(
|
|
239
|
+
const audioBuffer = await this.createNoteBuffer(instrumentKey, isSF3);
|
|
207
240
|
bufferSource.buffer = audioBuffer;
|
|
208
|
-
bufferSource.loop =
|
|
241
|
+
bufferSource.loop = instrumentKey.sampleModes % 2 !== 0;
|
|
209
242
|
if (bufferSource.loop) {
|
|
210
|
-
bufferSource.loopStart =
|
|
211
|
-
|
|
243
|
+
bufferSource.loopStart = instrumentKey.loopStart /
|
|
244
|
+
instrumentKey.sampleRate;
|
|
245
|
+
bufferSource.loopEnd = instrumentKey.loopEnd / instrumentKey.sampleRate;
|
|
212
246
|
}
|
|
213
247
|
return bufferSource;
|
|
214
248
|
}
|
|
@@ -246,7 +280,7 @@ class MidyGMLite {
|
|
|
246
280
|
this.handleProgramChange(event.channel, event.programNumber);
|
|
247
281
|
break;
|
|
248
282
|
case "pitchBend":
|
|
249
|
-
this.
|
|
283
|
+
this.setPitchBend(event.channel, event.value);
|
|
250
284
|
break;
|
|
251
285
|
case "sysEx":
|
|
252
286
|
this.handleSysEx(event.data);
|
|
@@ -326,7 +360,6 @@ class MidyGMLite {
|
|
|
326
360
|
const tmpChannels = new Array(16);
|
|
327
361
|
for (let i = 0; i < tmpChannels.length; i++) {
|
|
328
362
|
tmpChannels[i] = {
|
|
329
|
-
durationTicks: new Map(),
|
|
330
363
|
programNumber: -1,
|
|
331
364
|
bank: this.channels[i].bank,
|
|
332
365
|
};
|
|
@@ -343,16 +376,6 @@ class MidyGMLite {
|
|
|
343
376
|
instruments.add(`${channel.bank}:0`);
|
|
344
377
|
channel.programNumber = 0;
|
|
345
378
|
}
|
|
346
|
-
channel.durationTicks.set(event.noteNumber, {
|
|
347
|
-
ticks: event.ticks,
|
|
348
|
-
noteOn: event,
|
|
349
|
-
});
|
|
350
|
-
break;
|
|
351
|
-
}
|
|
352
|
-
case "noteOff": {
|
|
353
|
-
const { ticks, noteOn } = tmpChannels[event.channel].durationTicks
|
|
354
|
-
.get(event.noteNumber);
|
|
355
|
-
noteOn.durationTicks = event.ticks - ticks;
|
|
356
379
|
break;
|
|
357
380
|
}
|
|
358
381
|
case "programChange": {
|
|
@@ -366,8 +389,8 @@ class MidyGMLite {
|
|
|
366
389
|
});
|
|
367
390
|
});
|
|
368
391
|
const priority = {
|
|
369
|
-
|
|
370
|
-
|
|
392
|
+
controller: 0,
|
|
393
|
+
sysEx: 1,
|
|
371
394
|
};
|
|
372
395
|
timeline.sort((a, b) => {
|
|
373
396
|
if (a.ticks !== b.ticks)
|
|
@@ -450,30 +473,26 @@ class MidyGMLite {
|
|
|
450
473
|
const now = this.audioContext.currentTime;
|
|
451
474
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
452
475
|
}
|
|
453
|
-
getActiveNotes(channel) {
|
|
476
|
+
getActiveNotes(channel, time) {
|
|
454
477
|
const activeNotes = new Map();
|
|
455
|
-
channel.scheduledNotes.forEach((
|
|
456
|
-
const activeNote = this.
|
|
478
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
479
|
+
const activeNote = this.getActiveNote(noteList, time);
|
|
457
480
|
if (activeNote) {
|
|
458
481
|
activeNotes.set(activeNote.noteNumber, activeNote);
|
|
459
482
|
}
|
|
460
483
|
});
|
|
461
484
|
return activeNotes;
|
|
462
485
|
}
|
|
463
|
-
|
|
464
|
-
for (let i =
|
|
465
|
-
const
|
|
466
|
-
if (
|
|
467
|
-
return
|
|
486
|
+
getActiveNote(noteList, time) {
|
|
487
|
+
for (let i = noteList.length - 1; i >= 0; i--) {
|
|
488
|
+
const note = noteList[i];
|
|
489
|
+
if (!note)
|
|
490
|
+
return;
|
|
491
|
+
if (time < note.startTime)
|
|
492
|
+
continue;
|
|
493
|
+
return (note.ending) ? null : note;
|
|
468
494
|
}
|
|
469
|
-
|
|
470
|
-
createModulationEffect(audioContext) {
|
|
471
|
-
const lfo = new OscillatorNode(audioContext, {
|
|
472
|
-
frequency: 5,
|
|
473
|
-
});
|
|
474
|
-
return {
|
|
475
|
-
lfo,
|
|
476
|
-
};
|
|
495
|
+
return noteList[0];
|
|
477
496
|
}
|
|
478
497
|
connectNoteEffects(channel, gainNode) {
|
|
479
498
|
gainNode.connect(channel.pannerNode);
|
|
@@ -485,73 +504,89 @@ class MidyGMLite {
|
|
|
485
504
|
return 8.176 * Math.pow(2, cent / 1200);
|
|
486
505
|
}
|
|
487
506
|
calcSemitoneOffset(channel) {
|
|
488
|
-
return channel.pitchBend * channel.pitchBendRange
|
|
507
|
+
return channel.pitchBend * channel.pitchBendRange;
|
|
489
508
|
}
|
|
490
|
-
calcPlaybackRate(
|
|
491
|
-
return
|
|
509
|
+
calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset) {
|
|
510
|
+
return instrumentKey.playbackRate(noteNumber) *
|
|
511
|
+
Math.pow(2, semitoneOffset / 12);
|
|
492
512
|
}
|
|
493
|
-
|
|
494
|
-
const
|
|
495
|
-
|
|
496
|
-
bufferSource.playbackRate.value = this.calcPlaybackRate(noteInfo, noteNumber, semitoneOffset);
|
|
497
|
-
// volume envelope
|
|
498
|
-
const gainNode = new GainNode(this.audioContext, {
|
|
499
|
-
gain: 0,
|
|
500
|
-
});
|
|
513
|
+
setVolumeEnvelope(channel, note) {
|
|
514
|
+
const { instrumentKey, startTime, velocity } = note;
|
|
515
|
+
note.gainNode = new GainNode(this.audioContext, { gain: 0 });
|
|
501
516
|
let volume = (velocity / 127) * channel.volume * channel.expression;
|
|
502
517
|
if (volume === 0)
|
|
503
518
|
volume = 1e-6; // exponentialRampToValueAtTime() requires a non-zero value
|
|
504
|
-
const attackVolume = this.cbToRatio(-
|
|
505
|
-
|
|
506
|
-
const
|
|
507
|
-
const
|
|
508
|
-
const
|
|
509
|
-
const
|
|
510
|
-
|
|
519
|
+
const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation) *
|
|
520
|
+
volume;
|
|
521
|
+
const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
|
|
522
|
+
const volDelay = startTime + instrumentKey.volDelay;
|
|
523
|
+
const volAttack = volDelay + instrumentKey.volAttack;
|
|
524
|
+
const volHold = volAttack + instrumentKey.volHold;
|
|
525
|
+
const volDecay = volHold + instrumentKey.volDecay;
|
|
526
|
+
note.gainNode.gain
|
|
511
527
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
512
528
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
513
529
|
.setValueAtTime(attackVolume, volHold)
|
|
514
530
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
515
|
-
|
|
531
|
+
}
|
|
532
|
+
setFilterEnvelope(channel, note) {
|
|
533
|
+
const { instrumentKey, startTime, noteNumber } = note;
|
|
534
|
+
const softPedalFactor = 1 -
|
|
535
|
+
(0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
|
|
516
536
|
const maxFreq = this.audioContext.sampleRate / 2;
|
|
517
|
-
const baseFreq = this.centToHz(
|
|
518
|
-
|
|
519
|
-
const
|
|
520
|
-
|
|
537
|
+
const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
|
|
538
|
+
softPedalFactor;
|
|
539
|
+
const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
|
|
540
|
+
const sustainFreq = (baseFreq +
|
|
541
|
+
(peekFreq - baseFreq) * (1 - instrumentKey.modSustain)) * softPedalFactor;
|
|
542
|
+
const modDelay = startTime + instrumentKey.modDelay;
|
|
543
|
+
const modAttack = modDelay + instrumentKey.modAttack;
|
|
544
|
+
const modHold = modAttack + instrumentKey.modHold;
|
|
545
|
+
const modDecay = modHold + instrumentKey.modDecay;
|
|
521
546
|
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
522
547
|
const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
|
|
523
548
|
const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
|
|
524
|
-
|
|
549
|
+
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
525
550
|
type: "lowpass",
|
|
526
|
-
Q:
|
|
551
|
+
Q: instrumentKey.initialFilterQ / 10, // dB
|
|
527
552
|
frequency: adjustedBaseFreq,
|
|
528
553
|
});
|
|
529
|
-
|
|
530
|
-
const modAttack = modDelay + noteInfo.modAttack;
|
|
531
|
-
const modHold = modAttack + noteInfo.modHold;
|
|
532
|
-
const modDecay = modHold + noteInfo.modDecay;
|
|
533
|
-
filterNode.frequency
|
|
554
|
+
note.filterNode.frequency
|
|
534
555
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
535
556
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
536
557
|
.setValueAtTime(adjustedPeekFreq, modHold)
|
|
537
558
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
538
|
-
|
|
559
|
+
note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modEnvToPitch, modDelay);
|
|
560
|
+
}
|
|
561
|
+
startModulation(channel, note, time) {
|
|
562
|
+
const { instrumentKey } = note;
|
|
563
|
+
note.modLFOGain = new GainNode(this.audioContext, {
|
|
564
|
+
gain: this.cbToRatio(instrumentKey.modLfoToVolume) * channel.modulation,
|
|
565
|
+
});
|
|
566
|
+
note.modLFO = new OscillatorNode(this.audioContext, {
|
|
567
|
+
frequency: this.centToHz(instrumentKey.freqModLFO),
|
|
568
|
+
});
|
|
569
|
+
note.modLFO.start(time);
|
|
570
|
+
note.filterNode.frequency.setValueAtTime(note.filterNode.frequency.value + instrumentKey.modLfoToFilterFc, time);
|
|
571
|
+
note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modLfoToPitch, time);
|
|
572
|
+
note.modLFO.connect(note.modLFOGain);
|
|
573
|
+
note.modLFOGain.connect(note.bufferSource.detune);
|
|
574
|
+
}
|
|
575
|
+
async createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3) {
|
|
576
|
+
const semitoneOffset = this.calcSemitoneOffset(channel);
|
|
577
|
+
const note = new Note(noteNumber, velocity, startTime, instrumentKey);
|
|
578
|
+
note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
|
|
579
|
+
note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
|
|
580
|
+
this.setVolumeEnvelope(channel, note);
|
|
581
|
+
this.setFilterEnvelope(channel, note);
|
|
539
582
|
if (channel.modulation > 0) {
|
|
540
|
-
const
|
|
541
|
-
|
|
542
|
-
lfoGain = new GainNode(this.audioContext, {
|
|
543
|
-
gain: 0,
|
|
544
|
-
});
|
|
545
|
-
lfoGain.gain
|
|
546
|
-
.setValueAtTime(1e-6, vibratoDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
547
|
-
.exponentialRampToValueAtTime(channel.modulation, vibratoAttack);
|
|
548
|
-
channel.modulationEffect.lfo.connect(lfoGain);
|
|
549
|
-
lfoGain.connect(bufferSource.detune);
|
|
583
|
+
const delayModLFO = startTime + instrumentKey.delayModLFO;
|
|
584
|
+
this.startModulation(channel, note, delayModLFO);
|
|
550
585
|
}
|
|
551
|
-
bufferSource.connect(filterNode);
|
|
552
|
-
filterNode.connect(gainNode);
|
|
553
|
-
bufferSource.start(startTime,
|
|
554
|
-
return
|
|
586
|
+
note.bufferSource.connect(note.filterNode);
|
|
587
|
+
note.filterNode.connect(note.gainNode);
|
|
588
|
+
note.bufferSource.start(startTime, instrumentKey.start / instrumentKey.sampleRate);
|
|
589
|
+
return note;
|
|
555
590
|
}
|
|
556
591
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
557
592
|
const channel = this.channels[channelNumber];
|
|
@@ -561,27 +596,17 @@ class MidyGMLite {
|
|
|
561
596
|
return;
|
|
562
597
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
563
598
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
564
|
-
const
|
|
565
|
-
if (!
|
|
599
|
+
const instrumentKey = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
|
|
600
|
+
if (!instrumentKey)
|
|
566
601
|
return;
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
this.connectNoteEffects(channel, gainNode);
|
|
602
|
+
const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
|
|
603
|
+
this.connectNoteEffects(channel, note.gainNode);
|
|
570
604
|
const scheduledNotes = channel.scheduledNotes;
|
|
571
|
-
const scheduledNote = {
|
|
572
|
-
bufferSource,
|
|
573
|
-
filterNode,
|
|
574
|
-
gainNode,
|
|
575
|
-
lfoGain,
|
|
576
|
-
noteInfo,
|
|
577
|
-
noteNumber,
|
|
578
|
-
startTime,
|
|
579
|
-
};
|
|
580
605
|
if (scheduledNotes.has(noteNumber)) {
|
|
581
|
-
scheduledNotes.get(noteNumber).push(
|
|
606
|
+
scheduledNotes.get(noteNumber).push(note);
|
|
582
607
|
}
|
|
583
608
|
else {
|
|
584
|
-
scheduledNotes.set(noteNumber, [
|
|
609
|
+
scheduledNotes.set(noteNumber, [note]);
|
|
585
610
|
}
|
|
586
611
|
}
|
|
587
612
|
noteOn(channelNumber, noteNumber, velocity) {
|
|
@@ -601,15 +626,15 @@ class MidyGMLite {
|
|
|
601
626
|
continue;
|
|
602
627
|
if (targetNote.ending)
|
|
603
628
|
continue;
|
|
604
|
-
const { bufferSource, filterNode, gainNode,
|
|
629
|
+
const { bufferSource, filterNode, gainNode, modLFO, modLFOGain, instrumentKey, } = targetNote;
|
|
605
630
|
const velocityRate = (velocity + 127) / 127;
|
|
606
|
-
const volEndTime = stopTime +
|
|
631
|
+
const volEndTime = stopTime + instrumentKey.volRelease * velocityRate;
|
|
607
632
|
gainNode.gain.cancelScheduledValues(stopTime);
|
|
608
633
|
gainNode.gain.linearRampToValueAtTime(0, volEndTime);
|
|
609
634
|
const maxFreq = this.audioContext.sampleRate / 2;
|
|
610
|
-
const baseFreq = this.centToHz(
|
|
635
|
+
const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
|
|
611
636
|
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
612
|
-
const modEndTime = stopTime +
|
|
637
|
+
const modEndTime = stopTime + instrumentKey.modRelease * velocityRate;
|
|
613
638
|
filterNode.frequency
|
|
614
639
|
.cancelScheduledValues(stopTime)
|
|
615
640
|
.linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
|
|
@@ -623,8 +648,10 @@ class MidyGMLite {
|
|
|
623
648
|
bufferSource.disconnect(0);
|
|
624
649
|
filterNode.disconnect(0);
|
|
625
650
|
gainNode.disconnect(0);
|
|
626
|
-
if (
|
|
627
|
-
|
|
651
|
+
if (modLFOGain)
|
|
652
|
+
modLFOGain.disconnect(0);
|
|
653
|
+
if (modLFO)
|
|
654
|
+
modLFO.stop();
|
|
628
655
|
resolve();
|
|
629
656
|
};
|
|
630
657
|
bufferSource.stop(volEndTime);
|
|
@@ -675,20 +702,22 @@ class MidyGMLite {
|
|
|
675
702
|
}
|
|
676
703
|
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
677
704
|
const pitchBend = msb * 128 + lsb;
|
|
678
|
-
this.
|
|
705
|
+
this.setPitchBend(channelNumber, pitchBend);
|
|
679
706
|
}
|
|
680
|
-
|
|
707
|
+
setPitchBend(channelNumber, pitchBend) {
|
|
681
708
|
const now = this.audioContext.currentTime;
|
|
682
709
|
const channel = this.channels[channelNumber];
|
|
710
|
+
const prevPitchBend = channel.pitchBend;
|
|
683
711
|
channel.pitchBend = (pitchBend - 8192) / 8192;
|
|
684
|
-
const
|
|
685
|
-
|
|
712
|
+
const detuneChange = (channel.pitchBend - prevPitchBend) *
|
|
713
|
+
channel.pitchBendRange * 100;
|
|
714
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
686
715
|
activeNotes.forEach((activeNote) => {
|
|
687
|
-
const { bufferSource
|
|
688
|
-
const
|
|
689
|
-
bufferSource.
|
|
716
|
+
const { bufferSource } = activeNote;
|
|
717
|
+
const detune = bufferSource.detune.value + detuneChange;
|
|
718
|
+
bufferSource.detune
|
|
690
719
|
.cancelScheduledValues(now)
|
|
691
|
-
.setValueAtTime(
|
|
720
|
+
.setValueAtTime(detune, now);
|
|
692
721
|
});
|
|
693
722
|
}
|
|
694
723
|
handleControlChange(channelNumber, controller, value) {
|
|
@@ -722,21 +751,37 @@ class MidyGMLite {
|
|
|
722
751
|
}
|
|
723
752
|
}
|
|
724
753
|
setModulation(channelNumber, modulation) {
|
|
754
|
+
const now = this.audioContext.currentTime;
|
|
725
755
|
const channel = this.channels[channelNumber];
|
|
726
756
|
channel.modulation = (modulation / 127) *
|
|
727
757
|
(channel.modulationDepthRange * 100);
|
|
758
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
759
|
+
activeNotes.forEach((activeNote) => {
|
|
760
|
+
if (activeNote.modLFO) {
|
|
761
|
+
activeNote.gainNode.gain.setValueAtTime(this.cbToRatio(activeNote.instrumentKey.modLfoToVolume) *
|
|
762
|
+
channel.modulation, now);
|
|
763
|
+
}
|
|
764
|
+
else {
|
|
765
|
+
this.startModulation(channel, activeNote, now);
|
|
766
|
+
}
|
|
767
|
+
});
|
|
728
768
|
}
|
|
729
769
|
setVolume(channelNumber, volume) {
|
|
730
770
|
const channel = this.channels[channelNumber];
|
|
731
771
|
channel.volume = volume / 127;
|
|
732
772
|
this.updateChannelGain(channel);
|
|
733
773
|
}
|
|
774
|
+
panToGain(pan) {
|
|
775
|
+
const theta = Math.PI / 2 * Math.max(0, pan - 1) / 126;
|
|
776
|
+
return {
|
|
777
|
+
gainLeft: Math.cos(theta),
|
|
778
|
+
gainRight: Math.sin(theta),
|
|
779
|
+
};
|
|
780
|
+
}
|
|
734
781
|
setPan(channelNumber, pan) {
|
|
735
|
-
const now = this.audioContext.currentTime;
|
|
736
782
|
const channel = this.channels[channelNumber];
|
|
737
|
-
channel.pan = pan
|
|
738
|
-
|
|
739
|
-
channel.pannerNode.pan.setValueAtTime(channel.pan, now);
|
|
783
|
+
channel.pan = pan;
|
|
784
|
+
this.updateChannelGain(channel);
|
|
740
785
|
}
|
|
741
786
|
setExpression(channelNumber, expression) {
|
|
742
787
|
const channel = this.channels[channelNumber];
|
|
@@ -746,8 +791,13 @@ class MidyGMLite {
|
|
|
746
791
|
updateChannelGain(channel) {
|
|
747
792
|
const now = this.audioContext.currentTime;
|
|
748
793
|
const volume = channel.volume * channel.expression;
|
|
749
|
-
channel.
|
|
750
|
-
channel.
|
|
794
|
+
const { gainLeft, gainRight } = this.panToGain(channel.pan);
|
|
795
|
+
channel.gainL.gain
|
|
796
|
+
.cancelScheduledValues(now)
|
|
797
|
+
.setValueAtTime(volume * gainLeft, now);
|
|
798
|
+
channel.gainR.gain
|
|
799
|
+
.cancelScheduledValues(now)
|
|
800
|
+
.setValueAtTime(volume * gainRight, now);
|
|
751
801
|
}
|
|
752
802
|
setSustainPedal(channelNumber, value) {
|
|
753
803
|
const isOn = value >= 64;
|
|
@@ -769,20 +819,39 @@ class MidyGMLite {
|
|
|
769
819
|
const { dataMSB, dataLSB } = channel;
|
|
770
820
|
switch (rpn) {
|
|
771
821
|
case 0:
|
|
772
|
-
|
|
773
|
-
break;
|
|
822
|
+
return this.handlePitchBendRangeMessage(channelNumber, dataMSB, dataLSB);
|
|
774
823
|
default:
|
|
775
824
|
console.warn(`Channel ${channelNumber}: Unsupported RPN MSB=${channel.rpnMSB} LSB=${channel.rpnLSB}`);
|
|
776
825
|
}
|
|
777
826
|
}
|
|
827
|
+
handlePitchBendRangeMessage(channelNumber, dataMSB, dataLSB) {
|
|
828
|
+
const pitchBendRange = dataMSB + dataLSB / 100;
|
|
829
|
+
this.setPitchBendRange(channelNumber, pitchBendRange);
|
|
830
|
+
}
|
|
831
|
+
setPitchBendRange(channelNumber, pitchBendRange) {
|
|
832
|
+
const now = this.audioContext.currentTime;
|
|
833
|
+
const channel = this.channels[channelNumber];
|
|
834
|
+
const prevPitchBendRange = channel.pitchBendRange;
|
|
835
|
+
channel.pitchBendRange = pitchBendRange;
|
|
836
|
+
const detuneChange = (channel.pitchBendRange - prevPitchBendRange) *
|
|
837
|
+
channel.pitchBend * 100;
|
|
838
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
839
|
+
activeNotes.forEach((activeNote) => {
|
|
840
|
+
const { bufferSource } = activeNote;
|
|
841
|
+
const detune = bufferSource.detune.value + detuneChange;
|
|
842
|
+
bufferSource.detune
|
|
843
|
+
.cancelScheduledValues(now)
|
|
844
|
+
.setValueAtTime(detune, now);
|
|
845
|
+
});
|
|
846
|
+
}
|
|
778
847
|
allSoundOff(channelNumber) {
|
|
779
848
|
const now = this.audioContext.currentTime;
|
|
780
849
|
const channel = this.channels[channelNumber];
|
|
781
850
|
const velocity = 0;
|
|
782
851
|
const stopPedal = true;
|
|
783
852
|
const promises = [];
|
|
784
|
-
channel.scheduledNotes.forEach((
|
|
785
|
-
const activeNote = this.
|
|
853
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
854
|
+
const activeNote = this.getActiveNote(noteList, now);
|
|
786
855
|
if (activeNote) {
|
|
787
856
|
const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
|
|
788
857
|
promises.push(notePromise);
|
|
@@ -799,8 +868,8 @@ class MidyGMLite {
|
|
|
799
868
|
const velocity = 0;
|
|
800
869
|
const stopPedal = false;
|
|
801
870
|
const promises = [];
|
|
802
|
-
channel.scheduledNotes.forEach((
|
|
803
|
-
const activeNote = this.
|
|
871
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
872
|
+
const activeNote = this.getActiveNote(noteList, now);
|
|
804
873
|
if (activeNote) {
|
|
805
874
|
const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
|
|
806
875
|
promises.push(notePromise);
|
|
@@ -850,9 +919,9 @@ class MidyGMLite {
|
|
|
850
919
|
}
|
|
851
920
|
handleMasterVolumeSysEx(data) {
|
|
852
921
|
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
853
|
-
this.
|
|
922
|
+
this.setMasterVolume(volume);
|
|
854
923
|
}
|
|
855
|
-
|
|
924
|
+
setMasterVolume(volume) {
|
|
856
925
|
if (volume < 0 && 1 < volume) {
|
|
857
926
|
console.error("Master Volume is out of range");
|
|
858
927
|
}
|
|
@@ -894,10 +963,7 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
|
|
|
894
963
|
writable: true,
|
|
895
964
|
value: {
|
|
896
965
|
volume: 100 / 127,
|
|
897
|
-
pan:
|
|
898
|
-
vibratoRate: 5,
|
|
899
|
-
vibratoDepth: 0.5,
|
|
900
|
-
vibratoDelay: 2.5,
|
|
966
|
+
pan: 64,
|
|
901
967
|
bank: 0,
|
|
902
968
|
dataMSB: 0,
|
|
903
969
|
dataLSB: 0,
|