@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/esm/midy-GM2.js CHANGED
@@ -151,6 +151,39 @@ class Note {
151
151
  this.voiceParams = voiceParams;
152
152
  }
153
153
  }
154
+ const drumExclusiveClassesByKit = new Array(57);
155
+ const drumExclusiveClassCount = 10;
156
+ const standardSet = new Uint8Array(128);
157
+ standardSet[42] = 1;
158
+ standardSet[44] = 1;
159
+ standardSet[46] = 1; // HH
160
+ standardSet[71] = 2;
161
+ standardSet[72] = 2; // Whistle
162
+ standardSet[73] = 3;
163
+ standardSet[74] = 3; // Guiro
164
+ standardSet[78] = 4;
165
+ standardSet[79] = 4; // Cuica
166
+ standardSet[80] = 5;
167
+ standardSet[81] = 5; // Triangle
168
+ standardSet[29] = 6;
169
+ standardSet[30] = 6; // Scratch
170
+ standardSet[86] = 7;
171
+ standardSet[87] = 7; // Surdo
172
+ drumExclusiveClassesByKit[0] = standardSet;
173
+ const analogSet = new Uint8Array(128);
174
+ analogSet[42] = 8;
175
+ analogSet[44] = 8;
176
+ analogSet[46] = 8; // CHH
177
+ drumExclusiveClassesByKit[25] = analogSet;
178
+ const orchestraSet = new Uint8Array(128);
179
+ orchestraSet[27] = 9;
180
+ orchestraSet[28] = 9;
181
+ orchestraSet[29] = 9; // HH
182
+ drumExclusiveClassesByKit[48] = orchestraSet;
183
+ const sfxSet = new Uint8Array(128);
184
+ sfxSet[41] = 10;
185
+ sfxSet[42] = 10; // Scratch
186
+ drumExclusiveClassesByKit[56] = sfxSet;
154
187
  // normalized to 0-1 for use with the SF2 modulator model
155
188
  const defaultControllerState = {
156
189
  noteOnVelocity: { type: 2, defaultValue: 0 },
@@ -245,18 +278,6 @@ export class MidyGM2 {
245
278
  writable: true,
246
279
  value: "GM2"
247
280
  });
248
- Object.defineProperty(this, "ticksPerBeat", {
249
- enumerable: true,
250
- configurable: true,
251
- writable: true,
252
- value: 120
253
- });
254
- Object.defineProperty(this, "totalTime", {
255
- enumerable: true,
256
- configurable: true,
257
- writable: true,
258
- value: 0
259
- });
260
281
  Object.defineProperty(this, "masterFineTuning", {
261
282
  enumerable: true,
262
283
  configurable: true,
@@ -290,6 +311,24 @@ export class MidyGM2 {
290
311
  delayTimes: this.generateDistributedArray(0.02, 2, 0.5),
291
312
  }
292
313
  });
