@marmooo/midy 0.2.9 → 0.3.1

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/script/midy.js CHANGED
@@ -160,6 +160,39 @@ class Note {
160
160
  this.voiceParams = voiceParams;
161
161
  }
162
162
  }
163
+ const drumExclusiveClassesByKit = new Array(57);
164
+ const drumExclusiveClassCount = 10;
165
+ const standardSet = new Uint8Array(128);
166
+ standardSet[42] = 1;
167
+ standardSet[44] = 1;
168
+ standardSet[46] = 1; // HH
169
+ standardSet[71] = 2;
170
+ standardSet[72] = 2; // Whistle
171
+ standardSet[73] = 3;
172
+ standardSet[74] = 3; // Guiro
173
+ standardSet[78] = 4;
174
+ standardSet[79] = 4; // Cuica
175
+ standardSet[80] = 5;
176
+ standardSet[81] = 5; // Triangle
177
+ standardSet[29] = 6;
178
+ standardSet[30] = 6; // Scratch
179
+ standardSet[86] = 7;
180
+ standardSet[87] = 7; // Surdo
181
+ drumExclusiveClassesByKit[0] = standardSet;
182
+ const analogSet = new Uint8Array(128);
183
+ analogSet[42] = 8;
184
+ analogSet[44] = 8;
185
+ analogSet[46] = 8; // CHH
186
+ drumExclusiveClassesByKit[25] = analogSet;
187
+ const orchestraSet = new Uint8Array(128);
188
+ orchestraSet[27] = 9;
189
+ orchestraSet[28] = 9;
190
+ orchestraSet[29] = 9; // HH
191
+ drumExclusiveClassesByKit[48] = orchestraSet;
192
+ const sfxSet = new Uint8Array(128);
193
+ sfxSet[41] = 10;
194
+ sfxSet[42] = 10; // Scratch
195
+ drumExclusiveClassesByKit[56] = sfxSet;
163
196
  // normalized to 0-1 for use with the SF2 modulator model
164
197
  const defaultControllerState = {
165
198
  noteOnVelocity: { type: 2, defaultValue: 0 },
@@ -255,18 +288,6 @@ class Midy {
255
288
  writable: true,
256
289
  value: "GM2"
257
290
  });
258
- Object.defineProperty(this, "ticksPerBeat", {
259
- enumerable: true,
260
- configurable: true,
261
- writable: true,
262
- value: 120
263
- });
264
- Object.defineProperty(this, "totalTime", {
265
- enumerable: true,
266
- configurable: true,
267
- writable: true,
268
- value: 0
269
- });
270
291
  Object.defineProperty(this, "masterFineTuning", {
271
292
  enumerable: true,
272
293
  configurable: true,
@@ -300,6 +321,24 @@ class Midy {
300
321
  delayTimes: this.generateDistributedArray(0.02, 2, 0.5),
301
322
  }
302
323
  });
