@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.
Files changed (35) hide show
  1. package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/{soundfont-parser@0.0.4 → soundfont-parser@0.0.6}/+esm.d.ts +16 -20
  2. package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.d.ts.map +1 -0
  3. package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.js +180 -0
  4. package/esm/midy-GM1.d.ts +36 -8
  5. package/esm/midy-GM1.d.ts.map +1 -1
  6. package/esm/midy-GM1.js +87 -89
  7. package/esm/midy-GM2.d.ts +65 -10
  8. package/esm/midy-GM2.d.ts.map +1 -1
  9. package/esm/midy-GM2.js +233 -154
  10. package/esm/midy-GMLite.d.ts +36 -8
  11. package/esm/midy-GMLite.d.ts.map +1 -1
  12. package/esm/midy-GMLite.js +87 -89
  13. package/esm/midy.d.ts +85 -10
  14. package/esm/midy.d.ts.map +1 -1
  15. package/esm/midy.js +254 -174
  16. package/package.json +1 -1
  17. package/script/deps/cdn.jsdelivr.net/npm/@marmooo/{soundfont-parser@0.0.4 → soundfont-parser@0.0.6}/+esm.d.ts +16 -20
  18. package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.d.ts.map +1 -0
  19. package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.6/+esm.js +190 -0
  20. package/script/midy-GM1.d.ts +36 -8
  21. package/script/midy-GM1.d.ts.map +1 -1
  22. package/script/midy-GM1.js +87 -89
  23. package/script/midy-GM2.d.ts +65 -10
  24. package/script/midy-GM2.d.ts.map +1 -1
  25. package/script/midy-GM2.js +233 -154
  26. package/script/midy-GMLite.d.ts +36 -8
  27. package/script/midy-GMLite.d.ts.map +1 -1
  28. package/script/midy-GMLite.js +87 -89
  29. package/script/midy.d.ts +85 -10
  30. package/script/midy.d.ts.map +1 -1
  31. package/script/midy.js +254 -174
  32. package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.4/+esm.d.ts.map +0 -1
  33. package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.4/+esm.js +0 -162
  34. package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.4/+esm.d.ts.map +0 -1
  35. 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.4/+esm.js");
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.forEach((presetHeader) => {
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 = new Uint8Array(instrumentKey.sample.length);
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(0, sampleEnd);
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
- channelData.set(floatSample);
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
- convertToFloat32Array(uint8Array) {
348
- const int16Array = new Int16Array(uint8Array.buffer);
349
- const float32Array = new Float32Array(int16Array.length);
350
- for (let i = 0; i < int16Array.length; i++) {
351
- float32Array[i] = int16Array[i] / 32768;
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 float32Array;
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 notePromise = this.scheduleNoteRelease(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
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.forEach((track) => {
486
+ for (let i = 0; i < midi.tracks.length; i++) {
487
+ const track = midi.tracks[i];
473
488
  let currentTicks = 0;
474
- track.forEach((event) => {
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] || 2) - (priority[b.type] || 2);
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, stopPedal) {
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, stopPedal);
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, stopPedal) {
587
+ stopNotes(velocity, force) {
569
588
  for (let i = 0; i < this.channels.length; i++) {
570
- this.stopChannelNotes(i, velocity, stopPedal);
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
- this.setVolumeEnvelope(channel, note);
900
- this.setFilterEnvelope(channel, note);
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, instrumentKey.start / instrumentKey.sampleRate);
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
- scheduleNoteRelease(channelNumber, noteNumber, _velocity, stopTime, stopPedal = false) {
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 (stopPedal && channel.sustainPedal)
961
- return;
962
- if (stopPedal && channel.sostenutoNotes.has(noteNumber))
963
- return;
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
- const volEndTime = stopTime +
974
- note.instrumentKey.volRelease * channel.releaseTime;
975
- note.volumeNode.gain
976
- .cancelScheduledValues(stopTime)
977
- .linearRampToValueAtTime(0, volEndTime);
978
- const modRelease = stopTime + note.instrumentKey.modRelease;
979
- note.filterNode.frequency
980
- .cancelScheduledValues(stopTime)
981
- .linearRampToValueAtTime(0, modRelease);
982
- note.ending = true;
983
- this.scheduleTask(() => {
984
- note.bufferSource.loop = false;
985
- }, stopTime);
986
- return new Promise((resolve) => {
987
- note.bufferSource.onended = () => {
988
- scheduledNotes[i] = null;
989
- note.bufferSource.disconnect();
990
- note.volumeNode.disconnect();
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
- switch (controller) {
1113
- case 0:
1114
- return this.setBankMSB(channelNumber, value);
1115
- case 1:
1116
- return this.setModulationDepth(channelNumber, value);
1117
- case 5:
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].portamentoTime = portamentoTime / 127;
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.updateChannelGain(channel);
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.updateChannelGain(channel);
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.updateChannelGain(channel);
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
- updateChannelGain(channel) {
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
- const now = this.audioContext.currentTime;
1270
- channel.reverbSendLevel = reverbSendLevel / 127;
1271
- reverbEffect.output.gain.cancelScheduledValues(now);
1272
- reverbEffect.output.gain.setValueAtTime(channel.reverbSendLevel, now);
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 if (channel.reverbSendLevel !== 0) {
1275
- channel.merger.disconnect(reverbEffect.input);
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
- const now = this.audioContext.currentTime;
1283
- channel.chorusSendLevel = chorusSendLevel / 127;
1284
- chorusEffect.output.gain.cancelScheduledValues(now);
1285
- chorusEffect.output.gain.setValueAtTime(channel.chorusSendLevel, now);
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 if (channel.chorusSendLevel !== 0) {
1288
- channel.merger.disconnect(chorusEffect.input);
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.forEach((channel) => {
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.forEach((channel) => {
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: 0,
1956
+ portamentoTime: 1, // sec
1877
1957
  filterResonance: 1,
1878
1958
  releaseTime: 1,
1879
1959
  attackTime: 1,