@marmooo/midy 0.3.0 → 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.
@@ -430,7 +430,7 @@ class MidyGM1 {
430
430
  const startTime = event.startTime + this.startDelay - offset;
431
431
  switch (event.type) {
432
432
  case "noteOn":
433
- if (event.velocity !== 0) {
433
+ if (0 < event.velocity) {
434
434
  await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
435
435
  break;
436
436
  }
@@ -478,7 +478,7 @@ class MidyGM1 {
478
478
  if (queueIndex >= this.timeline.length) {
479
479
  await Promise.all(this.notePromises);
480
480
  this.notePromises = [];
481
- this.exclusiveClassNotes.flll(undefined);
481
+ this.exclusiveClassNotes.fill(undefined);
482
482
  this.audioBufferCache.clear();
483
483
  resolve();
484
484
  return;
@@ -597,6 +597,17 @@ class MidyGM1 {
597
597
  }
598
598
  return { instruments, timeline };
599
599
  }
600
+ stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
601
+ const channel = this.channels[channelNumber];
602
+ const promises = [];
603
+ const activeNotes = this.getActiveNotes(channel, scheduleTime);
604
+ activeNotes.forEach((note) => {
605
+ const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
606
+ this.notePromises.push(promise);
607
+ promises.push(promise);
608
+ });
609
+ return Promise.all(promises);
610
+ }
600
611
  stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
601
612
  const channel = this.channels[channelNumber];
602
613
  const promises = [];
@@ -668,6 +679,8 @@ class MidyGM1 {
668
679
  const note = noteList[i];
669
680
  if (!note)
670
681
  continue;
682
+ if (note.ending)
683
+ continue;
671
684
  callback(note);
672
685
  }
673
686
  });
@@ -880,21 +893,21 @@ class MidyGM1 {
880
893
  channel.sustainNotes.push(note);
881
894
  }
882
895
  this.handleExclusiveClass(note, channelNumber, startTime);
883
- let notes = scheduledNotes.get(noteNumber);
884
- if (notes) {
885
- notes.push(note);
896
+ const scheduledNotes = channel.scheduledNotes;
897
+ let noteList = scheduledNotes.get(noteNumber);
898
+ if (noteList) {
899
+ noteList.push(note);
886
900
  }
887
901
  else {
888
- notes = [note];
889
- scheduledNotes.set(noteNumber, notes);
902
+ noteList = [note];
903
+ scheduledNotes.set(noteNumber, noteList);
890
904
  }
891
905
  }
892
906
  noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
893
907
  scheduleTime ??= this.audioContext.currentTime;
894
908
  return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime);
895
909
  }
896
- disconnectNote(note, scheduledNotes, index) {
897
- scheduledNotes[index] = null;
910
+ disconnectNote(note) {
898
911
  note.bufferSource.disconnect();
899
912
  note.filterNode.disconnect();
900
913
  note.volumeEnvelopeNode.disconnect();
@@ -904,8 +917,8 @@ class MidyGM1 {
904
917
  note.modulationLFO.stop();
905
918
  }
906
919
  }
907
- stopNote(endTime, stopTime, scheduledNotes, index) {
908
- const note = scheduledNotes[index];
920
+ stopNote(endTime, stopTime, noteList, index) {
921
+ const note = noteList[index];
909
922
  note.volumeEnvelopeNode.gain
910
923
  .cancelScheduledValues(endTime)
911
924
  .linearRampToValueAtTime(0, stopTime);
@@ -915,33 +928,43 @@ class MidyGM1 {
915
928
  }, stopTime);
916
929
  return new Promise((resolve) => {
917
930
  note.bufferSource.onended = () => {
918
- this.disconnectNote(note, scheduledNotes, index);
931
+ noteList[index] = undefined;
932
+ this.disconnectNote(note);
919
933
  resolve();
920
934
  };
921
935
  note.bufferSource.stop(stopTime);
922
936
  });
923
937
  }
938
+ findNoteOffTarget(noteList) {
939
+ for (let i = 0; i < noteList.length; i++) {
940
+ const note = noteList[i];
941
+ if (!note)
942
+ continue;
943
+ if (note.ending)
944
+ continue;
945
+ return [note, i];
946
+ }
947
+ }
924
948
  scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
925
949
  const channel = this.channels[channelNumber];
926
950
  if (!force && 0.5 <= channel.state.sustainPedal)
927
951
  return;
928
952
  if (!channel.scheduledNotes.has(noteNumber))
929
953
  return;
930
- const scheduledNotes = channel.scheduledNotes.get(noteNumber);
931
- for (let i = 0; i < scheduledNotes.length; i++) {
932
- const note = scheduledNotes[i];
933
- if (!note)
934
- continue;
935
- if (note.ending)
936
- continue;
937
- const volRelease = endTime + note.voiceParams.volRelease;
938
- const modRelease = endTime + note.voiceParams.modRelease;
939
- note.filterNode.frequency
940
- .cancelScheduledValues(endTime)
941
- .linearRampToValueAtTime(0, modRelease);
942
- const stopTime = Math.min(volRelease, modRelease);
943
- return this.stopNote(endTime, stopTime, scheduledNotes, i);
944
- }
954
+ const noteList = channel.scheduledNotes.get(noteNumber);
955
+ if (!noteList)
956
+ return; // be careful with drum channel
957
+ const noteOffTarget = this.findNoteOffTarget(noteList, endTime);
958
+ if (!noteOffTarget)
959
+ return;
960
+ const [note, i] = noteOffTarget;
961
+ const volRelease = endTime + note.voiceParams.volRelease;
962
+ const modRelease = endTime + note.voiceParams.modRelease;
963
+ note.filterNode.frequency
964
+ .cancelScheduledValues(endTime)
965
+ .linearRampToValueAtTime(0, modRelease);
966
+ const stopTime = Math.min(volRelease, modRelease);
967
+ return this.stopNote(endTime, stopTime, noteList, i);
945
968
  }
