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