@marmooo/midy 0.3.2 → 0.3.4

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
@@ -11,11 +11,11 @@ class Note {
11
11
  writable: true,
12
12
  value: -1
13
13
  });
14
- Object.defineProperty(this, "noteOffEvent", {
14
+ Object.defineProperty(this, "ending", {
15
15
  enumerable: true,
16
16
  configurable: true,
17
17
  writable: true,
18
- value: void 0
18
+ value: false
19
19
  });
20
20
  Object.defineProperty(this, "bufferSource", {
21
21
  enumerable: true,
@@ -47,24 +47,6 @@ class Note {
47
47
  writable: true,
48
48
  value: void 0
49
49
  });
50
- Object.defineProperty(this, "volumeNode", {
51
- enumerable: true,
52
- configurable: true,
53
- writable: true,
54
- value: void 0
55
- });
56
- Object.defineProperty(this, "gainL", {
57
- enumerable: true,
58
- configurable: true,
59
- writable: true,
60
- value: void 0
61
- });
62
- Object.defineProperty(this, "gainR", {
63
- enumerable: true,
64
- configurable: true,
65
- writable: true,
66
- value: void 0
67
- });
68
50
  Object.defineProperty(this, "modulationLFO", {
69
51
  enumerable: true,
70
52
  configurable: true,
@@ -217,6 +199,16 @@ class ControllerState {
217
199
  }
218
200
  }
219
201
  }
202
+ const volumeEnvelopeKeys = [
203
+ "volDelay",
204
+ "volAttack",
205
+ "volHold",
206
+ "volDecay",
207
+ "volSustain",
208
+ "volRelease",
209
+ "initialAttenuation",
210
+ ];
211
+ const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
220
212
  const filterEnvelopeKeys = [
221
213
  "modEnvToPitch",
222
214
  "initialFilterFc",
@@ -226,22 +218,20 @@ const filterEnvelopeKeys = [
226
218
  "modHold",
227
219
  "modDecay",
228
220
  "modSustain",
229
- "modRelease",
230
- "playbackRate",
231
221
  ];
232
222
  const filterEnvelopeKeySet = new Set(filterEnvelopeKeys);
233
- const volumeEnvelopeKeys = [
234
- "volDelay",
235
- "volAttack",
236
- "volHold",
237
- "volDecay",
238
- "volSustain",
239
- "volRelease",
240
- "initialAttenuation",
223
+ const pitchEnvelopeKeys = [
224
+ "modEnvToPitch",
225
+ "modDelay",
226
+ "modAttack",
227
+ "modHold",
228
+ "modDecay",
229
+ "modSustain",
230
+ "playbackRate",
241
231
  ];
242
- const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
232
+ const pitchEnvelopeKeySet = new Set(pitchEnvelopeKeys);
243
233
  class Midy {
244
- constructor(audioContext, options = this.defaultOptions) {
234
+ constructor(audioContext) {
245
235
  Object.defineProperty(this, "mode", {
246
236
  enumerable: true,
247
237
  configurable: true,
@@ -265,6 +255,7 @@ class Midy {
265
255
  configurable: true,
266
256
  writable: true,
267
257
  value: {
258
+ algorithm: "SchroederReverb",
268
259
  time: this.getReverbTime(64),
269
260
  feedback: 0.8,
270
261
  }
@@ -413,30 +404,7 @@ class Midy {
413
404
  writable: true,
414
405
  value: new Array(this.numChannels * drumExclusiveClassCount)
415
406
  });
416
- Object.defineProperty(this, "defaultOptions", {
417
- enumerable: true,
418
- configurable: true,
419
- writable: true,
420
- value: {
421
- reverbAlgorithm: (audioContext) => {
422
- const { time: rt60, feedback } = this.reverb;
423
- // const delay = this.calcDelay(rt60, feedback);
424
- // const impulse = this.createConvolutionReverbImpulse(
425
- // audioContext,
426
- // rt60,
427
- // delay,
428
- // );
429
- // return this.createConvolutionReverb(audioContext, impulse);
430
- const combFeedbacks = this.generateDistributedArray(feedback, 4);
431
- const combDelays = combFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
432
- const allpassFeedbacks = this.generateDistributedArray(feedback, 4);
433
- const allpassDelays = allpassFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
434
- return this.createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays);
435
- },
436
- }
437
- });
438
407
  this.audioContext = audioContext;
439
- this.options = { ...this.defaultOptions, ...options };
440
408
  this.masterVolume = new GainNode(audioContext);
441
409
  this.scheduler = new GainNode(audioContext, { gain: 0 });
442
410
  this.schedulerBuffer = new AudioBuffer({
@@ -446,7 +414,7 @@ class Midy {
446
414
  this.voiceParamsHandlers = this.createVoiceParamsHandlers();
447
415
  this.controlChangeHandlers = this.createControlChangeHandlers();
448
416
  this.channels = this.createChannels(audioContext);
449
- this.reverbEffect = this.options.reverbAlgorithm(audioContext);
417
+ this.reverbEffect = this.createReverbEffect(audioContext);
450
418
  this.chorusEffect = this.createChorusEffect(audioContext);
451
419
  this.chorusEffect.output.connect(this.masterVolume);
452
420
  this.reverbEffect.output.connect(this.masterVolume);
@@ -473,24 +441,44 @@ class Midy {
473
441
  }
474
442
  }
475
443
  }
476
- async loadSoundFont(soundFontUrl) {
477
- const response = await fetch(soundFontUrl);
478
- const arrayBuffer = await response.arrayBuffer();
479
- const parsed = (0, soundfont_parser_1.parse)(new Uint8Array(arrayBuffer));
444
+ async loadSoundFont(input) {
445
+ let uint8Array;
446
+ if (typeof input === "string") {
447
+ const response = await fetch(input);
448
+ const arrayBuffer = await response.arrayBuffer();
449
+ uint8Array = new Uint8Array(arrayBuffer);
450
+ }
451
+ else if (input instanceof Uint8Array) {
452
+ uint8Array = input;
453
+ }
454
+ else {
455
+ throw new TypeError("input must be a URL string or Uint8Array");
456
+ }
457
+ const parsed = (0, soundfont_parser_1.parse)(uint8Array);
480
458
  const soundFont = new soundfont_parser_1.SoundFont(parsed);
481
459
  this.addSoundFont(soundFont);
482
460
  }
483
- async loadMIDI(midiUrl) {
484
- const response = await fetch(midiUrl);
485
- const arrayBuffer = await response.arrayBuffer();
486
- const midi = (0, midi_file_1.parseMidi)(new Uint8Array(arrayBuffer));
461
+ async loadMIDI(input) {
462
+ let uint8Array;
463
+ if (typeof input === "string") {
464
+ const response = await fetch(input);
465
+ const arrayBuffer = await response.arrayBuffer();
466
+ uint8Array = new Uint8Array(arrayBuffer);
467
+ }
468
+ else if (input instanceof Uint8Array) {
469
+ uint8Array = input;
470
+ }
471
+ else {
472
+ throw new TypeError("input must be a URL string or Uint8Array");
473
+ }
474
+ const midi = (0, midi_file_1.parseMidi)(uint8Array);
487
475
  this.ticksPerBeat = midi.header.ticksPerBeat;
488
476
  const midiData = this.extractMidiData(midi);
489
477
  this.instruments = midiData.instruments;
490
478
  this.timeline = midiData.timeline;
491
479
  this.totalTime = this.calcTotalTime();
492
480
  }
493
- setChannelAudioNodes(audioContext) {
481
+ createChannelAudioNodes(audioContext) {
494
482
  const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
495
483
  const gainL = new GainNode(audioContext, { gain: gainLeft });
496
484
  const gainR = new GainNode(audioContext, { gain: gainRight });
@@ -505,11 +493,11 @@ class Midy {
505
493
  };
506
494
  }
507
495
  resetChannelTable(channel) {
508
- this.resetControlTable(channel.controlTable);
496
+ channel.controlTable.fill(-1);
509
497
  channel.scaleOctaveTuningTable.fill(0); // [-100, 100] cent
510
- channel.channelPressureTable.set([64, 64, 64, 0, 0, 0]);
511
- channel.polyphonicKeyPressureTable.set([64, 64, 64, 0, 0, 0]);
512
- channel.keyBasedInstrumentControlTable.fill(0); // [-64, 63]
498
+ channel.channelPressureTable.fill(-1);
499
+ channel.polyphonicKeyPressureTable.fill(-1);
500
+ channel.keyBasedInstrumentControlTable.fill(-1);
513
501
  }
514
502
  createChannels(audioContext) {
515
503
  const channels = Array.from({ length: this.numChannels }, () => {
@@ -518,15 +506,17 @@ class Midy {
518
506
  isDrum: false,
519
507
  state: new ControllerState(),
520
508
  ...this.constructor.channelSettings,
521
- ...this.setChannelAudioNodes(audioContext),
509
+ ...this.createChannelAudioNodes(audioContext),
522
510
  scheduledNotes: [],
523
511
  sustainNotes: [],
524
512
  sostenutoNotes: [],
525
513
  controlTable: this.initControlTable(),
526
514
  scaleOctaveTuningTable: new Float32Array(12), // [-100, 100] cent
527
- channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
528
- polyphonicKeyPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
529
- keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
515
+ channelPressureTable: new Int8Array(6).fill(-1),
516
+ polyphonicKeyPressureTable: new Int8Array(6).fill(-1),
517
+ keyBasedInstrumentControlTable: new Int8Array(128 * 128).fill(-1),
518
+ keyBasedGainLs: new Array(128),
519
+ keyBasedGainRs: new Array(128),
530
520
  };
531
521
  });
532
522
  return channels;
@@ -560,10 +550,17 @@ class Midy {
560
550
  return audioBuffer;
561
551
  }
562
552
  }
563
- createBufferSource(voiceParams, audioBuffer) {
553
+ isLoopDrum(channel, noteNumber) {
554
+ const programNumber = channel.programNumber;
555
+ return ((programNumber === 48 && noteNumber === 88) ||
556
+ (programNumber === 56 && 47 <= noteNumber && noteNumber <= 84));
557
+ }
558
+ createBufferSource(channel, noteNumber, voiceParams, audioBuffer) {
564
559
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
565
560
  bufferSource.buffer = audioBuffer;
566
- bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
561
+ bufferSource.loop = channel.isDrum
562
+ ? this.isLoopDrum(channel, noteNumber)
563
+ : (voiceParams.sampleModes % 2 !== 0);
567
564
  if (bufferSource.loop) {
568
565
  bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
569
566
  bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
@@ -578,12 +575,13 @@ class Midy {
578
575
  const delay = this.startDelay - resumeTime;
579
576
  const startTime = event.startTime + delay;
580
577
  switch (event.type) {
581
- case "noteOn": {
582
- const noteOffEvent = {
583
- ...event.noteOffEvent,
584
- startTime: event.noteOffEvent.startTime + delay,
585
- };
586
- await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, noteOffEvent);
578
+ case "noteOn":
579
+ await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
580
+ break;
581
+ case "noteOff": {
582
+ const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
583
+ if (notePromise)
584
+ this.notePromises.push(notePromise);
587
585
  break;
588
586
  }
589
587
  case "noteAftertouch":
@@ -691,6 +689,7 @@ class Midy {
691
689
  return `${programNumber}:${noteNumber}:${velocity}`;
692
690
  }
693
691
  extractMidiData(midi) {
692
+ this.audioBufferCounter.clear();
694
693
  const instruments = new Set();
695
694
  const timeline = [];
696
695
  const tmpChannels = new Array(this.channels.length);
@@ -790,38 +789,13 @@ class Midy {
790
789
  prevTempoTicks = event.ticks;
791
790
  }
792
791
  }
793
- const activeNotes = new Array(this.channels.length * 128);
794
- for (let i = 0; i < activeNotes.length; i++) {
795
- activeNotes[i] = [];
796
- }
797
- for (let i = 0; i < timeline.length; i++) {
798
- const event = timeline[i];
799
- switch (event.type) {
800
- case "noteOn": {
801
- const index = event.channel * 128 + event.noteNumber;
802
- activeNotes[index].push(event);
803
- break;
804
- }
805
- case "noteOff": {
806
- const index = event.channel * 128 + event.noteNumber;
807
- const noteOn = activeNotes[index].pop();
808
- if (noteOn) {
809
- noteOn.noteOffEvent = event;
810
- }
811
- else {
812
- const eventString = JSON.stringify(event, null, 2);
813
- console.warn(`noteOff without matching noteOn: ${eventString}`);
814
- }
815
- }
816
- }
817
- }
818
792
  return { instruments, timeline };
819
793
  }
820
794
  stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
821
795
  const channel = this.channels[channelNumber];
822
796
  const promises = [];
823
797
  this.processActiveNotes(channel, scheduleTime, (note) => {
824
- const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
798
+ const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
825
799
  this.notePromises.push(promise);
826
800
  promises.push(promise);
827
801
  });
@@ -830,8 +804,8 @@ class Midy {
830
804
  stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
831
805
  const channel = this.channels[channelNumber];
832
806
  const promises = [];
833
- this.processScheduledNotes(channel, (note) => {
834
- const promise = this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, force);
807
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
808
+ const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
835
809
  this.notePromises.push(promise);
836
810
  promises.push(promise);
837
811
  });
@@ -889,7 +863,7 @@ class Midy {
889
863
  const now = this.audioContext.currentTime;
890
864
  return this.resumeTime + now - this.startTime - this.startDelay;
891
865
  }
892
- processScheduledNotes(channel, callback) {
866
+ processScheduledNotes(channel, scheduleTime, callback) {
893
867
  const scheduledNotes = channel.scheduledNotes;
894
868
  for (let i = 0; i < scheduledNotes.length; i++) {
895
869
  const note = scheduledNotes[i];
@@ -897,6 +871,8 @@ class Midy {
897
871
  continue;
898
872
  if (note.ending)
899
873
  continue;
874
+ if (note.startTime < scheduleTime)
875
+ continue;
900
876
  callback(note);
901
877
  }
902
878
  }
@@ -908,11 +884,8 @@ class Midy {
908
884
  continue;
909
885
  if (note.ending)
910
886
  continue;
911
- const noteOffEvent = note.noteOffEvent;
912
- if (noteOffEvent && noteOffEvent.startTime < scheduleTime)
913
- continue;
914
887
  if (scheduleTime < note.startTime)
915
- continue;
888
+ break;
916
889
  callback(note);
917
890
  }
918
891
  }
@@ -1001,6 +974,22 @@ class Midy {
1001
974
  const output = allpasses.at(-1);
1002
975
  return { input, output };
1003
976
  }
977
+ createReverbEffect(audioContext) {
978
+ const { algorithm, time: rt60, feedback } = this.reverb;
979
+ switch (algorithm) {
980
+ case "ConvolutionReverb": {
981
+ const impulse = this.createConvolutionReverbImpulse(audioContext, rt60, this.calcDelay(rt60, feedback));
982
+ return this.createConvolutionReverb(audioContext, impulse);
983
+ }
984
+ case "SchroederReverb": {
985
+ const combFeedbacks = this.generateDistributedArray(feedback, 4);
986
+ const combDelays = combFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
987
+ const allpassFeedbacks = this.generateDistributedArray(feedback, 4);
988
+ const allpassDelays = allpassFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
989
+ return this.createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays);
990
+ }
991
+ }
992
+ }
1004
993
  createChorusEffect(audioContext) {
1005
994
  const input = new GainNode(audioContext);
1006
995
  const output = new GainNode(audioContext);
@@ -1065,15 +1054,22 @@ class Midy {
1065
1054
  const pitchWheel = channel.state.pitchWheel * 2 - 1;
1066
1055
  const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
1067
1056
  const pitch = pitchWheel * pitchWheelSensitivity;
1068
- const pressureDepth = (channel.channelPressureTable[0] - 64) / 37.5; // 2400 / 64;
1069
- const pressure = pressureDepth * channel.state.channelPressure;
1070
- return tuning + pitch + pressure;
1057
+ const channelPressureRaw = channel.channelPressureTable[0];
1058
+ if (0 <= channelPressureRaw) {
1059
+ const channelPressureDepth = (channelPressureRaw - 64) / 37.5; // 2400 / 64;
1060
+ const channelPressure = channelPressureDepth *
1061
+ channel.state.channelPressure;
1062
+ return tuning + pitch + channelPressure;
1063
+ }
1064
+ else {
1065
+ return tuning + pitch;
1066
+ }
1071
1067
  }
1072
1068
  calcNoteDetune(channel, note) {
1073
1069
  return channel.scaleOctaveTuningTable[note.noteNumber % 12];
1074
1070
  }
1075
1071
  updateChannelDetune(channel, scheduleTime) {
1076
- this.processScheduledNotes(channel, (note) => {
1072
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
1077
1073
  this.updateDetune(channel, note, scheduleTime);
1078
1074
  });
1079
1075
  }
@@ -1223,9 +1219,8 @@ class Midy {
1223
1219
  }
1224
1220
  setPortamentoFilterEnvelope(channel, note, scheduleTime) {
1225
1221
  const state = channel.state;
1226
- const { voiceParams, noteNumber, startTime } = note;
1227
- const softPedalFactor = 1 -
1228
- (0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
1222
+ const { voiceParams, startTime } = note;
1223
+ const softPedalFactor = this.getSoftPedalFactor(channel, note);
1229
1224
  const baseCent = voiceParams.initialFilterFc +
1230
1225
  this.getFilterCutoffControl(channel, note);
1231
1226
  const baseFreq = this.centToHz(baseCent) * softPedalFactor *
@@ -1245,9 +1240,8 @@ class Midy {
1245
1240
  }
1246
1241
  setFilterEnvelope(channel, note, scheduleTime) {
1247
1242
  const state = channel.state;
1248
- const { voiceParams, noteNumber, startTime } = note;
1249
- const softPedalFactor = 1 -
1250
- (0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
1243
+ const { voiceParams, startTime } = note;
1244
+ const softPedalFactor = this.getSoftPedalFactor(channel, note);
1251
1245
  const baseCent = voiceParams.initialFilterFc +
1252
1246
  this.getFilterCutoffControl(channel, note);
1253
1247
  const baseFreq = this.centToHz(baseCent) * softPedalFactor *
@@ -1324,14 +1318,11 @@ class Midy {
1324
1318
  async createNote(channel, voice, noteNumber, velocity, startTime, isSF3) {
1325
1319
  const now = this.audioContext.currentTime;
1326
1320
  const state = channel.state;
1327
- const controllerState = this.getControllerState(channel, noteNumber, velocity);
1321
+ const controllerState = this.getControllerState(channel, noteNumber, velocity, 0);
1328
1322
  const voiceParams = voice.getAllParams(controllerState);
1329
1323
  const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
1330
1324
  const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
1331
- note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
1332
- note.volumeNode = new GainNode(this.audioContext);
1333
- note.gainL = new GainNode(this.audioContext);
1334
- note.gainR = new GainNode(this.audioContext);
1325
+ note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
1335
1326
  note.volumeEnvelopeNode = new GainNode(this.audioContext);
1336
1327
  note.filterNode = new BiquadFilterNode(this.audioContext, {
1337
1328
  type: "lowpass",
@@ -1364,13 +1355,10 @@ class Midy {
1364
1355
  }
1365
1356
  note.bufferSource.connect(note.filterNode);
1366
1357
  note.filterNode.connect(note.volumeEnvelopeNode);
1367
- note.volumeEnvelopeNode.connect(note.volumeNode);
1368
- note.volumeNode.connect(note.gainL);
1369
- note.volumeNode.connect(note.gainR);
1370
- if (0 < channel.chorusSendLevel) {
1358
+ if (0 < state.chorusSendLevel) {
1371
1359
  this.setChorusEffectsSend(channel, note, 0, now);
1372
1360
  }
1373
- if (0 < channel.reverbSendLevel) {
1361
+ if (0 < state.reverbSendLevel) {
1374
1362
  this.setReverbEffectsSend(channel, note, 0, now);
1375
1363
  }
1376
1364
  note.bufferSource.start(startTime);
@@ -1400,7 +1388,7 @@ class Midy {
1400
1388
  if (prev) {
1401
1389
  const [prevNote, prevChannelNumber] = prev;
1402
1390
  if (prevNote && !prevNote.ending) {
1403
- this.scheduleNoteOff(prevChannelNumber, prevNote, 0, // velocity,
1391
+ this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
1404
1392
  startTime, true);
1405
1393
  }
1406
1394
  }
@@ -1420,19 +1408,12 @@ class Midy {
1420
1408
  channelNumber;
1421
1409
  const prevNote = this.drumExclusiveClassNotes[index];
1422
1410
  if (prevNote && !prevNote.ending) {
1423
- this.scheduleNoteOff(channelNumber, prevNote, 0, // velocity,
1411
+ this.scheduleNoteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
1424
1412
  startTime, true);
1425
1413
  }
1426
1414
  this.drumExclusiveClassNotes[index] = note;
1427
1415
  }
1428
- isDrumNoteOffException(channel, noteNumber) {
1429
- if (!channel.isDrum)
1430
- return false;
1431
- const programNumber = channel.programNumber;
1432
- return !((programNumber === 48 && noteNumber === 88) ||
1433
- (programNumber === 56 && 47 <= noteNumber && noteNumber <= 84));
1434
- }
1435
- async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, noteOffEvent) {
1416
+ async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
1436
1417
  const channel = this.channels[channelNumber];
1437
1418
  const bankNumber = this.calcBank(channel, channelNumber);
1438
1419
  const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
@@ -1444,9 +1425,18 @@ class Midy {
1444
1425
  return;
1445
1426
  const isSF3 = soundFont.parsed.info.version.major === 3;
1446
1427
  const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
1447
- note.noteOffEvent = noteOffEvent;
1448
- note.gainL.connect(channel.gainL);
1449
- note.gainR.connect(channel.gainR);
1428
+ if (channel.isDrum) {
1429
+ const audioContext = this.audioContext;
1430
+ const { gainL, gainR } = this.createChannelAudioNodes(audioContext);
1431
+ channel.keyBasedGainLs[noteNumber] = gainL;
1432
+ channel.keyBasedGainRs[noteNumber] = gainR;
1433
+ note.volumeEnvelopeNode.connect(gainL);
1434
+ note.volumeEnvelopeNode.connect(gainR);
1435
+ }
1436
+ else {
1437
+ note.volumeEnvelopeNode.connect(channel.gainL);
1438
+ note.volumeEnvelopeNode.connect(channel.gainR);
1439
+ }
1450
1440
  if (0.5 <= channel.state.sustainPedal) {
1451
1441
  channel.sustainNotes.push(note);
1452
1442
  }
@@ -1455,31 +1445,6 @@ class Midy {
1455
1445
  const scheduledNotes = channel.scheduledNotes;
1456
1446
  note.index = scheduledNotes.length;
1457
1447
  scheduledNotes.push(note);
1458
- if (this.isDrumNoteOffException(channel, noteNumber)) {
1459
- const stopTime = startTime + note.bufferSource.buffer.duration;
1460
- const promise = new Promise((resolve) => {
1461
- note.bufferSource.onended = () => {
1462
- scheduledNotes[note.index] = undefined;
1463
- this.disconnectNote(note);
1464
- resolve();
1465
- };
1466
- note.bufferSource.stop(stopTime);
1467
- });
1468
- this.notePromises.push(promise);
1469
- }
1470
- else if (noteOffEvent) {
1471
- if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
1472
- const portamentoTime = this.getPortamentoTime(channel, note);
1473
- const portamentoEndTime = startTime + portamentoTime;
1474
- const notePromise = this.scheduleNoteOff(channelNumber, note, 0, // velocity
1475
- Math.max(noteOffEvent.startTime, portamentoEndTime), false);
1476
- this.notePromises.push(notePromise);
1477
- }
1478
- else {
1479
- const notePromise = this.scheduleNoteOff(channelNumber, note, noteOffEvent.velocity, noteOffEvent.startTime, false);
1480
- this.notePromises.push(notePromise);
1481
- }
1482
- }
1483
1448
  }
1484
1449
  noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
1485
1450
  scheduleTime ??= this.audioContext.currentTime;
@@ -1489,9 +1454,6 @@ class Midy {
1489
1454
  note.bufferSource.disconnect();
1490
1455
  note.filterNode.disconnect();
1491
1456
  note.volumeEnvelopeNode.disconnect();
1492
- note.volumeNode.disconnect();
1493
- note.gainL.disconnect();
1494
- note.gainR.disconnect();
1495
1457
  if (note.modulationDepth) {
1496
1458
  note.volumeDepth.disconnect();
1497
1459
  note.modulationDepth.disconnect();
@@ -1508,42 +1470,48 @@ class Midy {
1508
1470
  note.chorusEffectsSend.disconnect();
1509
1471
  }
1510
1472
  }
1511
- stopNote(channel, note, endTime, stopTime) {
1473
+ releaseNote(channel, note, endTime) {
1474
+ const volRelease = endTime +
1475
+ note.voiceParams.volRelease * channel.state.releaseTime * 2;
1476
+ const modRelease = endTime + note.voiceParams.modRelease;
1477
+ const stopTime = Math.min(volRelease, modRelease);
1478
+ note.filterNode.frequency
1479
+ .cancelScheduledValues(endTime)
1480
+ .linearRampToValueAtTime(0, modRelease);
1512
1481
  note.volumeEnvelopeNode.gain
1513
1482
  .cancelScheduledValues(endTime)
1514
- .linearRampToValueAtTime(0, stopTime);
1515
- note.ending = true;
1516
- this.scheduleTask(() => {
1517
- note.bufferSource.loop = false;
1518
- }, stopTime);
1483
+ .linearRampToValueAtTime(0, volRelease);
1519
1484
  return new Promise((resolve) => {
1520
- note.bufferSource.onended = () => {
1521
- channel.scheduledNotes[note.index] = undefined;
1485
+ this.scheduleTask(() => {
1486
+ const bufferSource = note.bufferSource;
1487
+ bufferSource.loop = false;
1488
+ bufferSource.stop(stopTime);
1522
1489
  this.disconnectNote(note);
1490
+ channel.scheduledNotes[note.index] = undefined;
1523
1491
  resolve();
1524
- };
1525
- note.bufferSource.stop(stopTime);
1492
+ }, stopTime);
1526
1493
  });
1527
1494
  }
1528
- scheduleNoteOff(channelNumber, note, _velocity, endTime, force) {
1495
+ scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
1529
1496
  const channel = this.channels[channelNumber];
1530
- if (this.isDrumNoteOffException(channel, note.noteNumber))
1531
- return;
1532
1497
  const state = channel.state;
1533
1498
  if (!force) {
1534
- if (0.5 <= state.sustainPedal)
1535
- return;
1536
- if (0.5 <= channel.state.sostenutoPedal)
1537
- return;
1499
+ if (channel.isDrum) {
1500
+ if (!this.isLoopDrum(channel, noteNumber))
1501
+ return;
1502
+ }
1503
+ else {
1504
+ if (0.5 <= state.sustainPedal)
1505
+ return;
1506
+ if (0.5 <= state.sostenutoPedal)
1507
+ return;
1508
+ }
1538
1509
  }
1539
- const volRelease = endTime +
1540
- note.voiceParams.volRelease * channel.state.releaseTime * 2;
1541
- const modRelease = endTime + note.voiceParams.modRelease;
1542
- note.filterNode.frequency
1543
- .cancelScheduledValues(endTime)
1544
- .linearRampToValueAtTime(0, modRelease);
1545
- const stopTime = Math.min(volRelease, modRelease);
1546
- return this.stopNote(channel, note, endTime, stopTime);
1510
+ const note = this.findNoteOffTarget(channel, noteNumber);
1511
+ if (!note)
1512
+ return;
1513
+ note.ending = true;
1514
+ this.releaseNote(channel, note, endTime);
1547
1515
  }
1548
1516
  findNoteOffTarget(channel, noteNumber) {
1549
1517
  const scheduledNotes = channel.scheduledNotes;
@@ -1560,16 +1528,14 @@ class Midy {
1560
1528
  }
1561
1529
  noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
1562
1530
  scheduleTime ??= this.audioContext.currentTime;
1563
- const channel = this.channels[channelNumber];
1564
- const note = this.findNoteOffTarget(channel, noteNumber);
1565
- return this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, false);
1531
+ return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false);
1566
1532
  }
1567
1533
  releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
1568
1534
  const velocity = halfVelocity * 2;
1569
1535
  const channel = this.channels[channelNumber];
1570
1536
  const promises = [];
1571
1537
  for (let i = 0; i < channel.sustainNotes.length; i++) {
1572
- const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i], velocity, scheduleTime);
1538
+ const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
1573
1539
  promises.push(promise);
1574
1540
  }
1575
1541
  channel.sustainNotes = [];
@@ -1583,7 +1549,7 @@ class Midy {
1583
1549
  channel.state.sostenutoPedal = 0;
1584
1550
  for (let i = 0; i < sostenutoNotes.length; i++) {
1585
1551
  const note = sostenutoNotes[i];
1586
- const promise = this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime);
1552
+ const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime);
1587
1553
  promises.push(promise);
1588
1554
  }
1589
1555
  channel.sostenutoNotes = [];
@@ -1613,10 +1579,10 @@ class Midy {
1613
1579
  }
1614
1580
  handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure, scheduleTime) {
1615
1581
  const channel = this.channels[channelNumber];
1616
- channel.state.polyphonicKeyPressure = pressure / 127;
1617
1582
  const table = channel.polyphonicKeyPressureTable;
1618
1583
  this.processActiveNotes(channel, scheduleTime, (note) => {
1619
1584
  if (note.noteNumber === noteNumber) {
1585
+ note.pressure = pressure;
1620
1586
  this.setControllerParameters(channel, note, table);
1621
1587
  }
1622
1588
  });
@@ -1644,9 +1610,10 @@ class Midy {
1644
1610
  const prev = channel.state.channelPressure;
1645
1611
  const next = value / 127;
1646
1612
  channel.state.channelPressure = next;
1647
- if (channel.channelPressureTable[0] !== 64) {
1648
- const pressureDepth = (channel.channelPressureTable[0] - 64) / 37.5; // 2400 / 64;
1649
- channel.detune += pressureDepth * (next - prev);
1613
+ const channelPressureRaw = channel.channelPressureTable[0];
1614
+ if (0 <= channelPressureRaw) {
1615
+ const channelPressureDepth = (channelPressureRaw - 64) / 37.5; // 2400 / 64;
1616
+ channel.detune += channelPressureDepth * (next - prev);
1650
1617
  }
1651
1618
  const table = channel.channelPressureTable;
1652
1619
  this.processActiveNotes(channel, scheduleTime, (note) => {
@@ -1706,10 +1673,15 @@ class Midy {
1706
1673
  .setValueAtTime(volumeDepth, scheduleTime);
1707
1674
  }
1708
1675
  setReverbEffectsSend(channel, note, prevValue, scheduleTime) {
1676
+ let value = note.voiceParams.reverbEffectsSend;
1677
+ if (channel.isDrum) {
1678
+ const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
1679
+ if (0 <= keyBasedValue) {
1680
+ value *= keyBasedValue / 127 / channel.state.reverbSendLevel;
1681
+ }
1682
+ }
1709
1683
  if (0 < prevValue) {
1710
- if (0 < note.voiceParams.reverbEffectsSend) {
1711
- const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
1712
- const value = note.voiceParams.reverbEffectsSend + keyBasedValue;
1684
+ if (0 < value) {
1713
1685
  note.reverbEffectsSend.gain
1714
1686
  .cancelScheduledValues(scheduleTime)
1715
1687
  .setValueAtTime(value, scheduleTime);
@@ -1719,22 +1691,27 @@ class Midy {
1719
1691
  }
1720
1692
  }
1721
1693
  else {
1722
- if (0 < note.voiceParams.reverbEffectsSend) {
1694
+ if (0 < value) {
1723
1695
  if (!note.reverbEffectsSend) {
1724
1696
  note.reverbEffectsSend = new GainNode(this.audioContext, {
1725
- gain: note.voiceParams.reverbEffectsSend,
1697
+ gain: value,
1726
1698
  });
1727
- note.volumeNode.connect(note.reverbEffectsSend);
1699
+ note.volumeEnvelopeNode.connect(note.reverbEffectsSend);
1728
1700
  }
1729
1701
  note.reverbEffectsSend.connect(this.reverbEffect.input);
1730
1702
  }
1731
1703
  }
1732
1704
  }
1733
1705
  setChorusEffectsSend(channel, note, prevValue, scheduleTime) {
1706
+ let value = note.voiceParams.chorusEffectsSend;
1707
+ if (channel.isDrum) {
1708
+ const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
1709
+ if (0 <= keyBasedValue) {
1710
+ value *= keyBasedValue / 127 / channel.state.chorusSendLevel;
1711
+ }
1712
+ }
1734
1713
  if (0 < prevValue) {
1735
- if (0 < note.voiceParams.chorusEffectsSend) {
1736
- const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
1737
- const value = note.voiceParams.chorusEffectsSend + keyBasedValue;
1714
+ if (0 < vaule) {
1738
1715
  note.chorusEffectsSend.gain
1739
1716
  .cancelScheduledValues(scheduleTime)
1740
1717
  .setValueAtTime(value, scheduleTime);
@@ -1744,12 +1721,12 @@ class Midy {
1744
1721
  }
1745
1722
  }
1746
1723
  else {
1747
- if (0 < note.voiceParams.chorusEffectsSend) {
1724
+ if (0 < value) {
1748
1725
  if (!note.chorusEffectsSend) {
1749
1726
  note.chorusEffectsSend = new GainNode(this.audioContext, {
1750
- gain: note.voiceParams.chorusEffectsSend,
1727
+ gain: value,
1751
1728
  });
1752
- note.volumeNode.connect(note.chorusEffectsSend);
1729
+ note.volumeEnvelopeNode.connect(note.chorusEffectsSend);
1753
1730
  }
1754
1731
  note.chorusEffectsSend.connect(this.chorusEffect.input);
1755
1732
  }
@@ -1824,21 +1801,22 @@ class Midy {
1824
1801
  },
1825
1802
  };
1826
1803
  }
1827
- getControllerState(channel, noteNumber, velocity) {
1804
+ getControllerState(channel, noteNumber, velocity, polyphonicKeyPressure) {
1828
1805
  const state = new Float32Array(channel.state.array.length);
1829
1806
  state.set(channel.state.array);
1830
1807
  state[2] = velocity / 127;
1831
1808
  state[3] = noteNumber / 127;
1832
- state[10] = state.polyphonicKeyPressure / 127;
1809
+ state[10] = polyphonicKeyPressure / 127;
1833
1810
  state[13] = state.channelPressure / 127;
1834
1811
  return state;
1835
1812
  }
1836
1813
  applyVoiceParams(channel, controllerType, scheduleTime) {
1837
- this.processScheduledNotes(channel, (note) => {
1838
- const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
1814
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
1815
+ const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity, note.pressure);
1839
1816
  const voiceParams = note.voice.getParams(controllerType, controllerState);
1840
- let appliedFilterEnvelope = false;
1841
- let appliedVolumeEnvelope = false;
1817
+ let applyVolumeEnvelope = false;
1818
+ let applyFilterEnvelope = false;
1819
+ let applyPitchEnvelope = false;
1842
1820
  for (const [key, value] of Object.entries(voiceParams)) {
1843
1821
  const prevValue = note.voiceParams[key];
1844
1822
  if (value === prevValue)
@@ -1847,37 +1825,23 @@ class Midy {
1847
1825
  if (key in this.voiceParamsHandlers) {
1848
1826
  this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
1849
1827
  }
1850
- else if (filterEnvelopeKeySet.has(key)) {
1851
- if (appliedFilterEnvelope)
1852
- continue;
1853
- appliedFilterEnvelope = true;
1854
- const noteVoiceParams = note.voiceParams;
1855
- for (let i = 0; i < filterEnvelopeKeys.length; i++) {
1856
- const key = filterEnvelopeKeys[i];
1857
- if (key in voiceParams)
1858
- noteVoiceParams[key] = voiceParams[key];
1859
- }
1860
- if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
1861
- this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
1862
- }
1863
- else {
1864
- this.setFilterEnvelope(channel, note, scheduleTime);
1865
- }
1866
- this.setPitchEnvelope(note, scheduleTime);
1867
- }
1868
- else if (volumeEnvelopeKeySet.has(key)) {
1869
- if (appliedVolumeEnvelope)
1870
- continue;
1871
- appliedVolumeEnvelope = true;
1872
- const noteVoiceParams = note.voiceParams;
1873
- for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
1874
- const key = volumeEnvelopeKeys[i];
1875
- if (key in voiceParams)
1876
- noteVoiceParams[key] = voiceParams[key];
1877
- }
1878
- this.setVolumeEnvelope(channel, note, scheduleTime);
1828
+ else {
1829
+ if (volumeEnvelopeKeySet.has(key))
1830
+ applyVolumeEnvelope = true;
1831
+ if (filterEnvelopeKeySet.has(key))
1832
+ applyFilterEnvelope = true;
1833
+ if (pitchEnvelopeKeySet.has(key))
1834
+ applyPitchEnvelope = true;
1879
1835
  }
1880
1836
  }
1837
+ if (applyVolumeEnvelope) {
1838
+ this.setVolumeEnvelope(channel, note, scheduleTime);
1839
+ }
1840
+ if (applyFilterEnvelope) {
1841
+ this.setFilterEnvelope(channel, note, scheduleTime);
1842
+ }
1843
+ if (applyPitchEnvelope)
1844
+ this.setPitchEnvelope(note, scheduleTime);
1881
1845
  });
1882
1846
  }
1883
1847
  createControlChangeHandlers() {
@@ -1924,7 +1888,7 @@ class Midy {
1924
1888
  handler.call(this, channelNumber, value, scheduleTime);
1925
1889
  const channel = this.channels[channelNumber];
1926
1890
  this.applyVoiceParams(channel, controllerType + 128, scheduleTime);
1927
- this.applyControlTable(channel, controllerType);
1891
+ this.applyControlTable(channel, controllerType, scheduleTime);
1928
1892
  }
1929
1893
  else {
1930
1894
  console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
@@ -1935,7 +1899,7 @@ class Midy {
1935
1899
  }
1936
1900
  updateModulation(channel, scheduleTime) {
1937
1901
  const depth = channel.state.modulationDepth * channel.modulationDepthRange;
1938
- this.processScheduledNotes(channel, (note) => {
1902
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
1939
1903
  if (note.modulationDepth) {
1940
1904
  note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
1941
1905
  }
@@ -1954,7 +1918,7 @@ class Midy {
1954
1918
  this.updateModulation(channel, scheduleTime);
1955
1919
  }
1956
1920
  updatePortamento(channel, scheduleTime) {
1957
- this.processScheduledNotes(channel, (note) => {
1921
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
1958
1922
  if (0.5 <= channel.state.portamento) {
1959
1923
  if (0 <= note.portamentoNoteNumber) {
1960
1924
  this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
@@ -1981,22 +1945,12 @@ class Midy {
1981
1945
  return;
1982
1946
  this.updatePortamento(channel, scheduleTime);
1983
1947
  }
1984
- setKeyBasedVolume(channel, scheduleTime) {
1985
- this.processScheduledNotes(channel, (note) => {
1986
- const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
1987
- if (keyBasedValue !== 0) {
1988
- note.volumeNode.gain
1989
- .cancelScheduledValues(scheduleTime)
1990
- .setValueAtTime(1 + keyBasedValue, scheduleTime);
1991
- }
1992
- });
1993
- }
1994
1948
  setVolume(channelNumber, volume, scheduleTime) {
1995
1949
  scheduleTime ??= this.audioContext.currentTime;
1996
1950
  const channel = this.channels[channelNumber];
1997
1951
  channel.state.volume = volume / 127;
1998
1952
  this.updateChannelVolume(channel, scheduleTime);
1999
- this.setKeyBasedVolume(channel, scheduleTime);
1953
+ this.updateKeyBasedVolume(channel, scheduleTime);
2000
1954
  }
2001
1955
  panToGain(pan) {
2002
1956
  const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
@@ -2005,26 +1959,12 @@ class Midy {
2005
1959
  gainRight: Math.sin(theta),
2006
1960
  };
2007
1961
  }
2008
- setKeyBasedPan(channel, scheduleTime) {
2009
- this.processScheduledNotes(channel, (note) => {
2010
- const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
2011
- if (keyBasedValue !== 0) {
2012
- const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
2013
- note.gainL.gain
2014
- .cancelScheduledValues(scheduleTime)
2015
- .setValueAtTime(gainLeft, scheduleTime);
2016
- note.gainR.gain
2017
- .cancelScheduledValues(scheduleTime)
2018
- .setValueAtTime(gainRight, scheduleTime);
2019
- }
2020
- });
2021
- }
2022
1962
  setPan(channelNumber, pan, scheduleTime) {
2023
1963
  scheduleTime ??= this.audioContext.currentTime;
2024
1964
  const channel = this.channels[channelNumber];
2025
1965
  channel.state.pan = pan / 127;
2026
1966
  this.updateChannelVolume(channel, scheduleTime);
2027
- this.setKeyBasedPan(channel, scheduleTime);
1967
+ this.updateKeyBasedVolume(channel, scheduleTime);
2028
1968
  }
2029
1969
  setExpression(channelNumber, expression, scheduleTime) {
2030
1970
  scheduleTime ??= this.audioContext.currentTime;
@@ -2050,6 +1990,34 @@ class Midy {
2050
1990
  .cancelScheduledValues(scheduleTime)
2051
1991
  .setValueAtTime(volume * gainRight, scheduleTime);
2052
1992
  }
1993
+ updateKeyBasedVolume(channel, scheduleTime) {
1994
+ if (!channel.isDrum)
1995
+ return;
1996
+ const state = channel.state;
1997
+ const defaultVolume = state.volume * state.expression;
1998
+ const defaultPan = state.pan;
1999
+ for (let i = 0; i < 128; i++) {
2000
+ const gainL = channel.keyBasedGainLs[i];
2001
+ const gainR = channel.keyBasedGainLs[i];
2002
+ if (!gainL)
2003
+ continue;
2004
+ if (!gainR)
2005
+ continue;
2006
+ const keyBasedVolume = this.getKeyBasedInstrumentControlValue(channel, i, 7);
2007
+ const volume = (0 <= keyBasedVolume)
2008
+ ? defaultVolume * keyBasedVolume / 64
2009
+ : defaultVolume;
2010
+ const keyBasedPan = this.getKeyBasedInstrumentControlValue(channel, i, 10);
2011
+ const pan = (0 <= keyBasedPan) ? keyBasedPan / 127 : defaultPan;
2012
+ const { gainLeft, gainRight } = this.panToGain(pan);
2013
+ gainL.gain
2014
+ .cancelScheduledValues(scheduleTime)
2015
+ .setValueAtTime(volume * gainLeft, scheduleTime);
2016
+ gainR.gain
2017
+ .cancelScheduledValues(scheduleTime)
2018
+ .setValueAtTime(volume * gainRight, scheduleTime);
2019
+ }
2020
+ }
2053
2021
  setSustainPedal(channelNumber, value, scheduleTime) {
2054
2022
  const channel = this.channels[channelNumber];
2055
2023
  if (channel.isDrum)
@@ -2057,7 +2025,7 @@ class Midy {
2057
2025
  scheduleTime ??= this.audioContext.currentTime;
2058
2026
  channel.state.sustainPedal = value / 127;
2059
2027
  if (64 <= value) {
2060
- this.processScheduledNotes(channel, (note) => {
2028
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
2061
2029
  channel.sustainNotes.push(note);
2062
2030
  });
2063
2031
  }
@@ -2090,6 +2058,9 @@ class Midy {
2090
2058
  this.releaseSostenutoPedal(channelNumber, value, scheduleTime);
2091
2059
  }
2092
2060
  }
2061
+ getSoftPedalFactor(channel, note) {
2062
+ return 1 - (0.1 + (note.noteNumber / 127) * 0.2) * channel.state.softPedal;
2063
+ }
2093
2064
  setSoftPedal(channelNumber, softPedal, scheduleTime) {
2094
2065
  const channel = this.channels[channelNumber];
2095
2066
  if (channel.isDrum)
@@ -2097,7 +2068,7 @@ class Midy {
2097
2068
  const state = channel.state;
2098
2069
  scheduleTime ??= this.audioContext.currentTime;
2099
2070
  state.softPedal = softPedal / 127;
2100
- this.processScheduledNotes(channel, (note) => {
2071
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
2101
2072
  if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
2102
2073
  this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
2103
2074
  this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
@@ -2115,7 +2086,7 @@ class Midy {
2115
2086
  scheduleTime ??= this.audioContext.currentTime;
2116
2087
  const state = channel.state;
2117
2088
  state.filterResonance = filterResonance / 127;
2118
- this.processScheduledNotes(channel, (note) => {
2089
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
2119
2090
  const Q = note.voiceParams.initialFilterQ / 5 * state.filterResonance;
2120
2091
  note.filterNode.Q.setValueAtTime(Q, scheduleTime);
2121
2092
  });
@@ -2133,7 +2104,7 @@ class Midy {
2133
2104
  return;
2134
2105
  scheduleTime ??= this.audioContext.currentTime;
2135
2106
  channel.state.attackTime = attackTime / 127;
2136
- this.processScheduledNotes(channel, (note) => {
2107
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
2137
2108
  if (note.startTime < scheduleTime)
2138
2109
  return false;
2139
2110
  this.setVolumeEnvelope(channel, note);
@@ -2146,7 +2117,7 @@ class Midy {
2146
2117
  const state = channel.state;
2147
2118
  scheduleTime ??= this.audioContext.currentTime;
2148
2119
  state.brightness = brightness / 127;
2149
- this.processScheduledNotes(channel, (note) => {
2120
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
2150
2121
  if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
2151
2122
  this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
2152
2123
  }
@@ -2161,7 +2132,7 @@ class Midy {
2161
2132
  return;
2162
2133
  scheduleTime ??= this.audioContext.currentTime;
2163
2134
  channel.state.decayTime = dacayTime / 127;
2164
- this.processScheduledNotes(channel, (note) => {
2135
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
2165
2136
  this.setVolumeEnvelope(channel, note, scheduleTime);
2166
2137
  });
2167
2138
  }
@@ -2173,7 +2144,7 @@ class Midy {
2173
2144
  channel.state.vibratoRate = vibratoRate / 127;
2174
2145
  if (channel.vibratoDepth <= 0)
2175
2146
  return;
2176
- this.processScheduledNotes(channel, (note) => {
2147
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
2177
2148
  this.setVibLfoToPitch(channel, note, scheduleTime);
2178
2149
  });
2179
2150
  }
@@ -2185,12 +2156,12 @@ class Midy {
2185
2156
  const prev = channel.state.vibratoDepth;
2186
2157
  channel.state.vibratoDepth = vibratoDepth / 127;
2187
2158
  if (0 < prev) {
2188
- this.processScheduledNotes(channel, (note) => {
2159
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
2189
2160
  this.setFreqVibLFO(channel, note, scheduleTime);
2190
2161
  });
2191
2162
  }
2192
2163
  else {
2193
- this.processScheduledNotes(channel, (note) => {
2164
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
2194
2165
  this.startVibrato(channel, note, scheduleTime);
2195
2166
  });
2196
2167
  }
@@ -2202,7 +2173,7 @@ class Midy {
2202
2173
  scheduleTime ??= this.audioContext.currentTime;
2203
2174
  channel.state.vibratoDelay = vibratoDelay / 127;
2204
2175
  if (0 < channel.state.vibratoDepth) {
2205
- this.processScheduledNotes(channel, (note) => {
2176
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
2206
2177
  this.startVibrato(channel, note, scheduleTime);
2207
2178
  });
2208
2179
  }
@@ -2220,16 +2191,17 @@ class Midy {
2220
2191
  .setValueAtTime(state.reverbSendLevel, scheduleTime);
2221
2192
  }
2222
2193
  else {
2223
- this.processScheduledNotes(channel, (note) => {
2194
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
2224
2195
  if (note.voiceParams.reverbEffectsSend <= 0)
2225
2196
  return false;
2226
- note.reverbEffectsSend.disconnect();
2197
+ if (note.reverbEffectsSend)
2198
+ note.reverbEffectsSend.disconnect();
2227
2199
  });
2228
2200
  }
2229
2201
  }
2230
2202
  else {
2231
2203
  if (0 < reverbSendLevel) {
2232
- this.processScheduledNotes(channel, (note) => {
2204
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
2233
2205
  this.setReverbEffectsSend(channel, note, 0, scheduleTime);
2234
2206
  });
2235
2207
  state.reverbSendLevel = reverbSendLevel / 127;
@@ -2252,16 +2224,17 @@ class Midy {
2252
2224
  .setValueAtTime(state.chorusSendLevel, scheduleTime);
2253
2225
  }
2254
2226
  else {
2255
- this.processScheduledNotes(channel, (note) => {
2227
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
2256
2228
  if (note.voiceParams.chorusEffectsSend <= 0)
2257
2229
  return false;
2258
- note.chorusEffectsSend.disconnect();
2230
+ if (note.chorusEffectsSend)
2231
+ note.chorusEffectsSend.disconnect();
2259
2232
  });
2260
2233
  }
2261
2234
  }
2262
2235
  else {
2263
2236
  if (0 < chorusSendLevel) {
2264
- this.processScheduledNotes(channel, (note) => {
2237
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
2265
2238
  this.setChorusEffectsSend(channel, note, 0, scheduleTime);
2266
2239
  });
2267
2240
  state.chorusSendLevel = chorusSendLevel / 127;
@@ -2676,8 +2649,7 @@ class Midy {
2676
2649
  setReverbType(type) {
2677
2650
  this.reverb.time = this.getReverbTimeFromType(type);
2678
2651
  this.reverb.feedback = (type === 8) ? 0.9 : 0.8;
2679
- const { audioContext, options } = this;
2680
- this.reverbEffect = options.reverbAlgorithm(audioContext);
2652
+ this.reverbEffect = this.createReverbEffect(this.audioContext);
2681
2653
  }
2682
2654
  getReverbTimeFromType(type) {
2683
2655
  switch (type) {
@@ -2699,8 +2671,7 @@ class Midy {
2699
2671
  }
2700
2672
  setReverbTime(value) {
2701
2673
  this.reverb.time = this.getReverbTime(value);
2702
- const { audioContext, options } = this;
2703
- this.reverbEffect = options.reverbAlgorithm(audioContext);
2674
+ this.reverbEffect = this.createReverbEffect(this.audioContext);
2704
2675
  }
2705
2676
  getReverbTime(value) {
2706
2677
  return Math.exp((value - 40) * 0.025);
@@ -2895,65 +2866,88 @@ class Midy {
2895
2866
  }
2896
2867
  }
2897
2868
  getPitchControl(channel, note) {
2898
- const polyphonicKeyPressure = (channel.polyphonicKeyPressureTable[0] - 64) *
2869
+ const polyphonicKeyPressureRaw = channel.polyphonicKeyPressureTable[0];
2870
+ if (polyphonicKeyPressureRaw < 0)
2871
+ return 0;
2872
+ const polyphonicKeyPressure = (polyphonicKeyPressureRaw - 64) *
2899
2873
  note.pressure;
2900
2874
  return polyphonicKeyPressure * note.pressure / 37.5; // 2400 / 64;
2901
2875
  }
2902
2876
  getFilterCutoffControl(channel, note) {
2903
- const channelPressure = (channel.channelPressureTable[1] - 64) *
2904
- channel.state.channelPressure;
2905
- const polyphonicKeyPressure = (channel.polyphonicKeyPressureTable[1] - 64) *
2906
- note.pressure;
2877
+ const channelPressureRaw = channel.channelPressureTable[1];
2878
+ const channelPressure = (0 <= channelPressureRaw)
2879
+ ? (channelPressureRaw - 64) * channel.state.channelPressure
2880
+ : 0;
2881
+ const polyphonicKeyPressureRaw = channel.polyphonicKeyPressureTable[1];
2882
+ const polyphonicKeyPressure = (0 <= polyphonicKeyPressureRaw)
2883
+ ? (polyphonicKeyPressureRaw - 64) * note.pressure
2884
+ : 0;
2907
2885
  return (channelPressure + polyphonicKeyPressure) * 15;
2908
2886
  }
2909
2887
  getAmplitudeControl(channel, note) {
2910
- const channelPressure = channel.channelPressureTable[2] *
2911
- channel.state.channelPressure;
2912
- const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[2] *
2913
- note.pressure;
2888
+ const channelPressureRaw = channel.channelPressureTable[2];
2889
+ const channelPressure = (0 <= channelPressureRaw)
2890
+ ? channelPressureRaw * channel.state.channelPressure
2891
+ : 0;
2892
+ const polyphonicKeyPressureRaw = channel.polyphonicKeyPressureTable[2];
2893
+ const polyphonicKeyPressure = (0 <= polyphonicKeyPressureRaw)
2894
+ ? polyphonicKeyPressureRaw * note.pressure
2895
+ : 0;
2914
2896
  return (channelPressure + polyphonicKeyPressure) / 128;
2915
2897
  }
2916
2898
  getLFOPitchDepth(channel, note) {
2917
- const channelPressure = channel.channelPressureTable[3] *
2918
- channel.state.channelPressure;
2919
- const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[3] *
2920
- note.pressure;
2899
+ const channelPressureRaw = channel.channelPressureTable[3];
2900
+ const channelPressure = (0 <= channelPressureRaw)
2901
+ ? channelPressureRaw * channel.state.channelPressure
2902
+ : 0;
2903
+ const polyphonicKeyPressureRaw = channel.polyphonicKeyPressureTable[3];
2904
+ const polyphonicKeyPressure = (0 <= polyphonicKeyPressureRaw)
2905
+ ? polyphonicKeyPressureRaw * note.pressure
2906
+ : 0;
2921
2907
  return (channelPressure + polyphonicKeyPressure) / 254 * 600;
2922
2908
  }
2923
2909
  getLFOFilterDepth(channel, note) {
2924
- const channelPressure = channel.channelPressureTable[4] *
2925
- channel.state.channelPressure;
2926
- const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[4] *
2927
- note.pressure;
2910
+ const channelPressureRaw = channel.channelPressureTable[4];
2911
+ const channelPressure = (0 <= channelPressureRaw)
2912
+ ? channelPressureRaw * channel.state.channelPressure
2913
+ : 0;
2914
+ const polyphonicKeyPressureRaw = channel.polyphonicKeyPressureTable[4];
2915
+ const polyphonicKeyPressure = (0 <= polyphonicKeyPressureRaw)
2916
+ ? polyphonicKeyPressureRaw * note.pressure
2917
+ : 0;
2928
2918
  return (channelPressure + polyphonicKeyPressure) / 254 * 2400;
2929
2919
  }
2930
2920
  getLFOAmplitudeDepth(channel, note) {
2931
- const channelPressure = channel.channelPressureTable[5] *
2932
- channel.state.channelPressure;
2933
- const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[5] *
2934
- note.pressure;
2921
+ const channelPressureRaw = channel.channelPressureTable[5];
2922
+ const channelPressure = (0 <= channelPressureRaw)
2923
+ ? channelPressureRaw * channel.state.channelPressure
2924
+ : 0;
2925
+ const polyphonicKeyPressureRaw = channel.polyphonicKeyPressureTable[5];
2926
+ const polyphonicKeyPressure = (0 <= polyphonicKeyPressureRaw)
2927
+ ? polyphonicKeyPressureRaw * note.pressure
2928
+ : 0;
2935
2929
  return (channelPressure + polyphonicKeyPressure) / 254;
2936
2930
  }
2937
2931
  setControllerParameters(channel, note, table) {
2938
- if (table[0] !== 64)
2932
+ if (0 <= table[0])
2939
2933
  this.updateDetune(channel, note);
2940
2934
  if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
2941
- if (table[1] !== 64)
2935
+ if (0 <= table[1])
2942
2936
  this.setPortamentoFilterEnvelope(channel, note);
2943
- if (table[2] !== 64)
2937
+ if (0 <= table[2])
2944
2938
  this.setPortamentoVolumeEnvelope(channel, note);
2945
2939
  }
2946
2940
  else {
2947
- if (table[1] !== 64)
2941
+ if (0 <= table[1])
2948
2942
  this.setFilterEnvelope(channel, note);
2949
- if (table[2] !== 64)
2943
+ if (0 <= table[2])
2950
2944
  this.setVolumeEnvelope(channel, note);
2951
2945
  }
2952
- if (table[3] !== 0)
2946
+ if (0 <= table[3])
2953
2947
  this.setModLfoToPitch(channel, note);
2954
- if (table[4] !== 0)
2948
+ if (0 <= table[4])
2955
2949
  this.setModLfoToFilterFc(channel, note);
2956
- if (table[5] !== 0)
2950
+ if (0 <= table[5])
2957
2951
  this.setModLfoToVolume(channel, note);
2958
2952
  }
2959
2953
  handlePressureSysEx(data, tableName) {
@@ -2969,26 +2963,15 @@ class Midy {
2969
2963
  }
2970
2964
  }
2971
2965
  initControlTable() {
2972
- const channelCount = 128;
2966
+ const ccCount = 128;
2973
2967
  const slotSize = 6;
2974
- const table = new Uint8Array(channelCount * slotSize);
2975
- return this.resetControlTable(table);
2968
+ return new Int8Array(ccCount * slotSize).fill(-1);
2976
2969
  }
2977
- resetControlTable(table) {
2978
- const channelCount = 128;
2979
- const slotSize = 6;
2980
- const defaultValues = [64, 64, 64, 0, 0, 0];
2981
- for (let ch = 0; ch < channelCount; ch++) {
2982
- const offset = ch * slotSize;
2983
- table.set(defaultValues, offset);
2984
- }
2985
- return table;
2986
- }
2987
- applyControlTable(channel, controllerType) {
2970
+ applyControlTable(channel, controllerType, scheduleTime) {
2988
2971
  const slotSize = 6;
2989
2972
  const offset = controllerType * slotSize;
2990
2973
  const table = channel.controlTable.subarray(offset, offset + slotSize);
2991
- this.processScheduledNotes(channel, (note) => {
2974
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
2992
2975
  this.setControllerParameters(channel, note, table);
2993
2976
  });
2994
2977
  }
@@ -3008,12 +2991,12 @@ class Midy {
3008
2991
  getKeyBasedInstrumentControlValue(channel, keyNumber, controllerType) {
3009
2992
  const index = keyNumber * 128 + controllerType;
3010
2993
  const controlValue = channel.keyBasedInstrumentControlTable[index];
3011
- return (controlValue + 64) / 64;
2994
+ return controlValue;
3012
2995
  }
3013
2996
  handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
3014
2997
  const channelNumber = data[4];
3015
2998
  const channel = this.channels[channelNumber];
3016
- if (channel.isDrum)
2999
+ if (!channel.isDrum)
3017
3000
  return;
3018
3001
  const keyNumber = data[5];
3019
3002
  const table = channel.keyBasedInstrumentControlTable;
@@ -3021,7 +3004,7 @@ class Midy {
3021
3004
  const controllerType = data[i];
3022
3005
  const value = data[i + 1];
3023
3006
  const index = keyNumber * 128 + controllerType;
3024
- table[index] = value - 64;
3007
+ table[index] = value;
3025
3008
  }
3026
3009
  this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
3027
3010
  }