946
969
  noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
947
970
  scheduleTime ??= this.audioContext.currentTime;
@@ -1307,7 +1330,7 @@ class MidyGM1 {
1307
1330
  }
1308
1331
  allSoundOff(channelNumber, _value, scheduleTime) {
1309
1332
  scheduleTime ??= this.audioContext.currentTime;
1310
- return this.stopChannelNotes(channelNumber, 0, true, scheduleTime);
1333
+ return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
1311
1334
  }
1312
1335
  resetAllStates(channelNumber) {
1313
1336
  const channel = this.channels[channelNumber];
@@ -1345,7 +1368,7 @@ class MidyGM1 {
1345
1368
  }
1346
1369
  allNotesOff(channelNumber, _value, scheduleTime) {
1347
1370
  scheduleTime ??= this.audioContext.currentTime;
1348
- return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
1371
+ return this.stopActiveNotes(channelNumber, 0, false, scheduleTime);
1349
1372
  }
1350
1373
  handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime) {
1351
1374
  switch (data[2]) {
@@ -147,6 +147,7 @@ export class MidyGM2 {
147
147
  instruments: Set<any>;
148
148
  timeline: any[];
149
149
  };
150
+ stopActiveNotes(channelNumber: any, velocity: any, force: any, scheduleTime: any): Promise<any[]>;
150
151
  stopChannelNotes(channelNumber: any, velocity: any, force: any, scheduleTime: any): Promise<any[]>;
151
152
  stopNotes(velocity: any, force: any, scheduleTime: any): Promise<any[]>;
152
153
  start(): Promise<void>;
@@ -206,8 +207,9 @@ export class MidyGM2 {
206
207
  isDrumNoteOffException(channel: any, noteNumber: any): boolean;
207
208
  scheduleNoteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any, portamento: any): Promise<void>;
208
209
  noteOn(channelNumber: any, noteNumber: any, velocity: any, scheduleTime: any): Promise<void>;
209
- disconnectNote(note: any, scheduledNotes: any, index: any): void;
210
- stopNote(endTime: any, stopTime: any, scheduledNotes: any, index: any): Promise<any>;
210
+ disconnectNote(note: any): void;
211
+ stopNote(endTime: any, stopTime: any, noteList: any, index: any): Promise<any>;
212
+ findNoteOffTarget(noteList: any): any[] | undefined;
211
213
  scheduleNoteOff(channelNumber: any, noteNumber: any, _velocity: any, endTime: any, force: any, portamentoNoteNumber: any): Promise<any> | undefined;
212
214
  noteOff(channelNumber: any, noteNumber: any, velocity: any, scheduleTime: any): Promise<any> | undefined;
213
215
  releaseSustainPedal(channelNumber: any, halfVelocity: any, scheduleTime: any): (Promise<any> | undefined)[];
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GM2.d.ts","sourceRoot":"","sources":["../src/midy-GM2.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"midy-GM2.d.ts","sourceRoot":"","sources":["../src/midy-GM2.js"],"names":[],"mappings":"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"}
@@ -621,7 +621,7 @@ class MidyGM2 {
621
621
  const startTime = event.startTime + this.startDelay - offset;
622
622
  switch (event.type) {
623
623
  case "noteOn":
624
- if (event.velocity !== 0) {
624
+ if (0 < event.velocity) {
625
625
  await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, event.portamento);
626
626
  break;
627
627
  }
@@ -834,6 +834,17 @@ class MidyGM2 {
834
834
  }
835
835
  return { instruments, timeline };
836
836
  }
837
+ stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
838
+ const channel = this.channels[channelNumber];
839
+ const promises = [];
840
+ const activeNotes = this.getActiveNotes(channel, scheduleTime);
841
+ activeNotes.forEach((note) => {
842
+ const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
843
+ this.notePromises.push(promise);
844
+ promises.push(promise);
845
+ });
846
+ return Promise.all(promises);
847
+ }
837
848
  stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
838
849
  const channel = this.channels[channelNumber];
839
850
  const promises = [];
@@ -905,6 +916,8 @@ class MidyGM2 {
905
916
  const note = noteList[i];
906
917
  if (!note)
907
918
  continue;
919
+ if (note.ending)
920
+ continue;
908
921
  callback(note);
909
922
  }
910
923
  });
