@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-GM2.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 MidyGM2 {
|
|
|
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 MidyGM2 {
|
|
|
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);
|
|
@@ -302,27 +305,25 @@ export class MidyGM2 {
|
|
|
302
305
|
return channels;
|
|
303
306
|
}
|
|
304
307
|
async createNoteBuffer(instrumentKey, isSF3) {
|
|
308
|
+
const sampleStart = instrumentKey.start;
|
|
305
309
|
const sampleEnd = instrumentKey.sample.length + instrumentKey.end;
|
|
306
310
|
if (isSF3) {
|
|
307
|
-
const sample =
|
|
308
|
-
sample.set(instrumentKey.sample);
|
|
311
|
+
const sample = instrumentKey.sample.slice(sampleStart, sampleEnd);
|
|
309
312
|
const audioBuffer = await this.audioContext.decodeAudioData(sample.buffer);
|
|
310
|
-
for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {
|
|
311
|
-
const channelData = audioBuffer.getChannelData(channel);
|
|
312
|
-
channelData.set(channelData.subarray(0, sampleEnd));
|
|
313
|
-
}
|
|
314
313
|
return audioBuffer;
|
|
315
314
|
}
|
|
316
315
|
else {
|
|
317
|
-
const sample = instrumentKey.sample.subarray(
|
|
318
|
-
const floatSample = this.convertToFloat32Array(sample);
|
|
316
|
+
const sample = instrumentKey.sample.subarray(sampleStart, sampleEnd);
|
|
319
317
|
const audioBuffer = new AudioBuffer({
|
|
320
318
|
numberOfChannels: 1,
|
|
321
319
|
length: sample.length,
|
|
322
320
|
sampleRate: instrumentKey.sampleRate,
|
|
323
321
|
});
|
|
324
322
|
const channelData = audioBuffer.getChannelData(0);
|
|
325
|
-
|
|
323
|
+
const int16Array = new Int16Array(sample.buffer);
|
|
324
|
+
for (let i = 0; i < int16Array.length; i++) {
|
|
325
|
+
channelData[i] = int16Array[i] / 32768;
|
|
326
|
+
}
|
|
326
327
|
return audioBuffer;
|
|
327
328
|
}
|
|
328
329
|
}
|
|
@@ -338,13 +339,23 @@ export class MidyGM2 {
|
|
|
338
339
|
}
|
|
339
340
|
return bufferSource;
|
|
340
341
|
}
|
|
341
|
-
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
342
|
+
findPortamentoTarget(queueIndex) {
|
|
343
|
+
const endEvent = this.timeline[queueIndex];
|
|
344
|
+
if (!this.channels[endEvent.channel].portamento)
|
|
345
|
+
return;
|
|
346
|
+
const endTime = endEvent.startTime;
|
|
347
|
+
let target;
|
|
348
|
+
while (++queueIndex < this.timeline.length) {
|
|
349
|
+
const event = this.timeline[queueIndex];
|
|
350
|
+
if (endTime !== event.startTime)
|
|
351
|
+
break;
|
|
352
|
+
if (event.type !== "noteOn")
|
|
353
|
+
continue;
|
|
354
|
+
if (!target || event.noteNumber < target.noteNumber) {
|
|
355
|
+
target = event;
|
|
356
|
+
}
|
|
346
357
|
}
|
|
347
|
-
return
|
|
358
|
+
return target;
|
|
348
359
|
}
|
|
349
360
|
async scheduleTimelineEvents(t, offset, queueIndex) {
|
|
350
361
|
while (queueIndex < this.timeline.length) {
|
|
@@ -354,12 +365,15 @@ export class MidyGM2 {
|
|
|
354
365
|
switch (event.type) {
|
|
355
366
|
case "noteOn":
|
|
356
367
|
if (event.velocity !== 0) {
|
|
357
|
-
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
|
|
368
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset, event.portamento);
|
|
358
369
|
break;
|
|
359
370
|
}
|
|
360
371
|
/* falls through */
|
|
361
372
|
case "noteOff": {
|
|
362
|
-
const
|
|
373
|
+
const portamentoTarget = this.findPortamentoTarget(queueIndex);
|
|
374
|
+
if (portamentoTarget)
|
|
375
|
+
portamentoTarget.portamento = true;
|
|
376
|
+
const notePromise = this.scheduleNoteRelease(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset, portamentoTarget?.noteNumber, false);
|
|
363
377
|
if (notePromise) {
|
|
364
378
|
this.notePromises.push(notePromise);
|
|
365
379
|
}
|
|
@@ -460,9 +474,11 @@ export class MidyGM2 {
|
|
|
460
474
|
bankLSB: this.channels[i].bankLSB,
|
|
461
475
|
};
|
|
462
476
|
}
|
|
463
|
-
midi.tracks.
|
|
477
|
+
for (let i = 0; i < midi.tracks.length; i++) {
|
|
478
|
+
const track = midi.tracks[i];
|
|
464
479
|
let currentTicks = 0;
|
|
465
|
-
track.
|
|
480
|
+
for (let j = 0; j < track.length; j++) {
|
|
481
|
+
const event = track[j];
|
|
466
482
|
currentTicks += event.deltaTime;
|
|
467
483
|
event.ticks = currentTicks;
|
|
468
484
|
switch (event.type) {
|
|
@@ -515,16 +531,18 @@ export class MidyGM2 {
|
|
|
515
531
|
}
|
|
516
532
|
delete event.deltaTime;
|
|
517
533
|
timeline.push(event);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
520
536
|
const priority = {
|
|
521
537
|
controller: 0,
|
|
522
538
|
sysEx: 1,
|
|
539
|
+
noteOff: 2, // for portamento
|
|
540
|
+
noteOn: 3,
|
|
523
541
|
};
|
|
524
542
|
timeline.sort((a, b) => {
|
|
525
543
|
if (a.ticks !== b.ticks)
|
|
526
544
|
return a.ticks - b.ticks;
|
|
527
|
-
return (priority[a.type] ||
|
|
545
|
+
return (priority[a.type] || 4) - (priority[b.type] || 4);
|
|
528
546
|
});
|
|
529
547
|
let prevTempoTime = 0;
|
|
530
548
|
let prevTempoTicks = 0;
|
|
@@ -541,7 +559,7 @@ export class MidyGM2 {
|
|
|
541
559
|
}
|
|
542
560
|
return { instruments, timeline };
|
|
543
561
|
}
|
|
544
|
-
async stopChannelNotes(channelNumber, velocity,
|
|
562
|
+
async stopChannelNotes(channelNumber, velocity, force) {
|
|
545
563
|
const now = this.audioContext.currentTime;
|
|
546
564
|
const channel = this.channels[channelNumber];
|
|
547
565
|
channel.scheduledNotes.forEach((noteList) => {
|
|
@@ -549,16 +567,17 @@ export class MidyGM2 {
|
|
|
549
567
|
const note = noteList[i];
|
|
550
568
|
if (!note)
|
|
551
569
|
continue;
|
|
552
|
-
const promise = this.scheduleNoteRelease(channelNumber, note.noteNumber, velocity, now,
|
|
570
|
+
const promise = this.scheduleNoteRelease(channelNumber, note.noteNumber, velocity, now, undefined, // portamentoNoteNumber
|
|
571
|
+
force);
|
|
553
572
|
this.notePromises.push(promise);
|
|
554
573
|
}
|
|
555
574
|
});
|
|
556
575
|
channel.scheduledNotes.clear();
|
|
557
576
|
await Promise.all(this.notePromises);
|
|
558
577
|
}
|
|
559
|
-
stopNotes(velocity,
|
|
578
|
+
stopNotes(velocity, force) {
|
|
560
579
|
for (let i = 0; i < this.channels.length; i++) {
|
|
561
|
-
this.stopChannelNotes(i, velocity,
|
|
580
|
+
this.stopChannelNotes(i, velocity, force);
|
|
562
581
|
}
|
|
563
582
|
return Promise.all(this.notePromises);
|
|
564
583
|
}
|
|
@@ -772,6 +791,17 @@ export class MidyGM2 {
|
|
|
772
791
|
return instrumentKey.playbackRate(noteNumber) *
|
|
773
792
|
Math.pow(2, semitoneOffset / 12);
|
|
774
793
|
}
|
|
794
|
+
setPortamentoStartVolumeEnvelope(channel, note) {
|
|
795
|
+
const { instrumentKey, startTime } = note;
|
|
796
|
+
const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
|
|
797
|
+
const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
|
|
798
|
+
const volDelay = startTime + instrumentKey.volDelay;
|
|
799
|
+
const portamentoTime = volDelay + channel.portamentoTime;
|
|
800
|
+
note.volumeNode.gain
|
|
801
|
+
.cancelScheduledValues(startTime)
|
|
802
|
+
.setValueAtTime(0, volDelay)
|
|
803
|
+
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
804
|
+
}
|
|
775
805
|
setVolumeEnvelope(note) {
|
|
776
806
|
const { instrumentKey, startTime } = note;
|
|
777
807
|
const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
|
|
@@ -811,6 +841,25 @@ export class MidyGM2 {
|
|
|
811
841
|
const maxFrequency = 20000; // max Hz of initialFilterFc
|
|
812
842
|
return Math.max(minFrequency, Math.min(frequency, maxFrequency));
|
|
813
843
|
}
|
|
844
|
+
setPortamentoStartFilterEnvelope(channel, note) {
|
|
845
|
+
const { instrumentKey, noteNumber, startTime } = note;
|
|
846
|
+
const softPedalFactor = 1 -
|
|
847
|
+
(0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
|
|
848
|
+
const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
|
|
849
|
+
softPedalFactor;
|
|
850
|
+
const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
|
|
851
|
+
const sustainFreq = baseFreq +
|
|
852
|
+
(peekFreq - baseFreq) * (1 - instrumentKey.modSustain);
|
|
853
|
+
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
854
|
+
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
855
|
+
const portamentoTime = startTime + channel.portamentoTime;
|
|
856
|
+
const modDelay = startTime + instrumentKey.modDelay;
|
|
857
|
+
note.filterNode.frequency
|
|
858
|
+
.cancelScheduledValues(startTime)
|
|
859
|
+
.setValueAtTime(adjustedBaseFreq, startTime)
|
|
860
|
+
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
861
|
+
.linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
|
|
862
|
+
}
|
|
814
863
|
setFilterEnvelope(channel, note) {
|
|
815
864
|
const { instrumentKey, noteNumber, startTime } = note;
|
|
816
865
|
const softPedalFactor = 1 -
|
|
@@ -878,17 +927,23 @@ export class MidyGM2 {
|
|
|
878
927
|
note.vibratoLFO.connect(note.vibratoDepth);
|
|
879
928
|
note.vibratoDepth.connect(note.bufferSource.detune);
|
|
880
929
|
}
|
|
881
|
-
async createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3) {
|
|
930
|
+
async createNote(channel, instrumentKey, noteNumber, velocity, startTime, portamento, isSF3) {
|
|
882
931
|
const semitoneOffset = this.calcSemitoneOffset(channel);
|
|
883
932
|
const note = new Note(noteNumber, velocity, startTime, instrumentKey);
|
|
884
933
|
note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
|
|
885
934
|
note.volumeNode = new GainNode(this.audioContext);
|
|
886
935
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
887
936
|
type: "lowpass",
|
|
888
|
-
Q: instrumentKey.initialFilterQ / 10
|
|
937
|
+
Q: instrumentKey.initialFilterQ / 10, // dB
|
|
889
938
|
});
|
|
890
|
-
|
|
891
|
-
|
|
939
|
+
if (portamento) {
|
|
940
|
+
this.setPortamentoStartVolumeEnvelope(channel, note);
|
|
941
|
+
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
942
|
+
}
|
|
943
|
+
else {
|
|
944
|
+
this.setVolumeEnvelope(note);
|
|
945
|
+
this.setFilterEnvelope(channel, note);
|
|
946
|
+
}
|
|
892
947
|
if (0 < channel.vibratoDepth) {
|
|
893
948
|
this.startVibrato(channel, note, startTime);
|
|
894
949
|
}
|
|
@@ -905,7 +960,7 @@ export class MidyGM2 {
|
|
|
905
960
|
}
|
|
906
961
|
note.bufferSource.connect(note.filterNode);
|
|
907
962
|
note.filterNode.connect(note.volumeNode);
|
|
908
|
-
note.bufferSource.start(startTime
|
|
963
|
+
note.bufferSource.start(startTime);
|
|
909
964
|
return note;
|
|
910
965
|
}
|
|
911
966
|
calcBank(channel, channelNumber) {
|
|
@@ -917,7 +972,7 @@ export class MidyGM2 {
|
|
|
917
972
|
}
|
|
918
973
|
return channel.bank;
|
|
919
974
|
}
|
|
920
|
-
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
|
|
975
|
+
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, portamento) {
|
|
921
976
|
const channel = this.channels[channelNumber];
|
|
922
977
|
const bankNumber = this.calcBank(channel, channelNumber);
|
|
923
978
|
const soundFontIndex = this.soundFontTable[channel.program].get(bankNumber);
|
|
@@ -925,10 +980,10 @@ export class MidyGM2 {
|
|
|
925
980
|
return;
|
|
926
981
|
const soundFont = this.soundFonts[soundFontIndex];
|
|
927
982
|
const isSF3 = soundFont.parsed.info.version.major === 3;
|
|
928
|
-
const instrumentKey = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
|
|
983
|
+
const instrumentKey = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber, velocity);
|
|
929
984
|
if (!instrumentKey)
|
|
930
985
|
return;
|
|
931
|
-
const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
|
|
986
|
+
const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, portamento, isSF3);
|
|
932
987
|
note.volumeNode.connect(channel.gainL);
|
|
933
988
|
note.volumeNode.connect(channel.gainR);
|
|
934
989
|
if (channel.sostenutoPedal) {
|
|
@@ -942,16 +997,47 @@ export class MidyGM2 {
|
|
|
942
997
|
scheduledNotes.set(noteNumber, [note]);
|
|
943
998
|
}
|
|
944
999
|
}
|
|
945
|
-
noteOn(channelNumber, noteNumber, velocity) {
|
|
1000
|
+
noteOn(channelNumber, noteNumber, velocity, portamento) {
|
|
946
1001
|
const now = this.audioContext.currentTime;
|
|
947
|
-
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, now);
|
|
1002
|
+
return this.scheduleNoteOn(channelNumber, noteNumber, velocity, now, portamento);
|
|
1003
|
+
}
|
|
1004
|
+
stopNote(stopTime, endTime, scheduledNotes, index) {
|
|
1005
|
+
const note = scheduledNotes[index];
|
|
1006
|
+
note.volumeNode.gain
|
|
1007
|
+
.cancelScheduledValues(stopTime)
|
|
1008
|
+
.linearRampToValueAtTime(0, endTime);
|
|
1009
|
+
note.ending = true;
|
|
1010
|
+
this.scheduleTask(() => {
|
|
1011
|
+
note.bufferSource.loop = false;
|
|
1012
|
+
}, endTime);
|
|
1013
|
+
return new Promise((resolve) => {
|
|
1014
|
+
note.bufferSource.onended = () => {
|
|
1015
|
+
scheduledNotes[index] = null;
|
|
1016
|
+
note.bufferSource.disconnect();
|
|
1017
|
+
note.volumeNode.disconnect();
|
|
1018
|
+
note.filterNode.disconnect();
|
|
1019
|
+
if (note.modulationDepth) {
|
|
1020
|
+
note.volumeDepth.disconnect();
|
|
1021
|
+
note.modulationDepth.disconnect();
|
|
1022
|
+
note.modulationLFO.stop();
|
|
1023
|
+
}
|
|
1024
|
+
if (note.vibratoDepth) {
|
|
1025
|
+
note.vibratoDepth.disconnect();
|
|
1026
|
+
note.vibratoLFO.stop();
|
|
1027
|
+
}
|
|
1028
|
+
resolve();
|
|
1029
|
+
};
|
|
1030
|
+
note.bufferSource.stop(endTime);
|
|
1031
|
+
});
|
|
948
1032
|
}
|
|
949
|
-
scheduleNoteRelease(channelNumber, noteNumber, _velocity, stopTime,
|
|
1033
|
+
scheduleNoteRelease(channelNumber, noteNumber, _velocity, stopTime, portamentoNoteNumber, force) {
|
|
950
1034
|
const channel = this.channels[channelNumber];
|
|
951
|
-
if (
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1035
|
+
if (!force) {
|
|
1036
|
+
if (channel.sustainPedal)
|
|
1037
|
+
return;
|
|
1038
|
+
if (channel.sostenutoNotes.has(noteNumber))
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
955
1041
|
if (!channel.scheduledNotes.has(noteNumber))
|
|
956
1042
|
return;
|
|
957
1043
|
const scheduledNotes = channel.scheduledNotes.get(noteNumber);
|
|
@@ -961,43 +1047,28 @@ export class MidyGM2 {
|
|
|
961
1047
|
continue;
|
|
962
1048
|
if (note.ending)
|
|
963
1049
|
continue;
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
.
|
|
967
|
-
.
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
.
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
note.filterNode.disconnect();
|
|
982
|
-
if (note.volumeDepth)
|
|
983
|
-
note.volumeDepth.disconnect();
|
|
984
|
-
if (note.modulationDepth)
|
|
985
|
-
note.modulationDepth.disconnect();
|
|
986
|
-
if (note.modulationLFO)
|
|
987
|
-
note.modulationLFO.stop();
|
|
988
|
-
if (note.vibratoDepth)
|
|
989
|
-
note.vibratoDepth.disconnect();
|
|
990
|
-
if (note.vibratoLFO)
|
|
991
|
-
note.vibratoLFO.stop();
|
|
992
|
-
resolve();
|
|
993
|
-
};
|
|
994
|
-
note.bufferSource.stop(volEndTime);
|
|
995
|
-
});
|
|
1050
|
+
if (portamentoNoteNumber === undefined) {
|
|
1051
|
+
const volEndTime = stopTime + note.instrumentKey.volRelease;
|
|
1052
|
+
const modRelease = stopTime + note.instrumentKey.modRelease;
|
|
1053
|
+
note.filterNode.frequency
|
|
1054
|
+
.cancelScheduledValues(stopTime)
|
|
1055
|
+
.linearRampToValueAtTime(0, modRelease);
|
|
1056
|
+
return this.stopNote(stopTime, volEndTime, scheduledNotes, i);
|
|
1057
|
+
}
|
|
1058
|
+
else {
|
|
1059
|
+
const portamentoTime = stopTime + channel.portamentoTime;
|
|
1060
|
+
const detuneChange = (portamentoNoteNumber - noteNumber) * 100;
|
|
1061
|
+
const detune = note.bufferSource.detune.value + detuneChange;
|
|
1062
|
+
note.bufferSource.detune
|
|
1063
|
+
.cancelScheduledValues(stopTime)
|
|
1064
|
+
.linearRampToValueAtTime(detune, portamentoTime);
|
|
1065
|
+
return this.stopNote(stopTime, portamentoTime, scheduledNotes, i);
|
|
1066
|
+
}
|
|
996
1067
|
}
|
|
997
1068
|
}
|
|
998
|
-
releaseNote(channelNumber, noteNumber, velocity) {
|
|
1069
|
+
releaseNote(channelNumber, noteNumber, velocity, portamentoNoteNumber) {
|
|
999
1070
|
const now = this.audioContext.currentTime;
|
|
1000
|
-
return this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now);
|
|
1071
|
+
return this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, portamentoNoteNumber, false);
|
|
1001
1072
|
}
|
|
1002
1073
|
releaseSustainPedal(channelNumber, halfVelocity) {
|
|
1003
1074
|
const velocity = halfVelocity * 2;
|
|
@@ -1081,58 +1152,41 @@ export class MidyGM2 {
|
|
|
1081
1152
|
channel.pitchBendRange * 100;
|
|
1082
1153
|
this.updateDetune(channel, detuneChange);
|
|
1083
1154
|
}
|
|
1155
|
+
createControlChangeHandlers() {
|
|
1156
|
+
return {
|
|
1157
|
+
0: this.setBankMSB,
|
|
1158
|
+
1: this.setModulationDepth,
|
|
1159
|
+
5: this.setPortamentoTime,
|
|
1160
|
+
6: this.dataEntryMSB,
|
|
1161
|
+
7: this.setVolume,
|
|
1162
|
+
10: this.setPan,
|
|
1163
|
+
11: this.setExpression,
|
|
1164
|
+
32: this.setBankLSB,
|
|
1165
|
+
38: this.dataEntryLSB,
|
|
1166
|
+
64: this.setSustainPedal,
|
|
1167
|
+
65: this.setPortamento,
|
|
1168
|
+
66: this.setSostenutoPedal,
|
|
1169
|
+
67: this.setSoftPedal,
|
|
1170
|
+
91: this.setReverbSendLevel,
|
|
1171
|
+
93: this.setChorusSendLevel,
|
|
1172
|
+
100: this.setRPNLSB,
|
|
1173
|
+
101: this.setRPNMSB,
|
|
1174
|
+
120: this.allSoundOff,
|
|
1175
|
+
121: this.resetAllControllers,
|
|
1176
|
+
123: this.allNotesOff,
|
|
1177
|
+
124: this.omniOff,
|
|
1178
|
+
125: this.omniOn,
|
|
1179
|
+
126: this.monoOn,
|
|
1180
|
+
127: this.polyOn,
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1084
1183
|
handleControlChange(channelNumber, controller, value) {
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
return this.setPortamentoTime(channelNumber, value);
|
|
1092
|
-
case 6:
|
|
1093
|
-
return this.dataEntryMSB(channelNumber, value);
|
|
1094
|
-
case 7:
|
|
1095
|
-
return this.setVolume(channelNumber, value);
|
|
1096
|
-
case 10:
|
|
1097
|
-
return this.setPan(channelNumber, value);
|
|
1098
|
-
case 11:
|
|
1099
|
-
return this.setExpression(channelNumber, value);
|
|
1100
|
-
case 32:
|
|
1101
|
-
return this.setBankLSB(channelNumber, value);
|
|
1102
|
-
case 38:
|
|
1103
|
-
return this.dataEntryLSB(channelNumber, value);
|
|
1104
|
-
case 64:
|
|
1105
|
-
return this.setSustainPedal(channelNumber, value);
|
|
1106
|
-
case 65:
|
|
1107
|
-
return this.setPortamento(channelNumber, value);
|
|
1108
|
-
case 66:
|
|
1109
|
-
return this.setSostenutoPedal(channelNumber, value);
|
|
1110
|
-
case 67:
|
|
1111
|
-
return this.setSoftPedal(channelNumber, value);
|
|
1112
|
-
case 91:
|
|
1113
|
-
return this.setReverbSendLevel(channelNumber, value);
|
|
1114
|
-
case 93:
|
|
1115
|
-
return this.setChorusSendLevel(channelNumber, value);
|
|
1116
|
-
case 100:
|
|
1117
|
-
return this.setRPNLSB(channelNumber, value);
|
|
1118
|
-
case 101:
|
|
1119
|
-
return this.setRPNMSB(channelNumber, value);
|
|
1120
|
-
case 120:
|
|
1121
|
-
return this.allSoundOff(channelNumber);
|
|
1122
|
-
case 121:
|
|
1123
|
-
return this.resetAllControllers(channelNumber);
|
|
1124
|
-
case 123:
|
|
1125
|
-
return this.allNotesOff(channelNumber);
|
|
1126
|
-
case 124:
|
|
1127
|
-
return this.omniOff();
|
|
1128
|
-
case 125:
|
|
1129
|
-
return this.omniOn();
|
|
1130
|
-
case 126:
|
|
1131
|
-
return this.monoOn();
|
|
1132
|
-
case 127:
|
|
1133
|
-
return this.polyOn();
|
|
1134
|
-
default:
|
|
1135
|
-
console.warn(`Unsupported Control change: controller=${controller} value=${value}`);
|
|
1184
|
+
const handler = this.controlChangeHandlers[controller];
|
|
1185
|
+
if (handler) {
|
|
1186
|
+
handler.call(this, channelNumber, value);
|
|
1187
|
+
}
|
|
1188
|
+
else {
|
|
1189
|
+
console.warn(`Unsupported Control change: controller=${controller} value=${value}`);
|
|
1136
1190
|
}
|
|
1137
1191
|
}
|
|
1138
1192
|
setBankMSB(channelNumber, msb) {
|
|
@@ -1162,12 +1216,14 @@ export class MidyGM2 {
|
|
|
1162
1216
|
this.updateModulation(channel);
|
|
1163
1217
|
}
|
|
1164
1218
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
1165
|
-
this.channels[channelNumber]
|
|
1219
|
+
const channel = this.channels[channelNumber];
|
|
1220
|
+
const factor = 5 * Math.log(10) / 127;
|
|
1221
|
+
channel.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1166
1222
|
}
|
|
1167
1223
|
setVolume(channelNumber, volume) {
|
|
1168
1224
|
const channel = this.channels[channelNumber];
|
|
1169
1225
|
channel.volume = volume / 127;
|
|
1170
|
-
this.
|
|
1226
|
+
this.updateChannelVolume(channel);
|
|
1171
1227
|
}
|
|
1172
1228
|
panToGain(pan) {
|
|
1173
1229
|
const theta = Math.PI / 2 * Math.max(0, pan - 1) / 126;
|
|
@@ -1179,12 +1235,12 @@ export class MidyGM2 {
|
|
|
1179
1235
|
setPan(channelNumber, pan) {
|
|
1180
1236
|
const channel = this.channels[channelNumber];
|
|
1181
1237
|
channel.pan = pan;
|
|
1182
|
-
this.
|
|
1238
|
+
this.updateChannelVolume(channel);
|
|
1183
1239
|
}
|
|
1184
1240
|
setExpression(channelNumber, expression) {
|
|
1185
1241
|
const channel = this.channels[channelNumber];
|
|
1186
1242
|
channel.expression = expression / 127;
|
|
1187
|
-
this.
|
|
1243
|
+
this.updateChannelVolume(channel);
|
|
1188
1244
|
}
|
|
1189
1245
|
setBankLSB(channelNumber, lsb) {
|
|
1190
1246
|
this.channels[channelNumber].bankLSB = lsb;
|
|
@@ -1193,7 +1249,7 @@ export class MidyGM2 {
|
|
|
1193
1249
|
this.channels[channelNumber].dataLSB = value;
|
|
1194
1250
|
this.handleRPN(channelNumber);
|
|
1195
1251
|
}
|
|
1196
|
-
|
|
1252
|
+
updateChannelVolume(channel) {
|
|
1197
1253
|
const now = this.audioContext.currentTime;
|
|
1198
1254
|
const volume = channel.volume * channel.expression;
|
|
1199
1255
|
const { gainLeft, gainRight } = this.panToGain(channel.pan);
|
|
@@ -1211,34 +1267,55 @@ export class MidyGM2 {
|
|
|
1211
1267
|
this.releaseSustainPedal(channelNumber, value);
|
|
1212
1268
|
}
|
|
1213
1269
|
}
|
|
1214
|
-
// TODO
|
|
1215
1270
|
setPortamento(channelNumber, value) {
|
|
1216
1271
|
this.channels[channelNumber].portamento = value >= 64;
|
|
1217
1272
|
}
|
|
1218
1273
|
setReverbSendLevel(channelNumber, reverbSendLevel) {
|
|
1219
1274
|
const channel = this.channels[channelNumber];
|
|
1220
1275
|
const reverbEffect = this.reverbEffect;
|
|
1221
|
-
if (0 < reverbSendLevel) {
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1276
|
+
if (0 < channel.reverbSendLevel) {
|
|
1277
|
+
if (0 < reverbSendLevel) {
|
|
1278
|
+
const now = this.audioContext.currentTime;
|
|
1279
|
+
channel.reverbSendLevel = reverbSendLevel / 127;
|
|
1280
|
+
reverbEffect.output.gain.cancelScheduledValues(now);
|
|
1281
|
+
reverbEffect.output.gain.setValueAtTime(channel.reverbSendLevel, now);
|
|
1282
|
+
}
|
|
1283
|
+
else {
|
|
1284
|
+
channel.merger.disconnect(reverbEffect.input);
|
|
1285
|
+
}
|
|
1226
1286
|
}
|
|
1227
|
-
else
|
|
1228
|
-
|
|
1287
|
+
else {
|
|
1288
|
+
if (0 < reverbSendLevel) {
|
|
1289
|
+
channel.merger.connect(reverbEffect.input);
|
|
1290
|
+
const now = this.audioContext.currentTime;
|
|
1291
|
+
channel.reverbSendLevel = reverbSendLevel / 127;
|
|
1292
|
+
reverbEffect.output.gain.cancelScheduledValues(now);
|
|
1293
|
+
reverbEffect.output.gain.setValueAtTime(channel.reverbSendLevel, now);
|
|
1294
|
+
}
|
|
1229
1295
|
}
|
|
1230
1296
|
}
|
|
1231
1297
|
setChorusSendLevel(channelNumber, chorusSendLevel) {
|
|
1232
1298
|
const channel = this.channels[channelNumber];
|
|
1233
1299
|
const chorusEffect = this.chorusEffect;
|
|
1234
|
-
if (0 < chorusSendLevel) {
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1300
|
+
if (0 < channel.chorusSendLevel) {
|
|
1301
|
+
if (0 < chorusSendLevel) {
|
|
1302
|
+
const now = this.audioContext.currentTime;
|
|
1303
|
+
channel.chorusSendLevel = chorusSendLevel / 127;
|
|
1304
|
+
chorusEffect.output.gain.cancelScheduledValues(now);
|
|
1305
|
+
chorusEffect.output.gain.setValueAtTime(channel.chorusSendLevel, now);
|
|
1306
|
+
}
|
|
1307
|
+
else {
|
|
1308
|
+
channel.merger.disconnect(chorusEffect.input);
|
|
1309
|
+
}
|
|
1239
1310
|
}
|
|
1240
|
-
else
|
|
1241
|
-
|
|
1311
|
+
else {
|
|
1312
|
+
if (0 < chorusSendLevel) {
|
|
1313
|
+
channel.merger.connect(chorusEffect.input);
|
|
1314
|
+
const now = this.audioContext.currentTime;
|
|
1315
|
+
channel.chorusSendLevel = chorusSendLevel / 127;
|
|
1316
|
+
chorusEffect.output.gain.cancelScheduledValues(now);
|
|
1317
|
+
chorusEffect.output.gain.setValueAtTime(channel.chorusSendLevel, now);
|
|
1318
|
+
}
|
|
1242
1319
|
}
|
|
1243
1320
|
}
|
|
1244
1321
|
setSostenutoPedal(channelNumber, value) {
|
|
@@ -1423,20 +1500,22 @@ export class MidyGM2 {
|
|
|
1423
1500
|
}
|
|
1424
1501
|
}
|
|
1425
1502
|
GM1SystemOn() {
|
|
1426
|
-
this.channels.
|
|
1503
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
1504
|
+
const channel = this.channels[i];
|
|
1427
1505
|
channel.bankMSB = 0;
|
|
1428
1506
|
channel.bankLSB = 0;
|
|
1429
1507
|
channel.bank = 0;
|
|
1430
|
-
}
|
|
1508
|
+
}
|
|
1431
1509
|
this.channels[9].bankMSB = 1;
|
|
1432
1510
|
this.channels[9].bank = 128;
|
|
1433
1511
|
}
|
|
1434
1512
|
GM2SystemOn() {
|
|
1435
|
-
this.channels.
|
|
1513
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
1514
|
+
const channel = this.channels[i];
|
|
1436
1515
|
channel.bankMSB = 121;
|
|
1437
1516
|
channel.bankLSB = 0;
|
|
1438
1517
|
channel.bank = 121 * 128;
|
|
1439
|
-
}
|
|
1518
|
+
}
|
|
1440
1519
|
this.channels[9].bankMSB = 120;
|
|
1441
1520
|
this.channels[9].bank = 120 * 128;
|
|
1442
1521
|
}
|
|
@@ -1745,7 +1824,7 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
1745
1824
|
currentBufferSource: null,
|
|
1746
1825
|
volume: 100 / 127,
|
|
1747
1826
|
pan: 64,
|
|
1748
|
-
portamentoTime:
|
|
1827
|
+
portamentoTime: 1, // sec
|
|
1749
1828
|
reverbSendLevel: 0,
|
|
1750
1829
|
chorusSendLevel: 0,
|
|
1751
1830
|
vibratoRate: 1,
|