@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/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.4/+esm.js";
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.forEach((presetHeader) => {
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 = new Uint8Array(instrumentKey.sample.length);
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(0, sampleEnd);
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
- channelData.set(floatSample);
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
- convertToFloat32Array(uint8Array) {
342
- const int16Array = new Int16Array(uint8Array.buffer);
343
- const float32Array = new Float32Array(int16Array.length);
344
- for (let i = 0; i < int16Array.length; i++) {
345
- float32Array[i] = int16Array[i] / 32768;
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 float32Array;
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 notePromise = this.scheduleNoteRelease(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
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.forEach((track) => {
477
+ for (let i = 0; i < midi.tracks.length; i++) {
478
+ const track = midi.tracks[i];
464
479
  let currentTicks = 0;
465
- track.forEach((event) => {
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] || 2) - (priority[b.type] || 2);
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, stopPedal) {
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, stopPedal);
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, stopPedal) {
578
+ stopNotes(velocity, force) {
560
579
  for (let i = 0; i < this.channels.length; i++) {
561
- this.stopChannelNotes(i, velocity, stopPedal);
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 * channel.filterResonance, // dB
937
+ Q: instrumentKey.initialFilterQ / 10, // dB
889
938
  });
890
- this.setVolumeEnvelope(note);
891
- this.setFilterEnvelope(channel, note);
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, instrumentKey.start / instrumentKey.sampleRate);
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, stopPedal = false) {
1033
+ scheduleNoteRelease(channelNumber, noteNumber, _velocity, stopTime, portamentoNoteNumber, force) {
950
1034
  const channel = this.channels[channelNumber];
951
- if (stopPedal && channel.sustainPedal)
952
- return;
953
- if (stopPedal && channel.sostenutoNotes.has(noteNumber))
954
- return;
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
- const volEndTime = stopTime + note.instrumentKey.volRelease;
965
- note.volumeNode.gain
966
- .cancelScheduledValues(stopTime)
967
- .linearRampToValueAtTime(0, volEndTime);
968
- const modRelease = stopTime + note.instrumentKey.modRelease;
969
- note.filterNode.frequency
970
- .cancelScheduledValues(stopTime)
971
- .linearRampToValueAtTime(0, modRelease);
972
- note.ending = true;
973
- this.scheduleTask(() => {
974
- note.bufferSource.loop = false;
975
- }, stopTime);
976
- return new Promise((resolve) => {
977
- note.bufferSource.onended = () => {
978
- scheduledNotes[i] = null;
979
- note.bufferSource.disconnect();
980
- note.volumeNode.disconnect();
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
- switch (controller) {
1086
- case 0:
1087
- return this.setBankMSB(channelNumber, value);
1088
- case 1:
1089
- return this.setModulationDepth(channelNumber, value);
1090
- case 5:
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].portamentoTime = portamentoTime / 127;
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.updateChannelGain(channel);
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.updateChannelGain(channel);
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.updateChannelGain(channel);
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
- updateChannelGain(channel) {
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
- const now = this.audioContext.currentTime;
1223
- channel.reverbSendLevel = reverbSendLevel / 127;
1224
- reverbEffect.output.gain.cancelScheduledValues(now);
1225
- reverbEffect.output.gain.setValueAtTime(channel.reverbSendLevel, now);
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 if (channel.reverbSendLevel !== 0) {
1228
- channel.merger.disconnect(reverbEffect.input);
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
- const now = this.audioContext.currentTime;
1236
- channel.chorusSendLevel = chorusSendLevel / 127;
1237
- chorusEffect.output.gain.cancelScheduledValues(now);
1238
- chorusEffect.output.gain.setValueAtTime(channel.chorusSendLevel, now);
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 if (channel.chorusSendLevel !== 0) {
1241
- channel.merger.disconnect(chorusEffect.input);
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.forEach((channel) => {
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.forEach((channel) => {
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: 0,
1827
+ portamentoTime: 1, // sec
1749
1828
  reverbSendLevel: 0,
1750
1829
  chorusSendLevel: 0,
1751
1830
  vibratoRate: 1,