314
+ Object.defineProperty(this, "numChannels", {
315
+ enumerable: true,
316
+ configurable: true,
317
+ writable: true,
318
+ value: 16
319
+ });
320
+ Object.defineProperty(this, "ticksPerBeat", {
321
+ enumerable: true,
322
+ configurable: true,
323
+ writable: true,
324
+ value: 120
325
+ });
326
+ Object.defineProperty(this, "totalTime", {
327
+ enumerable: true,
328
+ configurable: true,
329
+ writable: true,
330
+ value: 0
331
+ });
293
332
  Object.defineProperty(this, "noteCheckInterval", {
294
333
  enumerable: true,
295
334
  configurable: true,
@@ -392,11 +431,17 @@ export class MidyGM2 {
392
431
  writable: true,
393
432
  value: []
394
433
  });
395
- Object.defineProperty(this, "exclusiveClassMap", {
434
+ Object.defineProperty(this, "exclusiveClassNotes", {
396
435
  enumerable: true,
397
436
  configurable: true,
398
437
  writable: true,
399
- value: new SparseMap(128)
438
+ value: new Array(128)
439
+ });
440
+ Object.defineProperty(this, "drumExclusiveClassNotes", {
441
+ enumerable: true,
442
+ configurable: true,
443
+ writable: true,
444
+ value: new Array(this.numChannels * drumExclusiveClassCount)
400
445
  });
401
446
  Object.defineProperty(this, "defaultOptions", {
402
447
  enumerable: true,
@@ -490,8 +535,10 @@ export class MidyGM2 {
490
535
  };
491
536
  }
492
537
  createChannels(audioContext) {
493
- const channels = Array.from({ length: 16 }, () => {
538
+ const channels = Array.from({ length: this.numChannels }, () => {
494
539
  return {
540
+ currentBufferSource: null,
541
+ isDrum: false,
495
542
  ...this.constructor.channelSettings,
496
543
  state: new ControllerState(),
497
544
  controlTable: this.initControlTable(),
@@ -535,24 +582,10 @@ export class MidyGM2 {
535
582
  return audioBuffer;
536
583
  }
537
584
  }
538
- calcLoopMode(channel, note, voiceParams) {
539
- if (channel.isDrum) {
540
- const noteNumber = note.noteNumber;
541
- if (noteNumber === 88 || 47 <= noteNumber && noteNumber <= 84) {
542
- return true;
543
- }
544
- else {
545
- return false;
546
- }
547
- }
548
- else {
549
- return voiceParams.sampleModes % 2 !== 0;
550
- }
551
- }
552
- createBufferSource(channel, note, voiceParams, audioBuffer) {
585
+ createBufferSource(voiceParams, audioBuffer) {
553
586
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
554
587
  bufferSource.buffer = audioBuffer;
555
- bufferSource.loop = this.calcLoopMode(channel, note, voiceParams);
588
+ bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
556
589
  if (bufferSource.loop) {
557
590
  bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
558
591
  bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
@@ -585,7 +618,7 @@ export class MidyGM2 {
585
618
  const startTime = event.startTime + this.startDelay - offset;
586
619
  switch (event.type) {
587
620
  case "noteOn":
588
- if (event.velocity !== 0) {
621
+ if (0 < event.velocity) {
589
622
  await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, event.portamento);
590
623
  break;
591
624
  }
@@ -640,7 +673,8 @@ export class MidyGM2 {
640
673
  if (queueIndex >= this.timeline.length) {
641
674
  await Promise.all(this.notePromises);
642
675
  this.notePromises = [];
643
- this.exclusiveClassMap.clear();
676
+ this.exclusiveClassNotes.fill(undefined);
677
+ this.drumExclusiveClassNotes.fill(undefined);
644
678
  this.audioBufferCache.clear();
645
679
  resolve();
646
680
  return;
@@ -659,7 +693,8 @@ export class MidyGM2 {
659
693
  else if (this.isStopping) {
660
694
  await this.stopNotes(0, true, now);
661
695
  this.notePromises = [];
662
- this.exclusiveClassMap.clear();
696
+ this.exclusiveClassNotes.fill(undefined);
697
+ this.drumExclusiveClassNotes.fill(undefined);
663
698
  this.audioBufferCache.clear();
664
699
  resolve();
665
700
  this.isStopping = false;
@@ -668,7 +703,8 @@ export class MidyGM2 {
668
703
  }
669
704
  else if (this.isSeeking) {
670
705
  this.stopNotes(0, true, now);
671
- this.exclusiveClassMap.clear();
706
+ this.exclusiveClassNotes.fill(undefined);
707
+ this.drumExclusiveClassNotes.fill(undefined);
672
708
  this.startTime = this.audioContext.currentTime;
673
709
  queueIndex = this.getQueueIndex(this.resumeTime);
674
710
  offset = this.resumeTime - this.startTime;
@@ -696,7 +732,7 @@ export class MidyGM2 {
696
732
  extractMidiData(midi) {
697
733
  const instruments = new Set();
698
734
  const timeline = [];
699
- const tmpChannels = new Array(16);
735
+ const tmpChannels = new Array(this.channels.length);
700
736
  for (let i = 0; i < tmpChannels.length; i++) {
701
737
  tmpChannels[i] = {
702
738
  programNumber: -1,
@@ -795,6 +831,17 @@ export class MidyGM2 {
795
831
  }
796
832
  return { instruments, timeline };
797
833
  }
834
+ stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
835
+ const channel = this.channels[channelNumber];
836
+ const promises = [];
837
+ const activeNotes = this.getActiveNotes(channel, scheduleTime);
838
+ activeNotes.forEach((note) => {
839
+ const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
840
+ this.notePromises.push(promise);
841
+ promises.push(promise);
842
+ });
843
+ return Promise.all(promises);
844
+ }
798
845
  stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
799
846
  const channel = this.channels[channelNumber];
800
847
  const promises = [];
@@ -824,6 +871,9 @@ export class MidyGM2 {
824
871
  if (!this.isPlaying)
825
872
  return;
826
873
  this.isStopping = true;
874
+ for (let i = 0; i < this.channels.length; i++) {
875
+ this.resetAllStates(i);
876
+ }
827
877
  }
828
878
  pause() {
829
879
  if (!this.isPlaying || this.isPaused)
@@ -863,6 +913,8 @@ export class MidyGM2 {
863
913
  const note = noteList[i];
864
914
  if (!note)
865
915
  continue;
916
+ if (note.ending)
917
+ continue;
866
918
  callback(note);
867
919
  }
868
920
  });
@@ -1194,8 +1246,8 @@ export class MidyGM2 {
1194
1246
  note.vibratoLFO.connect(note.vibratoDepth);
1195
1247
  note.vibratoDepth.connect(note.bufferSource.detune);
1196
1248
  }
1197
- async getAudioBuffer(program, noteNumber, velocity, voiceParams, isSF3) {
1198
- const audioBufferId = this.getAudioBufferId(program, noteNumber, velocity);
1249
+ async getAudioBuffer(programNumber, noteNumber, velocity, voiceParams, isSF3) {
1250
+ const audioBufferId = this.getAudioBufferId(programNumber, noteNumber, velocity);
1199
1251
  const cache = this.audioBufferCache.get(audioBufferId);
1200
1252
  if (cache) {
1201
1253
  cache.counter += 1;
@@ -1218,8 +1270,8 @@ export class MidyGM2 {
1218
1270
  const controllerState = this.getControllerState(channel, noteNumber, velocity);
1219
1271
  const voiceParams = voice.getAllParams(controllerState);
1220
1272
  const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
1221
- const audioBuffer = await this.getAudioBuffer(channel.program, noteNumber, velocity, voiceParams, isSF3);
1222
- note.bufferSource = this.createBufferSource(channel, note, voiceParams, audioBuffer);
1273
+ const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
1274
+ note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
1223
1275
  note.volumeNode = new GainNode(this.audioContext);
1224
1276
  note.gainL = new GainNode(this.audioContext);
1225
1277
  note.gainR = new GainNode(this.audioContext);
@@ -1228,7 +1280,7 @@ export class MidyGM2 {
1228
1280
  type: "lowpass",
1229
1281
  Q: voiceParams.initialFilterQ / 10, // dB
1230
1282
  });
1231
- if (portamento) {
1283
+ if (0.5 <= state.portamento && portamento) {
1232
1284
  note.portamento = true;
1233
1285
  this.setPortamentoStartVolumeEnvelope(channel, note, now);
1234
1286
  this.setPortamentoStartFilterEnvelope(channel, note, now);
@@ -1279,14 +1331,56 @@ export class MidyGM2 {
1279
1331
  return channel.bank;
1280
1332
  }
1281
1333
  }
1334
+ handleExclusiveClass(note, channelNumber, startTime) {
1335
+ const exclusiveClass = note.voiceParams.exclusiveClass;
1336
+ if (exclusiveClass === 0)
1337
+ return;
1338
+ const prev = this.exclusiveClassNotes[exclusiveClass];
1339
+ if (prev) {
1340
+ const [prevNote, prevChannelNumber] = prev;
1341
+ if (prevNote && !prevNote.ending) {
1342
+ this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
1343
+ startTime, true, // force
1344
+ undefined);
1345
+ }
1346
+ }
1347
+ this.exclusiveClassNotes[exclusiveClass] = [note, channelNumber];
1348
+ }
1349
+ handleDrumExclusiveClass(note, channelNumber, startTime) {
1350
+ const channel = this.channels[channelNumber];
1351
+ if (!channel.isDrum)
1352
+ return;
1353
+ const kitTable = drumExclusiveClassesByKit[channel.programNumber];
1354
+ if (!kitTable)
1355
+ return;
1356
+ const drumExclusiveClass = kitTable[note.noteNumber];
1357
+ if (drumExclusiveClass === 0)
1358
+ return;
1359
+ const index = (drumExclusiveClass - 1) * this.channels.length +
1360
+ channelNumber;
1361
+ const prevNote = this.drumExclusiveClassNotes[index];
1362
+ if (prevNote && !prevNote.ending) {
1363
+ this.scheduleNoteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
1364
+ startTime, true, // force
1365
+ undefined);
1366
+ }
1367
+ this.drumExclusiveClassNotes[index] = note;
1368
+ }
1369
+ isDrumNoteOffException(channel, noteNumber) {
1370
+ if (!channel.isDrum)
1371
+ return false;
1372
+ const programNumber = channel.programNumber;
1373
+ return !((programNumber === 48 && noteNumber === 88) ||
1374
+ (programNumber === 56 && 47 <= noteNumber && noteNumber <= 84));
1375
+ }
1282
1376
  async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, portamento) {
1283
1377
  const channel = this.channels[channelNumber];
1284
1378
  const bankNumber = this.calcBank(channel, channelNumber);
1285
- const soundFontIndex = this.soundFontTable[channel.program].get(bankNumber);
1379
+ const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
1286
1380
  if (soundFontIndex === undefined)
1287
1381
  return;
1288
1382
  const soundFont = this.soundFonts[soundFontIndex];
1289
- const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
1383
+ const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
1290
1384
  if (!voice)
1291
1385
  return;
1292
1386
  const isSF3 = soundFont.parsed.info.version.major === 3;
@@ -1296,33 +1390,60 @@ export class MidyGM2 {
1296
1390
  if (0.5 <= channel.state.sustainPedal) {
1297
1391
  channel.sustainNotes.push(note);
1298
1392
  }
1299
- const exclusiveClass = note.voiceParams.exclusiveClass;
1300
- if (exclusiveClass !== 0) {
1301
- if (this.exclusiveClassMap.has(exclusiveClass)) {
1302
- const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
1303
- const [prevNote, prevChannelNumber] = prevEntry;
1304
- if (prevNote && !prevNote.ending) {
1305
- this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
1306
- startTime, true, // force
1307
- undefined);
1308
- }
1309
- }
1310
- this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
1311
- }
1393
+ this.handleExclusiveClass(note, channelNumber, startTime);
1394
+ this.handleDrumExclusiveClass(note, channelNumber, startTime);
1312
1395
  const scheduledNotes = channel.scheduledNotes;
1313
- if (scheduledNotes.has(noteNumber)) {
1314
- scheduledNotes.get(noteNumber).push(note);
1396
+ let noteList = scheduledNotes.get(noteNumber);
1397
+ if (noteList) {
1398
+ noteList.push(note);
1315
1399
  }
1316
1400
  else {
1317
- scheduledNotes.set(noteNumber, [note]);
1401
+ noteList = [note];
1402
+ scheduledNotes.set(noteNumber, noteList);
1403
+ }
1404
+ if (this.isDrumNoteOffException(channel, noteNumber)) {
1405
+ const stopTime = startTime + note.bufferSource.buffer.duration;
1406
+ const index = noteList.length - 1;
1407
+ const promise = new Promise((resolve) => {
1408
+ note.bufferSource.onended = () => {
1409
+ noteList[index] = undefined;
1410
+ this.disconnectNote(note);
1411
+ resolve();
1412
+ };
1413
+ note.bufferSource.stop(stopTime);
1414
+ });
1415
+ this.notePromises.push(promise);
1318
1416
  }
1319
1417
  }
1320
1418
  noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
1321
1419
  scheduleTime ??= this.audioContext.currentTime;
1322
1420
  return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, false);
1323
1421
  }
1324
- stopNote(endTime, stopTime, scheduledNotes, index) {
1325
- const note = scheduledNotes[index];
1422
+ disconnectNote(note) {
1423
+ note.bufferSource.disconnect();
1424
+ note.filterNode.disconnect();
1425
+ note.volumeEnvelopeNode.disconnect();
1426
+ note.volumeNode.disconnect();
1427
+ note.gainL.disconnect();
1428
+ note.gainR.disconnect();
1429
+ if (note.modulationDepth) {
1430
+ note.volumeDepth.disconnect();
1431
+ note.modulationDepth.disconnect();
1432
+ note.modulationLFO.stop();
1433
+ }
1434
+ if (note.vibratoDepth) {
1435
+ note.vibratoDepth.disconnect();
1436
+ note.vibratoLFO.stop();
1437
+ }
1438
+ if (note.reverbEffectsSend) {
1439
+ note.reverbEffectsSend.disconnect();
1440
+ }
1441
+ if (note.chorusEffectsSend) {
1442
+ note.chorusEffectsSend.disconnect();
1443
+ }
1444
+ }
1445
+ stopNote(endTime, stopTime, noteList, index) {
1446
+ const note = noteList[index];
1326
1447
  note.volumeEnvelopeNode.gain
1327
1448
  .cancelScheduledValues(endTime)
1328
1449
  .linearRampToValueAtTime(0, stopTime);
@@ -1332,35 +1453,27 @@ export class MidyGM2 {
1332
1453
  }, stopTime);
1333
1454
  return new Promise((resolve) => {
1334
1455
  note.bufferSource.onended = () => {
1335
- scheduledNotes[index] = null;
1336
- note.bufferSource.disconnect();
1337
- note.filterNode.disconnect();
1338
- note.volumeEnvelopeNode.disconnect();
1339
- note.volumeNode.disconnect();
1340
- note.gainL.disconnect();
1341
- note.gainR.disconnect();
1342
- if (note.modulationDepth) {
1343
- note.volumeDepth.disconnect();
1344
- note.modulationDepth.disconnect();
1345
- note.modulationLFO.stop();
1346
- }
1347
- if (note.vibratoDepth) {
1348
- note.vibratoDepth.disconnect();
1349
- note.vibratoLFO.stop();
1350
- }
1351
- if (note.reverbEffectsSend) {
1352
- note.reverbEffectsSend.disconnect();
1353
- }
1354
- if (note.chorusEffectsSend) {
1355
- note.chorusEffectsSend.disconnect();
1356
- }
1456
+ noteList[index] = undefined;
1457
+ this.disconnectNote(note);
1357
1458
  resolve();
1358
1459
  };
1359
1460
  note.bufferSource.stop(stopTime);
1360
1461
  });
1361
1462
  }
1463
+ findNoteOffTarget(noteList) {
1464
+ for (let i = 0; i < noteList.length; i++) {
1465
+ const note = noteList[i];
1466
+ if (!note)
1467
+ continue;
1468
+ if (note.ending)
1469
+ continue;
1470
+ return [note, i];
1471
+ }
1472
+ }
1362
1473
  scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force, portamentoNoteNumber) {
1363
1474
  const channel = this.channels[channelNumber];
1475
+ if (this.isDrumNoteOffException(channel, noteNumber))
1476
+ return;
1364
1477
  const state = channel.state;
1365
1478
  if (!force) {
1366
1479
  if (0.5 <= state.sustainPedal)
@@ -1368,34 +1481,32 @@ export class MidyGM2 {
1368
1481
  if (channel.sostenutoNotes.has(noteNumber))
1369
1482
  return;
1370
1483
  }
1371
- if (!channel.scheduledNotes.has(noteNumber))
1484
+ const noteList = channel.scheduledNotes.get(noteNumber);
1485
+ if (!noteList)
1486
+ return; // be careful with drum channel
1487
+ const noteOffTarget = this.findNoteOffTarget(noteList, endTime);
1488
+ if (!noteOffTarget)
1372
1489
  return;
1373
- const scheduledNotes = channel.scheduledNotes.get(noteNumber);
1374
- for (let i = 0; i < scheduledNotes.length; i++) {
1375
- const note = scheduledNotes[i];
1376
- if (!note)
1377
- continue;
1378
- if (note.ending)
1379
- continue;
1380
- if (portamentoNoteNumber === undefined) {
1381
- const volRelease = endTime + note.voiceParams.volRelease;
1382
- const modRelease = endTime + note.voiceParams.modRelease;
1383
- note.filterNode.frequency
1384
- .cancelScheduledValues(endTime)
1385
- .linearRampToValueAtTime(0, modRelease);
1386
- const stopTime = Math.min(volRelease, modRelease);
1387
- return this.stopNote(endTime, stopTime, scheduledNotes, i);
1388
- }
1389
- else {
1390
- const portamentoTime = endTime + this.getPortamentoTime(channel);
1391
- const deltaNote = portamentoNoteNumber - noteNumber;
1392
- const baseRate = note.voiceParams.playbackRate;
1393
- const targetRate = baseRate * Math.pow(2, deltaNote / 12);
1394
- note.bufferSource.playbackRate
1395
- .cancelScheduledValues(endTime)
1396
- .linearRampToValueAtTime(targetRate, portamentoTime);
1397
- return this.stopNote(endTime, portamentoTime, scheduledNotes, i);
1398
- }
1490
+ const [note, i] = noteOffTarget;
1491
+ if (0.5 <= state.portamento && portamentoNoteNumber !== undefined) {
1492
+ const portamentoTime = endTime + this.getPortamentoTime(channel);
1493
+ const deltaNote = portamentoNoteNumber - noteNumber;
1494
+ const baseRate = note.voiceParams.playbackRate;
1495
+ const targetRate = baseRate * Math.pow(2, deltaNote / 12);
1496
+ note.bufferSource.playbackRate
1497
+ .cancelScheduledValues(endTime)
1498
+ .linearRampToValueAtTime(targetRate, portamentoTime);
1499
+ return this.stopNote(endTime, portamentoTime, noteList, i);
1500
+ }
1501
+ else {
1502
+ const volRelease = endTime +
1503
+ note.voiceParams.volRelease * state.releaseTime * 2;
1504
+ const modRelease = endTime + note.voiceParams.modRelease;
1505
+ note.filterNode.frequency
1506
+ .cancelScheduledValues(endTime)
1507
+ .linearRampToValueAtTime(0, modRelease);
1508
+ const stopTime = Math.min(volRelease, modRelease);
1509
+ return this.stopNote(endTime, stopTime, noteList, i);
1399
1510
  }
1400
1511
  }
1401
1512
  noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
@@ -1446,10 +1557,10 @@ export class MidyGM2 {
1446
1557
  console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
1447
1558
  }
1448
1559
  }
1449
- handleProgramChange(channelNumber, program, _scheduleTime) {
1560
+ handleProgramChange(channelNumber, programNumber, _scheduleTime) {
1450
1561
  const channel = this.channels[channelNumber];
1451
1562
  channel.bank = channel.bankMSB * 128 + channel.bankLSB;
1452
- channel.program = program;
1563
+ channel.programNumber = programNumber;
1453
1564
  if (this.mode === "GM2") {
1454
1565
  switch (channel.bankMSB) {
1455
1566
  case 120:
@@ -1476,7 +1587,7 @@ export class MidyGM2 {
1476
1587
  this.getActiveNotes(channel, scheduleTime).forEach((note) => {
1477
1588
  this.setControllerParameters(channel, note, table);
1478
1589
  });
1479
- // this.applyVoiceParams(channel, 13);
1590
+ this.applyVoiceParams(channel, 13);
1480
1591
  }
1481
1592
  handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
1482
1593
  const pitchBend = msb * 128 + lsb;
@@ -1653,6 +1764,7 @@ export class MidyGM2 {
1653
1764
  state.set(channel.state.array);
1654
1765
  state[2] = velocity / 127;
1655
1766
  state[3] = noteNumber / 127;
1767
+ state[13] = state.channelPressure / 127;
1656
1768
  return state;
1657
1769
  }
1658
1770
  applyVoiceParams(channel, controllerType, scheduleTime) {
@@ -1679,7 +1791,7 @@ export class MidyGM2 {
1679
1791
  if (key in voiceParams)
1680
1792
  noteVoiceParams[key] = voiceParams[key];
1681
1793
  }
1682
- if (note.portamento) {
1794
+ if (0.5 <= channel.state.portamento && note.portamento) {
1683
1795
  this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
1684
1796
  }
1685
1797
  else {
@@ -1876,10 +1988,11 @@ export class MidyGM2 {
1876
1988
  const channel = this.channels[channelNumber];
1877
1989
  if (channel.isDrum)
1878
1990
  return;
1991
+ const state = channel.state;
1879
1992
  scheduleTime ??= this.audioContext.currentTime;
1880
- channel.state.softPedal = softPedal / 127;
1993
+ state.softPedal = softPedal / 127;
1881
1994
  this.processScheduledNotes(channel, (note) => {
1882
- if (note.portamento) {
1995
+ if (0.5 <= state.portamento && note.portamento) {
1883
1996
  this.setPortamentoStartVolumeEnvelope(channel, note, scheduleTime);
1884
1997
  this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
1885
1998
  }
@@ -2078,18 +2191,32 @@ export class MidyGM2 {
2078
2191
  }
2079
2192
  allSoundOff(channelNumber, _value, scheduleTime) {
2080
2193
  scheduleTime ??= this.audioContext.currentTime;
2081
- return this.stopChannelNotes(channelNumber, 0, true, scheduleTime);
2194
+ return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
2082
2195
  }
2196
+ resetAllStates(channelNumber) {
2197
+ const channel = this.channels[channelNumber];
2198
+ const state = channel.state;
2199
+ for (const type of Object.keys(defaultControllerState)) {
2200
+ state[type] = defaultControllerState[type].defaultValue;
2201
+ }
2202
+ for (const type of Object.keys(this.constructor.channelSettings)) {
2203
+ channel[type] = this.constructor.channelSettings[type];
2204
+ }
2205
+ this.mode = "GM2";
2206
+ this.masterFineTuning = 0; // cb
2207
+ this.masterCoarseTuning = 0; // cb
2208
+ }
2209
+ // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
2083
2210
  resetAllControllers(channelNumber) {
2084
2211
  const stateTypes = [
2212
+ "channelPressure",
2213
+ "pitchWheel",
2085
2214
  "expression",
2086
2215
  "modulationDepth",
2087
2216
  "sustainPedal",
2088
2217
  "portamento",
2089
2218
  "sostenutoPedal",
2090
2219
  "softPedal",
2091
- "channelPressure",
2092
- "pitchWheelSensitivity",
2093
2220
  ];
2094
2221
  const channel = this.channels[channelNumber];
2095
2222
  const state = channel.state;
@@ -2108,7 +2235,7 @@ export class MidyGM2 {
2108
2235
  }
2109
2236
  allNotesOff(channelNumber, _value, scheduleTime) {
2110
2237
  scheduleTime ??= this.audioContext.currentTime;
2111
- return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
2238
+ return this.stopActiveNotes(channelNumber, 0, false, scheduleTime);
2112
2239
  }
2113
2240
  omniOff(channelNumber, value, scheduleTime) {
2114
2241
  this.allNotesOff(channelNumber, value, scheduleTime);
@@ -2454,7 +2581,7 @@ export class MidyGM2 {
2454
2581
  return value * 0.00787;
2455
2582
  }
2456
2583
  getChannelBitmap(data) {
2457
- const bitmap = new Array(16).fill(false);
2584
+ const bitmap = new Array(this.channels.length).fill(false);
2458
2585
  const ff = data[4] & 0b11;
2459
2586
  const gg = data[5] & 0x7F;
2460
2587
  const hh = data[6] & 0x7F;
@@ -2596,6 +2723,7 @@ export class MidyGM2 {
2596
2723
  console.warn(`Unsupported Exclusive Message: ${data}`);
2597
2724
  }
2598
2725
  }
2726
+ // https://github.com/marmooo/js-timer-benchmark
2599
2727
  scheduleTask(callback, scheduleTime) {
2600
2728
  return new Promise((resolve) => {
2601
2729
  const bufferSource = new AudioBufferSourceNode(this.audioContext, {
@@ -2620,10 +2748,8 @@ Object.defineProperty(MidyGM2, "channelSettings", {
2620
2748
  configurable: true,
2621
2749
  writable: true,
2622
2750
  value: {
2623
- currentBufferSource: null,
2624
- isDrum: false,
2625
2751
  detune: 0,
2626
- program: 0,
2752
+ programNumber: 0,
2627
2753
  bank: 121 * 128,
2628
2754
  bankMSB: 121,
2629
2755
  bankLSB: 0,