@marmooo/midy 0.1.4 → 0.1.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.4 → soundfont-parser@0.0.6}/+esm.d.ts +16 -20
- package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.d.ts.map +1 -0
- package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.js +180 -0
- package/esm/midy-GM1.d.ts +36 -8
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +87 -89
- package/esm/midy-GM2.d.ts +65 -10
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +233 -154
- package/esm/midy-GMLite.d.ts +36 -8
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +87 -89
- package/esm/midy.d.ts +85 -10
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +254 -174
- package/package.json +1 -1
- package/script/deps/cdn.jsdelivr.net/npm/@marmooo/{soundfont-parser@0.0.4 → soundfont-parser@0.0.6}/+esm.d.ts +16 -20
- package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.d.ts.map +1 -0
- package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.js +190 -0
- package/script/midy-GM1.d.ts +36 -8
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +87 -89
- package/script/midy-GM2.d.ts +65 -10
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +233 -154
- package/script/midy-GMLite.d.ts +36 -8
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +87 -89
- package/script/midy.d.ts +85 -10
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +254 -174
- package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.4/+esm.d.ts.map +0 -1
- package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.4/+esm.js +0 -162
- package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.4/+esm.d.ts.map +0 -1
- package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.4/+esm.js +0 -169
package/script/midy.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Midy = 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.6/+esm.js");
|
|
6
6
|
class Note {
|
|
7
7
|
constructor(noteNumber, velocity, startTime, instrumentKey) {
|
|
8
8
|
Object.defineProperty(this, "bufferSource", {
|
|
@@ -233,6 +233,7 @@ class Midy {
|
|
|
233
233
|
this.audioContext = audioContext;
|
|
234
234
|
this.options = { ...this.defaultOptions, ...options };
|
|
235
235
|
this.masterGain = new GainNode(audioContext);
|
|
236
|
+
this.controlChangeHandlers = this.createControlChangeHandlers();
|
|
236
237
|
this.channels = this.createChannels(audioContext);
|
|
237
238
|
this.reverbEffect = this.options.reverbAlgorithm(audioContext);
|
|
238
239
|
this.chorusEffect = this.createChorusEffect(audioContext);
|
|
@@ -251,12 +252,14 @@ class Midy {
|
|
|
251
252
|
addSoundFont(soundFont) {
|
|
252
253
|
const index = this.soundFonts.length;
|
|
253
254
|
this.soundFonts.push(soundFont);
|
|
254
|
-
soundFont.parsed.presetHeaders
|
|
255
|
+
const presetHeaders = soundFont.parsed.presetHeaders;
|
|
256
|
+
for (let i = 0; i < presetHeaders.length; i++) {
|
|
257
|
+
const presetHeader = presetHeaders[i];
|
|
255
258
|
if (!presetHeader.presetName.startsWith("\u0000")) { // TODO: Only SF3 generated by PolyPone?
|
|
256
259
|
const banks = this.soundFontTable[presetHeader.preset];
|
|
257
260
|
banks.set(presetHeader.bank, index);
|
|
258
261
|
}
|
|
259
|
-
}
|
|
262
|
+
}
|
|
260
263
|
}
|
|
261
264
|
async loadSoundFont(soundFontUrl) {
|
|
262
265
|
const response = await fetch(soundFontUrl);
|
|
@@ -308,27 +311,25 @@ class Midy {
|
|
|
308
311
|
return channels;
|
|
309
312
|
}
|
|
310
313
|
async createNoteBuffer(instrumentKey, isSF3) {
|
|
314
|
+
const sampleStart = instrumentKey.start;
|
|
311
315
|
const sampleEnd = instrumentKey.sample.length + instrumentKey.end;
|
|
312
316
|
if (isSF3) {
|
|
313
|
-
const sample =
|
|
314
|
-
sample.set(instrumentKey.sample);
|
|
317
|
+
const sample = instrumentKey.sample.slice(sampleStart, sampleEnd);
|
|
315
318
|
const audioBuffer = await this.audioContext.decodeAudioData(sample.buffer);
|
|
316
|
-
for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {
|
|
317
|
-
const channelData = audioBuffer.getChannelData(channel);
|
|
318
|
-
channelData.set(channelData.subarray(0, sampleEnd));
|
|
319
|
-
}
|
|
320
319
|
return audioBuffer;
|
|
321
320
|
}
|
|
322
321
|
else {
|
|
323
|
-
const sample = instrumentKey.sample.subarray(
|
|
324
|
-
const floatSample = this.convertToFloat32Array(sample);
|
|
322
|
+
const sample = instrumentKey.sample.subarray(sampleStart, sampleEnd);
|
|
325
323
|
const audioBuffer = new AudioBuffer({
|
|
326
324
|
numberOfChannels: 1,
|
|
327
325
|
length: sample.length,
|
|
328
326
|
sampleRate: instrumentKey.sampleRate,
|
|
329
327
|
});
|
|
330
328
|
const channelData = audioBuffer.getChannelData(0);
|
|
331
|
-
|
|
329
|
+
const int16Array = new Int16Array(sample.buffer);
|
|
330
|
+
for (let i = 0; i < int16Array.length; i++) {
|
|
331
|
+
channelData[i] = int16Array[i] / 32768;
|
|
332
|
+
}
|
|
332
333
|
return audioBuffer;
|
|
333
334
|
}
|
|
334
335
|
}
|
|
@@ -344,13 +345,23 @@ class Midy {
|
|
|
344
345
|
}
|
|
345
346
|
return bufferSource;
|
|
346
347
|
}
|
|
347
|
-
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
348
|
+
findPortamentoTarget(queueIndex) {
|
|
349
|
+
const endEvent = this.timeline[queueIndex];
|
|
350
|
+
if (!this.channels[endEvent.channel].portamento)
|
|
351
|
+
return;
|
|
352
|
+
const endTime = endEvent.startTime;
|
|
353
|
+
let target;
|
|
354
|
+
while (++queueIndex < this.timeline.length) {
|
|
355
|
+
const event = this.timeline[queueIndex];
|
|
356
|
+
if (endTime !== event.startTime)
|
|
357
|
+
break;
|
|
358
|
+
if (event.type !== "noteOn")
|
|
359
|
+
continue;
|
|
360
|
+
if (!target || event.noteNumber < target.noteNumber) {
|
|
361
|
+
target = event;
|
|
362
|
+
}
|
|
352
363
|
}
|
|
353
|
-
return
|
|
364
|
+
return target;
|
|
354
365
|
}
|
|
355
366
|
async scheduleTimelineEvents(t, offset, queueIndex) {
|
|
356
367
|
while (queueIndex < this.timeline.length) {
|
|
@@ -360,12 +371,15 @@ class Midy {
|
|
|
360
371
|
switch (event.type) {
|
|
361
372
|
case "noteOn":
|
|
362
373
|
if (event.velocity !== 0) {
|
|
363
|
-
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
|
|
374
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset, event.portamento);
|
|
364
375
|
break;
|
|
365
376
|
}
|
|
366
377
|
/* falls through */
|
|
367
378
|
case "noteOff": {
|
|
368
|
-
const
|
|
379
|
+
const portamentoTarget = this.findPortamentoTarget(queueIndex);
|
|
380
|
+
if (portamentoTarget)
|
|
381
|
+
portamentoTarget.portamento = true;
|
|
382
|
+
const notePromise = this.scheduleNoteRelease(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset, portamentoTarget?.noteNumber, false);
|
|
369
383
|
if (notePromise) {
|
|
370
384
|
this.notePromises.push(notePromise);
|
|
371
385
|
}
|
|
@@ -469,9 +483,11 @@ class Midy {
|
|
|
469
483
|
bankLSB: this.channels[i].bankLSB,
|
|
470
484
|
};
|
|
471
485
|
}
|
|
472
|
-
midi.tracks.
|
|
486
|
+
for (let i = 0; i < midi.tracks.length; i++) {
|
|
487
|
+
const track = midi.tracks[i];
|
|
473
488
|
let currentTicks = 0;
|
|
474
|
-
track.
|
|
489
|
+
for (let j = 0; j < track.length; j++) {
|
|
490
|
+
const event = track[j];
|
|
475
491
|
currentTicks += event.deltaTime;
|
|
476
492
|
event.ticks = currentTicks;
|
|
477
493
|
switch (event.type) {
|
|
@@ -524,16 +540,18 @@ class Midy {
|
|
|
524
540
|
}
|
|
525
541
|
delete event.deltaTime;
|
|
526
542
|
timeline.push(event);
|
|
527
|
-
}
|
|
528
|
-
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
529
545
|
const priority = {
|
|
530
546
|
controller: 0,
|
|
531
547
|
sysEx: 1,
|
|
548
|
+
noteOff: 2, // for portamento
|
|
549
|
+
noteOn: 3,
|
|
532
550
|
};
|
|
533
551
|
timeline.sort((a, b) => {
|
|
534
552
|
if (a.ticks !== b.ticks)
|
|
535
553
|
return a.ticks - b.ticks;
|
|
536
|
-
return (priority[a.type] ||
|
|
554
|
+
return (priority[a.type] || 4) - (priority[b.type] || 4);
|
|
537
555
|
});
|
|
538
556
|
let prevTempoTime = 0;
|
|
539
557
|
let prevTempoTicks = 0;
|
|
@@ -550,7 +568,7 @@ class Midy {
|
|
|
550
568
|
}
|
|
551
569
|
return { instruments, timeline };
|
|
552
570
|
}
|
|
553
|
-
async stopChannelNotes(channelNumber, velocity,
|
|
571
|
+
async stopChannelNotes(channelNumber, velocity, force) {
|
|
554
572
|
const now = this.audioContext.currentTime;
|
|
555
573
|
const channel = this.channels[channelNumber];
|
|
556
574
|
channel.scheduledNotes.forEach((noteList) => {
|
|
@@ -558,16 +576,17 @@ class Midy {
|
|
|
558
576
|
const note = noteList[i];
|
|
559
577
|
if (!note)
|
|
560
578
|
continue;
|
|
561
|
-
const promise = this.scheduleNoteRelease(channelNumber, note.noteNumber, velocity, now,
|
|
579
|
+
const promise = this.scheduleNoteRelease(channelNumber, note.noteNumber, velocity, now, undefined, // portamentoNoteNumber
|
|
580
|
+
force);
|
|
562
581
|
this.notePromises.push(promise);
|
|
563
582
|
}
|
|
564
583
|
});
|
|
565
584
|
channel.scheduledNotes.clear();
|
|
566
585
|
await Promise.all(this.notePromises);
|
|
567
586
|
}
|
|
568
|
-
stopNotes(velocity,
|
|
587
|
+
stopNotes(velocity, force) {
|
|
569
588
|
for (let i = 0; i < this.channels.length; i++) {
|
|
570
|
-
this.stopChannelNotes(i, velocity,
|
|
589
|
+
this.stopChannelNotes(i, velocity, force);
|
|
571
590
|
}
|
|
572
591
|
return Promise.all(this.notePromises);
|
|
573
592
|
}
|
|
@@ -781,6 +800,17 @@ class Midy {
|
|
|
781
800
|
return instrumentKey.playbackRate(noteNumber) *
|
|
782
801
|
Math.pow(2, semitoneOffset / 12);
|
|
783
802
|
}
|
|
803
|
+
setPortamentoStartVolumeEnvelope(channel, note) {
|
|
804
|
+
const { instrumentKey, startTime } = note;
|
|
805
|
+
const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
|
|
806
|
+
const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
|
|
807
|
+
const volDelay = startTime + instrumentKey.volDelay;
|
|
808
|
+
const portamentoTime = volDelay + channel.portamentoTime;
|
|
809
|
+
note.volumeNode.gain
|
|
810
|
+
.cancelScheduledValues(startTime)
|
|
811
|
+
.setValueAtTime(0, volDelay)
|
|
812
|
+
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
813
|
+
}
|
|
784
814
|
setVolumeEnvelope(channel, note) {
|
|
785
815
|
const { instrumentKey, startTime } = note;
|
|
786
816
|
const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
|
|
@@ -820,6 +850,25 @@ class Midy {
|
|
|
820
850
|
const maxFrequency = 20000; // max Hz of initialFilterFc
|
|
821
851
|
return Math.max(minFrequency, Math.min(frequency, maxFrequency));
|
|
822
852
|
}
|
|
853
|
+
setPortamentoStartFilterEnvelope(channel, note) {
|
|
854
|
+
const { instrumentKey, noteNumber, startTime } = note;
|
|
855
|
+
const softPedalFactor = 1 -
|
|
856
|
+
(0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
|
|
857
|
+
const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
|
|
858
|
+
softPedalFactor * channel.brightness;
|
|
859
|
+
const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor * channel.brightness;
|
|
860
|
+
const sustainFreq = baseFreq +
|
|
861
|
+
(peekFreq - baseFreq) * (1 - instrumentKey.modSustain);
|
|
862
|
+
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
863
|
+
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
864
|
+
const portamentoTime = startTime + channel.portamentoTime;
|
|
865
|
+
const modDelay = startTime + instrumentKey.modDelay;
|
|
866
|
+
note.filterNode.frequency
|
|
867
|
+
.cancelScheduledValues(startTime)
|
|
868
|
+
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
869
|
+
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
870
|
+
.linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
|
|
871
|
+
}
|
|
823
872
|
setFilterEnvelope(channel, note) {
|
|
824
873
|
const { instrumentKey, noteNumber, startTime } = note;
|
|
825
874
|
const softPedalFactor = 1 -
|
|
@@ -887,7 +936,7 @@ class Midy {
|
|
|
887
936
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
888
937
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
889
938
|
}
|
|
890
|
-
async createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3) {
|
|
939
|
+
async createNote(channel, instrumentKey, noteNumber, velocity, startTime, portamento, isSF3) {
|
|
891
940
|
const semitoneOffset = this.calcSemitoneOffset(channel);
|
|
892
941
|
const note = new Note(noteNumber, velocity, startTime, instrumentKey);
|
|
893
942
|
note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
|
|
@@ -896,8 +945,14 @@ class Midy {
|
|
|
896
945
|
type: "lowpass",
|
|
897
946
|
Q: instrumentKey.initialFilterQ / 10 * channel.filterResonance, // dB
|
|
898
947
|
});
|
|
899
|
-
|
|
900
|
-
|
|
948
|
+
if (portamento) {
|
|
949
|
+
this.setPortamentoStartVolumeEnvelope(channel, note);
|
|
950
|
+
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
951
|
+
}
|
|
952
|
+
else {
|
|
953
|
+
this.setVolumeEnvelope(channel, note);
|
|
954
|
+
this.setFilterEnvelope(channel, note);
|
|
955
|
+
}
|
|
901
956
|
if (0 < channel.vibratoDepth) {
|
|
902
957
|
this.startVibrato(channel, note, startTime);
|
|
903
958
|
}
|
|
@@ -914,7 +969,7 @@ class Midy {
|
|
|
914
969
|
}
|
|
915
970
|
note.bufferSource.connect(note.filterNode);
|
|
916
971
|
note.filterNode.connect(note.volumeNode);
|
|
917
|
-
note.bufferSource.start(startTime
|
|
972
|
+
note.bufferSource.start(startTime);
|
|
918
973
|
return note;
|
|
919
974
|
}
|
|
920
975
|
calcBank(channel, channelNumber) {
|
|
@@ -926,7 +981,7 @@ class Midy {
|
|
|
926
981
|
}
|
|
927
982
|
return channel.bank;
|
|
928
983
|
}
|
|
929
|
-
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
984
|
+
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, portamento) {
|
|
930
985
|
const channel = this.channels[channelNumber];
|
|
931
986
|
const bankNumber = this.calcBank(channel, channelNumber);
|
|
932
987
|
const soundFontIndex = this.soundFontTable[channel.program].get(bankNumber);
|
|
@@ -934,10 +989,10 @@ class Midy {
|
|
|
934
989
|
return;
|
|
935
990
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
936
991
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
937
|
-
const instrumentKey = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
|
|
992
|
+
const instrumentKey = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber, velocity);
|
|
938
993
|
if (!instrumentKey)
|
|
939
994
|
return;
|
|
940
|
-
const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
|
|
995
|
+
const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, portamento, isSF3);
|
|
941
996
|
note.volumeNode.connect(channel.gainL);
|
|
942
997
|
note.volumeNode.connect(channel.gainR);
|
|
943
998
|
if (channel.sostenutoPedal) {
|
|
@@ -951,16 +1006,47 @@ class Midy {
|
|
|
951
1006
|
scheduledNotes.set(noteNumber, [note]);
|
|
952
1007
|
}
|
|
953
1008
|
}
|
|
954
|
-
noteOn(channelNumber, noteNumber, velocity) {
|
|
1009
|
+
noteOn(channelNumber, noteNumber, velocity, portamento) {
|
|
955
1010
|
const now = this.audioContext.currentTime;
|
|
956
|
-
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, now);
|
|
1011
|
+
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, now, portamento);
|
|
957
1012
|
}
|
|
958
|
-
|
|
1013
|
+
stopNote(stopTime, endTime, scheduledNotes, index) {
|
|
1014
|
+
const note = scheduledNotes[index];
|
|
1015
|
+
note.volumeNode.gain
|
|
1016
|
+
.cancelScheduledValues(stopTime)
|
|
1017
|
+
.linearRampToValueAtTime(0, endTime);
|
|
1018
|
+
note.ending = true;
|
|
1019
|
+
this.scheduleTask(() => {
|
|
1020
|
+
note.bufferSource.loop = false;
|
|
1021
|
+
}, endTime);
|
|
1022
|
+
return new Promise((resolve) => {
|
|
1023
|
+
note.bufferSource.onended = () => {
|
|
1024
|
+
scheduledNotes[index] = null;
|
|
1025
|
+
note.bufferSource.disconnect();
|
|
1026
|
+
note.volumeNode.disconnect();
|
|
1027
|
+
note.filterNode.disconnect();
|
|
1028
|
+
if (note.modulationDepth) {
|
|
1029
|
+
note.volumeDepth.disconnect();
|
|
1030
|
+
note.modulationDepth.disconnect();
|
|
1031
|
+
note.modulationLFO.stop();
|
|
1032
|
+
}
|
|
1033
|
+
if (note.vibratoDepth) {
|
|
1034
|
+
note.vibratoDepth.disconnect();
|
|
1035
|
+
note.vibratoLFO.stop();
|
|
1036
|
+
}
|
|
1037
|
+
resolve();
|
|
1038
|
+
};
|
|
1039
|
+
note.bufferSource.stop(endTime);
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
scheduleNoteRelease(channelNumber, noteNumber, _velocity, stopTime, portamentoNoteNumber, force) {
|
|
959
1043
|
const channel = this.channels[channelNumber];
|
|
960
|
-
if (
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
1044
|
+
if (!force) {
|
|
1045
|
+
if (channel.sustainPedal)
|
|
1046
|
+
return;
|
|
1047
|
+
if (channel.sostenutoNotes.has(noteNumber))
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
964
1050
|
if (!channel.scheduledNotes.has(noteNumber))
|
|
965
1051
|
return;
|
|
966
1052
|
const scheduledNotes = channel.scheduledNotes.get(noteNumber);
|
|
@@ -970,44 +1056,29 @@ class Midy {
|
|
|
970
1056
|
continue;
|
|
971
1057
|
if (note.ending)
|
|
972
1058
|
continue;
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
.
|
|
977
|
-
.
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
.
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
note.filterNode.disconnect();
|
|
992
|
-
if (note.volumeDepth)
|
|
993
|
-
note.volumeDepth.disconnect();
|
|
994
|
-
if (note.modulationDepth)
|
|
995
|
-
note.modulationDepth.disconnect();
|
|
996
|
-
if (note.modulationLFO)
|
|
997
|
-
note.modulationLFO.stop();
|
|
998
|
-
if (note.vibratoDepth)
|
|
999
|
-
note.vibratoDepth.disconnect();
|
|
1000
|
-
if (note.vibratoLFO)
|
|
1001
|
-
note.vibratoLFO.stop();
|
|
1002
|
-
resolve();
|
|
1003
|
-
};
|
|
1004
|
-
note.bufferSource.stop(volEndTime);
|
|
1005
|
-
});
|
|
1059
|
+
if (portamentoNoteNumber === undefined) {
|
|
1060
|
+
const volEndTime = stopTime +
|
|
1061
|
+
note.instrumentKey.volRelease * channel.releaseTime;
|
|
1062
|
+
const modRelease = stopTime + note.instrumentKey.modRelease;
|
|
1063
|
+
note.filterNode.frequency
|
|
1064
|
+
.cancelScheduledValues(stopTime)
|
|
1065
|
+
.linearRampToValueAtTime(0, modRelease);
|
|
1066
|
+
return this.stopNote(stopTime, volEndTime, scheduledNotes, i);
|
|
1067
|
+
}
|
|
1068
|
+
else {
|
|
1069
|
+
const portamentoTime = stopTime + channel.portamentoTime;
|
|
1070
|
+
const detuneChange = (portamentoNoteNumber - noteNumber) * 100;
|
|
1071
|
+
const detune = note.bufferSource.detune.value + detuneChange;
|
|
1072
|
+
note.bufferSource.detune
|
|
1073
|
+
.cancelScheduledValues(stopTime)
|
|
1074
|
+
.linearRampToValueAtTime(detune, portamentoTime);
|
|
1075
|
+
return this.stopNote(stopTime, portamentoTime, scheduledNotes, i);
|
|
1076
|
+
}
|
|
1006
1077
|
}
|
|
1007
1078
|
}
|
|
1008
|
-
releaseNote(channelNumber, noteNumber, velocity) {
|
|
1079
|
+
releaseNote(channelNumber, noteNumber, velocity, portamentoNoteNumber) {
|
|
1009
1080
|
const now = this.audioContext.currentTime;
|
|
1010
|
-
return this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now);
|
|
1081
|
+
return this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, portamentoNoteNumber, false);
|
|
1011
1082
|
}
|
|
1012
1083
|
releaseSustainPedal(channelNumber, halfVelocity) {
|
|
1013
1084
|
const velocity = halfVelocity * 2;
|
|
@@ -1108,78 +1179,51 @@ class Midy {
|
|
|
1108
1179
|
channel.pitchBendRange * 100;
|
|
1109
1180
|
this.updateDetune(channel, detuneChange);
|
|
1110
1181
|
}
|
|
1182
|
+
createControlChangeHandlers() {
|
|
1183
|
+
return {
|
|
1184
|
+
0: this.setBankMSB,
|
|
1185
|
+
1: this.setModulationDepth,
|
|
1186
|
+
5: this.setPortamentoTime,
|
|
1187
|
+
6: this.dataEntryMSB,
|
|
1188
|
+
7: this.setVolume,
|
|
1189
|
+
10: this.setPan,
|
|
1190
|
+
11: this.setExpression,
|
|
1191
|
+
32: this.setBankLSB,
|
|
1192
|
+
38: this.dataEntryLSB,
|
|
1193
|
+
64: this.setSustainPedal,
|
|
1194
|
+
65: this.setPortamento,
|
|
1195
|
+
66: this.setSostenutoPedal,
|
|
1196
|
+
67: this.setSoftPedal,
|
|
1197
|
+
71: this.setFilterResonance,
|
|
1198
|
+
72: this.setReleaseTime,
|
|
1199
|
+
73: this.setAttackTime,
|
|
1200
|
+
74: this.setBrightness,
|
|
1201
|
+
75: this.setDecayTime,
|
|
1202
|
+
76: this.setVibratoRate,
|
|
1203
|
+
77: this.setVibratoDepth,
|
|
1204
|
+
78: this.setVibratoDelay,
|
|
1205
|
+
91: this.setReverbSendLevel,
|
|
1206
|
+
93: this.setChorusSendLevel,
|
|
1207
|
+
96: this.dataIncrement,
|
|
1208
|
+
97: this.dataDecrement,
|
|
1209
|
+
100: this.setRPNLSB,
|
|
1210
|
+
101: this.setRPNMSB,
|
|
1211
|
+
120: this.allSoundOff,
|
|
1212
|
+
121: this.resetAllControllers,
|
|
1213
|
+
123: this.allNotesOff,
|
|
1214
|
+
124: this.omniOff,
|
|
1215
|
+
125: this.omniOn,
|
|
1216
|
+
126: this.monoOn,
|
|
1217
|
+
127: this.polyOn,
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1111
1220
|
handleControlChange(channelNumber, controller, value) {
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
return this.setPortamentoTime(channelNumber, value);
|
|
1119
|
-
case 6:
|
|
1120
|
-
return this.dataEntryMSB(channelNumber, value);
|
|
1121
|
-
case 7:
|
|
1122
|
-
return this.setVolume(channelNumber, value);
|
|
1123
|
-
case 10:
|
|
1124
|
-
return this.setPan(channelNumber, value);
|
|
1125
|
-
case 11:
|
|
1126
|
-
return this.setExpression(channelNumber, value);
|
|
1127
|
-
case 32:
|
|
1128
|
-
return this.setBankLSB(channelNumber, value);
|
|
1129
|
-
case 38:
|
|
1130
|
-
return this.dataEntryLSB(channelNumber, value);
|
|
1131
|
-
case 64:
|
|
1132
|
-
return this.setSustainPedal(channelNumber, value);
|
|
1133
|
-
case 65:
|
|
1134
|
-
return this.setPortamento(channelNumber, value);
|
|
1135
|
-
case 66:
|
|
1136
|
-
return this.setSostenutoPedal(channelNumber, value);
|
|
1137
|
-
case 67:
|
|
1138
|
-
return this.setSoftPedal(channelNumber, value);
|
|
1139
|
-
case 71:
|
|
1140
|
-
return this.setFilterResonance(channelNumber, value);
|
|
1141
|
-
case 72:
|
|
1142
|
-
return this.setReleaseTime(channelNumber, value);
|
|
1143
|
-
case 73:
|
|
1144
|
-
return this.setAttackTime(channelNumber, value);
|
|
1145
|
-
case 74:
|
|
1146
|
-
return this.setBrightness(channelNumber, value);
|
|
1147
|
-
case 75:
|
|
1148
|
-
return this.setDecayTime(channelNumber, value);
|
|
1149
|
-
case 76:
|
|
1150
|
-
return this.setVibratoRate(channelNumber, value);
|
|
1151
|
-
case 77:
|
|
1152
|
-
return this.setVibratoDepth(channelNumber, value);
|
|
1153
|
-
case 78:
|
|
1154
|
-
return this.setVibratoDelay(channelNumber, value);
|
|
1155
|
-
case 91:
|
|
1156
|
-
return this.setReverbSendLevel(channelNumber, value);
|
|
1157
|
-
case 93:
|
|
1158
|
-
return this.setChorusSendLevel(channelNumber, value);
|
|
1159
|
-
case 96: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp18.pdf
|
|
1160
|
-
return this.dataIncrement(channelNumber);
|
|
1161
|
-
case 97: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp18.pdf
|
|
1162
|
-
return this.dataDecrement(channelNumber);
|
|
1163
|
-
case 100:
|
|
1164
|
-
return this.setRPNLSB(channelNumber, value);
|
|
1165
|
-
case 101:
|
|
1166
|
-
return this.setRPNMSB(channelNumber, value);
|
|
1167
|
-
case 120:
|
|
1168
|
-
return this.allSoundOff(channelNumber);
|
|
1169
|
-
case 121:
|
|
1170
|
-
return this.resetAllControllers(channelNumber);
|
|
1171
|
-
case 123:
|
|
1172
|
-
return this.allNotesOff(channelNumber);
|
|
1173
|
-
case 124:
|
|
1174
|
-
return this.omniOff();
|
|
1175
|
-
case 125:
|
|
1176
|
-
return this.omniOn();
|
|
1177
|
-
case 126:
|
|
1178
|
-
return this.monoOn();
|
|
1179
|
-
case 127:
|
|
1180
|
-
return this.polyOn();
|
|
1181
|
-
default:
|
|
1182
|
-
console.warn(`Unsupported Control change: controller=${controller} value=${value}`);
|
|
1221
|
+
const handler = this.controlChangeHandlers[controller];
|
|
1222
|
+
if (handler) {
|
|
1223
|
+
handler.call(this, channelNumber, value);
|
|
1224
|
+
}
|
|
1225
|
+
else {
|
|
1226
|
+
console.warn(`Unsupported Control change: controller=${controller} value=${value}`);
|
|
1183
1227
|
}
|
|
1184
1228
|
}
|
|
1185
1229
|
setBankMSB(channelNumber, msb) {
|
|
@@ -1209,12 +1253,14 @@ class Midy {
|
|
|
1209
1253
|
this.updateModulation(channel);
|
|
1210
1254
|
}
|
|
1211
1255
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
1212
|
-
this.channels[channelNumber]
|
|
1256
|
+
const channel = this.channels[channelNumber];
|
|
1257
|
+
const factor = 5 * Math.log(10) / 127;
|
|
1258
|
+
channel.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1213
1259
|
}
|
|
1214
1260
|
setVolume(channelNumber, volume) {
|
|
1215
1261
|
const channel = this.channels[channelNumber];
|
|
1216
1262
|
channel.volume = volume / 127;
|
|
1217
|
-
this.
|
|
1263
|
+
this.updateChannelVolume(channel);
|
|
1218
1264
|
}
|
|
1219
1265
|
panToGain(pan) {
|
|
1220
1266
|
const theta = Math.PI / 2 * Math.max(0, pan - 1) / 126;
|
|
@@ -1226,12 +1272,12 @@ class Midy {
|
|
|
1226
1272
|
setPan(channelNumber, pan) {
|
|
1227
1273
|
const channel = this.channels[channelNumber];
|
|
1228
1274
|
channel.pan = pan;
|
|
1229
|
-
this.
|
|
1275
|
+
this.updateChannelVolume(channel);
|
|
1230
1276
|
}
|
|
1231
1277
|
setExpression(channelNumber, expression) {
|
|
1232
1278
|
const channel = this.channels[channelNumber];
|
|
1233
1279
|
channel.expression = expression / 127;
|
|
1234
|
-
this.
|
|
1280
|
+
this.updateChannelVolume(channel);
|
|
1235
1281
|
}
|
|
1236
1282
|
setBankLSB(channelNumber, lsb) {
|
|
1237
1283
|
this.channels[channelNumber].bankLSB = lsb;
|
|
@@ -1240,7 +1286,7 @@ class Midy {
|
|
|
1240
1286
|
this.channels[channelNumber].dataLSB = value;
|
|
1241
1287
|
this.handleRPN(channelNumber, 0);
|
|
1242
1288
|
}
|
|
1243
|
-
|
|
1289
|
+
updateChannelVolume(channel) {
|
|
1244
1290
|
const now = this.audioContext.currentTime;
|
|
1245
1291
|
const volume = channel.volume * channel.expression;
|
|
1246
1292
|
const { gainLeft, gainRight } = this.panToGain(channel.pan);
|
|
@@ -1258,34 +1304,55 @@ class Midy {
|
|
|
1258
1304
|
this.releaseSustainPedal(channelNumber, value);
|
|
1259
1305
|
}
|
|
1260
1306
|
}
|
|
1261
|
-
// TODO
|
|
1262
1307
|
setPortamento(channelNumber, value) {
|
|
1263
1308
|
this.channels[channelNumber].portamento = value >= 64;
|
|
1264
1309
|
}
|
|
1265
1310
|
setReverbSendLevel(channelNumber, reverbSendLevel) {
|
|
1266
1311
|
const channel = this.channels[channelNumber];
|
|
1267
1312
|
const reverbEffect = this.reverbEffect;
|
|
1268
|
-
if (0 < reverbSendLevel) {
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1313
|
+
if (0 < channel.reverbSendLevel) {
|
|
1314
|
+
if (0 < reverbSendLevel) {
|
|
1315
|
+
const now = this.audioContext.currentTime;
|
|
1316
|
+
channel.reverbSendLevel = reverbSendLevel / 127;
|
|
1317
|
+
reverbEffect.output.gain.cancelScheduledValues(now);
|
|
1318
|
+
reverbEffect.output.gain.setValueAtTime(channel.reverbSendLevel, now);
|
|
1319
|
+
}
|
|
1320
|
+
else {
|
|
1321
|
+
channel.merger.disconnect(reverbEffect.input);
|
|
1322
|
+
}
|
|
1273
1323
|
}
|
|
1274
|
-
else
|
|
1275
|
-
|
|
1324
|
+
else {
|
|
1325
|
+
if (0 < reverbSendLevel) {
|
|
1326
|
+
channel.merger.connect(reverbEffect.input);
|
|
1327
|
+
const now = this.audioContext.currentTime;
|
|
1328
|
+
channel.reverbSendLevel = reverbSendLevel / 127;
|
|
1329
|
+
reverbEffect.output.gain.cancelScheduledValues(now);
|
|
1330
|
+
reverbEffect.output.gain.setValueAtTime(channel.reverbSendLevel, now);
|
|
1331
|
+
}
|
|
1276
1332
|
}
|
|
1277
1333
|
}
|
|
1278
1334
|
setChorusSendLevel(channelNumber, chorusSendLevel) {
|
|
1279
1335
|
const channel = this.channels[channelNumber];
|
|
1280
1336
|
const chorusEffect = this.chorusEffect;
|
|
1281
|
-
if (0 < chorusSendLevel) {
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1337
|
+
if (0 < channel.chorusSendLevel) {
|
|
1338
|
+
if (0 < chorusSendLevel) {
|
|
1339
|
+
const now = this.audioContext.currentTime;
|
|
1340
|
+
channel.chorusSendLevel = chorusSendLevel / 127;
|
|
1341
|
+
chorusEffect.output.gain.cancelScheduledValues(now);
|
|
1342
|
+
chorusEffect.output.gain.setValueAtTime(channel.chorusSendLevel, now);
|
|
1343
|
+
}
|
|
1344
|
+
else {
|
|
1345
|
+
channel.merger.disconnect(chorusEffect.input);
|
|
1346
|
+
}
|
|
1286
1347
|
}
|
|
1287
|
-
else
|
|
1288
|
-
|
|
1348
|
+
else {
|
|
1349
|
+
if (0 < chorusSendLevel) {
|
|
1350
|
+
channel.merger.connect(chorusEffect.input);
|
|
1351
|
+
const now = this.audioContext.currentTime;
|
|
1352
|
+
channel.chorusSendLevel = chorusSendLevel / 127;
|
|
1353
|
+
chorusEffect.output.gain.cancelScheduledValues(now);
|
|
1354
|
+
chorusEffect.output.gain.setValueAtTime(channel.chorusSendLevel, now);
|
|
1355
|
+
}
|
|
1289
1356
|
}
|
|
1290
1357
|
}
|
|
1291
1358
|
setSostenutoPedal(channelNumber, value) {
|
|
@@ -1366,6 +1433,15 @@ class Midy {
|
|
|
1366
1433
|
setVibratoRate(channelNumber, vibratoRate) {
|
|
1367
1434
|
const channel = this.channels[channelNumber];
|
|
1368
1435
|
channel.vibratoRate = vibratoRate / 64;
|
|
1436
|
+
if (channel.vibratoDepth <= 0)
|
|
1437
|
+
return;
|
|
1438
|
+
const now = this.audioContext.currentTime;
|
|
1439
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
1440
|
+
activeNotes.forEach((activeNote) => {
|
|
1441
|
+
activeNote.vibratoLFO.frequency
|
|
1442
|
+
.cancelScheduledValues(now)
|
|
1443
|
+
.setValueAtTime(channel.vibratoRate, now);
|
|
1444
|
+
});
|
|
1369
1445
|
}
|
|
1370
1446
|
setVibratoDepth(channelNumber, vibratoDepth) {
|
|
1371
1447
|
const channel = this.channels[channelNumber];
|
|
@@ -1425,9 +1501,11 @@ class Midy {
|
|
|
1425
1501
|
console.warn(`Channel ${channelNumber}: Unsupported RPN MSB=${channel.rpnMSB} LSB=${channel.rpnLSB}`);
|
|
1426
1502
|
}
|
|
1427
1503
|
}
|
|
1504
|
+
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp18.pdf
|
|
1428
1505
|
dataIncrement(channelNumber) {
|
|
1429
1506
|
this.handleRPN(channelNumber, 1);
|
|
1430
1507
|
}
|
|
1508
|
+
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp18.pdf
|
|
1431
1509
|
dataDecrement(channelNumber) {
|
|
1432
1510
|
this.handleRPN(channelNumber, -1);
|
|
1433
1511
|
}
|
|
@@ -1550,20 +1628,22 @@ class Midy {
|
|
|
1550
1628
|
}
|
|
1551
1629
|
}
|
|
1552
1630
|
GM1SystemOn() {
|
|
1553
|
-
this.channels.
|
|
1631
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
1632
|
+
const channel = this.channels[i];
|
|
1554
1633
|
channel.bankMSB = 0;
|
|
1555
1634
|
channel.bankLSB = 0;
|
|
1556
1635
|
channel.bank = 0;
|
|
1557
|
-
}
|
|
1636
|
+
}
|
|
1558
1637
|
this.channels[9].bankMSB = 1;
|
|
1559
1638
|
this.channels[9].bank = 128;
|
|
1560
1639
|
}
|
|
1561
1640
|
GM2SystemOn() {
|
|
1562
|
-
this.channels.
|
|
1641
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
1642
|
+
const channel = this.channels[i];
|
|
1563
1643
|
channel.bankMSB = 121;
|
|
1564
1644
|
channel.bankLSB = 0;
|
|
1565
1645
|
channel.bank = 121 * 128;
|
|
1566
|
-
}
|
|
1646
|
+
}
|
|
1567
1647
|
this.channels[9].bankMSB = 120;
|
|
1568
1648
|
this.channels[9].bank = 120 * 128;
|
|
1569
1649
|
}
|
|
@@ -1873,7 +1953,7 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
1873
1953
|
currentBufferSource: null,
|
|
1874
1954
|
volume: 100 / 127,
|
|
1875
1955
|
pan: 64,
|
|
1876
|
-
portamentoTime:
|
|
1956
|
+
portamentoTime: 1, // sec
|
|
1877
1957
|
filterResonance: 1,
|
|
1878
1958
|
releaseTime: 1,
|
|
1879
1959
|
attackTime: 1,
|