@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-GM1.js
CHANGED
|
@@ -2,7 +2,45 @@
|
|
|
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
|
+
this.noteNumber = noteNumber;
|
|
39
|
+
this.velocity = velocity;
|
|
40
|
+
this.startTime = startTime;
|
|
41
|
+
this.instrumentKey = instrumentKey;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
6
44
|
class MidyGM1 {
|
|
7
45
|
constructor(audioContext) {
|
|
8
46
|
Object.defineProperty(this, "ticksPerBeat", {
|
|
@@ -148,20 +186,16 @@ class MidyGM1 {
|
|
|
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(MidyGM1.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 MidyGM1 {
|
|
|
171
205
|
...MidyGM1.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 MidyGM1 {
|
|
|
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 MidyGM1 {
|
|
|
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 MidyGM1 {
|
|
|
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 MidyGM1 {
|
|
|
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 MidyGM1 {
|
|
|
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 MidyGM1 {
|
|
|
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);
|
|
@@ -488,71 +507,87 @@ class MidyGM1 {
|
|
|
488
507
|
const tuning = channel.coarseTuning + channel.fineTuning;
|
|
489
508
|
return channel.pitchBend * channel.pitchBendRange + tuning;
|
|
490
509
|
}
|
|
491
|
-
calcPlaybackRate(
|
|
492
|
-
return
|
|
510
|
+
calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset) {
|
|
511
|
+
return instrumentKey.playbackRate(noteNumber) *
|
|
512
|
+
Math.pow(2, semitoneOffset / 12);
|
|
493
513
|
}
|
|
494
|
-
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
bufferSource.playbackRate.value = this.calcPlaybackRate(noteInfo, noteNumber, semitoneOffset);
|
|
498
|
-
// volume envelope
|
|
499
|
-
const gainNode = new GainNode(this.audioContext, {
|
|
500
|
-
gain: 0,
|
|
501
|
-
});
|
|
514
|
+
setVolumeEnvelope(channel, note) {
|
|
515
|
+
const { instrumentKey, startTime, velocity } = note;
|
|
516
|
+
note.gainNode = new GainNode(this.audioContext, { gain: 0 });
|
|
502
517
|
let volume = (velocity / 127) * channel.volume * channel.expression;
|
|
503
518
|
if (volume === 0)
|
|
504
519
|
volume = 1e-6; // exponentialRampToValueAtTime() requires a non-zero value
|
|
505
|
-
const attackVolume = this.cbToRatio(-
|
|
506
|
-
|
|
507
|
-
const
|
|
508
|
-
const
|
|
509
|
-
const
|
|
510
|
-
const
|
|
511
|
-
|
|
520
|
+
const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation) *
|
|
521
|
+
volume;
|
|
522
|
+
const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
|
|
523
|
+
const volDelay = startTime + instrumentKey.volDelay;
|
|
524
|
+
const volAttack = volDelay + instrumentKey.volAttack;
|
|
525
|
+
const volHold = volAttack + instrumentKey.volHold;
|
|
526
|
+
const volDecay = volHold + instrumentKey.volDecay;
|
|
527
|
+
note.gainNode.gain
|
|
512
528
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
513
529
|
.exponentialRampToValueAtTime(attackVolume, volAttack)
|
|
514
530
|
.setValueAtTime(attackVolume, volHold)
|
|
515
531
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
516
|
-
|
|
532
|
+
}
|
|
533
|
+
setFilterEnvelope(channel, note) {
|
|
534
|
+
const { instrumentKey, startTime, noteNumber } = note;
|
|
535
|
+
const softPedalFactor = 1 -
|
|
536
|
+
(0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
|
|
517
537
|
const maxFreq = this.audioContext.sampleRate / 2;
|
|
518
|
-
const baseFreq = this.centToHz(
|
|
519
|
-
|
|
520
|
-
const
|
|
521
|
-
|
|
538
|
+
const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
|
|
539
|
+
softPedalFactor;
|
|
540
|
+
const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
|
|
541
|
+
const sustainFreq = (baseFreq +
|
|
542
|
+
(peekFreq - baseFreq) * (1 - instrumentKey.modSustain)) * softPedalFactor;
|
|
543
|
+
const modDelay = startTime + instrumentKey.modDelay;
|
|
544
|
+
const modAttack = modDelay + instrumentKey.modAttack;
|
|
545
|
+
const modHold = modAttack + instrumentKey.modHold;
|
|
546
|
+
const modDecay = modHold + instrumentKey.modDecay;
|
|
522
547
|
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
523
548
|
const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
|
|
524
549
|
const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
|
|
525
|
-
|
|
550
|
+
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
526
551
|
type: "lowpass",
|
|
527
|
-
Q:
|
|
552
|
+
Q: instrumentKey.initialFilterQ / 10, // dB
|
|
528
553
|
frequency: adjustedBaseFreq,
|
|
529
554
|
});
|
|
530
|
-
|
|
531
|
-
const modAttack = modDelay + noteInfo.modAttack;
|
|
532
|
-
const modHold = modAttack + noteInfo.modHold;
|
|
533
|
-
const modDecay = modHold + noteInfo.modDecay;
|
|
534
|
-
filterNode.frequency
|
|
555
|
+
note.filterNode.frequency
|
|
535
556
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
536
557
|
.exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
|
|
537
558
|
.setValueAtTime(adjustedPeekFreq, modHold)
|
|
538
559
|
.linearRampToValueAtTime(adjustedSustainFreq, modDecay);
|
|
539
|
-
|
|
560
|
+
note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modEnvToPitch, modDelay);
|
|
561
|
+
}
|
|
562
|
+
startModulation(channel, note, time) {
|
|
563
|
+
const { instrumentKey } = note;
|
|
564
|
+
note.modLFOGain = new GainNode(this.audioContext, {
|
|
565
|
+
gain: this.cbToRatio(instrumentKey.modLfoToVolume) * channel.modulation,
|
|
566
|
+
});
|
|
567
|
+
note.modLFO = new OscillatorNode(this.audioContext, {
|
|
568
|
+
frequency: this.centToHz(instrumentKey.freqModLFO),
|
|
569
|
+
});
|
|
570
|
+
note.modLFO.start(time);
|
|
571
|
+
note.filterNode.frequency.setValueAtTime(note.filterNode.frequency.value + instrumentKey.modLfoToFilterFc, time);
|
|
572
|
+
note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modLfoToPitch, time);
|
|
573
|
+
note.modLFO.connect(note.modLFOGain);
|
|
574
|
+
note.modLFOGain.connect(note.bufferSource.detune);
|
|
575
|
+
}
|
|
576
|
+
async createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3) {
|
|
577
|
+
const semitoneOffset = this.calcSemitoneOffset(channel);
|
|
578
|
+
const note = new Note(noteNumber, velocity, startTime, instrumentKey);
|
|
579
|
+
note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
|
|
580
|
+
note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
|
|
581
|
+
this.setVolumeEnvelope(channel, note);
|
|
582
|
+
this.setFilterEnvelope(channel, note);
|
|
540
583
|
if (channel.modulation > 0) {
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
lfoGain = new GainNode(this.audioContext, {
|
|
544
|
-
gain: 0,
|
|
545
|
-
});
|
|
546
|
-
lfoGain.gain
|
|
547
|
-
.setValueAtTime(1e-6, vibratoDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
548
|
-
.exponentialRampToValueAtTime(channel.modulation, vibratoAttack);
|
|
549
|
-
channel.modulationEffect.lfo.connect(lfoGain);
|
|
550
|
-
lfoGain.connect(bufferSource.detune);
|
|
584
|
+
const delayModLFO = startTime + instrumentKey.delayModLFO;
|
|
585
|
+
this.startModulation(channel, note, delayModLFO);
|
|
551
586
|
}
|
|
552
|
-
bufferSource.connect(filterNode);
|
|
553
|
-
filterNode.connect(gainNode);
|
|
554
|
-
bufferSource.start(startTime,
|
|
555
|
-
return
|
|
587
|
+
note.bufferSource.connect(note.filterNode);
|
|
588
|
+
note.filterNode.connect(note.gainNode);
|
|
589
|
+
note.bufferSource.start(startTime, instrumentKey.start / instrumentKey.sampleRate);
|
|
590
|
+
return note;
|
|
556
591
|
}
|
|
557
592
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
558
593
|
const channel = this.channels[channelNumber];
|
|
@@ -562,27 +597,17 @@ class MidyGM1 {
|
|
|
562
597
|
return;
|
|
563
598
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
564
599
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
565
|
-
const
|
|
566
|
-
if (!
|
|
600
|
+
const instrumentKey = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
|
|
601
|
+
if (!instrumentKey)
|
|
567
602
|
return;
|
|
568
|
-
const
|
|
569
|
-
|
|
570
|
-
this.connectNoteEffects(channel, gainNode);
|
|
603
|
+
const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
|
|
604
|
+
this.connectNoteEffects(channel, note.gainNode);
|
|
571
605
|
const scheduledNotes = channel.scheduledNotes;
|
|
572
|
-
const scheduledNote = {
|
|
573
|
-
bufferSource,
|
|
574
|
-
filterNode,
|
|
575
|
-
gainNode,
|
|
576
|
-
lfoGain,
|
|
577
|
-
noteInfo,
|
|
578
|
-
noteNumber,
|
|
579
|
-
startTime,
|
|
580
|
-
};
|
|
581
606
|
if (scheduledNotes.has(noteNumber)) {
|
|
582
|
-
scheduledNotes.get(noteNumber).push(
|
|
607
|
+
scheduledNotes.get(noteNumber).push(note);
|
|
583
608
|
}
|
|
584
609
|
else {
|
|
585
|
-
scheduledNotes.set(noteNumber, [
|
|
610
|
+
scheduledNotes.set(noteNumber, [note]);
|
|
586
611
|
}
|
|
587
612
|
}
|
|
588
613
|
noteOn(channelNumber, noteNumber, velocity) {
|
|
@@ -602,15 +627,15 @@ class MidyGM1 {
|
|
|
602
627
|
continue;
|
|
603
628
|
if (targetNote.ending)
|
|
604
629
|
continue;
|
|
605
|
-
const { bufferSource, filterNode, gainNode,
|
|
630
|
+
const { bufferSource, filterNode, gainNode, modLFO, modLFOGain, instrumentKey, } = targetNote;
|
|
606
631
|
const velocityRate = (velocity + 127) / 127;
|
|
607
|
-
const volEndTime = stopTime +
|
|
632
|
+
const volEndTime = stopTime + instrumentKey.volRelease * velocityRate;
|
|
608
633
|
gainNode.gain.cancelScheduledValues(stopTime);
|
|
609
634
|
gainNode.gain.linearRampToValueAtTime(0, volEndTime);
|
|
610
635
|
const maxFreq = this.audioContext.sampleRate / 2;
|
|
611
|
-
const baseFreq = this.centToHz(
|
|
636
|
+
const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
|
|
612
637
|
const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
|
|
613
|
-
const modEndTime = stopTime +
|
|
638
|
+
const modEndTime = stopTime + instrumentKey.modRelease * velocityRate;
|
|
614
639
|
filterNode.frequency
|
|
615
640
|
.cancelScheduledValues(stopTime)
|
|
616
641
|
.linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
|
|
@@ -624,8 +649,10 @@ class MidyGM1 {
|
|
|
624
649
|
bufferSource.disconnect(0);
|
|
625
650
|
filterNode.disconnect(0);
|
|
626
651
|
gainNode.disconnect(0);
|
|
627
|
-
if (
|
|
628
|
-
|
|
652
|
+
if (modLFOGain)
|
|
653
|
+
modLFOGain.disconnect(0);
|
|
654
|
+
if (modLFO)
|
|
655
|
+
modLFO.stop();
|
|
629
656
|
resolve();
|
|
630
657
|
};
|
|
631
658
|
bufferSource.stop(volEndTime);
|
|
@@ -676,20 +703,22 @@ class MidyGM1 {
|
|
|
676
703
|
}
|
|
677
704
|
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
678
705
|
const pitchBend = msb * 128 + lsb;
|
|
679
|
-
this.
|
|
706
|
+
this.setPitchBend(channelNumber, pitchBend);
|
|
680
707
|
}
|
|
681
|
-
|
|
708
|
+
setPitchBend(channelNumber, pitchBend) {
|
|
682
709
|
const now = this.audioContext.currentTime;
|
|
683
710
|
const channel = this.channels[channelNumber];
|
|
711
|
+
const prevPitchBend = channel.pitchBend;
|
|
684
712
|
channel.pitchBend = (pitchBend - 8192) / 8192;
|
|
685
|
-
const
|
|
686
|
-
|
|
713
|
+
const detuneChange = (channel.pitchBend - prevPitchBend) *
|
|
714
|
+
channel.pitchBendRange * 100;
|
|
715
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
687
716
|
activeNotes.forEach((activeNote) => {
|
|
688
|
-
const { bufferSource
|
|
689
|
-
const
|
|
690
|
-
bufferSource.
|
|
717
|
+
const { bufferSource } = activeNote;
|
|
718
|
+
const detune = bufferSource.detune.value + detuneChange;
|
|
719
|
+
bufferSource.detune
|
|
691
720
|
.cancelScheduledValues(now)
|
|
692
|
-
.setValueAtTime(
|
|
721
|
+
.setValueAtTime(detune, now);
|
|
693
722
|
});
|
|
694
723
|
}
|
|
695
724
|
handleControlChange(channelNumber, controller, value) {
|
|
@@ -723,21 +752,37 @@ class MidyGM1 {
|
|
|
723
752
|
}
|
|
724
753
|
}
|
|
725
754
|
setModulation(channelNumber, modulation) {
|
|
755
|
+
const now = this.audioContext.currentTime;
|
|
726
756
|
const channel = this.channels[channelNumber];
|
|
727
757
|
channel.modulation = (modulation / 127) *
|
|
728
758
|
(channel.modulationDepthRange * 100);
|
|
759
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
760
|
+
activeNotes.forEach((activeNote) => {
|
|
761
|
+
if (activeNote.modLFO) {
|
|
762
|
+
activeNote.gainNode.gain.setValueAtTime(this.cbToRatio(activeNote.instrumentKey.modLfoToVolume) *
|
|
763
|
+
channel.modulation, now);
|
|
764
|
+
}
|
|
765
|
+
else {
|
|
766
|
+
this.startModulation(channel, activeNote, now);
|
|
767
|
+
}
|
|
768
|
+
});
|
|
729
769
|
}
|
|
730
770
|
setVolume(channelNumber, volume) {
|
|
731
771
|
const channel = this.channels[channelNumber];
|
|
732
772
|
channel.volume = volume / 127;
|
|
733
773
|
this.updateChannelGain(channel);
|
|
734
774
|
}
|
|
775
|
+
panToGain(pan) {
|
|
776
|
+
const theta = Math.PI / 2 * Math.max(0, pan - 1) / 126;
|
|
777
|
+
return {
|
|
778
|
+
gainLeft: Math.cos(theta),
|
|
779
|
+
gainRight: Math.sin(theta),
|
|
780
|
+
};
|
|
781
|
+
}
|
|
735
782
|
setPan(channelNumber, pan) {
|
|
736
|
-
const now = this.audioContext.currentTime;
|
|
737
783
|
const channel = this.channels[channelNumber];
|
|
738
|
-
channel.pan = pan
|
|
739
|
-
|
|
740
|
-
channel.pannerNode.pan.setValueAtTime(channel.pan, now);
|
|
784
|
+
channel.pan = pan;
|
|
785
|
+
this.updateChannelGain(channel);
|
|
741
786
|
}
|
|
742
787
|
setExpression(channelNumber, expression) {
|
|
743
788
|
const channel = this.channels[channelNumber];
|
|
@@ -747,8 +792,13 @@ class MidyGM1 {
|
|
|
747
792
|
updateChannelGain(channel) {
|
|
748
793
|
const now = this.audioContext.currentTime;
|
|
749
794
|
const volume = channel.volume * channel.expression;
|
|
750
|
-
channel.
|
|
751
|
-
channel.
|
|
795
|
+
const { gainLeft, gainRight } = this.panToGain(channel.pan);
|
|
796
|
+
channel.gainL.gain
|
|
797
|
+
.cancelScheduledValues(now)
|
|
798
|
+
.setValueAtTime(volume * gainLeft, now);
|
|
799
|
+
channel.gainR.gain
|
|
800
|
+
.cancelScheduledValues(now)
|
|
801
|
+
.setValueAtTime(volume * gainRight, now);
|
|
752
802
|
}
|
|
753
803
|
setSustainPedal(channelNumber, value) {
|
|
754
804
|
const isOn = value >= 64;
|
|
@@ -770,8 +820,7 @@ class MidyGM1 {
|
|
|
770
820
|
const { dataMSB, dataLSB } = channel;
|
|
771
821
|
switch (rpn) {
|
|
772
822
|
case 0:
|
|
773
|
-
|
|
774
|
-
break;
|
|
823
|
+
return this.handlePitchBendRangeMessage(channelNumber, dataMSB, dataLSB);
|
|
775
824
|
case 1:
|
|
776
825
|
channel.fineTuning = (dataMSB * 128 + dataLSB - 8192) / 8192;
|
|
777
826
|
break;
|
|
@@ -782,14 +831,34 @@ class MidyGM1 {
|
|
|
782
831
|
console.warn(`Channel ${channelNumber}: Unsupported RPN MSB=${channel.rpnMSB} LSB=${channel.rpnLSB}`);
|
|
783
832
|
}
|
|
784
833
|
}
|
|
834
|
+
handlePitchBendRangeMessage(channelNumber, dataMSB, dataLSB) {
|
|
835
|
+
const pitchBendRange = dataMSB + dataLSB / 100;
|
|
836
|
+
this.setPitchBendRange(channelNumber, pitchBendRange);
|
|
837
|
+
}
|
|
838
|
+
setPitchBendRange(channelNumber, pitchBendRange) {
|
|
839
|
+
const now = this.audioContext.currentTime;
|
|
840
|
+
const channel = this.channels[channelNumber];
|
|
841
|
+
const prevPitchBendRange = channel.pitchBendRange;
|
|
842
|
+
channel.pitchBendRange = pitchBendRange;
|
|
843
|
+
const detuneChange = (channel.pitchBendRange - prevPitchBendRange) *
|
|
844
|
+
channel.pitchBend * 100;
|
|
845
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
846
|
+
activeNotes.forEach((activeNote) => {
|
|
847
|
+
const { bufferSource } = activeNote;
|
|
848
|
+
const detune = bufferSource.detune.value + detuneChange;
|
|
849
|
+
bufferSource.detune
|
|
850
|
+
.cancelScheduledValues(now)
|
|
851
|
+
.setValueAtTime(detune, now);
|
|
852
|
+
});
|
|
853
|
+
}
|
|
785
854
|
allSoundOff(channelNumber) {
|
|
786
855
|
const now = this.audioContext.currentTime;
|
|
787
856
|
const channel = this.channels[channelNumber];
|
|
788
857
|
const velocity = 0;
|
|
789
858
|
const stopPedal = true;
|
|
790
859
|
const promises = [];
|
|
791
|
-
channel.scheduledNotes.forEach((
|
|
792
|
-
const activeNote = this.
|
|
860
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
861
|
+
const activeNote = this.getActiveNote(noteList, now);
|
|
793
862
|
if (activeNote) {
|
|
794
863
|
const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
|
|
795
864
|
promises.push(notePromise);
|
|
@@ -806,8 +875,8 @@ class MidyGM1 {
|
|
|
806
875
|
const velocity = 0;
|
|
807
876
|
const stopPedal = false;
|
|
808
877
|
const promises = [];
|
|
809
|
-
channel.scheduledNotes.forEach((
|
|
810
|
-
const activeNote = this.
|
|
878
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
879
|
+
const activeNote = this.getActiveNote(noteList, now);
|
|
811
880
|
if (activeNote) {
|
|
812
881
|
const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
|
|
813
882
|
promises.push(notePromise);
|
|
@@ -857,9 +926,9 @@ class MidyGM1 {
|
|
|
857
926
|
}
|
|
858
927
|
handleMasterVolumeSysEx(data) {
|
|
859
928
|
const volume = (data[5] * 128 + data[4]) / 16383;
|
|
860
|
-
this.
|
|
929
|
+
this.setMasterVolume(volume);
|
|
861
930
|
}
|
|
862
|
-
|
|
931
|
+
setMasterVolume(volume) {
|
|
863
932
|
if (volume < 0 && 1 < volume) {
|
|
864
933
|
console.error("Master Volume is out of range");
|
|
865
934
|
}
|
|
@@ -901,10 +970,7 @@ Object.defineProperty(MidyGM1, "channelSettings", {
|
|
|
901
970
|
writable: true,
|
|
902
971
|
value: {
|
|
903
972
|
volume: 100 / 127,
|
|
904
|
-
pan:
|
|
905
|
-
vibratoRate: 5,
|
|
906
|
-
vibratoDepth: 0.5,
|
|
907
|
-
vibratoDelay: 2.5,
|
|
973
|
+
pan: 64,
|
|
908
974
|
bank: 0,
|
|
909
975
|
dataMSB: 0,
|
|
910
976
|
dataLSB: 0,
|