@@ -1270,7 +1283,7 @@ class MidyGM2 {
1270
1283
  type: "lowpass",
1271
1284
  Q: voiceParams.initialFilterQ / 10, // dB
1272
1285
  });
1273
- if (portamento) {
1286
+ if (0.5 <= state.portamento && portamento) {
1274
1287
  note.portamento = true;
1275
1288
  this.setPortamentoStartVolumeEnvelope(channel, note, now);
1276
1289
  this.setPortamentoStartFilterEnvelope(channel, note, now);
@@ -1360,8 +1373,8 @@ class MidyGM2 {
1360
1373
  if (!channel.isDrum)
1361
1374
  return false;
1362
1375
  const programNumber = channel.programNumber;
1363
- return (programNumber === 48 && noteNumber === 88) ||
1364
- (programNumber === 56 && 47 <= noteNumber && noteNumber <= 84);
1376
+ return !((programNumber === 48 && noteNumber === 88) ||
1377
+ (programNumber === 56 && 47 <= noteNumber && noteNumber <= 84));
1365
1378
  }
1366
1379
  async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, portamento) {
1367
1380
  const channel = this.channels[channelNumber];
@@ -1383,20 +1396,21 @@ class MidyGM2 {
1383
1396
  this.handleExclusiveClass(note, channelNumber, startTime);
1384
1397
  this.handleDrumExclusiveClass(note, channelNumber, startTime);
1385
1398
  const scheduledNotes = channel.scheduledNotes;
1386
- let notes = scheduledNotes.get(noteNumber);
1387
- if (notes) {
1388
- notes.push(note);
1399
+ let noteList = scheduledNotes.get(noteNumber);
1400
+ if (noteList) {
1401
+ noteList.push(note);
1389
1402
  }
1390
1403
  else {
1391
- notes = [note];
1392
- scheduledNotes.set(noteNumber, notes);
1404
+ noteList = [note];
1405
+ scheduledNotes.set(noteNumber, noteList);
1393
1406
  }
1394
1407
  if (this.isDrumNoteOffException(channel, noteNumber)) {
1395
1408
  const stopTime = startTime + note.bufferSource.buffer.duration;
1396
- const index = notes.length - 1;
1409
+ const index = noteList.length - 1;
1397
1410
  const promise = new Promise((resolve) => {
1398
1411
  note.bufferSource.onended = () => {
1399
- this.disconnectNote(note, scheduledNotes, index);
1412
+ noteList[index] = undefined;
1413
+ this.disconnectNote(note);
1400
1414
  resolve();
1401
1415
  };
1402
1416
  note.bufferSource.stop(stopTime);
@@ -1408,8 +1422,7 @@ class MidyGM2 {
1408
1422
  scheduleTime ??= this.audioContext.currentTime;
1409
1423
  return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, false);
1410
1424
  }
1411
- disconnectNote(note, scheduledNotes, index) {
1412
- scheduledNotes[index] = null;
1425
+ disconnectNote(note) {
1413
1426
  note.bufferSource.disconnect();
1414
1427
  note.filterNode.disconnect();
1415
1428
  note.volumeEnvelopeNode.disconnect();
@@ -1432,8 +1445,8 @@ class MidyGM2 {
1432
1445
  note.chorusEffectsSend.disconnect();
1433
1446
  }
1434
1447
  }
1435
- stopNote(endTime, stopTime, scheduledNotes, index) {
1436
- const note = scheduledNotes[index];
1448
+ stopNote(endTime, stopTime, noteList, index) {
1449
+ const note = noteList[index];
1437
1450
  note.volumeEnvelopeNode.gain
1438
1451
  .cancelScheduledValues(endTime)
1439
1452
  .linearRampToValueAtTime(0, stopTime);
@@ -1443,12 +1456,23 @@ class MidyGM2 {
1443
1456
  }, stopTime);
1444
1457
  return new Promise((resolve) => {
1445
1458
  note.bufferSource.onended = () => {
1446
- this.disconnectNote(note, scheduledNotes, index);
1459
+ noteList[index] = undefined;
1460
+ this.disconnectNote(note);
1447
1461
  resolve();
1448
1462
  };
1449
1463
  note.bufferSource.stop(stopTime);
1450
1464
  });
1451
1465
  }
1466
+ findNoteOffTarget(noteList) {
1467
+ for (let i = 0; i < noteList.length; i++) {
1468
+ const note = noteList[i];
1469
+ if (!note)
1470
+ continue;
1471
+ if (note.ending)
1472
+ continue;
1473
+ return [note, i];
1474
+ }
1475
+ }
1452
1476
  scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force, portamentoNoteNumber) {
1453
1477
  const channel = this.channels[channelNumber];
1454
1478
  if (this.isDrumNoteOffException(channel, noteNumber))
@@ -1460,34 +1484,32 @@ class MidyGM2 {
1460
1484
  if (channel.sostenutoNotes.has(noteNumber))
1461
1485
  return;
1462
1486
  }
1463
- if (!channel.scheduledNotes.has(noteNumber))
1487
+ const noteList = channel.scheduledNotes.get(noteNumber);
1488
+ if (!noteList)
1489
+ return; // be careful with drum channel
1490
+ const noteOffTarget = this.findNoteOffTarget(noteList, endTime);
1491
+ if (!noteOffTarget)
1464
1492
  return;
1465
- const scheduledNotes = channel.scheduledNotes.get(noteNumber);
1466
- for (let i = 0; i < scheduledNotes.length; i++) {
1467
- const note = scheduledNotes[i];
1468
- if (!note)
1469
- continue;
1470
- if (note.ending)
1471
- continue;
1472
- if (portamentoNoteNumber === undefined) {
1473
- const volRelease = endTime + note.voiceParams.volRelease;
1474
- const modRelease = endTime + note.voiceParams.modRelease;
1475
- note.filterNode.frequency
1476
- .cancelScheduledValues(endTime)
1477
- .linearRampToValueAtTime(0, modRelease);
1478
- const stopTime = Math.min(volRelease, modRelease);
1479
- return this.stopNote(endTime, stopTime, scheduledNotes, i);
1480
- }
1481
- else {
1482
- const portamentoTime = endTime + this.getPortamentoTime(channel);
1483
- const deltaNote = portamentoNoteNumber - noteNumber;
1484
- const baseRate = note.voiceParams.playbackRate;
1485
- const targetRate = baseRate * Math.pow(2, deltaNote / 12);
1486
- note.bufferSource.playbackRate
1487
- .cancelScheduledValues(endTime)
1488
- .linearRampToValueAtTime(targetRate, portamentoTime);
1489
- return this.stopNote(endTime, portamentoTime, scheduledNotes, i);
1490
- }
1493
+ const [note, i] = noteOffTarget;
1494
+ if (0.5 <= state.portamento && portamentoNoteNumber !== undefined) {
1495
+ const portamentoTime = endTime + this.getPortamentoTime(channel);
1496
+ const deltaNote = portamentoNoteNumber - noteNumber;
1497
+ const baseRate = note.voiceParams.playbackRate;
1498
+ const targetRate = baseRate * Math.pow(2, deltaNote / 12);
1499
+ note.bufferSource.playbackRate
1500
+ .cancelScheduledValues(endTime)
1501
+ .linearRampToValueAtTime(targetRate, portamentoTime);
1502
+ return this.stopNote(endTime, portamentoTime, noteList, i);
1503
+ }
1504
+ else {
1505
+ const volRelease = endTime +
1506
+ note.voiceParams.volRelease * state.releaseTime * 2;
1507
+ const modRelease = endTime + note.voiceParams.modRelease;
1508
+ note.filterNode.frequency
1509
+ .cancelScheduledValues(endTime)
1510
+ .linearRampToValueAtTime(0, modRelease);
1511
+ const stopTime = Math.min(volRelease, modRelease);
1512
+ return this.stopNote(endTime, stopTime, noteList, i);
1491
1513
  }
1492
1514
  }
1493
1515
  noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
@@ -1568,7 +1590,7 @@ class MidyGM2 {
1568
1590
  this.getActiveNotes(channel, scheduleTime).forEach((note) => {
1569
1591
  this.setControllerParameters(channel, note, table);
1570
1592
  });
1571
- // this.applyVoiceParams(channel, 13);
1593
+ this.applyVoiceParams(channel, 13);
1572
1594
  }
1573
1595
  handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
1574
1596
  const pitchBend = msb * 128 + lsb;
@@ -1745,6 +1767,7 @@ class MidyGM2 {
1745
1767
  state.set(channel.state.array);
1746
1768
  state[2] = velocity / 127;
1747
1769
  state[3] = noteNumber / 127;
1770
+ state[13] = state.channelPressure / 127;
1748
1771
  return state;
1749
1772
  }
1750
1773
  applyVoiceParams(channel, controllerType, scheduleTime) {
@@ -1771,7 +1794,7 @@ class MidyGM2 {
1771
1794
  if (key in voiceParams)
1772
1795
  noteVoiceParams[key] = voiceParams[key];
1773
1796
  }
1774
- if (note.portamento) {
1797
+ if (0.5 <= channel.state.portamento && note.portamento) {
1775
1798
  this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
1776
1799
  }
1777
1800
  else {
@@ -1968,10 +1991,11 @@ class MidyGM2 {
1968
1991
  const channel = this.channels[channelNumber];
1969
1992
  if (channel.isDrum)
1970
1993
  return;
1994
+ const state = channel.state;
1971
1995
  scheduleTime ??= this.audioContext.currentTime;
1972
- channel.state.softPedal = softPedal / 127;
1996
+ state.softPedal = softPedal / 127;
1973
1997
  this.processScheduledNotes(channel, (note) => {
1974
- if (note.portamento) {
1998
+ if (0.5 <= state.portamento && note.portamento) {
1975
1999
  this.setPortamentoStartVolumeEnvelope(channel, note, scheduleTime);
1976
2000
  this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
1977
2001
  }
@@ -2170,7 +2194,7 @@ class MidyGM2 {
2170
2194
  }
2171
2195
  allSoundOff(channelNumber, _value, scheduleTime) {
2172
2196
  scheduleTime ??= this.audioContext.currentTime;
2173
- return this.stopChannelNotes(channelNumber, 0, true, scheduleTime);
2197
+ return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
2174
2198
  }
2175
2199
  resetAllStates(channelNumber) {
2176
2200
  const channel = this.channels[channelNumber];
@@ -2214,7 +2238,7 @@ class MidyGM2 {
2214
2238
  }
2215
2239
  allNotesOff(channelNumber, _value, scheduleTime) {
2216
2240
  scheduleTime ??= this.audioContext.currentTime;
2217
- return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
2241
+ return this.stopActiveNotes(channelNumber, 0, false, scheduleTime);
2218
2242
  }
2219
2243
  omniOff(channelNumber, value, scheduleTime) {
2220
2244
  this.allNotesOff(channelNumber, value, scheduleTime);
@@ -86,6 +86,7 @@ export class MidyGMLite {
86
86
  instruments: Set<any>;
87
87
  timeline: any[];
88
88
  };
89
+ stopActiveNotes(channelNumber: any, velocity: any, force: any, scheduleTime: any): Promise<any[]>;
89
90
  stopChannelNotes(channelNumber: any, velocity: any, force: any, scheduleTime: any): Promise<any[]>;
90
91
  stopNotes(velocity: any, force: any, scheduleTime: any): Promise<any[]>;
91
92
  start(): Promise<void>;
@@ -116,8 +117,9 @@ export class MidyGMLite {
116
117
  handleDrumExclusiveClass(note: any, channelNumber: any, startTime: any): void;
117
118
  scheduleNoteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any): Promise<void>;
118
119
  noteOn(channelNumber: any, noteNumber: any, velocity: any, scheduleTime: any): Promise<void>;
119
- disconnectNote(note: any, scheduledNotes: any, index: any): void;
120
- stopNote(endTime: any, stopTime: any, scheduledNotes: any, index: any): Promise<any>;
120
+ disconnectNote(note: any): void;
121
+ stopNote(endTime: any, stopTime: any, noteList: any, index: any): Promise<any>;
122
+ findNoteOffTarget(noteList: any): any[] | undefined;
121
123
  scheduleNoteOff(channelNumber: any, noteNumber: any, _velocity: any, endTime: any, force: any): Promise<any> | undefined;
122
124
  noteOff(channelNumber: any, noteNumber: any, velocity: any, scheduleTime: any): Promise<any> | undefined;
123
125
  releaseSustainPedal(channelNumber: any, halfVelocity: any, scheduleTime: any): (Promise<any> | undefined)[];
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AA+JA;IA2BE;;;;;;;;;MASE;IAEF,+BAcC;IAnDD,aAAa;IACb,oBAAiB;IACjB,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,kCAA+B;IAC/B,gCAA6B;IAC7B,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IAClB,2BAAqC;IACrC,+BAEE;IAcA,kBAAgC;IAChC,kBAA8C;IAC9C,eAAwD;IACxD,qBAGE;IACF;;;;;;;;;;;MAA2D;IAC3D;;;;;;;;;;;;;MAA+D;IAC/D,gBAAiD;IAMnD,4BAMC;IAED,mCAWC;IAED,gDAMC;IAED,sCASC;IAED;;;;MAeC;IAED,yCAaC;IAED,6DA2BC;IAED,4DASC;IAED,2EAsDC;IAED,mCAOC;IAED,0BAoDC;IAED,uDAEC;IAED,wDAEC;IAED,6EAEC;IAED;;;MA4EC;IAED,mGAgBC;IAED,wEAMC;IAED,uBAKC;IAED,aAMC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,yDAQC;IAED,2DASC;IAED,qDAQC;IAED,2BAEC;IAED,8BAEC;IAED,8BAEC;IAED,4BAEC;IAED,wCAIC;IAED,2DAIC;IAED,+DAIC;IAED,sDAeC;IAED,qDAoBC;IAED,6CAIC;IAED,sDAsBC;IAED,kEAoBC;IAED,+GA0BC;IAED,gHAwCC;IAED,0EAiBC;IAED,8EAiBC;IAED,kGAmDC;IAED,6FAQC;IAED,iEAUC;IAED,qFAgBC;IAED,yHAwBC;IAED,yGASC;IAED,4GAeC;IAED,mGA2BC;IAED,sFAGC;IAED,wFAGC;IAED,sEAUC;IAED,mEAQC;IAED,wDAKC;IAED,sDAOC;IAED,mDAMC;IAED,kDAKC;IAED;;;;;;;;;;;MA2BC;IAED,oFAMC;IAED,6EA2CC;IAED;;;;;;;;;;;;;MAeC;IAED,kGAWC;IAED,wDAUC;IAED,iFAKC;IAED,oEAKC;IAED;;;MAMC;IAED,8DAKC;IAED,4EAKC;IAED,sEAGC;IAED,2DAUC;IAED,yEAWC;IAED,kFAeC;IAED,uDAYC;IAED,gDAEC;IAED,gDAEC;IAED,sEAGC;IAED,qEAKC;IAED,2EAUC;IAED,gFAGC;IAED,yCAUC;IAGD,8CAqBC;IAED,gFAGC;IAED,+EAgBC;IAED,qCAWC;IAED,4EAaC;IAED,4DAGC;IAED,sDASC;IAED,gDAYC;IAGD,6DAgBC;CACF;AA79CD;IACE,uBAGC;IAFC,YAA2B;IAC3B,qBAAuB;IAGzB,gCAKC;IAED,mBAEC;IAED,0BAUC;IAED,uBAEC;IAED,mBAEC;IAED,cAMC;IASD,6BAKC;IAZD,qDAKC;CAQF;AAED;IASE,0FAMC;IAdD,kBAAa;IACb,gBAAW;IACX,iBAAY;IACZ,wBAAmB;IACnB,iBAAY;IACZ,mBAAc;IACd,qBAAgB;IAGd,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,WAAkB;IAClB,iBAA8B;CAEjC"}
1
+ {"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AA+JA;IA2BE;;;;;;;;;MASE;IAEF,+BAcC;IAnDD,aAAa;IACb,oBAAiB;IACjB,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,kCAA+B;IAC/B,gCAA6B;IAC7B,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IAClB,2BAAqC;IACrC,+BAEE;IAcA,kBAAgC;IAChC,kBAA8C;IAC9C,eAAwD;IACxD,qBAGE;IACF;;;;;;;;;;;MAA2D;IAC3D;;;;;;;;;;;;;MAA+D;IAC/D,gBAAiD;IAMnD,4BAMC;IAED,mCAWC;IAED,gDAMC;IAED,sCASC;IAED;;;;MAeC;IAED,yCAaC;IAED,6DA2BC;IAED,4DASC;IAED,2EAsDC;IAED,mCAOC;IAED,0BAoDC;IAED,uDAEC;IAED,wDAEC;IAED,6EAEC;IAED;;;MA4EC;IAED,kGAiBC;IAED,mGAgBC;IAED,wEAMC;IAED,uBAKC;IAED,aAMC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,yDASC;IAED,2DASC;IAED,qDAQC;IAED,2BAEC;IAED,8BAEC;IAED,8BAEC;IAED,4BAEC;IAED,wCAIC;IAED,2DAIC;IAED,+DAIC;IAED,sDAeC;IAED,qDAoBC;IAED,6CAIC;IAED,sDAsBC;IAED,kEAoBC;IAED,+GA0BC;IAED,gHAwCC;IAED,0EAiBC;IAED,8EAiBC;IAED,kGAoDC;IAED,6FAQC;IAED,gCASC;IAED,+EAiBC;IAED,oDAOC;IAED,yHAuBC;IAED,yGASC;IAED,4GAeC;IAED,mGA2BC;IAED,sFAGC;IAED,wFAGC;IAED,sEAUC;IAED,mEAQC;IAED,wDAKC;IAED,sDAOC;IAED,mDAMC;IAED,kDAKC;IAED;;;;;;;;;;;MA2BC;IAED,oFAMC;IAED,6EA2CC;IAED;;;;;;;;;;;;;MAeC;IAED,kGAWC;IAED,wDAUC;IAED,iFAKC;IAED,oEAKC;IAED;;;MAMC;IAED,8DAKC;IAED,4EAKC;IAED,sEAGC;IAED,2DAUC;IAED,yEAWC;IAED,kFAeC;IAED,uDAYC;IAED,gDAEC;IAED,gDAEC;IAED,sEAGC;IAED,qEAKC;IAED,2EAUC;IAED,gFAGC;IAED,yCAUC;IAGD,8CAqBC;IAED,gFAGC;IAED,+EAgBC;IAED,qCAWC;IAED,4EAaC;IAED,4DAGC;IAED,sDASC;IAED,gDAYC;IAGD,6DAgBC;CACF;AA1/CD;IACE,uBAGC;IAFC,YAA2B;IAC3B,qBAAuB;IAGzB,gCAKC;IAED,mBAEC;IAED,0BAUC;IAED,uBAEC;IAED,mBAEC;IAED,cAMC;IASD,6BAKC;IAZD,qDAKC;CAQF;AAED;IASE,0FAMC;IAdD,kBAAa;IACb,gBAAW;IACX,iBAAY;IACZ,wBAAmB;IACnB,iBAAY;IACZ,mBAAc;IACd,qBAAgB;IAGd,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,WAAkB;IAClB,iBAA8B;CAEjC"}
@@ -449,7 +449,7 @@ class MidyGMLite {
449
449
  const startTime = event.startTime + this.startDelay - offset;
450
450
  switch (event.type) {
451
451
  case "noteOn":
452
- if (event.velocity !== 0) {
452
+ if (0 < event.velocity) {
453
453
  await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
454
454
  break;
455
455
  }
@@ -497,7 +497,7 @@ class MidyGMLite {
497
497
  if (queueIndex >= this.timeline.length) {
498
498
  await Promise.all(this.notePromises);
499
499
  this.notePromises = [];
500
- this.exclusiveClassNotes.flll(undefined);
500
+ this.exclusiveClassNotes.fill(undefined);
501
501
  this.audioBufferCache.clear();
502
502
  resolve();
503
503
  return;
@@ -616,6 +616,17 @@ class MidyGMLite {
616
616
  }
617
617
  return { instruments, timeline };
618
618
  }
619
+ stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
620
+ const channel = this.channels[channelNumber];
621
+ const promises = [];
622
+ const activeNotes = this.getActiveNotes(channel, scheduleTime);
623
+ activeNotes.forEach((note) => {
624
+ const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
625
+ this.notePromises.push(promise);
626
+ promises.push(promise);
627
+ });
628
+ return Promise.all(promises);
629
+ }
619
630
  stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
620
631
  const channel = this.channels[channelNumber];
621
632
  const promises = [];
@@ -687,6 +698,8 @@ class MidyGMLite {
687
698
  const note = noteList[i];
688
699
  if (!note)
689
700
  continue;
701
+ if (note.ending)
702
+ continue;
690
703
  callback(note);
691
704
  }
692
705
  });
@@ -914,20 +927,21 @@ class MidyGMLite {
914
927
  this.handleExclusiveClass(note, channelNumber, startTime);
915
928
  this.handleDrumExclusiveClass(note, channelNumber, startTime);
916
929
  const scheduledNotes = channel.scheduledNotes;
917
- let notes = scheduledNotes.get(noteNumber);
918
- if (notes) {
919
- notes.push(note);
930
+ let noteList = scheduledNotes.get(noteNumber);
931
+ if (noteList) {
932
+ noteList.push(note);
920
933
  }
921
934
  else {
922
- notes = [note];
923
- scheduledNotes.set(noteNumber, notes);
935
+ noteList = [note];
936
+ scheduledNotes.set(noteNumber, noteList);
924
937
  }
925
- if (this.isDrumNoteOffException(channel, noteNumber)) {
938
+ if (channel.isDrum) {
926
939
  const stopTime = startTime + note.bufferSource.buffer.duration;
927
- const index = notes.length - 1;
940
+ const index = noteList.length - 1;
928
941
  const promise = new Promise((resolve) => {
929
942
  note.bufferSource.onended = () => {
930
- this.disconnectNote(note, scheduledNotes, index);
943
+ noteList[index] = undefined;
944
+ this.disconnectNote(note);
931
945
  resolve();
932
946
  };
933
947
  note.bufferSource.stop(stopTime);
@@ -939,8 +953,7 @@ class MidyGMLite {
939
953
  scheduleTime ??= this.audioContext.currentTime;
940
954
  return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime);
941
955
  }
942
- disconnectNote(note, scheduledNotes, index) {
943
- scheduledNotes[index] = null;
956
+ disconnectNote(note) {
944
957
  note.bufferSource.disconnect();
945
958
  note.filterNode.disconnect();
946
959
  note.volumeEnvelopeNode.disconnect();
@@ -950,8 +963,8 @@ class MidyGMLite {
950
963
  note.modulationLFO.stop();
951
964
  }
952
965
  }
953
- stopNote(endTime, stopTime, scheduledNotes, index) {
954
- const note = scheduledNotes[index];
966
+ stopNote(endTime, stopTime, noteList, index) {
967
+ const note = noteList[index];
955
968
  note.volumeEnvelopeNode.gain
956
969
  .cancelScheduledValues(endTime)
957
970
  .linearRampToValueAtTime(0, stopTime);
@@ -961,12 +974,23 @@ class MidyGMLite {
961
974
  }, stopTime);
962
975
  return new Promise((resolve) => {
963
976
  note.bufferSource.onended = () => {
964
- this.disconnectNote(note, scheduledNotes, index);
977
+ noteList[index] = undefined;
978
+ this.disconnectNote(note);
965
979
  resolve();
966
980
  };
967
981
  note.bufferSource.stop(stopTime);
968
982
  });
969
983
  }
984
+ findNoteOffTarget(noteList) {
985
+ for (let i = 0; i < noteList.length; i++) {
986
+ const note = noteList[i];
987
+ if (!note)
988
+ continue;
989
+ if (note.ending)
990
+ continue;
991
+ return [note, i];
992
+ }
993
+ }
970
994
  scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
971
995
  const channel = this.channels[channelNumber];
972
996
  if (channel.isDrum)
@@ -975,21 +999,20 @@ class MidyGMLite {
975
999
  return;
976
1000
  if (!channel.scheduledNotes.has(noteNumber))
977
1001
  return;
978
- const scheduledNotes = channel.scheduledNotes.get(noteNumber);
979
- for (let i = 0; i < scheduledNotes.length; i++) {
980
- const note = scheduledNotes[i];
981
- if (!note)
982
- continue;
983
- if (note.ending)
984
- continue;
985
- const volRelease = endTime + note.voiceParams.volRelease;
986
- const modRelease = endTime + note.voiceParams.modRelease;
987
- note.filterNode.frequency
988
- .cancelScheduledValues(endTime)
989
- .linearRampToValueAtTime(0, modRelease);
990
- const stopTime = Math.min(volRelease, modRelease);
991
- return this.stopNote(endTime, stopTime, scheduledNotes, i);
992
- }
1002
+ const noteList = channel.scheduledNotes.get(noteNumber);
1003
+ if (!noteList)
1004
+ return; // be careful with drum channel
1005
+ const noteOffTarget = this.findNoteOffTarget(noteList, endTime);
1006
+ if (!noteOffTarget)
1007
+ return;
1008
+ const [note, i] = noteOffTarget;
1009
+ const volRelease = endTime + note.voiceParams.volRelease;
1010
+ const modRelease = endTime + note.voiceParams.modRelease;
1011
+ note.filterNode.frequency
1012
+ .cancelScheduledValues(endTime)
1013
+ .linearRampToValueAtTime(0, modRelease);
1014
+ const stopTime = Math.min(volRelease, modRelease);
1015
+ return this.stopNote(endTime, stopTime, noteList, i);
993
1016
  }
994
1017
  noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
995
1018
  scheduleTime ??= this.audioContext.currentTime;
@@ -1311,7 +1334,7 @@ class MidyGMLite {
1311
1334
  }
1312
1335
  allSoundOff(channelNumber, _value, scheduleTime) {
1313
1336
  scheduleTime ??= this.audioContext.currentTime;
1314
- return this.stopChannelNotes(channelNumber, 0, true, scheduleTime);
1337
+ return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
1315
1338
  }
1316
1339
  resetAllStates(channelNumber) {
1317
1340
  const channel = this.channels[channelNumber];
@@ -1349,7 +1372,7 @@ class MidyGMLite {
1349
1372
  }
1350
1373
  allNotesOff(channelNumber, _value, scheduleTime) {
1351
1374
  scheduleTime ??= this.audioContext.currentTime;
1352
- return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
1375
+ return this.stopActiveNotes(channelNumber, 0, false, scheduleTime);
1353
1376
  }
1354
1377
  handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime) {
1355
1378
  switch (data[2]) {