324
+ Object.defineProperty(this, "numChannels", {
325
+ enumerable: true,
326
+ configurable: true,
327
+ writable: true,
328
+ value: 16
329
+ });
330
+ Object.defineProperty(this, "ticksPerBeat", {
331
+ enumerable: true,
332
+ configurable: true,
333
+ writable: true,
334
+ value: 120
335
+ });
336
+ Object.defineProperty(this, "totalTime", {
337
+ enumerable: true,
338
+ configurable: true,
339
+ writable: true,
340
+ value: 0
341
+ });
303
342
  Object.defineProperty(this, "noteCheckInterval", {
304
343
  enumerable: true,
305
344
  configurable: true,
@@ -402,11 +441,17 @@ class Midy {
402
441
  writable: true,
403
442
  value: []
404
443
  });
405
- Object.defineProperty(this, "exclusiveClassMap", {
444
+ Object.defineProperty(this, "exclusiveClassNotes", {
406
445
  enumerable: true,
407
446
  configurable: true,
408
447
  writable: true,
409
- value: new SparseMap(128)
448
+ value: new Array(128)
449
+ });
450
+ Object.defineProperty(this, "drumExclusiveClassNotes", {
451
+ enumerable: true,
452
+ configurable: true,
453
+ writable: true,
454
+ value: new Array(this.numChannels * drumExclusiveClassCount)
410
455
  });
411
456
  Object.defineProperty(this, "defaultOptions", {
412
457
  enumerable: true,
@@ -500,8 +545,10 @@ class Midy {
500
545
  };
501
546
  }
502
547
  createChannels(audioContext) {
503
- const channels = Array.from({ length: 16 }, () => {
548
+ const channels = Array.from({ length: this.numChannels }, () => {
504
549
  return {
550
+ currentBufferSource: null,
551
+ isDrum: false,
505
552
  ...this.constructor.channelSettings,
506
553
  state: new ControllerState(),
507
554
  controlTable: this.initControlTable(),
@@ -546,24 +593,10 @@ class Midy {
546
593
  return audioBuffer;
547
594
  }
548
595
  }
549
- calcLoopMode(channel, note, voiceParams) {
550
- if (channel.isDrum) {
551
- const noteNumber = note.noteNumber;
552
- if (noteNumber === 88 || 47 <= noteNumber && noteNumber <= 84) {
553
- return true;
554
- }
555
- else {
556
- return false;
557
- }
558
- }
559
- else {
560
- return voiceParams.sampleModes % 2 !== 0;
561
- }
562
- }
563
- createBufferSource(channel, note, voiceParams, audioBuffer) {
596
+ createBufferSource(voiceParams, audioBuffer) {
564
597
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
565
598
  bufferSource.buffer = audioBuffer;
566
- bufferSource.loop = this.calcLoopMode(channel, note, voiceParams);
599
+ bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
567
600
  if (bufferSource.loop) {
568
601
  bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
569
602
  bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
@@ -596,7 +629,7 @@ class Midy {
596
629
  const startTime = event.startTime + this.startDelay - offset;
597
630
  switch (event.type) {
598
631
  case "noteOn":
599
- if (event.velocity !== 0) {
632
+ if (0 < event.velocity) {
600
633
  await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, event.portamento);
601
634
  break;
602
635
  }
@@ -654,7 +687,8 @@ class Midy {
654
687
  if (queueIndex >= this.timeline.length) {
655
688
  await Promise.all(this.notePromises);
656
689
  this.notePromises = [];
657
- this.exclusiveClassMap.clear();
690
+ this.exclusiveClassNotes.fill(undefined);
691
+ this.drumExclusiveClassNotes.fill(undefined);
658
692
  this.audioBufferCache.clear();
659
693
  resolve();
660
694
  return;
@@ -673,7 +707,8 @@ class Midy {
673
707
  else if (this.isStopping) {
674
708
  await this.stopNotes(0, true, now);
675
709
  this.notePromises = [];
676
- this.exclusiveClassMap.clear();
710
+ this.exclusiveClassNotes.fill(undefined);
711
+ this.drumExclusiveClassNotes.fill(undefined);
677
712
  this.audioBufferCache.clear();
678
713
  resolve();
679
714
  this.isStopping = false;
@@ -682,7 +717,8 @@ class Midy {
682
717
  }
683
718
  else if (this.isSeeking) {
684
719
  this.stopNotes(0, true, now);
685
- this.exclusiveClassMap.clear();
720
+ this.exclusiveClassNotes.fill(undefined);
721
+ this.drumExclusiveClassNotes.fill(undefined);
686
722
  this.startTime = this.audioContext.currentTime;
687
723
  queueIndex = this.getQueueIndex(this.resumeTime);
688
724
  offset = this.resumeTime - this.startTime;
@@ -710,7 +746,7 @@ class Midy {
710
746
  extractMidiData(midi) {
711
747
  const instruments = new Set();
712
748
  const timeline = [];
713
- const tmpChannels = new Array(16);
749
+ const tmpChannels = new Array(this.channels.length);
714
750
  for (let i = 0; i < tmpChannels.length; i++) {
715
751
  tmpChannels[i] = {
716
752
  programNumber: -1,
@@ -809,6 +845,17 @@ class Midy {
809
845
  }
810
846
  return { instruments, timeline };
811
847
  }
848
+ stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
849
+ const channel = this.channels[channelNumber];
850
+ const promises = [];
851
+ const activeNotes = this.getActiveNotes(channel, scheduleTime);
852
+ activeNotes.forEach((note) => {
853
+ const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
854
+ this.notePromises.push(promise);
855
+ promises.push(promise);
856
+ });
857
+ return Promise.all(promises);
858
+ }
812
859
  stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
813
860
  const channel = this.channels[channelNumber];
814
861
  const promises = [];
@@ -838,6 +885,9 @@ class Midy {
838
885
  if (!this.isPlaying)
839
886
  return;
840
887
  this.isStopping = true;
888
+ for (let i = 0; i < this.channels.length; i++) {
889
+ this.resetAllStates(i);
890
+ }
841
891
  }
842
892
  pause() {
843
893
  if (!this.isPlaying || this.isPaused)
@@ -877,6 +927,8 @@ class Midy {
877
927
  const note = noteList[i];
878
928
  if (!note)
879
929
  continue;
930
+ if (note.ending)
931
+ continue;
880
932
  callback(note);
881
933
  }
882
934
  });
@@ -1212,8 +1264,8 @@ class Midy {
1212
1264
  note.vibratoLFO.connect(note.vibratoDepth);
1213
1265
  note.vibratoDepth.connect(note.bufferSource.detune);
1214
1266
  }
1215
- async getAudioBuffer(program, noteNumber, velocity, voiceParams, isSF3) {
1216
- const audioBufferId = this.getAudioBufferId(program, noteNumber, velocity);
1267
+ async getAudioBuffer(programNumber, noteNumber, velocity, voiceParams, isSF3) {
1268
+ const audioBufferId = this.getAudioBufferId(programNumber, noteNumber, velocity);
1217
1269
  const cache = this.audioBufferCache.get(audioBufferId);
1218
1270
  if (cache) {
1219
1271
  cache.counter += 1;
@@ -1236,8 +1288,8 @@ class Midy {
1236
1288
  const controllerState = this.getControllerState(channel, noteNumber, velocity);
1237
1289
  const voiceParams = voice.getAllParams(controllerState);
1238
1290
  const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
1239
- const audioBuffer = await this.getAudioBuffer(channel.program, noteNumber, velocity, voiceParams, isSF3);
1240
- note.bufferSource = this.createBufferSource(channel, note, voiceParams, audioBuffer);
1291
+ const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
1292
+ note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
1241
1293
  note.volumeNode = new GainNode(this.audioContext);
1242
1294
  note.gainL = new GainNode(this.audioContext);
1243
1295
  note.gainR = new GainNode(this.audioContext);
@@ -1246,7 +1298,7 @@ class Midy {
1246
1298
  type: "lowpass",
1247
1299
  Q: voiceParams.initialFilterQ / 5 * state.filterResonance, // dB
1248
1300
  });
1249
- if (portamento) {
1301
+ if (0.5 <= state.portamento && portamento) {
1250
1302
  note.portamento = true;
1251
1303
  this.setPortamentoStartVolumeEnvelope(channel, note, now);
1252
1304
  this.setPortamentoStartFilterEnvelope(channel, note, now);
@@ -1297,14 +1349,56 @@ class Midy {
1297
1349
  return channel.bank;
1298
1350
  }
1299
1351
  }
1352
+ handleExclusiveClass(note, channelNumber, startTime) {
1353
+ const exclusiveClass = note.voiceParams.exclusiveClass;
1354
+ if (exclusiveClass === 0)
1355
+ return;
1356
+ const prev = this.exclusiveClassNotes[exclusiveClass];
1357
+ if (prev) {
1358
+ const [prevNote, prevChannelNumber] = prev;
1359
+ if (prevNote && !prevNote.ending) {
1360
+ this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
1361
+ startTime, true, // force
1362
+ undefined);
1363
+ }
1364
+ }
1365
+ this.exclusiveClassNotes[exclusiveClass] = [note, channelNumber];
1366
+ }
1367
+ handleDrumExclusiveClass(note, channelNumber, startTime) {
1368
+ const channel = this.channels[channelNumber];
1369
+ if (!channel.isDrum)
1370
+ return;
1371
+ const kitTable = drumExclusiveClassesByKit[channel.programNumber];
1372
+ if (!kitTable)
1373
+ return;
1374
+ const drumExclusiveClass = kitTable[note.noteNumber];
1375
+ if (drumExclusiveClass === 0)
1376
+ return;
1377
+ const index = (drumExclusiveClass - 1) * this.channels.length +
1378
+ channelNumber;
1379
+ const prevNote = this.drumExclusiveClassNotes[index];
1380
+ if (prevNote && !prevNote.ending) {
1381
+ this.scheduleNoteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
1382
+ startTime, true, // force
1383
+ undefined);
1384
+ }
1385
+ this.drumExclusiveClassNotes[index] = note;
1386
+ }
1387
+ isDrumNoteOffException(channel, noteNumber) {
1388
+ if (!channel.isDrum)
1389
+ return false;
1390
+ const programNumber = channel.programNumber;
1391
+ return !((programNumber === 48 && noteNumber === 88) ||
1392
+ (programNumber === 56 && 47 <= noteNumber && noteNumber <= 84));
1393
+ }
1300
1394
  async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, portamento) {
1301
1395
  const channel = this.channels[channelNumber];
1302
1396
  const bankNumber = this.calcBank(channel, channelNumber);
1303
- const soundFontIndex = this.soundFontTable[channel.program].get(bankNumber);
1397
+ const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
1304
1398
  if (soundFontIndex === undefined)
1305
1399
  return;
1306
1400
  const soundFont = this.soundFonts[soundFontIndex];
1307
- const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
1401
+ const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
1308
1402
  if (!voice)
1309
1403
  return;
1310
1404
  const isSF3 = soundFont.parsed.info.version.major === 3;
@@ -1314,33 +1408,60 @@ class Midy {
1314
1408
  if (0.5 <= channel.state.sustainPedal) {
1315
1409
  channel.sustainNotes.push(note);
1316
1410
  }
1317
- const exclusiveClass = note.voiceParams.exclusiveClass;
1318
- if (exclusiveClass !== 0) {
1319
- if (this.exclusiveClassMap.has(exclusiveClass)) {
1320
- const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
1321
- const [prevNote, prevChannelNumber] = prevEntry;
1322
- if (prevNote && !prevNote.ending) {
1323
- this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
1324
- startTime, true, // force
1325
- undefined);
1326
- }
1327
- }
1328
- this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
1329
- }
1411
+ this.handleExclusiveClass(note, channelNumber, startTime);
1412
+ this.handleDrumExclusiveClass(note, channelNumber, startTime);
1330
1413
  const scheduledNotes = channel.scheduledNotes;
1331
- if (scheduledNotes.has(noteNumber)) {
1332
- scheduledNotes.get(noteNumber).push(note);
1414
+ let noteList = scheduledNotes.get(noteNumber);
1415
+ if (noteList) {
1416
+ noteList.push(note);
1333
1417
  }
1334
1418
  else {
1335
- scheduledNotes.set(noteNumber, [note]);
1419
+ noteList = [note];
1420
+ scheduledNotes.set(noteNumber, noteList);
1421
+ }
1422
+ if (this.isDrumNoteOffException(channel, noteNumber)) {
1423
+ const stopTime = startTime + note.bufferSource.buffer.duration;
1424
+ const index = noteList.length - 1;
1425
+ const promise = new Promise((resolve) => {
1426
+ note.bufferSource.onended = () => {
1427
+ noteList[index] = undefined;
1428
+ this.disconnectNote(note);
1429
+ resolve();
1430
+ };
1431
+ note.bufferSource.stop(stopTime);
1432
+ });
1433
+ this.notePromises.push(promise);
1336
1434
  }
1337
1435
  }
1338
1436
  noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
1339
1437
  scheduleTime ??= this.audioContext.currentTime;
1340
1438
  return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, false);
1341
1439
  }
1342
- stopNote(endTime, stopTime, scheduledNotes, index) {
1343
- const note = scheduledNotes[index];
1440
+ disconnectNote(note) {
1441
+ note.bufferSource.disconnect();
1442
+ note.filterNode.disconnect();
1443
+ note.volumeEnvelopeNode.disconnect();
1444
+ note.volumeNode.disconnect();
1445
+ note.gainL.disconnect();
1446
+ note.gainR.disconnect();
1447
+ if (note.modulationDepth) {
1448
+ note.volumeDepth.disconnect();
1449
+ note.modulationDepth.disconnect();
1450
+ note.modulationLFO.stop();
1451
+ }
1452
+ if (note.vibratoDepth) {
1453
+ note.vibratoDepth.disconnect();
1454
+ note.vibratoLFO.stop();
1455
+ }
1456
+ if (note.reverbEffectsSend) {
1457
+ note.reverbEffectsSend.disconnect();
1458
+ }
1459
+ if (note.chorusEffectsSend) {
1460
+ note.chorusEffectsSend.disconnect();
1461
+ }
1462
+ }
1463
+ stopNote(endTime, stopTime, noteList, index) {
1464
+ const note = noteList[index];
1344
1465
  note.volumeEnvelopeNode.gain
1345
1466
  .cancelScheduledValues(endTime)
1346
1467
  .linearRampToValueAtTime(0, stopTime);
@@ -1350,35 +1471,27 @@ class Midy {
1350
1471
  }, stopTime);
1351
1472
  return new Promise((resolve) => {
1352
1473
  note.bufferSource.onended = () => {
1353
- scheduledNotes[index] = null;
1354
- note.bufferSource.disconnect();
1355
- note.filterNode.disconnect();
1356
- note.volumeEnvelopeNode.disconnect();
1357
- note.volumeNode.disconnect();
1358
- note.gainL.disconnect();
1359
- note.gainR.disconnect();
1360
- if (note.modulationDepth) {
1361
- note.volumeDepth.disconnect();
1362
- note.modulationDepth.disconnect();
1363
- note.modulationLFO.stop();
1364
- }
1365
- if (note.vibratoDepth) {
1366
- note.vibratoDepth.disconnect();
1367
- note.vibratoLFO.stop();
1368
- }
1369
- if (note.reverbEffectsSend) {
1370
- note.reverbEffectsSend.disconnect();
1371
- }
1372
- if (note.chorusEffectsSend) {
1373
- note.chorusEffectsSend.disconnect();
1374
- }
1474
+ noteList[index] = undefined;
1475
+ this.disconnectNote(note);
1375
1476
  resolve();
1376
1477
  };
1377
1478
  note.bufferSource.stop(stopTime);
1378
1479
  });
1379
1480
  }
1481
+ findNoteOffTarget(noteList) {
1482
+ for (let i = 0; i < noteList.length; i++) {
1483
+ const note = noteList[i];
1484
+ if (!note)
1485
+ continue;
1486
+ if (note.ending)
1487
+ continue;
1488
+ return [note, i];
1489
+ }
1490
+ }
1380
1491
  scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force, portamentoNoteNumber) {
1381
1492
  const channel = this.channels[channelNumber];
1493
+ if (this.isDrumNoteOffException(channel, noteNumber))
1494
+ return;
1382
1495
  const state = channel.state;
1383
1496
  if (!force) {
1384
1497
  if (0.5 <= state.sustainPedal)
@@ -1386,35 +1499,32 @@ class Midy {
1386
1499
  if (channel.sostenutoNotes.has(noteNumber))
1387
1500
  return;
1388
1501
  }
1389
- if (!channel.scheduledNotes.has(noteNumber))
1502
+ const noteList = channel.scheduledNotes.get(noteNumber);
1503
+ if (!noteList)
1504
+ return; // be careful with drum channel
1505
+ const noteOffTarget = this.findNoteOffTarget(noteList, endTime);
1506
+ if (!noteOffTarget)
1390
1507
  return;
1391
- const scheduledNotes = channel.scheduledNotes.get(noteNumber);
1392
- for (let i = 0; i < scheduledNotes.length; i++) {
1393
- const note = scheduledNotes[i];
1394
- if (!note)
1395
- continue;
1396
- if (note.ending)
1397
- continue;
1398
- if (portamentoNoteNumber === undefined) {
1399
- const volRelease = endTime +
1400
- note.voiceParams.volRelease * state.releaseTime * 2;
1401
- const modRelease = endTime + note.voiceParams.modRelease;
1402
- note.filterNode.frequency
1403
- .cancelScheduledValues(endTime)
1404
- .linearRampToValueAtTime(0, modRelease);
1405
- const stopTime = Math.min(volRelease, modRelease);
1406
- return this.stopNote(endTime, stopTime, scheduledNotes, i);
1407
- }
1408
- else {
1409
- const portamentoTime = endTime + this.getPortamentoTime(channel);
1410
- const deltaNote = portamentoNoteNumber - noteNumber;
1411
- const baseRate = note.voiceParams.playbackRate;
1412
- const targetRate = baseRate * Math.pow(2, deltaNote / 12);
1413
- note.bufferSource.playbackRate
1414
- .cancelScheduledValues(endTime)
1415
- .linearRampToValueAtTime(targetRate, portamentoTime);
1416
- return this.stopNote(endTime, portamentoTime, scheduledNotes, i);
1417
- }
1508
+ const [note, i] = noteOffTarget;
1509
+ if (0.5 <= state.portamento && portamentoNoteNumber !== undefined) {
1510
+ const portamentoTime = endTime + this.getPortamentoTime(channel);
1511
+ const deltaNote = portamentoNoteNumber - noteNumber;
1512
+ const baseRate = note.voiceParams.playbackRate;
1513
+ const targetRate = baseRate * Math.pow(2, deltaNote / 12);
1514
+ note.bufferSource.playbackRate
1515
+ .cancelScheduledValues(endTime)
1516
+ .linearRampToValueAtTime(targetRate, portamentoTime);
1517
+ return this.stopNote(endTime, portamentoTime, noteList, i);
1518
+ }
1519
+ else {
1520
+ const volRelease = endTime +
1521
+ note.voiceParams.volRelease * state.releaseTime * 2;
1522
+ const modRelease = endTime + note.voiceParams.modRelease;
1523
+ note.filterNode.frequency
1524
+ .cancelScheduledValues(endTime)
1525
+ .linearRampToValueAtTime(0, modRelease);
1526
+ const stopTime = Math.min(volRelease, modRelease);
1527
+ return this.stopNote(endTime, stopTime, noteList, i);
1418
1528
  }
1419
1529
  }
1420
1530
  noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
@@ -1476,12 +1586,12 @@ class Midy {
1476
1586
  const note = activeNotes.get(noteNumber);
1477
1587
  this.setControllerParameters(channel, note, table);
1478
1588
  }
1479
- // this.applyVoiceParams(channel, 10);
1589
+ this.applyVoiceParams(channel, 10);
1480
1590
  }
1481
- handleProgramChange(channelNumber, program, _scheduleTime) {
1591
+ handleProgramChange(channelNumber, programNumber, _scheduleTime) {
1482
1592
  const channel = this.channels[channelNumber];
1483
1593
  channel.bank = channel.bankMSB * 128 + channel.bankLSB;
1484
- channel.program = program;
1594
+ channel.programNumber = programNumber;
1485
1595
  if (this.mode === "GM2") {
1486
1596
  switch (channel.bankMSB) {
1487
1597
  case 120:
@@ -1508,7 +1618,7 @@ class Midy {
1508
1618
  this.getActiveNotes(channel, scheduleTime).forEach((note) => {
1509
1619
  this.setControllerParameters(channel, note, table);
1510
1620
  });
1511
- // this.applyVoiceParams(channel, 13);
1621
+ this.applyVoiceParams(channel, 13);
1512
1622
  }
1513
1623
  handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
1514
1624
  const pitchBend = msb * 128 + lsb;
@@ -1685,6 +1795,8 @@ class Midy {
1685
1795
  state.set(channel.state.array);
1686
1796
  state[2] = velocity / 127;
1687
1797
  state[3] = noteNumber / 127;
1798
+ state[10] = state.polyphonicKeyPressure / 127;
1799
+ state[13] = state.channelPressure / 127;
1688
1800
  return state;
1689
1801
  }
1690
1802
  applyVoiceParams(channel, controllerType, scheduleTime) {
@@ -1711,7 +1823,7 @@ class Midy {
1711
1823
  if (key in voiceParams)
1712
1824
  noteVoiceParams[key] = voiceParams[key];
1713
1825
  }
1714
- if (note.portamento) {
1826
+ if (0.5 <= channel.state.portamento && note.portamento) {
1715
1827
  this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
1716
1828
  }
1717
1829
  else {
@@ -1918,10 +2030,11 @@ class Midy {
1918
2030
  const channel = this.channels[channelNumber];
1919
2031
  if (channel.isDrum)
1920
2032
  return;
2033
+ const state = channel.state;
1921
2034
  scheduleTime ??= this.audioContext.currentTime;
1922
- channel.state.softPedal = softPedal / 127;
2035
+ state.softPedal = softPedal / 127;
1923
2036
  this.processScheduledNotes(channel, (note) => {
1924
- if (note.portamento) {
2037
+ if (0.5 <= state.portamento && note.portamento) {
1925
2038
  this.setPortamentoStartVolumeEnvelope(channel, note, scheduleTime);
1926
2039
  this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
1927
2040
  }
@@ -1943,7 +2056,7 @@ class Midy {
1943
2056
  note.filterNode.Q.setValueAtTime(Q, scheduleTime);
1944
2057
  });
1945
2058
  }
1946
- setReleaseTime(channelNumber, releaseTime, _scheduleTime) {
2059
+ setReleaseTime(channelNumber, releaseTime, scheduleTime) {
1947
2060
  const channel = this.channels[channelNumber];
1948
2061
  if (channel.isDrum)
1949
2062
  return;
@@ -1966,10 +2079,11 @@ class Midy {
1966
2079
  const channel = this.channels[channelNumber];
1967
2080
  if (channel.isDrum)
1968
2081
  return;
2082
+ const state = channel.state;
1969
2083
  scheduleTime ??= this.audioContext.currentTime;
1970
- channel.state.brightness = brightness / 64;
2084
+ state.brightness = brightness / 64;
1971
2085
  this.processScheduledNotes(channel, (note) => {
1972
- if (note.portamento) {
2086
+ if (0.5 <= state.portamento && note.portamento) {
1973
2087
  this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
1974
2088
  }
1975
2089
  else {
@@ -2230,18 +2344,33 @@ class Midy {
2230
2344
  }
2231
2345
  allSoundOff(channelNumber, _value, scheduleTime) {
2232
2346
  scheduleTime ??= this.audioContext.currentTime;
2233
- return this.stopChannelNotes(channelNumber, 0, true, scheduleTime);
2347
+ return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
2234
2348
  }
2349
+ resetAllStates(channelNumber) {
2350
+ const channel = this.channels[channelNumber];
2351
+ const state = channel.state;
2352
+ for (const type of Object.keys(defaultControllerState)) {
2353
+ state[type] = defaultControllerState[type].defaultValue;
2354
+ }
2355
+ for (const type of Object.keys(this.constructor.channelSettings)) {
2356
+ channel[type] = this.constructor.channelSettings[type];
2357
+ }
2358
+ this.mode = "GM2";
2359
+ this.masterFineTuning = 0; // cb
2360
+ this.masterCoarseTuning = 0; // cb
2361
+ }
2362
+ // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
2235
2363
  resetAllControllers(channelNumber) {
2236
2364
  const stateTypes = [
2365
+ "polyphonicKeyPressure",
2366
+ "channelPressure",
2367
+ "pitchWheel",
2237
2368
  "expression",
2238
2369
  "modulationDepth",
2239
2370
  "sustainPedal",
2240
2371
  "portamento",
2241
2372
  "sostenutoPedal",
2242
2373
  "softPedal",
2243
- "channelPressure",
2244
- "pitchWheelSensitivity",
2245
2374
  ];
2246
2375
  const channel = this.channels[channelNumber];
2247
2376
  const state = channel.state;
@@ -2260,7 +2389,7 @@ class Midy {
2260
2389
  }
2261
2390
  allNotesOff(channelNumber, _value, scheduleTime) {
2262
2391
  scheduleTime ??= this.audioContext.currentTime;
2263
- return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
2392
+ return this.stopActiveNotes(channelNumber, 0, false, scheduleTime);
2264
2393
  }
2265
2394
  omniOff(channelNumber, value, scheduleTime) {
2266
2395
  this.allNotesOff(channelNumber, value, scheduleTime);
@@ -2622,7 +2751,7 @@ class Midy {
2622
2751
  return value * 0.00787;
2623
2752
  }
2624
2753
  getChannelBitmap(data) {
2625
- const bitmap = new Array(16).fill(false);
2754
+ const bitmap = new Array(this.channels.length).fill(false);
2626
2755
  const ff = data[4] & 0b11;
2627
2756
  const gg = data[5] & 0x7F;
2628
2757
  const hh = data[6] & 0x7F;
@@ -2814,6 +2943,7 @@ class Midy {
2814
2943
  console.warn(`Unsupported Exclusive Message: ${data}`);
2815
2944
  }
2816
2945
  }
2946
+ // https://github.com/marmooo/js-timer-benchmark
2817
2947
  scheduleTask(callback, scheduleTime) {
2818
2948
  return new Promise((resolve) => {
2819
2949
  const bufferSource = new AudioBufferSourceNode(this.audioContext, {
@@ -2839,10 +2969,8 @@ Object.defineProperty(Midy, "channelSettings", {
2839
2969
  configurable: true,
2840
2970
  writable: true,
2841
2971
  value: {
2842
- currentBufferSource: null,
2843
- isDrum: false,
2844
2972
  detune: 0,
2845
- program: 0,
2973
+ programNumber: 0,
2846
2974
  bank: 121 * 128,
2847
2975
  bankMSB: 121,
2848
2976
  bankLSB: 0,