@marmooo/midy 0.3.3 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/script/midy.js CHANGED
@@ -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
  }
@@ -341,13 +332,13 @@ class Midy {
341
332
  writable: true,
342
333
  value: this.initSoundFontTable()
343
334
  });
344
- Object.defineProperty(this, "audioBufferCounter", {
335
+ Object.defineProperty(this, "voiceCounter", {
345
336
  enumerable: true,
346
337
  configurable: true,
347
338
  writable: true,
348
339
  value: new Map()
349
340
  });
350
- Object.defineProperty(this, "audioBufferCache", {
341
+ Object.defineProperty(this, "voiceCache", {
351
342
  enumerable: true,
352
343
  configurable: true,
353
344
  writable: true,
@@ -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);
@@ -467,13 +435,11 @@ class Midy {
467
435
  const presetHeaders = soundFont.parsed.presetHeaders;
468
436
  for (let i = 0; i < presetHeaders.length; i++) {
469
437
  const presetHeader = presetHeaders[i];
470
- if (!presetHeader.presetName.startsWith("\u0000")) { // TODO: Only SF3 generated by PolyPone?
471
- const banks = this.soundFontTable[presetHeader.preset];
472
- banks.set(presetHeader.bank, index);
473
- }
438
+ const banks = this.soundFontTable[presetHeader.preset];
439
+ banks.set(presetHeader.bank, index);
474
440
  }
475
441
  }
476
- async loadSoundFont(input) {
442
+ async toUint8Array(input) {
477
443
  let uint8Array;
478
444
  if (typeof input === "string") {
479
445
  const response = await fetch(input);
@@ -486,23 +452,32 @@ class Midy {
486
452
  else {
487
453
  throw new TypeError("input must be a URL string or Uint8Array");
488
454
  }
489
- const parsed = (0, soundfont_parser_1.parse)(uint8Array);
490
- const soundFont = new soundfont_parser_1.SoundFont(parsed);
491
- this.addSoundFont(soundFont);
455
+ return uint8Array;
492
456
  }
493
- async loadMIDI(input) {
494
- let uint8Array;
495
- if (typeof input === "string") {
496
- const response = await fetch(input);
497
- const arrayBuffer = await response.arrayBuffer();
498
- uint8Array = new Uint8Array(arrayBuffer);
499
- }
500
- else if (input instanceof Uint8Array) {
501
- uint8Array = input;
457
+ async loadSoundFont(input) {
458
+ this.voiceCounter.clear();
459
+ if (Array.isArray(input)) {
460
+ const promises = new Array(input.length);
461
+ for (let i = 0; i < input.length; i++) {
462
+ promises[i] = this.toUint8Array(input[i]);
463
+ }
464
+ const uint8Arrays = await Promise.all(promises);
465
+ for (let i = 0; i < uint8Arrays.length; i++) {
466
+ const parsed = (0, soundfont_parser_1.parse)(uint8Arrays[i]);
467
+ const soundFont = new soundfont_parser_1.SoundFont(parsed);
468
+ this.addSoundFont(soundFont);
469
+ }
502
470
  }
503
471
  else {
504
- throw new TypeError("input must be a URL string or Uint8Array");
472
+ const uint8Array = await this.toUint8Array(input);
473
+ const parsed = (0, soundfont_parser_1.parse)(uint8Array);
474
+ const soundFont = new soundfont_parser_1.SoundFont(parsed);
475
+ this.addSoundFont(soundFont);
505
476
  }
477
+ }
478
+ async loadMIDI(input) {
479
+ this.voiceCounter.clear();
480
+ const uint8Array = await this.toUint8Array(input);
506
481
  const midi = (0, midi_file_1.parseMidi)(uint8Array);
507
482
  this.ticksPerBeat = midi.header.ticksPerBeat;
508
483
  const midiData = this.extractMidiData(midi);
@@ -510,7 +485,46 @@ class Midy {
510
485
  this.timeline = midiData.timeline;
511
486
  this.totalTime = this.calcTotalTime();
512
487
  }
513
- setChannelAudioNodes(audioContext) {
488
+ cacheVoiceIds() {
489
+ const timeline = this.timeline;
490
+ for (let i = 0; i < timeline.length; i++) {
491
+ const event = timeline[i];
492
+ switch (event.type) {
493
+ case "noteOn": {
494
+ const audioBufferId = this.getVoiceId(this.channels[event.channel], event.noteNumber, event.velocity);
495
+ this.voiceCounter.set(audioBufferId, (this.voiceCounter.get(audioBufferId) ?? 0) + 1);
496
+ break;
497
+ }
498
+ case "controller":
499
+ if (event.controllerType === 0) {
500
+ this.setBankMSB(event.channel, event.value);
501
+ }
502
+ else if (event.controllerType === 32) {
503
+ this.setBankLSB(event.channel, event.value);
504
+ }
505
+ break;
506
+ case "programChange":
507
+ this.setProgramChange(event.channel, event.programNumber, event.startTime);
508
+ }
509
+ }
510
+ for (const [audioBufferId, count] of this.voiceCounter) {
511
+ if (count === 1)
512
+ this.voiceCounter.delete(audioBufferId);
513
+ }
514
+ this.GM2SystemOn();
515
+ }
516
+ getVoiceId(channel, noteNumber, velocity) {
517
+ const bankNumber = this.calcBank(channel);
518
+ const soundFontIndex = this.soundFontTable[channel.programNumber]
519
+ .get(bankNumber);
520
+ if (soundFontIndex === undefined)
521
+ return;
522
+ const soundFont = this.soundFonts[soundFontIndex];
523
+ const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
524
+ const { instrument, sampleID } = voice.generators;
525
+ return `${soundFontIndex}:${instrument}:${sampleID}`;
526
+ }
527
+ createChannelAudioNodes(audioContext) {
514
528
  const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
515
529
  const gainL = new GainNode(audioContext, { gain: gainLeft });
516
530
  const gainR = new GainNode(audioContext, { gain: gainRight });
@@ -525,10 +539,10 @@ class Midy {
525
539
  };
526
540
  }
527
541
  resetChannelTable(channel) {
528
- this.resetControlTable(channel.controlTable);
542
+ channel.controlTable.fill(-1);
529
543
  channel.scaleOctaveTuningTable.fill(0); // [-100, 100] cent
530
- channel.channelPressureTable.set([64, 64, 64, 0, 0, 0]);
531
- channel.polyphonicKeyPressureTable.set([64, 64, 64, 0, 0, 0]);
544
+ channel.channelPressureTable.fill(-1);
545
+ channel.polyphonicKeyPressureTable.fill(-1);
532
546
  channel.keyBasedInstrumentControlTable.fill(-1);
533
547
  }
534
548
  createChannels(audioContext) {
@@ -538,47 +552,27 @@ class Midy {
538
552
  isDrum: false,
539
553
  state: new ControllerState(),
540
554
  ...this.constructor.channelSettings,
541
- ...this.setChannelAudioNodes(audioContext),
555
+ ...this.createChannelAudioNodes(audioContext),
542
556
  scheduledNotes: [],
543
557
  sustainNotes: [],
544
558
  sostenutoNotes: [],
545
559
  controlTable: this.initControlTable(),
546
560
  scaleOctaveTuningTable: new Float32Array(12), // [-100, 100] cent
547
- channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
548
- polyphonicKeyPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
561
+ channelPressureTable: new Int8Array(6).fill(-1),
562
+ polyphonicKeyPressureTable: new Int8Array(6).fill(-1),
549
563
  keyBasedInstrumentControlTable: new Int8Array(128 * 128).fill(-1),
564
+ keyBasedGainLs: new Array(128),
565
+ keyBasedGainRs: new Array(128),
550
566
  };
551
567
  });
552
568
  return channels;
553
569
  }
554
- async createNoteBuffer(voiceParams, isSF3) {
570
+ async createAudioBuffer(voiceParams) {
571
+ const sample = voiceParams.sample;
555
572
  const sampleStart = voiceParams.start;
556
- const sampleEnd = voiceParams.sample.length + voiceParams.end;
557
- if (isSF3) {
558
- const sample = voiceParams.sample;
559
- const start = sample.byteOffset + sampleStart;
560
- const end = sample.byteOffset + sampleEnd;
561
- const buffer = sample.buffer.slice(start, end);
562
- const audioBuffer = await this.audioContext.decodeAudioData(buffer);
563
- return audioBuffer;
564
- }
565
- else {
566
- const sample = voiceParams.sample;
567
- const start = sample.byteOffset + sampleStart;
568
- const end = sample.byteOffset + sampleEnd;
569
- const buffer = sample.buffer.slice(start, end);
570
- const audioBuffer = new AudioBuffer({
571
- numberOfChannels: 1,
572
- length: sample.length,
573
- sampleRate: voiceParams.sampleRate,
574
- });
575
- const channelData = audioBuffer.getChannelData(0);
576
- const int16Array = new Int16Array(buffer);
577
- for (let i = 0; i < int16Array.length; i++) {
578
- channelData[i] = int16Array[i] / 32768;
579
- }
580
- return audioBuffer;
581
- }
573
+ const sampleEnd = sample.data.length + voiceParams.end;
574
+ const audioBuffer = await sample.toAudioBuffer(this.audioContext, sampleStart, sampleEnd);
575
+ return audioBuffer;
582
576
  }
583
577
  isLoopDrum(channel, noteNumber) {
584
578
  const programNumber = channel.programNumber;
@@ -588,10 +582,9 @@ class Midy {
588
582
  createBufferSource(channel, noteNumber, voiceParams, audioBuffer) {
589
583
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
590
584
  bufferSource.buffer = audioBuffer;
591
- bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
592
- if (channel.isDrum) {
593
- bufferSource.loop = this.isLoopDrum(channel, noteNumber);
594
- }
585
+ bufferSource.loop = channel.isDrum
586
+ ? this.isLoopDrum(channel, noteNumber)
587
+ : (voiceParams.sampleModes % 2 !== 0);
595
588
  if (bufferSource.loop) {
596
589
  bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
597
590
  bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
@@ -616,16 +609,16 @@ class Midy {
616
609
  break;
617
610
  }
618
611
  case "noteAftertouch":
619
- this.handlePolyphonicKeyPressure(event.channel, event.noteNumber, event.amount, startTime);
612
+ this.setPolyphonicKeyPressure(event.channel, event.noteNumber, event.amount, startTime);
620
613
  break;
621
614
  case "controller":
622
- this.handleControlChange(event.channel, event.controllerType, event.value, startTime);
615
+ this.setControlChange(event.channel, event.controllerType, event.value, startTime);
623
616
  break;
624
617
  case "programChange":
625
- this.handleProgramChange(event.channel, event.programNumber, startTime);
618
+ this.setProgramChange(event.channel, event.programNumber, startTime);
626
619
  break;
627
620
  case "channelAftertouch":
628
- this.handleChannelPressure(event.channel, event.amount, startTime);
621
+ this.setChannelPressure(event.channel, event.amount, startTime);
629
622
  break;
630
623
  case "pitchBend":
631
624
  this.setPitchBend(event.channel, event.value + 8192, startTime);
@@ -659,8 +652,9 @@ class Midy {
659
652
  this.notePromises = [];
660
653
  this.exclusiveClassNotes.fill(undefined);
661
654
  this.drumExclusiveClassNotes.fill(undefined);
662
- this.audioBufferCache.clear();
655
+ this.voiceCache.clear();
663
656
  for (let i = 0; i < this.channels.length; i++) {
657
+ this.channels[i].scheduledNotes = [];
664
658
  this.resetAllStates(i);
665
659
  }
666
660
  resolve();
@@ -682,8 +676,9 @@ class Midy {
682
676
  this.notePromises = [];
683
677
  this.exclusiveClassNotes.fill(undefined);
684
678
  this.drumExclusiveClassNotes.fill(undefined);
685
- this.audioBufferCache.clear();
679
+ this.voiceCache.clear();
686
680
  for (let i = 0; i < this.channels.length; i++) {
681
+ this.channels[i].scheduledNotes = [];
687
682
  this.resetAllStates(i);
688
683
  }
689
684
  this.isStopping = false;
@@ -716,11 +711,7 @@ class Midy {
716
711
  secondToTicks(second, secondsPerBeat) {
717
712
  return second * this.ticksPerBeat / secondsPerBeat;
718
713
  }
719
- getAudioBufferId(programNumber, noteNumber, velocity) {
720
- return `${programNumber}:${noteNumber}:${velocity}`;
721
- }
722
714
  extractMidiData(midi) {
723
- this.audioBufferCounter.clear();
724
715
  const instruments = new Set();
725
716
  const timeline = [];
726
717
  const tmpChannels = new Array(this.channels.length);
@@ -741,8 +732,6 @@ class Midy {
741
732
  switch (event.type) {
742
733
  case "noteOn": {
743
734
  const channel = tmpChannels[event.channel];
744
- const audioBufferId = this.getAudioBufferId(channel.programNumber, event.noteNumber, event.velocity);
745
- this.audioBufferCounter.set(audioBufferId, (this.audioBufferCounter.get(audioBufferId) ?? 0) + 1);
746
735
  if (channel.programNumber < 0) {
747
736
  channel.programNumber = event.programNumber;
748
737
  switch (channel.bankMSB) {
@@ -792,10 +781,6 @@ class Midy {
792
781
  timeline.push(event);
793
782
  }
794
783
  }
795
- for (const [audioBufferId, count] of this.audioBufferCounter) {
796
- if (count === 1)
797
- this.audioBufferCounter.delete(audioBufferId);
798
- }
799
784
  const priority = {
800
785
  controller: 0,
801
786
  sysEx: 1,
@@ -840,7 +825,6 @@ class Midy {
840
825
  this.notePromises.push(promise);
841
826
  promises.push(promise);
842
827
  });
843
- channel.scheduledNotes = [];
844
828
  return Promise.all(promises);
845
829
  }
846
830
  stopNotes(velocity, force, scheduleTime) {
@@ -854,6 +838,8 @@ class Midy {
854
838
  if (this.isPlaying || this.isPaused)
855
839
  return;
856
840
  this.resumeTime = 0;
841
+ if (this.voiceCounter.size === 0)
842
+ this.cacheVoiceIds();
857
843
  await this.playNotes();
858
844
  this.isPlaying = false;
859
845
  }
@@ -896,7 +882,7 @@ class Midy {
896
882
  }
897
883
  processScheduledNotes(channel, callback) {
898
884
  const scheduledNotes = channel.scheduledNotes;
899
- for (let i = 0; i < scheduledNotes.length; i++) {
885
+ for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
900
886
  const note = scheduledNotes[i];
901
887
  if (!note)
902
888
  continue;
@@ -907,14 +893,14 @@ class Midy {
907
893
  }
908
894
  processActiveNotes(channel, scheduleTime, callback) {
909
895
  const scheduledNotes = channel.scheduledNotes;
910
- for (let i = 0; i < scheduledNotes.length; i++) {
896
+ for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
911
897
  const note = scheduledNotes[i];
912
898
  if (!note)
913
899
  continue;
914
900
  if (note.ending)
915
901
  continue;
916
902
  if (scheduleTime < note.startTime)
917
- continue;
903
+ break;
918
904
  callback(note);
919
905
  }
920
906
  }
@@ -1003,6 +989,22 @@ class Midy {
1003
989
  const output = allpasses.at(-1);
1004
990
  return { input, output };
1005
991
  }
992
+ createReverbEffect(audioContext) {
993
+ const { algorithm, time: rt60, feedback } = this.reverb;
994
+ switch (algorithm) {
995
+ case "ConvolutionReverb": {
996
+ const impulse = this.createConvolutionReverbImpulse(audioContext, rt60, this.calcDelay(rt60, feedback));
997
+ return this.createConvolutionReverb(audioContext, impulse);
998
+ }
999
+ case "SchroederReverb": {
1000
+ const combFeedbacks = this.generateDistributedArray(feedback, 4);
1001
+ const combDelays = combFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
1002
+ const allpassFeedbacks = this.generateDistributedArray(feedback, 4);
1003
+ const allpassDelays = allpassFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
1004
+ return this.createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays);
1005
+ }
1006
+ }
1007
+ }
1006
1008
  createChorusEffect(audioContext) {
1007
1009
  const input = new GainNode(audioContext);
1008
1010
  const output = new GainNode(audioContext);
@@ -1067,9 +1069,16 @@ class Midy {
1067
1069
  const pitchWheel = channel.state.pitchWheel * 2 - 1;
1068
1070
  const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
1069
1071
  const pitch = pitchWheel * pitchWheelSensitivity;
1070
- const pressureDepth = (channel.channelPressureTable[0] - 64) / 37.5; // 2400 / 64;
1071
- const pressure = pressureDepth * channel.state.channelPressure;
1072
- return tuning + pitch + pressure;
1072
+ const channelPressureRaw = channel.channelPressureTable[0];
1073
+ if (0 <= channelPressureRaw) {
1074
+ const channelPressureDepth = (channelPressureRaw - 64) / 37.5; // 2400 / 64;
1075
+ const channelPressure = channelPressureDepth *
1076
+ channel.state.channelPressure;
1077
+ return tuning + pitch + channelPressure;
1078
+ }
1079
+ else {
1080
+ return tuning + pitch;
1081
+ }
1073
1082
  }
1074
1083
  calcNoteDetune(channel, note) {
1075
1084
  return channel.scaleOctaveTuningTable[note.noteNumber % 12];
@@ -1303,35 +1312,32 @@ class Midy {
1303
1312
  note.vibratoLFO.connect(note.vibratoDepth);
1304
1313
  note.vibratoDepth.connect(note.bufferSource.detune);
1305
1314
  }
1306
- async getAudioBuffer(programNumber, noteNumber, velocity, voiceParams, isSF3) {
1307
- const audioBufferId = this.getAudioBufferId(programNumber, noteNumber, velocity);
1308
- const cache = this.audioBufferCache.get(audioBufferId);
1315
+ async getAudioBuffer(channel, noteNumber, velocity, voiceParams) {
1316
+ const audioBufferId = this.getVoiceId(channel, noteNumber, velocity);
1317
+ const cache = this.voiceCache.get(audioBufferId);
1309
1318
  if (cache) {
1310
1319
  cache.counter += 1;
1311
1320
  if (cache.maxCount <= cache.counter) {
1312
- this.audioBufferCache.delete(audioBufferId);
1321
+ this.voiceCache.delete(audioBufferId);
1313
1322
  }
1314
1323
  return cache.audioBuffer;
1315
1324
  }
1316
1325
  else {
1317
- const maxCount = this.audioBufferCounter.get(audioBufferId) ?? 0;
1318
- const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
1326
+ const maxCount = this.voiceCounter.get(audioBufferId) ?? 0;
1327
+ const audioBuffer = await this.createAudioBuffer(voiceParams);
1319
1328
  const cache = { audioBuffer, maxCount, counter: 1 };
1320
- this.audioBufferCache.set(audioBufferId, cache);
1329
+ this.voiceCache.set(audioBufferId, cache);
1321
1330
  return audioBuffer;
1322
1331
  }
1323
1332
  }
1324
- async createNote(channel, voice, noteNumber, velocity, startTime, isSF3) {
1333
+ async createNote(channel, voice, noteNumber, velocity, startTime) {
1325
1334
  const now = this.audioContext.currentTime;
1326
1335
  const state = channel.state;
1327
- const controllerState = this.getControllerState(channel, noteNumber, velocity);
1336
+ const controllerState = this.getControllerState(channel, noteNumber, velocity, 0);
1328
1337
  const voiceParams = voice.getAllParams(controllerState);
1329
1338
  const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
1330
- const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
1339
+ const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams);
1331
1340
  note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
1332
- note.volumeNode = new GainNode(this.audioContext);
1333
- note.gainL = new GainNode(this.audioContext);
1334
- note.gainR = new GainNode(this.audioContext);
1335
1341
  note.volumeEnvelopeNode = new GainNode(this.audioContext);
1336
1342
  note.filterNode = new BiquadFilterNode(this.audioContext, {
1337
1343
  type: "lowpass",
@@ -1364,9 +1370,6 @@ class Midy {
1364
1370
  }
1365
1371
  note.bufferSource.connect(note.filterNode);
1366
1372
  note.filterNode.connect(note.volumeEnvelopeNode);
1367
- note.volumeEnvelopeNode.connect(note.volumeNode);
1368
- note.volumeNode.connect(note.gainL);
1369
- note.volumeNode.connect(note.gainR);
1370
1373
  if (0 < state.chorusSendLevel) {
1371
1374
  this.setChorusEffectsSend(channel, note, 0, now);
1372
1375
  }
@@ -1425,21 +1428,30 @@ class Midy {
1425
1428
  }
1426
1429
  this.drumExclusiveClassNotes[index] = note;
1427
1430
  }
1428
- async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, noteOffEvent) {
1431
+ async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
1429
1432
  const channel = this.channels[channelNumber];
1430
1433
  const bankNumber = this.calcBank(channel, channelNumber);
1431
- const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
1434
+ const soundFontIndex = this.soundFontTable[channel.programNumber]
1435
+ .get(bankNumber);
1432
1436
  if (soundFontIndex === undefined)
1433
1437
  return;
1434
1438
  const soundFont = this.soundFonts[soundFontIndex];
1435
1439
  const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
1436
1440
  if (!voice)
1437
1441
  return;
1438
- const isSF3 = soundFont.parsed.info.version.major === 3;
1439
- const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
1440
- note.noteOffEvent = noteOffEvent;
1441
- note.gainL.connect(channel.gainL);
1442
- note.gainR.connect(channel.gainR);
1442
+ const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
1443
+ if (channel.isDrum) {
1444
+ const audioContext = this.audioContext;
1445
+ const { gainL, gainR } = this.createChannelAudioNodes(audioContext);
1446
+ channel.keyBasedGainLs[noteNumber] = gainL;
1447
+ channel.keyBasedGainRs[noteNumber] = gainR;
1448
+ note.volumeEnvelopeNode.connect(gainL);
1449
+ note.volumeEnvelopeNode.connect(gainR);
1450
+ }
1451
+ else {
1452
+ note.volumeEnvelopeNode.connect(channel.gainL);
1453
+ note.volumeEnvelopeNode.connect(channel.gainR);
1454
+ }
1443
1455
  if (0.5 <= channel.state.sustainPedal) {
1444
1456
  channel.sustainNotes.push(note);
1445
1457
  }
@@ -1457,9 +1469,6 @@ class Midy {
1457
1469
  note.bufferSource.disconnect();
1458
1470
  note.filterNode.disconnect();
1459
1471
  note.volumeEnvelopeNode.disconnect();
1460
- note.volumeNode.disconnect();
1461
- note.gainL.disconnect();
1462
- note.gainR.disconnect();
1463
1472
  if (note.modulationDepth) {
1464
1473
  note.volumeDepth.disconnect();
1465
1474
  note.modulationDepth.disconnect();
@@ -1513,15 +1522,29 @@ class Midy {
1513
1522
  return;
1514
1523
  }
1515
1524
  }
1516
- const note = this.findNoteOffTarget(channel, noteNumber);
1517
- if (!note)
1525
+ const index = this.findNoteOffIndex(channel, noteNumber);
1526
+ if (index < 0)
1518
1527
  return;
1528
+ const note = channel.scheduledNotes[index];
1519
1529
  note.ending = true;
1530
+ this.setNoteIndex(channel, index);
1520
1531
  this.releaseNote(channel, note, endTime);
1521
1532
  }
1522
- findNoteOffTarget(channel, noteNumber) {
1533
+ setNoteIndex(channel, index) {
1534
+ let allEnds = true;
1535
+ for (let i = channel.scheduleIndex; i < index; i++) {
1536
+ const note = channel.scheduledNotes[i];
1537
+ if (note && !note.ending) {
1538
+ allEnds = false;
1539
+ break;
1540
+ }
1541
+ }
1542
+ if (allEnds)
1543
+ channel.scheduleIndex = index + 1;
1544
+ }
1545
+ findNoteOffIndex(channel, noteNumber) {
1523
1546
  const scheduledNotes = channel.scheduledNotes;
1524
- for (let i = 0; i < scheduledNotes.length; i++) {
1547
+ for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
1525
1548
  const note = scheduledNotes[i];
1526
1549
  if (!note)
1527
1550
  continue;
@@ -1529,8 +1552,9 @@ class Midy {
1529
1552
  continue;
1530
1553
  if (note.noteNumber !== noteNumber)
1531
1554
  continue;
1532
- return note;
1555
+ return i;
1533
1556
  }
1557
+ return -1;
1534
1558
  }
1535
1559
  noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
1536
1560
  scheduleTime ??= this.audioContext.currentTime;
@@ -1570,31 +1594,31 @@ class Midy {
1570
1594
  case 0x90:
1571
1595
  return this.noteOn(channelNumber, data1, data2, scheduleTime);
1572
1596
  case 0xA0:
1573
- return this.handlePolyphonicKeyPressure(channelNumber, data1, data2, scheduleTime);
1597
+ return this.setPolyphonicKeyPressure(channelNumber, data1, data2, scheduleTime);
1574
1598
  case 0xB0:
1575
- return this.handleControlChange(channelNumber, data1, data2, scheduleTime);
1599
+ return this.setControlChange(channelNumber, data1, data2, scheduleTime);
1576
1600
  case 0xC0:
1577
- return this.handleProgramChange(channelNumber, data1, scheduleTime);
1601
+ return this.setProgramChange(channelNumber, data1, scheduleTime);
1578
1602
  case 0xD0:
1579
- return this.handleChannelPressure(channelNumber, data1, scheduleTime);
1603
+ return this.setChannelPressure(channelNumber, data1, scheduleTime);
1580
1604
  case 0xE0:
1581
1605
  return this.handlePitchBendMessage(channelNumber, data1, data2, scheduleTime);
1582
1606
  default:
1583
1607
  console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
1584
1608
  }
1585
1609
  }
1586
- handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure, scheduleTime) {
1610
+ setPolyphonicKeyPressure(channelNumber, noteNumber, pressure, scheduleTime) {
1587
1611
  const channel = this.channels[channelNumber];
1588
- channel.state.polyphonicKeyPressure = pressure / 127;
1589
1612
  const table = channel.polyphonicKeyPressureTable;
1590
1613
  this.processActiveNotes(channel, scheduleTime, (note) => {
1591
1614
  if (note.noteNumber === noteNumber) {
1592
- this.setControllerParameters(channel, note, table);
1615
+ note.pressure = pressure;
1616
+ this.setControllerParameters(channel, note, table, scheduleTime);
1593
1617
  }
1594
1618
  });
1595
1619
  this.applyVoiceParams(channel, 10);
1596
1620
  }
1597
- handleProgramChange(channelNumber, programNumber, _scheduleTime) {
1621
+ setProgramChange(channelNumber, programNumber, _scheduleTime) {
1598
1622
  const channel = this.channels[channelNumber];
1599
1623
  channel.bank = channel.bankMSB * 128 + channel.bankLSB;
1600
1624
  channel.programNumber = programNumber;
@@ -1609,20 +1633,21 @@ class Midy {
1609
1633
  }
1610
1634
  }
1611
1635
  }
1612
- handleChannelPressure(channelNumber, value, scheduleTime) {
1636
+ setChannelPressure(channelNumber, value, scheduleTime) {
1613
1637
  const channel = this.channels[channelNumber];
1614
1638
  if (channel.isDrum)
1615
1639
  return;
1616
1640
  const prev = channel.state.channelPressure;
1617
1641
  const next = value / 127;
1618
1642
  channel.state.channelPressure = next;
1619
- if (channel.channelPressureTable[0] !== 64) {
1620
- const pressureDepth = (channel.channelPressureTable[0] - 64) / 37.5; // 2400 / 64;
1621
- channel.detune += pressureDepth * (next - prev);
1643
+ const channelPressureRaw = channel.channelPressureTable[0];
1644
+ if (0 <= channelPressureRaw) {
1645
+ const channelPressureDepth = (channelPressureRaw - 64) / 37.5; // 2400 / 64;
1646
+ channel.detune += channelPressureDepth * (next - prev);
1622
1647
  }
1623
1648
  const table = channel.channelPressureTable;
1624
1649
  this.processActiveNotes(channel, scheduleTime, (note) => {
1625
- this.setControllerParameters(channel, note, table);
1650
+ this.setControllerParameters(channel, note, table, scheduleTime);
1626
1651
  });
1627
1652
  this.applyVoiceParams(channel, 13);
1628
1653
  }
@@ -1678,10 +1703,12 @@ class Midy {
1678
1703
  .setValueAtTime(volumeDepth, scheduleTime);
1679
1704
  }
1680
1705
  setReverbEffectsSend(channel, note, prevValue, scheduleTime) {
1681
- const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
1682
1706
  let value = note.voiceParams.reverbEffectsSend;
1683
- if (0 <= keyBasedValue) {
1684
- value *= keyBasedValue / 127 / channel.state.reverbSendLevel;
1707
+ if (channel.isDrum) {
1708
+ const keyBasedValue = this.getKeyBasedValue(channel, note.noteNumber, 91);
1709
+ if (0 <= keyBasedValue) {
1710
+ value *= keyBasedValue / 127 / channel.state.reverbSendLevel;
1711
+ }
1685
1712
  }
1686
1713
  if (0 < prevValue) {
1687
1714
  if (0 < value) {
@@ -1699,20 +1726,22 @@ class Midy {
1699
1726
  note.reverbEffectsSend = new GainNode(this.audioContext, {
1700
1727
  gain: value,
1701
1728
  });
1702
- note.volumeNode.connect(note.reverbEffectsSend);
1729
+ note.volumeEnvelopeNode.connect(note.reverbEffectsSend);
1703
1730
  }
1704
1731
  note.reverbEffectsSend.connect(this.reverbEffect.input);
1705
1732
  }
1706
1733
  }
1707
1734
  }
1708
1735
  setChorusEffectsSend(channel, note, prevValue, scheduleTime) {
1709
- const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
1710
1736
  let value = note.voiceParams.chorusEffectsSend;
1711
- if (0 <= keyBasedValue) {
1712
- value *= keyBasedValue / 127 / channel.state.chorusSendLevel;
1737
+ if (channel.isDrum) {
1738
+ const keyBasedValue = this.getKeyBasedValue(channel, note.noteNumber, 93);
1739
+ if (0 <= keyBasedValue) {
1740
+ value *= keyBasedValue / 127 / channel.state.chorusSendLevel;
1741
+ }
1713
1742
  }
1714
1743
  if (0 < prevValue) {
1715
- if (0 < vaule) {
1744
+ if (0 < value) {
1716
1745
  note.chorusEffectsSend.gain
1717
1746
  .cancelScheduledValues(scheduleTime)
1718
1747
  .setValueAtTime(value, scheduleTime);
@@ -1727,7 +1756,7 @@ class Midy {
1727
1756
  note.chorusEffectsSend = new GainNode(this.audioContext, {
1728
1757
  gain: value,
1729
1758
  });
1730
- note.volumeNode.connect(note.chorusEffectsSend);
1759
+ note.volumeEnvelopeNode.connect(note.chorusEffectsSend);
1731
1760
  }
1732
1761
  note.chorusEffectsSend.connect(this.chorusEffect.input);
1733
1762
  }
@@ -1802,21 +1831,22 @@ class Midy {
1802
1831
  },
1803
1832
  };
1804
1833
  }
1805
- getControllerState(channel, noteNumber, velocity) {
1834
+ getControllerState(channel, noteNumber, velocity, polyphonicKeyPressure) {
1806
1835
  const state = new Float32Array(channel.state.array.length);
1807
1836
  state.set(channel.state.array);
1808
1837
  state[2] = velocity / 127;
1809
1838
  state[3] = noteNumber / 127;
1810
- state[10] = state.polyphonicKeyPressure / 127;
1839
+ state[10] = polyphonicKeyPressure / 127;
1811
1840
  state[13] = state.channelPressure / 127;
1812
1841
  return state;
1813
1842
  }
1814
1843
  applyVoiceParams(channel, controllerType, scheduleTime) {
1815
1844
  this.processScheduledNotes(channel, (note) => {
1816
- const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
1845
+ const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity, note.pressure);
1817
1846
  const voiceParams = note.voice.getParams(controllerType, controllerState);
1818
- let appliedFilterEnvelope = false;
1819
- let appliedVolumeEnvelope = false;
1847
+ let applyVolumeEnvelope = false;
1848
+ let applyFilterEnvelope = false;
1849
+ let applyPitchEnvelope = false;
1820
1850
  for (const [key, value] of Object.entries(voiceParams)) {
1821
1851
  const prevValue = note.voiceParams[key];
1822
1852
  if (value === prevValue)
@@ -1825,37 +1855,23 @@ class Midy {
1825
1855
  if (key in this.voiceParamsHandlers) {
1826
1856
  this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
1827
1857
  }
1828
- else if (filterEnvelopeKeySet.has(key)) {
1829
- if (appliedFilterEnvelope)
1830
- continue;
1831
- appliedFilterEnvelope = true;
1832
- const noteVoiceParams = note.voiceParams;
1833
- for (let i = 0; i < filterEnvelopeKeys.length; i++) {
1834
- const key = filterEnvelopeKeys[i];
1835
- if (key in voiceParams)
1836
- noteVoiceParams[key] = voiceParams[key];
1837
- }
1838
- if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
1839
- this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
1840
- }
1841
- else {
1842
- this.setFilterEnvelope(channel, note, scheduleTime);
1843
- }
1844
- this.setPitchEnvelope(note, scheduleTime);
1845
- }
1846
- else if (volumeEnvelopeKeySet.has(key)) {
1847
- if (appliedVolumeEnvelope)
1848
- continue;
1849
- appliedVolumeEnvelope = true;
1850
- const noteVoiceParams = note.voiceParams;
1851
- for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
1852
- const key = volumeEnvelopeKeys[i];
1853
- if (key in voiceParams)
1854
- noteVoiceParams[key] = voiceParams[key];
1855
- }
1856
- this.setVolumeEnvelope(channel, note, scheduleTime);
1858
+ else {
1859
+ if (volumeEnvelopeKeySet.has(key))
1860
+ applyVolumeEnvelope = true;
1861
+ if (filterEnvelopeKeySet.has(key))
1862
+ applyFilterEnvelope = true;
1863
+ if (pitchEnvelopeKeySet.has(key))
1864
+ applyPitchEnvelope = true;
1857
1865
  }
1858
1866
  }
1867
+ if (applyVolumeEnvelope) {
1868
+ this.setVolumeEnvelope(channel, note, scheduleTime);
1869
+ }
1870
+ if (applyFilterEnvelope) {
1871
+ this.setFilterEnvelope(channel, note, scheduleTime);
1872
+ }
1873
+ if (applyPitchEnvelope)
1874
+ this.setPitchEnvelope(note, scheduleTime);
1859
1875
  });
1860
1876
  }
1861
1877
  createControlChangeHandlers() {
@@ -1896,13 +1912,13 @@ class Midy {
1896
1912
  handlers[127] = this.polyOn;
1897
1913
  return handlers;
1898
1914
  }
1899
- handleControlChange(channelNumber, controllerType, value, scheduleTime) {
1915
+ setControlChange(channelNumber, controllerType, value, scheduleTime) {
1900
1916
  const handler = this.controlChangeHandlers[controllerType];
1901
1917
  if (handler) {
1902
1918
  handler.call(this, channelNumber, value, scheduleTime);
1903
1919
  const channel = this.channels[channelNumber];
1904
1920
  this.applyVoiceParams(channel, controllerType + 128, scheduleTime);
1905
- this.applyControlTable(channel, controllerType);
1921
+ this.applyControlTable(channel, controllerType, scheduleTime);
1906
1922
  }
1907
1923
  else {
1908
1924
  console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
@@ -1959,22 +1975,12 @@ class Midy {
1959
1975
  return;
1960
1976
  this.updatePortamento(channel, scheduleTime);
1961
1977
  }
1962
- setKeyBasedVolume(channel, scheduleTime) {
1963
- this.processScheduledNotes(channel, (note) => {
1964
- const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
1965
- if (0 <= keyBasedValue) {
1966
- note.volumeNode.gain
1967
- .cancelScheduledValues(scheduleTime)
1968
- .setValueAtTime(keyBasedValue / 127, scheduleTime);
1969
- }
1970
- });
1971
- }
1972
1978
  setVolume(channelNumber, volume, scheduleTime) {
1973
1979
  scheduleTime ??= this.audioContext.currentTime;
1974
1980
  const channel = this.channels[channelNumber];
1975
1981
  channel.state.volume = volume / 127;
1976
1982
  this.updateChannelVolume(channel, scheduleTime);
1977
- this.setKeyBasedVolume(channel, scheduleTime);
1983
+ this.updateKeyBasedVolume(channel, scheduleTime);
1978
1984
  }
1979
1985
  panToGain(pan) {
1980
1986
  const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
@@ -1983,26 +1989,12 @@ class Midy {
1983
1989
  gainRight: Math.sin(theta),
1984
1990
  };
1985
1991
  }
1986
- setKeyBasedPan(channel, scheduleTime) {
1987
- this.processScheduledNotes(channel, (note) => {
1988
- const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
1989
- if (0 <= keyBasedValue) {
1990
- const { gainLeft, gainRight } = this.panToGain(keyBasedValue / 127);
1991
- note.gainL.gain
1992
- .cancelScheduledValues(scheduleTime)
1993
- .setValueAtTime(gainLeft, scheduleTime);
1994
- note.gainR.gain
1995
- .cancelScheduledValues(scheduleTime)
1996
- .setValueAtTime(gainRight, scheduleTime);
1997
- }
1998
- });
1999
- }
2000
1992
  setPan(channelNumber, pan, scheduleTime) {
2001
1993
  scheduleTime ??= this.audioContext.currentTime;
2002
1994
  const channel = this.channels[channelNumber];
2003
1995
  channel.state.pan = pan / 127;
2004
1996
  this.updateChannelVolume(channel, scheduleTime);
2005
- this.setKeyBasedPan(channel, scheduleTime);
1997
+ this.updateKeyBasedVolume(channel, scheduleTime);
2006
1998
  }
2007
1999
  setExpression(channelNumber, expression, scheduleTime) {
2008
2000
  scheduleTime ??= this.audioContext.currentTime;
@@ -2028,6 +2020,34 @@ class Midy {
2028
2020
  .cancelScheduledValues(scheduleTime)
2029
2021
  .setValueAtTime(volume * gainRight, scheduleTime);
2030
2022
  }
2023
+ updateKeyBasedVolume(channel, scheduleTime) {
2024
+ if (!channel.isDrum)
2025
+ return;
2026
+ const state = channel.state;
2027
+ const defaultVolume = state.volume * state.expression;
2028
+ const defaultPan = state.pan;
2029
+ for (let i = 0; i < 128; i++) {
2030
+ const gainL = channel.keyBasedGainLs[i];
2031
+ const gainR = channel.keyBasedGainLs[i];
2032
+ if (!gainL)
2033
+ continue;
2034
+ if (!gainR)
2035
+ continue;
2036
+ const keyBasedVolume = this.getKeyBasedValue(channel, i, 7);
2037
+ const volume = (0 <= keyBasedVolume)
2038
+ ? defaultVolume * keyBasedVolume / 64
2039
+ : defaultVolume;
2040
+ const keyBasedPan = this.getKeyBasedValue(channel, i, 10);
2041
+ const pan = (0 <= keyBasedPan) ? keyBasedPan / 127 : defaultPan;
2042
+ const { gainLeft, gainRight } = this.panToGain(pan);
2043
+ gainL.gain
2044
+ .cancelScheduledValues(scheduleTime)
2045
+ .setValueAtTime(volume * gainLeft, scheduleTime);
2046
+ gainR.gain
2047
+ .cancelScheduledValues(scheduleTime)
2048
+ .setValueAtTime(volume * gainRight, scheduleTime);
2049
+ }
2050
+ }
2031
2051
  setSustainPedal(channelNumber, value, scheduleTime) {
2032
2052
  const channel = this.channels[channelNumber];
2033
2053
  if (channel.isDrum)
@@ -2117,7 +2137,7 @@ class Midy {
2117
2137
  this.processScheduledNotes(channel, (note) => {
2118
2138
  if (note.startTime < scheduleTime)
2119
2139
  return false;
2120
- this.setVolumeEnvelope(channel, note);
2140
+ this.setVolumeEnvelope(channel, note, scheduleTime);
2121
2141
  });
2122
2142
  }
2123
2143
  setBrightness(channelNumber, brightness, scheduleTime) {
@@ -2132,7 +2152,7 @@ class Midy {
2132
2152
  this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
2133
2153
  }
2134
2154
  else {
2135
- this.setFilterEnvelope(channel, note);
2155
+ this.setFilterEnvelope(channel, note, scheduleTime);
2136
2156
  }
2137
2157
  });
2138
2158
  }
@@ -2402,7 +2422,7 @@ class Midy {
2402
2422
  const entries = Object.entries(defaultControllerState);
2403
2423
  for (const [key, { type, defaultValue }] of entries) {
2404
2424
  if (128 <= type) {
2405
- this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
2425
+ this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
2406
2426
  }
2407
2427
  else {
2408
2428
  state[key] = defaultValue;
@@ -2435,7 +2455,7 @@ class Midy {
2435
2455
  const key = keys[i];
2436
2456
  const { type, defaultValue } = defaultControllerState[key];
2437
2457
  if (128 <= type) {
2438
- this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
2458
+ this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
2439
2459
  }
2440
2460
  else {
2441
2461
  state[key] = defaultValue;
@@ -2659,8 +2679,7 @@ class Midy {
2659
2679
  setReverbType(type) {
2660
2680
  this.reverb.time = this.getReverbTimeFromType(type);
2661
2681
  this.reverb.feedback = (type === 8) ? 0.9 : 0.8;
2662
- const { audioContext, options } = this;
2663
- this.reverbEffect = options.reverbAlgorithm(audioContext);
2682
+ this.reverbEffect = this.createReverbEffect(this.audioContext);
2664
2683
  }
2665
2684
  getReverbTimeFromType(type) {
2666
2685
  switch (type) {
@@ -2682,8 +2701,7 @@ class Midy {
2682
2701
  }
2683
2702
  setReverbTime(value) {
2684
2703
  this.reverb.time = this.getReverbTime(value);
2685
- const { audioContext, options } = this;
2686
- this.reverbEffect = options.reverbAlgorithm(audioContext);
2704
+ this.reverbEffect = this.createReverbEffect(this.audioContext);
2687
2705
  }
2688
2706
  getReverbTime(value) {
2689
2707
  return Math.exp((value - 40) * 0.025);
@@ -2878,66 +2896,91 @@ class Midy {
2878
2896
  }
2879
2897
  }
2880
2898
  getPitchControl(channel, note) {
2881
- const polyphonicKeyPressure = (channel.polyphonicKeyPressureTable[0] - 64) *
2899
+ const polyphonicKeyPressureRaw = channel.polyphonicKeyPressureTable[0];
2900
+ if (polyphonicKeyPressureRaw < 0)
2901
+ return 0;
2902
+ const polyphonicKeyPressure = (polyphonicKeyPressureRaw - 64) *
2882
2903
  note.pressure;
2883
2904
  return polyphonicKeyPressure * note.pressure / 37.5; // 2400 / 64;
2884
2905
  }
2885
2906
  getFilterCutoffControl(channel, note) {
2886
- const channelPressure = (channel.channelPressureTable[1] - 64) *
2887
- channel.state.channelPressure;
2888
- const polyphonicKeyPressure = (channel.polyphonicKeyPressureTable[1] - 64) *
2889
- note.pressure;
2907
+ const channelPressureRaw = channel.channelPressureTable[1];
2908
+ const channelPressure = (0 <= channelPressureRaw)
2909
+ ? (channelPressureRaw - 64) * channel.state.channelPressure
2910
+ : 0;
2911
+ const polyphonicKeyPressureRaw = channel.polyphonicKeyPressureTable[1];
2912
+ const polyphonicKeyPressure = (0 <= polyphonicKeyPressureRaw)
2913
+ ? (polyphonicKeyPressureRaw - 64) * note.pressure
2914
+ : 0;
2890
2915
  return (channelPressure + polyphonicKeyPressure) * 15;
2891
2916
  }
2892
2917
  getAmplitudeControl(channel, note) {
2893
- const channelPressure = channel.channelPressureTable[2] *
2894
- channel.state.channelPressure;
2895
- const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[2] *
2896
- note.pressure;
2918
+ const channelPressureRaw = channel.channelPressureTable[2];
2919
+ const channelPressure = (0 <= channelPressureRaw)
2920
+ ? channelPressureRaw * channel.state.channelPressure
2921
+ : 0;
2922
+ const polyphonicKeyPressureRaw = channel.polyphonicKeyPressureTable[2];
2923
+ const polyphonicKeyPressure = (0 <= polyphonicKeyPressureRaw)
2924
+ ? polyphonicKeyPressureRaw * note.pressure
2925
+ : 0;
2897
2926
  return (channelPressure + polyphonicKeyPressure) / 128;
2898
2927
  }
2899
2928
  getLFOPitchDepth(channel, note) {
2900
- const channelPressure = channel.channelPressureTable[3] *
2901
- channel.state.channelPressure;
2902
- const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[3] *
2903
- note.pressure;
2929
+ const channelPressureRaw = channel.channelPressureTable[3];
2930
+ const channelPressure = (0 <= channelPressureRaw)
2931
+ ? channelPressureRaw * channel.state.channelPressure
2932
+ : 0;
2933
+ const polyphonicKeyPressureRaw = channel.polyphonicKeyPressureTable[3];
2934
+ const polyphonicKeyPressure = (0 <= polyphonicKeyPressureRaw)
2935
+ ? polyphonicKeyPressureRaw * note.pressure
2936
+ : 0;
2904
2937
  return (channelPressure + polyphonicKeyPressure) / 254 * 600;
2905
2938
  }
2906
2939
  getLFOFilterDepth(channel, note) {
2907
- const channelPressure = channel.channelPressureTable[4] *
2908
- channel.state.channelPressure;
2909
- const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[4] *
2910
- note.pressure;
2940
+ const channelPressureRaw = channel.channelPressureTable[4];
2941
+ const channelPressure = (0 <= channelPressureRaw)
2942
+ ? channelPressureRaw * channel.state.channelPressure
2943
+ : 0;
2944
+ const polyphonicKeyPressureRaw = channel.polyphonicKeyPressureTable[4];
2945
+ const polyphonicKeyPressure = (0 <= polyphonicKeyPressureRaw)
2946
+ ? polyphonicKeyPressureRaw * note.pressure
2947
+ : 0;
2911
2948
  return (channelPressure + polyphonicKeyPressure) / 254 * 2400;
2912
2949
  }
2913
2950
  getLFOAmplitudeDepth(channel, note) {
2914
- const channelPressure = channel.channelPressureTable[5] *
2915
- channel.state.channelPressure;
2916
- const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[5] *
2917
- note.pressure;
2951
+ const channelPressureRaw = channel.channelPressureTable[5];
2952
+ const channelPressure = (0 <= channelPressureRaw)
2953
+ ? channelPressureRaw * channel.state.channelPressure
2954
+ : 0;
2955
+ const polyphonicKeyPressureRaw = channel.polyphonicKeyPressureTable[5];
2956
+ const polyphonicKeyPressure = (0 <= polyphonicKeyPressureRaw)
2957
+ ? polyphonicKeyPressureRaw * note.pressure
2958
+ : 0;
2918
2959
  return (channelPressure + polyphonicKeyPressure) / 254;
2919
2960
  }
2920
- setControllerParameters(channel, note, table) {
2921
- if (table[0] !== 64)
2922
- this.updateDetune(channel, note);
2961
+ setControllerParameters(channel, note, table, scheduleTime) {
2962
+ if (0 <= table[0])
2963
+ this.updateDetune(channel, note, scueduleTime);
2923
2964
  if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
2924
- if (table[1] !== 64)
2925
- this.setPortamentoFilterEnvelope(channel, note);
2926
- if (table[2] !== 64)
2927
- this.setPortamentoVolumeEnvelope(channel, note);
2965
+ if (0 <= table[1]) {
2966
+ this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
2967
+ }
2968
+ if (0 <= table[2]) {
2969
+ this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
2970
+ }
2928
2971
  }
2929
2972
  else {
2930
- if (table[1] !== 64)
2931
- this.setFilterEnvelope(channel, note);
2932
- if (table[2] !== 64)
2933
- this.setVolumeEnvelope(channel, note);
2934
- }
2935
- if (table[3] !== 0)
2936
- this.setModLfoToPitch(channel, note);
2937
- if (table[4] !== 0)
2938
- this.setModLfoToFilterFc(channel, note);
2939
- if (table[5] !== 0)
2940
- this.setModLfoToVolume(channel, note);
2973
+ if (0 <= table[1])
2974
+ this.setFilterEnvelope(channel, note, scheduleTime);
2975
+ if (0 <= table[2])
2976
+ this.setVolumeEnvelope(channel, note, scheduleTime);
2977
+ }
2978
+ if (0 <= table[3])
2979
+ this.setModLfoToPitch(channel, note, scheduleTime);
2980
+ if (0 <= table[4])
2981
+ this.setModLfoToFilterFc(channel, note, scheduleTime);
2982
+ if (0 <= table[5])
2983
+ this.setModLfoToVolume(channel, note, scheduleTime);
2941
2984
  }
2942
2985
  handlePressureSysEx(data, tableName) {
2943
2986
  const channelNumber = data[4];
@@ -2952,27 +2995,16 @@ class Midy {
2952
2995
  }
2953
2996
  }
2954
2997
  initControlTable() {
2955
- const channelCount = 128;
2998
+ const ccCount = 128;
2956
2999
  const slotSize = 6;
2957
- const table = new Uint8Array(channelCount * slotSize);
2958
- return this.resetControlTable(table);
3000
+ return new Int8Array(ccCount * slotSize).fill(-1);
2959
3001
  }
2960
- resetControlTable(table) {
2961
- const channelCount = 128;
2962
- const slotSize = 6;
2963
- const defaultValues = [64, 64, 64, 0, 0, 0];
2964
- for (let ch = 0; ch < channelCount; ch++) {
2965
- const offset = ch * slotSize;
2966
- table.set(defaultValues, offset);
2967
- }
2968
- return table;
2969
- }
2970
- applyControlTable(channel, controllerType) {
3002
+ applyControlTable(channel, controllerType, scheduleTime) {
2971
3003
  const slotSize = 6;
2972
3004
  const offset = controllerType * slotSize;
2973
3005
  const table = channel.controlTable.subarray(offset, offset + slotSize);
2974
3006
  this.processScheduledNotes(channel, (note) => {
2975
- this.setControllerParameters(channel, note, table);
3007
+ this.setControllerParameters(channel, note, table, scheduleTime);
2976
3008
  });
2977
3009
  }
2978
3010
  handleControlChangeSysEx(data) {
@@ -2988,7 +3020,7 @@ class Midy {
2988
3020
  table[pp] = rr;
2989
3021
  }
2990
3022
  }
2991
- getKeyBasedInstrumentControlValue(channel, keyNumber, controllerType) {
3023
+ getKeyBasedValue(channel, keyNumber, controllerType) {
2992
3024
  const index = keyNumber * 128 + controllerType;
2993
3025
  const controlValue = channel.keyBasedInstrumentControlTable[index];
2994
3026
  return controlValue;
@@ -2996,7 +3028,7 @@ class Midy {
2996
3028
  handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
2997
3029
  const channelNumber = data[4];
2998
3030
  const channel = this.channels[channelNumber];
2999
- if (channel.isDrum)
3031
+ if (!channel.isDrum)
3000
3032
  return;
3001
3033
  const keyNumber = data[5];
3002
3034
  const table = channel.keyBasedInstrumentControlTable;
@@ -3006,7 +3038,7 @@ class Midy {
3006
3038
  const index = keyNumber * 128 + controllerType;
3007
3039
  table[index] = value;
3008
3040
  }
3009
- this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
3041
+ this.setChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
3010
3042
  }
3011
3043
  handleSysEx(data, scheduleTime) {
3012
3044
  switch (data[0]) {
@@ -3044,6 +3076,7 @@ Object.defineProperty(Midy, "channelSettings", {
3044
3076
  configurable: true,
3045
3077
  writable: true,
3046
3078
  value: {
3079
+ scheduleIndex: 0,
3047
3080
  detune: 0,
3048
3081
  programNumber: 0,
3049
3082
  bank: 121 * 128,