@marmooo/midy 0.2.3 → 0.2.5

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.
@@ -3,6 +3,58 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MidyGM2 = void 0;
4
4
  const midi_file_1 = require("midi-file");
5
5
  const soundfont_parser_1 = require("@marmooo/soundfont-parser");
6
+ // 2-3 times faster than Map
7
+ class SparseMap {
8
+ constructor(size) {
9
+ this.data = new Array(size);
10
+ this.activeIndices = [];
11
+ }
12
+ set(key, value) {
13
+ if (this.data[key] === undefined) {
14
+ this.activeIndices.push(key);
15
+ }
16
+ this.data[key] = value;
17
+ }
18
+ get(key) {
19
+ return this.data[key];
20
+ }
21
+ delete(key) {
22
+ if (this.data[key] !== undefined) {
23
+ this.data[key] = undefined;
24
+ const index = this.activeIndices.indexOf(key);
25
+ if (index !== -1) {
26
+ this.activeIndices.splice(index, 1);
27
+ }
28
+ return true;
29
+ }
30
+ return false;
31
+ }
32
+ has(key) {
33
+ return this.data[key] !== undefined;
34
+ }
35
+ get size() {
36
+ return this.activeIndices.length;
37
+ }
38
+ clear() {
39
+ for (let i = 0; i < this.activeIndices.length; i++) {
40
+ const key = this.activeIndices[i];
41
+ this.data[key] = undefined;
42
+ }
43
+ this.activeIndices = [];
44
+ }
45
+ *[Symbol.iterator]() {
46
+ for (let i = 0; i < this.activeIndices.length; i++) {
47
+ const key = this.activeIndices[i];
48
+ yield [key, this.data[key]];
49
+ }
50
+ }
51
+ forEach(callback) {
52
+ for (let i = 0; i < this.activeIndices.length; i++) {
53
+ const key = this.activeIndices[i];
54
+ callback(this.data[key], key, this);
55
+ }
56
+ }
57
+ }
6
58
  class Note {
7
59
  constructor(noteNumber, velocity, startTime, voice, voiceParams) {
8
60
  Object.defineProperty(this, "bufferSource", {
@@ -100,7 +152,6 @@ class Note {
100
152
  const defaultControllerState = {
101
153
  noteOnVelocity: { type: 2, defaultValue: 0 },
102
154
  noteOnKeyNumber: { type: 3, defaultValue: 0 },
103
- polyPressure: { type: 10, defaultValue: 0 },
104
155
  channelPressure: { type: 13, defaultValue: 0 },
105
156
  pitchWheel: { type: 14, defaultValue: 8192 / 16383 },
106
157
  pitchWheelSensitivity: { type: 16, defaultValue: 2 / 128 },
@@ -284,6 +335,18 @@ class MidyGM2 {
284
335
  writable: true,
285
336
  value: this.initSoundFontTable()
286
337
  });
338
+ Object.defineProperty(this, "audioBufferCounter", {
339
+ enumerable: true,
340
+ configurable: true,
341
+ writable: true,
342
+ value: new Map()
343
+ });
344
+ Object.defineProperty(this, "audioBufferCache", {
345
+ enumerable: true,
346
+ configurable: true,
347
+ writable: true,
348
+ value: new Map()
349
+ });
287
350
  Object.defineProperty(this, "isPlaying", {
288
351
  enumerable: true,
289
352
  configurable: true,
@@ -336,7 +399,7 @@ class MidyGM2 {
336
399
  enumerable: true,
337
400
  configurable: true,
338
401
  writable: true,
339
- value: new Map()
402
+ value: new SparseMap(128)
340
403
  });
341
404
  Object.defineProperty(this, "defaultOptions", {
342
405
  enumerable: true,
@@ -376,7 +439,7 @@ class MidyGM2 {
376
439
  initSoundFontTable() {
377
440
  const table = new Array(128);
378
441
  for (let i = 0; i < 128; i++) {
379
- table[i] = new Map();
442
+ table[i] = new SparseMap(128);
380
443
  }
381
444
  return table;
382
445
  }
@@ -430,11 +493,8 @@ class MidyGM2 {
430
493
  state: new ControllerState(),
431
494
  controlTable: this.initControlTable(),
432
495
  ...this.setChannelAudioNodes(audioContext),
433
- scheduledNotes: new Map(),
434
- sostenutoNotes: new Map(),
435
- channelPressure: {
436
- ...this.constructor.controllerDestinationSettings,
437
- },
496
+ scheduledNotes: new SparseMap(128),
497
+ sostenutoNotes: new SparseMap(128),
438
498
  };
439
499
  });
440
500
  return channels;
@@ -468,9 +528,8 @@ class MidyGM2 {
468
528
  return audioBuffer;
469
529
  }
470
530
  }
471
- async createNoteBufferNode(voiceParams, isSF3) {
531
+ createNoteBufferNode(audioBuffer, voiceParams) {
472
532
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
473
- const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
474
533
  bufferSource.buffer = audioBuffer;
475
534
  bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
476
535
  if (bufferSource.loop) {
@@ -559,6 +618,7 @@ class MidyGM2 {
559
618
  await Promise.all(this.notePromises);
560
619
  this.notePromises = [];
561
620
  this.exclusiveClassMap.clear();
621
+ this.audioBufferCache.clear();
562
622
  resolve();
563
623
  return;
564
624
  }
@@ -574,8 +634,9 @@ class MidyGM2 {
574
634
  }
575
635
  else if (this.isStopping) {
576
636
  await this.stopNotes(0, true);
577
- this.exclusiveClassMap.clear();
578
637
  this.notePromises = [];
638
+ this.exclusiveClassMap.clear();
639
+ this.audioBufferCache.clear();
579
640
  resolve();
580
641
  this.isStopping = false;
581
642
  this.isPaused = false;
@@ -606,6 +667,9 @@ class MidyGM2 {
606
667
  secondToTicks(second, secondsPerBeat) {
607
668
  return second * this.ticksPerBeat / secondsPerBeat;
608
669
  }
670
+ getAudioBufferId(programNumber, noteNumber, velocity) {
671
+ return `${programNumber}:${noteNumber}:${velocity}`;
672
+ }
609
673
  extractMidiData(midi) {
610
674
  const instruments = new Set();
611
675
  const timeline = [];
@@ -627,6 +691,8 @@ class MidyGM2 {
627
691
  switch (event.type) {
628
692
  case "noteOn": {
629
693
  const channel = tmpChannels[event.channel];
694
+ const audioBufferId = this.getAudioBufferId(channel.programNumber, event.noteNumber, event.velocity);
695
+ this.audioBufferCounter.set(audioBufferId, (this.audioBufferCounter.get(audioBufferId) ?? 0) + 1);
630
696
  if (channel.programNumber < 0) {
631
697
  channel.programNumber = event.programNumber;
632
698
  switch (channel.bankMSB) {
@@ -676,6 +742,10 @@ class MidyGM2 {
676
742
  timeline.push(event);
677
743
  }
678
744
  }
745
+ for (const [audioBufferId, count] of this.audioBufferCounter) {
746
+ if (count === 1)
747
+ this.audioBufferCounter.delete(audioBufferId);
748
+ }
679
749
  const priority = {
680
750
  controller: 0,
681
751
  sysEx: 1,
@@ -769,7 +839,7 @@ class MidyGM2 {
769
839
  return this.resumeTime + now - this.startTime - this.startDelay;
770
840
  }
771
841
  getActiveNotes(channel, time) {
772
- const activeNotes = new Map();
842
+ const activeNotes = new SparseMap(128);
773
843
  channel.scheduledNotes.forEach((noteList) => {
774
844
  const activeNote = this.getActiveNote(noteList, time);
775
845
  if (activeNote) {
@@ -936,28 +1006,31 @@ class MidyGM2 {
936
1006
  const pitchWheel = channel.state.pitchWheel * 2 - 1;
937
1007
  const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
938
1008
  const pitch = pitchWheel * pitchWheelSensitivity;
939
- const pressureDepth = (channel.pressureTable[0] - 64) / 37.5; // 2400 / 64;
1009
+ const pressureDepth = (channel.channelPressureTable[0] - 64) / 37.5; // 2400 / 64;
940
1010
  const pressure = pressureDepth * channel.state.channelPressure;
941
1011
  return tuning + pitch + pressure;
942
1012
  }
943
1013
  calcNoteDetune(channel, note) {
944
1014
  return channel.scaleOctaveTuningTable[note.noteNumber % 12];
945
1015
  }
946
- updateDetune(channel) {
947
- const now = this.audioContext.currentTime;
1016
+ updateChannelDetune(channel) {
948
1017
  channel.scheduledNotes.forEach((noteList) => {
949
1018
  for (let i = 0; i < noteList.length; i++) {
950
1019
  const note = noteList[i];
951
1020
  if (!note)
952
1021
  continue;
953
- const noteDetune = this.calcNoteDetune(channel, note);
954
- const detune = channel.detune + noteDetune;
955
- note.bufferSource.detune
956
- .cancelScheduledValues(now)
957
- .setValueAtTime(detune, now);
1022
+ this.updateDetune(channel, note, 0);
958
1023
  }
959
1024
  });
960
1025
  }
1026
+ updateDetune(channel, note, pressure) {
1027
+ const now = this.audioContext.currentTime;
1028
+ const noteDetune = this.calcNoteDetune(channel, note);
1029
+ const detune = channel.detune + noteDetune + pressure;
1030
+ note.bufferSource.detune
1031
+ .cancelScheduledValues(now)
1032
+ .setValueAtTime(detune, now);
1033
+ }
961
1034
  getPortamentoTime(channel) {
962
1035
  const factor = 5 * Math.log(10) / 127;
963
1036
  const time = channel.state.portamentoTime;
@@ -975,14 +1048,11 @@ class MidyGM2 {
975
1048
  .setValueAtTime(0, volDelay)
976
1049
  .linearRampToValueAtTime(sustainVolume, portamentoTime);
977
1050
  }
978
- setVolumeEnvelope(channel, note) {
1051
+ setVolumeEnvelope(note, pressure) {
979
1052
  const now = this.audioContext.currentTime;
980
- const state = channel.state;
981
1053
  const { voiceParams, startTime } = note;
982
- const pressureDepth = channel.pressureTable[2] / 64;
983
- const pressure = 1 + pressureDepth * channel.state.channelPressure;
984
1054
  const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
985
- pressure;
1055
+ (1 + pressure);
986
1056
  const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
987
1057
  const volDelay = startTime + voiceParams.volDelay;
988
1058
  const volAttack = volDelay + voiceParams.volAttack;
@@ -1030,10 +1100,8 @@ class MidyGM2 {
1030
1100
  const { voiceParams, noteNumber, startTime } = note;
1031
1101
  const softPedalFactor = 1 -
1032
1102
  (0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
1033
- const pressureDepth = (channel.pressureTable[1] - 64) * 15;
1034
- const pressure = pressureDepth * channel.state.channelPressure;
1035
- const baseCent = voiceParams.initialFilterFc + pressure;
1036
- const baseFreq = this.centToHz(baseCent) * softPedalFactor;
1103
+ const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
1104
+ softPedalFactor;
1037
1105
  const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor;
1038
1106
  const sustainFreq = baseFreq +
1039
1107
  (peekFreq - baseFreq) * (1 - voiceParams.modSustain);
@@ -1047,15 +1115,16 @@ class MidyGM2 {
1047
1115
  .setValueAtTime(adjustedBaseFreq, modDelay)
1048
1116
  .linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
1049
1117
  }
1050
- setFilterEnvelope(channel, note) {
1118
+ setFilterEnvelope(channel, note, pressure) {
1051
1119
  const now = this.audioContext.currentTime;
1052
1120
  const state = channel.state;
1053
1121
  const { voiceParams, noteNumber, startTime } = note;
1054
1122
  const softPedalFactor = 1 -
1055
1123
  (0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
1056
- const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
1124
+ const baseCent = voiceParams.initialFilterFc + pressure;
1125
+ const baseFreq = this.centToHz(baseCent) * softPedalFactor;
1126
+ const peekFreq = this.centToHz(baseCent + voiceParams.modEnvToFilterFc) *
1057
1127
  softPedalFactor;
1058
- const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor;
1059
1128
  const sustainFreq = baseFreq +
1060
1129
  (peekFreq - baseFreq) * (1 - voiceParams.modSustain);
1061
1130
  const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
@@ -1082,9 +1151,9 @@ class MidyGM2 {
1082
1151
  gain: voiceParams.modLfoToFilterFc,
1083
1152
  });
1084
1153
  note.modulationDepth = new GainNode(this.audioContext);
1085
- this.setModLfoToPitch(channel, note);
1154
+ this.setModLfoToPitch(channel, note, 0);
1086
1155
  note.volumeDepth = new GainNode(this.audioContext);
1087
- this.setModLfoToVolume(channel, note);
1156
+ this.setModLfoToVolume(note, 0);
1088
1157
  note.modulationLFO.start(startTime + voiceParams.delayModLFO);
1089
1158
  note.modulationLFO.connect(note.filterDepth);
1090
1159
  note.filterDepth.connect(note.filterNode.frequency);
@@ -1097,8 +1166,7 @@ class MidyGM2 {
1097
1166
  const { voiceParams } = note;
1098
1167
  const state = channel.state;
1099
1168
  note.vibratoLFO = new OscillatorNode(this.audioContext, {
1100
- frequency: this.centToHz(voiceParams.freqVibLFO) *
1101
- state.vibratoRate,
1169
+ frequency: this.centToHz(voiceParams.freqVibLFO) * state.vibratoRate * 2,
1102
1170
  });
1103
1171
  note.vibratoLFO.start(startTime + voiceParams.delayVibLFO * state.vibratoDelay * 2);
1104
1172
  note.vibratoDepth = new GainNode(this.audioContext);
@@ -1106,12 +1174,31 @@ class MidyGM2 {
1106
1174
  note.vibratoLFO.connect(note.vibratoDepth);
1107
1175
  note.vibratoDepth.connect(note.bufferSource.detune);
1108
1176
  }
1177
+ async getAudioBuffer(program, noteNumber, velocity, voiceParams, isSF3) {
1178
+ const audioBufferId = this.getAudioBufferId(program, noteNumber, velocity);
1179
+ const cache = this.audioBufferCache.get(audioBufferId);
1180
+ if (cache) {
1181
+ cache.counter += 1;
1182
+ if (cache.maxCount <= cache.counter) {
1183
+ this.audioBufferCache.delete(audioBufferId);
1184
+ }
1185
+ return cache.audioBuffer;
1186
+ }
1187
+ else {
1188
+ const maxCount = this.audioBufferCounter.get(audioBufferId) ?? 0;
1189
+ const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
1190
+ const cache = { audioBuffer, maxCount, counter: 1 };
1191
+ this.audioBufferCache.set(audioBufferId, cache);
1192
+ return audioBuffer;
1193
+ }
1194
+ }
1109
1195
  async createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3) {
1110
1196
  const state = channel.state;
1111
1197
  const controllerState = this.getControllerState(channel, noteNumber, velocity);
1112
1198
  const voiceParams = voice.getAllParams(controllerState);
1113
1199
  const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
1114
- note.bufferSource = await this.createNoteBufferNode(voiceParams, isSF3);
1200
+ const audioBuffer = await this.getAudioBuffer(channel.program, noteNumber, velocity, voiceParams, isSF3);
1201
+ note.bufferSource = this.createNoteBufferNode(audioBuffer, voiceParams);
1115
1202
  note.volumeNode = new GainNode(this.audioContext);
1116
1203
  note.gainL = new GainNode(this.audioContext);
1117
1204
  note.gainR = new GainNode(this.audioContext);
@@ -1127,8 +1214,8 @@ class MidyGM2 {
1127
1214
  }
1128
1215
  else {
1129
1216
  note.portamento = false;
1130
- this.setVolumeEnvelope(channel, note);
1131
- this.setFilterEnvelope(channel, note);
1217
+ this.setVolumeEnvelope(note, 0);
1218
+ this.setFilterEnvelope(channel, note, 0);
1132
1219
  }
1133
1220
  if (0 < state.vibratoDepth) {
1134
1221
  this.startVibrato(channel, note, startTime);
@@ -1171,10 +1258,10 @@ class MidyGM2 {
1171
1258
  if (soundFontIndex === undefined)
1172
1259
  return;
1173
1260
  const soundFont = this.soundFonts[soundFontIndex];
1174
- const isSF3 = soundFont.parsed.info.version.major === 3;
1175
1261
  const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
1176
1262
  if (!voice)
1177
1263
  return;
1264
+ const isSF3 = soundFont.parsed.info.version.major === 3;
1178
1265
  const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
1179
1266
  note.gainL.connect(channel.gainL);
1180
1267
  note.gainR.connect(channel.gainR);
@@ -1343,47 +1430,21 @@ class MidyGM2 {
1343
1430
  channel.program = program;
1344
1431
  }
1345
1432
  handleChannelPressure(channelNumber, value) {
1433
+ const now = this.audioContext.currentTime;
1346
1434
  const channel = this.channels[channelNumber];
1347
1435
  const prev = channel.state.channelPressure;
1348
1436
  const next = value / 127;
1349
1437
  channel.state.channelPressure = next;
1350
- if (channel.pressureTable[0] !== 64) {
1351
- const pressureDepth = (channel.pressureTable[0] - 64) / 37.5; // 2400 / 64;
1438
+ if (channel.channelPressureTable[0] !== 64) {
1439
+ const pressureDepth = (channel.channelPressureTable[0] - 64) / 37.5; // 2400 / 64;
1352
1440
  channel.detune += pressureDepth * (next - prev);
1353
1441
  }
1354
- const table = channel.pressureTable;
1355
- channel.scheduledNotes.forEach((noteList) => {
1356
- for (let i = 0; i < noteList.length; i++) {
1357
- const note = noteList[i];
1358
- if (!note)
1359
- continue;
1360
- this.applyDestinationSettings(channel, note, table);
1361
- }
1442
+ const table = channel.channelPressureTable;
1443
+ this.getActiveNotes(channel, now).forEach((note) => {
1444
+ this.applyDestinationSettings(channel, note, table);
1362
1445
  });
1363
1446
  // this.applyVoiceParams(channel, 13);
1364
1447
  }
1365
- setChannelPressure(channel, note) {
1366
- if (channel.pressureTable[0] !== 64) {
1367
- this.updateDetune(channel);
1368
- }
1369
- if (!note.portamento) {
1370
- if (channel.pressureTable[1] !== 64) {
1371
- this.setFilterEnvelope(channel, note);
1372
- }
1373
- if (channel.pressureTable[2] !== 64) {
1374
- this.setVolumeEnvelope(channel, note);
1375
- }
1376
- }
1377
- if (channel.pressureTable[3] !== 0) {
1378
- this.setModLfoToPitch(channel, note);
1379
- }
1380
- if (channel.pressureTable[4] !== 0) {
1381
- this.setModLfoToFilterFc(channel, note);
1382
- }
1383
- if (channel.pressureTable[5] !== 0) {
1384
- this.setModLfoToVolume(channel, note);
1385
- }
1386
- }
1387
1448
  handlePitchBendMessage(channelNumber, lsb, msb) {
1388
1449
  const pitchBend = msb * 128 + lsb;
1389
1450
  this.setPitchBend(channelNumber, pitchBend);
@@ -1395,16 +1456,13 @@ class MidyGM2 {
1395
1456
  const next = (value - 8192) / 8192;
1396
1457
  state.pitchWheel = value / 16383;
1397
1458
  channel.detune += (next - prev) * state.pitchWheelSensitivity * 12800;
1398
- this.updateDetune(channel);
1459
+ this.updateChannelDetune(channel);
1399
1460
  this.applyVoiceParams(channel, 14);
1400
1461
  }
1401
- setModLfoToPitch(channel, note) {
1462
+ setModLfoToPitch(channel, note, pressure) {
1402
1463
  const now = this.audioContext.currentTime;
1403
- const pressureDepth = channel.pressureTable[3] / 127 * 600;
1404
- const pressure = pressureDepth * channel.state.channelPressure;
1405
1464
  const modLfoToPitch = note.voiceParams.modLfoToPitch + pressure;
1406
- const baseDepth = Math.abs(modLfoToPitch) +
1407
- channel.state.modulationDepth;
1465
+ const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
1408
1466
  const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
1409
1467
  note.modulationDepth.gain
1410
1468
  .cancelScheduledValues(now)
@@ -1420,22 +1478,18 @@ class MidyGM2 {
1420
1478
  .cancelScheduledValues(now)
1421
1479
  .setValueAtTime(vibratoDepth * vibratoDepthSign, now);
1422
1480
  }
1423
- setModLfoToFilterFc(channel, note) {
1481
+ setModLfoToFilterFc(note, pressure) {
1424
1482
  const now = this.audioContext.currentTime;
1425
- const pressureDepth = channel.pressureTable[4] / 127 * 2400;
1426
- const pressure = pressureDepth * channel.state.channelPressure;
1427
1483
  const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc + pressure;
1428
1484
  note.filterDepth.gain
1429
1485
  .cancelScheduledValues(now)
1430
1486
  .setValueAtTime(modLfoToFilterFc, now);
1431
1487
  }
1432
- setModLfoToVolume(channel, note) {
1488
+ setModLfoToVolume(note, pressure) {
1433
1489
  const now = this.audioContext.currentTime;
1434
1490
  const modLfoToVolume = note.voiceParams.modLfoToVolume;
1435
1491
  const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
1436
- const pressureDepth = channel.pressureTable[5] / 127;
1437
- const pressure = 1 + pressureDepth * channel.state.channelPressure;
1438
- const volumeDepth = baseDepth * Math.sign(modLfoToVolume) * pressure;
1492
+ const volumeDepth = baseDepth * Math.sign(modLfoToVolume) * (1 + pressure);
1439
1493
  note.volumeDepth.gain
1440
1494
  .cancelScheduledValues(now)
1441
1495
  .setValueAtTime(volumeDepth, now);
@@ -1508,11 +1562,18 @@ class MidyGM2 {
1508
1562
  .cancelScheduledValues(now)
1509
1563
  .setValueAtTime(freqModLFO, now);
1510
1564
  }
1565
+ setFreqVibLFO(channel, note) {
1566
+ const now = this.audioContext.currentTime;
1567
+ const freqVibLFO = note.voiceParams.freqVibLFO;
1568
+ note.vibratoLFO.frequency
1569
+ .cancelScheduledValues(now)
1570
+ .setValueAtTime(freqVibLFO * channel.state.vibratoRate * 2, now);
1571
+ }
1511
1572
  createVoiceParamsHandlers() {
1512
1573
  return {
1513
1574
  modLfoToPitch: (channel, note, _prevValue) => {
1514
1575
  if (0 < channel.state.modulationDepth) {
1515
- this.setModLfoToPitch(channel, note);
1576
+ this.setModLfoToPitch(channel, note, 0);
1516
1577
  }
1517
1578
  },
1518
1579
  vibLfoToPitch: (channel, note, _prevValue) => {
@@ -1522,12 +1583,12 @@ class MidyGM2 {
1522
1583
  },
1523
1584
  modLfoToFilterFc: (channel, note, _prevValue) => {
1524
1585
  if (0 < channel.state.modulationDepth) {
1525
- this.setModLfoToFilterFc(channel, note);
1586
+ this.setModLfoToFilterFc(note, 0);
1526
1587
  }
1527
1588
  },
1528
1589
  modLfoToVolume: (channel, note, _prevValue) => {
1529
1590
  if (0 < channel.state.modulationDepth) {
1530
- this.setModLfoToVolume(channel, note);
1591
+ this.setModLfoToVolume(note, 0);
1531
1592
  }
1532
1593
  },
1533
1594
  chorusEffectsSend: (channel, note, prevValue) => {
@@ -1553,11 +1614,7 @@ class MidyGM2 {
1553
1614
  },
1554
1615
  freqVibLFO: (channel, note, _prevValue) => {
1555
1616
  if (0 < channel.state.vibratoDepth) {
1556
- const now = this.audioContext.currentTime;
1557
- const freqVibLFO = note.voiceParams.freqVibLFO;
1558
- note.vibratoLFO.frequency
1559
- .cancelScheduledValues(now)
1560
- .setValueAtTime(freqVibLFO * channel.state.vibratoRate, now);
1617
+ this.setFreqVibLFO(channel, note);
1561
1618
  }
1562
1619
  },
1563
1620
  };
@@ -1601,7 +1658,7 @@ class MidyGM2 {
1601
1658
  this.setPortamentoStartFilterEnvelope(channel, note);
1602
1659
  }
1603
1660
  else {
1604
- this.setFilterEnvelope(channel, note);
1661
+ this.setFilterEnvelope(channel, note, 0);
1605
1662
  }
1606
1663
  this.setPitchEnvelope(note);
1607
1664
  }
@@ -1615,7 +1672,7 @@ class MidyGM2 {
1615
1672
  if (key in voiceParams)
1616
1673
  noteVoiceParams[key] = voiceParams[key];
1617
1674
  }
1618
- this.setVolumeEnvelope(channel, note);
1675
+ this.setVolumeEnvelope(note, 0);
1619
1676
  }
1620
1677
  }
1621
1678
  }
@@ -1654,7 +1711,7 @@ class MidyGM2 {
1654
1711
  if (handler) {
1655
1712
  handler.call(this, channelNumber, value);
1656
1713
  const channel = this.channels[channelNumber];
1657
- this.applyVoiceParams(channel, controller + 128);
1714
+ this.applyVoiceParams(channel, controllerType + 128);
1658
1715
  this.applyControlTable(channel, controllerType);
1659
1716
  }
1660
1717
  else {
@@ -1785,8 +1842,7 @@ class MidyGM2 {
1785
1842
  channel.state.sostenutoPedal = value / 127;
1786
1843
  if (64 <= value) {
1787
1844
  const now = this.audioContext.currentTime;
1788
- const activeNotes = this.getActiveNotes(channel, now);
1789
- channel.sostenutoNotes = new Map(activeNotes);
1845
+ channel.sostenutoNotes = this.getActiveNotes(channel, now);
1790
1846
  }
1791
1847
  else {
1792
1848
  this.releaseSostenutoPedal(channelNumber, value);
@@ -1947,7 +2003,7 @@ class MidyGM2 {
1947
2003
  const next = value / 128;
1948
2004
  state.pitchWheelSensitivity = next;
1949
2005
  channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
1950
- this.updateDetune(channel);
2006
+ this.updateChannelDetune(channel);
1951
2007
  this.applyVoiceParams(channel, 16);
1952
2008
  }
1953
2009
  handleFineTuningRPN(channelNumber) {
@@ -1962,7 +2018,7 @@ class MidyGM2 {
1962
2018
  const next = (value - 8192) / 8.192; // cent
1963
2019
  channel.fineTuning = next;
1964
2020
  channel.detune += next - prev;
1965
- this.updateDetune(channel);
2021
+ this.updateChannelDetune(channel);
1966
2022
  }
1967
2023
  handleCoarseTuningRPN(channelNumber) {
1968
2024
  const channel = this.channels[channelNumber];
@@ -1976,7 +2032,7 @@ class MidyGM2 {
1976
2032
  const next = (value - 64) * 100; // cent
1977
2033
  channel.coarseTuning = next;
1978
2034
  channel.detune += next - prev;
1979
- this.updateDetune(channel);
2035
+ this.updateChannelDetune(channel);
1980
2036
  }
1981
2037
  handleModulationDepthRangeRPN(channelNumber) {
1982
2038
  const channel = this.channels[channelNumber];
@@ -2007,7 +2063,7 @@ class MidyGM2 {
2007
2063
  const state = channel.state;
2008
2064
  for (let i = 0; i < stateTypes.length; i++) {
2009
2065
  const type = stateTypes[i];
2010
- state[type] = defaultControllerState[type];
2066
+ state[type] = defaultControllerState[type].defaultValue;
2011
2067
  }
2012
2068
  const settingTypes = [
2013
2069
  "rpnMSB",
@@ -2039,7 +2095,7 @@ class MidyGM2 {
2039
2095
  switch (data[3]) {
2040
2096
  case 8:
2041
2097
  // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
2042
- return this.handleScaleOctaveTuning1ByteFormatSysEx(data);
2098
+ return this.handleScaleOctaveTuning1ByteFormatSysEx(data, false);
2043
2099
  default:
2044
2100
  console.warn(`Unsupported Exclusive Message: ${data}`);
2045
2101
  }
@@ -2101,7 +2157,7 @@ class MidyGM2 {
2101
2157
  case 9:
2102
2158
  switch (data[3]) {
2103
2159
  case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
2104
- return this.handleChannelPressureSysEx(data);
2160
+ return this.handlePressureSysEx(data, "channelPressureTable");
2105
2161
  case 3: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
2106
2162
  return this.handleControlChangeSysEx(data);
2107
2163
  default:
@@ -2143,7 +2199,7 @@ class MidyGM2 {
2143
2199
  const next = (value - 8192) / 8.192; // cent
2144
2200
  this.masterFineTuning = next;
2145
2201
  channel.detune += next - prev;
2146
- this.updateDetune(channel);
2202
+ this.updateChannelDetune(channel);
2147
2203
  }
2148
2204
  handleMasterCoarseTuningSysEx(data) {
2149
2205
  const coarseTuning = data[4];
@@ -2154,7 +2210,7 @@ class MidyGM2 {
2154
2210
  const next = (value - 64) * 100; // cent
2155
2211
  this.masterCoarseTuning = next;
2156
2212
  channel.detune += next - prev;
2157
- this.updateDetune(channel);
2213
+ this.updateChannelDetune(channel);
2158
2214
  }
2159
2215
  handleGlobalParameterControlSysEx(data) {
2160
2216
  if (data[7] === 1) {
@@ -2361,8 +2417,8 @@ class MidyGM2 {
2361
2417
  }
2362
2418
  return bitmap;
2363
2419
  }
2364
- handleScaleOctaveTuning1ByteFormatSysEx(data) {
2365
- if (data.length < 18) {
2420
+ handleScaleOctaveTuning1ByteFormatSysEx(data, realtime) {
2421
+ if (data.length < 19) {
2366
2422
  console.error("Data length is too short");
2367
2423
  return;
2368
2424
  }
@@ -2370,37 +2426,55 @@ class MidyGM2 {
2370
2426
  for (let i = 0; i < channelBitmap.length; i++) {
2371
2427
  if (!channelBitmap[i])
2372
2428
  continue;
2429
+ const channel = this.channels[i];
2373
2430
  for (let j = 0; j < 12; j++) {
2374
- const value = data[j + 7] - 64; // cent
2375
- this.channels[i].scaleOctaveTuningTable[j] = value;
2431
+ const centValue = data[j + 7] - 64;
2432
+ channel.scaleOctaveTuningTable[j] = centValue;
2376
2433
  }
2434
+ if (realtime)
2435
+ this.updateChannelDetune(channel);
2377
2436
  }
2378
2437
  }
2379
2438
  applyDestinationSettings(channel, note, table) {
2380
2439
  if (table[0] !== 64) {
2381
- this.updateDetune(channel);
2440
+ this.updateDetune(channel, note, 0);
2382
2441
  }
2383
2442
  if (!note.portamento) {
2384
2443
  if (table[1] !== 64) {
2385
- this.setFilterEnvelope(channel, note);
2444
+ const channelPressure = channel.channelPressureTable[1] *
2445
+ channel.state.channelPressure;
2446
+ const pressure = (channelPressure - 64) * 15;
2447
+ this.setFilterEnvelope(channel, note, pressure);
2386
2448
  }
2387
2449
  if (table[2] !== 64) {
2388
- this.setVolumeEnvelope(channel, note);
2450
+ const channelPressure = channel.channelPressureTable[2] *
2451
+ channel.state.channelPressure;
2452
+ const pressure = channelPressure / 64;
2453
+ this.setVolumeEnvelope(note, pressure);
2389
2454
  }
2390
2455
  }
2391
2456
  if (table[3] !== 0) {
2392
- this.setModLfoToPitch(channel, note);
2457
+ const channelPressure = channel.channelPressureTable[3] *
2458
+ channel.state.channelPressure;
2459
+ const pressure = channelPressure / 127 * 600;
2460
+ this.setModLfoToPitch(channel, note, pressure);
2393
2461
  }
2394
2462
  if (table[4] !== 0) {
2395
- this.setModLfoToFilterFc(channel, note);
2463
+ const channelPressure = channel.channelPressureTable[4] *
2464
+ channel.state.channelPressure;
2465
+ const pressure = channelPressure / 127 * 2400;
2466
+ this.setModLfoToFilterFc(note, pressure);
2396
2467
  }
2397
2468
  if (table[5] !== 0) {
2398
- this.setModLfoToVolume(channel, note);
2469
+ const channelPressure = channel.channelPressureTable[5] *
2470
+ channel.state.channelPressure;
2471
+ const pressure = channelPressure / 127;
2472
+ this.setModLfoToVolume(note, pressure);
2399
2473
  }
2400
2474
  }
2401
- handleChannelPressureSysEx(data) {
2475
+ handleChannelPressureSysEx(data, tableName) {
2402
2476
  const channelNumber = data[4];
2403
- const table = this.channels[channelNumber].pressureTable;
2477
+ const table = this.channels[channelNumber][tableName];
2404
2478
  for (let i = 5; i < data.length - 1; i += 2) {
2405
2479
  const pp = data[i];
2406
2480
  const rr = data[i + 1];
@@ -2491,8 +2565,8 @@ Object.defineProperty(MidyGM2, "channelSettings", {
2491
2565
  value: {
2492
2566
  currentBufferSource: null,
2493
2567
  detune: 0,
2494
- scaleOctaveTuningTable: new Array(12).fill(0), // cent
2495
- pressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
2568
+ scaleOctaveTuningTable: new Int8Array(12), // [-64, 63] cent
2569
+ channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
2496
2570
  keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
2497
2571
  program: 0,
2498
2572
  bank: 121 * 128,
@@ -2507,16 +2581,3 @@ Object.defineProperty(MidyGM2, "channelSettings", {
2507
2581
  modulationDepthRange: 50, // cent
2508
2582
  }
2509
2583
  });
2510
- Object.defineProperty(MidyGM2, "controllerDestinationSettings", {
2511
- enumerable: true,
2512
- configurable: true,
2513
- writable: true,
2514
- value: {
2515
- pitchControl: 0,
2516
- filterCutoffControl: 0,
2517
- amplitudeControl: 1,
2518
- lfoPitchDepth: 0,
2519
- lfoFilterDepth: 0,
2520
- lfoAmplitudeDepth: 0,
2521
- }
2522
- });