@marmooo/midy 0.0.2 → 0.0.3

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.
@@ -8,12 +8,6 @@ export class MidyGMLite {
8
8
  writable: true,
9
9
  value: 120
10
10
  });
11
- Object.defineProperty(this, "secondsPerBeat", {
12
- enumerable: true,
13
- configurable: true,
14
- writable: true,
15
- value: 0.5
16
- });
17
11
  Object.defineProperty(this, "totalTime", {
18
12
  enumerable: true,
19
13
  configurable: true,
@@ -144,10 +138,10 @@ export class MidyGMLite {
144
138
  const response = await fetch(midiUrl);
145
139
  const arrayBuffer = await response.arrayBuffer();
146
140
  const midi = parseMidi(new Uint8Array(arrayBuffer));
141
+ this.ticksPerBeat = midi.header.ticksPerBeat;
147
142
  const midiData = this.extractMidiData(midi);
148
143
  this.instruments = midiData.instruments;
149
144
  this.timeline = midiData.timeline;
150
- this.ticksPerBeat = midi.header.ticksPerBeat;
151
145
  this.totalTime = this.calcTotalTime();
152
146
  }
153
147
  setChannelAudioNodes(audioContext) {
@@ -226,18 +220,20 @@ export class MidyGMLite {
226
220
  async scheduleTimelineEvents(t, offset, queueIndex) {
227
221
  while (queueIndex < this.timeline.length) {
228
222
  const event = this.timeline[queueIndex];
229
- const time = this.ticksToSecond(event.ticks, this.secondsPerBeat);
230
- if (time > t + this.lookAhead)
223
+ if (event.startTime > t + this.lookAhead)
231
224
  break;
232
225
  switch (event.type) {
233
226
  case "controller":
234
227
  this.handleControlChange(event.channel, event.controllerType, event.value);
235
228
  break;
236
229
  case "noteOn":
237
- await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, time + this.startDelay - offset);
238
- break;
230
+ if (event.velocity !== 0) {
231
+ await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
232
+ break;
233
+ }
234
+ /* falls through */
239
235
  case "noteOff": {
240
- const notePromise = this.scheduleNoteRelease(event.channel, event.noteNumber, event.velocity, time + this.startDelay - offset);
236
+ const notePromise = this.scheduleNoteRelease(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
241
237
  if (notePromise) {
242
238
  this.notePromises.push(notePromise);
243
239
  }
@@ -246,9 +242,6 @@ export class MidyGMLite {
246
242
  case "programChange":
247
243
  this.handleProgramChange(event.channel, event.programNumber);
248
244
  break;
249
- case "setTempo":
250
- this.secondsPerBeat = event.microsecondsPerBeat / 1000000;
251
- break;
252
245
  case "sysEx":
253
246
  this.handleSysEx(event.data);
254
247
  }
@@ -257,9 +250,8 @@ export class MidyGMLite {
257
250
  return queueIndex;
258
251
  }
259
252
  getQueueIndex(second) {
260
- const ticks = this.secondToTicks(second, this.secondsPerBeat);
261
253
  for (let i = 0; i < this.timeline.length; i++) {
262
- if (ticks <= this.timeline[i].ticks) {
254
+ if (second <= this.timeline[i].startTime) {
263
255
  return i;
264
256
  }
265
257
  }
@@ -367,18 +359,28 @@ export class MidyGMLite {
367
359
  timeline.push(event);
368
360
  });
369
361
  });
362
+ const priority = {
363
+ setTempo: 0,
364
+ controller: 1,
365
+ };
370
366
  timeline.sort((a, b) => {
371
- if (a.ticks !== b.ticks) {
367
+ if (a.ticks !== b.ticks)
372
368
  return a.ticks - b.ticks;
373
- }
374
- if (a.type !== "controller" && b.type === "controller") {
375
- return -1;
376
- }
377
- if (a.type === "controller" && b.type !== "controller") {
378
- return 1;
379
- }
380
- return 0;
369
+ return (priority[a.type] || 2) - (priority[b.type] || 2);
381
370
  });
371
+ let prevTempoTime = 0;
372
+ let prevTempoTicks = 0;
373
+ let secondsPerBeat = 0.5;
374
+ for (let i = 0; i < timeline.length; i++) {
375
+ const event = timeline[i];
376
+ const timeFromPrevTempo = this.ticksToSecond(event.ticks - prevTempoTicks, secondsPerBeat);
377
+ event.startTime = prevTempoTime + timeFromPrevTempo;
378
+ if (event.type === "setTempo") {
379
+ prevTempoTime += this.ticksToSecond(event.ticks - prevTempoTicks, secondsPerBeat);
380
+ secondsPerBeat = event.microsecondsPerBeat / 1000000;
381
+ prevTempoTicks = event.ticks;
382
+ }
383
+ }
382
384
  return { instruments, timeline };
383
385
  }
384
386
  stopNotes() {
@@ -430,32 +432,12 @@ export class MidyGMLite {
430
432
  }
431
433
  }
432
434
  calcTotalTime() {
433
- const endOfTracks = [];
434
- let prevTicks = 0;
435
435
  let totalTime = 0;
436
- let secondsPerBeat = 0.5;
437
436
  for (let i = 0; i < this.timeline.length; i++) {
438
437
  const event = this.timeline[i];
439
- switch (event.type) {
440
- case "setTempo": {
441
- const durationTicks = event.ticks - prevTicks;
442
- totalTime += this.ticksToSecond(durationTicks, secondsPerBeat);
443
- secondsPerBeat = event.microsecondsPerBeat / 1000000;
444
- prevTicks = event.ticks;
445
- break;
446
- }
447
- case "endOfTrack":
448
- endOfTracks.push(event);
449
- }
438
+ if (totalTime < event.startTime)
439
+ totalTime = event.startTime;
450
440
  }
451
- let maxTicks = 0;
452
- for (let i = 0; i < endOfTracks.length; i++) {
453
- const event = endOfTracks[i];
454
- if (maxTicks < event.ticks)
455
- maxTicks = event.ticks;
456
- }
457
- const durationTicks = maxTicks - prevTicks;
458
- totalTime += this.ticksToSecond(durationTicks, secondsPerBeat);
459
441
  return totalTime;
460
442
  }
461
443
  currentTime() {
@@ -483,43 +465,8 @@ export class MidyGMLite {
483
465
  const lfo = new OscillatorNode(audioContext, {
484
466
  frequency: 5,
485
467
  });
486
- const lfoGain = new GainNode(audioContext);
487
- lfo.connect(lfoGain);
488
468
  return {
489
469
  lfo,
490
- lfoGain,
491
- };
492
- }
493
- createReverbEffect(audioContext, options = {}) {
494
- const { decay = 0.8, preDecay = 0, } = options;
495
- const sampleRate = audioContext.sampleRate;
496
- const length = sampleRate * decay;
497
- const impulse = new AudioBuffer({
498
- numberOfChannels: 2,
499
- length,
500
- sampleRate,
501
- });
502
- const preDecayLength = Math.min(sampleRate * preDecay, length);
503
- for (let channel = 0; channel < impulse.numberOfChannels; channel++) {
504
- const channelData = impulse.getChannelData(channel);
505
- for (let i = 0; i < preDecayLength; i++) {
506
- channelData[i] = Math.random() * 2 - 1;
507
- }
508
- for (let i = preDecayLength; i < length; i++) {
509
- const attenuation = Math.exp(-(i - preDecayLength) / sampleRate / decay);
510
- channelData[i] = (Math.random() * 2 - 1) * attenuation;
511
- }
512
- }
513
- const convolverNode = new ConvolverNode(audioContext, {
514
- buffer: impulse,
515
- });
516
- const dryGain = new GainNode(audioContext);
517
- const wetGain = new GainNode(audioContext);
518
- convolverNode.connect(wetGain);
519
- return {
520
- convolverNode,
521
- dryGain,
522
- wetGain,
523
470
  };
524
471
  }
525
472
  connectNoteEffects(channel, gainNode) {
@@ -543,7 +490,7 @@ export class MidyGMLite {
543
490
  });
544
491
  let volume = (velocity / 127) * channel.volume * channel.expression;
545
492
  if (volume === 0)
546
- volume = 1e-6; // exponentialRampToValueAtTime() requirea a non-zero value
493
+ volume = 1e-6; // exponentialRampToValueAtTime() requires a non-zero value
547
494
  const attackVolume = this.cbToRatio(-noteInfo.initialAttenuation) * volume;
548
495
  const sustainVolume = attackVolume * (1 - noteInfo.volSustain);
549
496
  const volDelay = startTime + noteInfo.volDelay;
@@ -555,12 +502,6 @@ export class MidyGMLite {
555
502
  .exponentialRampToValueAtTime(attackVolume, volAttack)
556
503
  .setValueAtTime(attackVolume, volHold)
557
504
  .linearRampToValueAtTime(sustainVolume, volDecay);
558
- if (channel.modulation > 0) {
559
- const lfoGain = channel.modulationEffect.lfoGain;
560
- lfoGain.connect(bufferSource.detune);
561
- lfoGain.gain.cancelScheduledValues(startTime + channel.vibratoDelay);
562
- lfoGain.gain.setValueAtTime(channel.modulation, startTime + channel.vibratoDelay);
563
- }
564
505
  // filter envelope
565
506
  const maxFreq = this.audioContext.sampleRate / 2;
566
507
  const baseFreq = this.centToHz(noteInfo.initialFilterFc);
@@ -584,10 +525,23 @@ export class MidyGMLite {
584
525
  .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
585
526
  .setValueAtTime(adjustedPeekFreq, modHold)
586
527
  .linearRampToValueAtTime(adjustedSustainFreq, modDecay);
528
+ let lfoGain;
529
+ if (channel.modulation > 0) {
530
+ const vibratoDelay = startTime + channel.vibratoDelay;
531
+ const vibratoAttack = vibratoDelay + 0.1;
532
+ lfoGain = new GainNode(this.audioContext, {
533
+ gain: 0,
534
+ });
535
+ lfoGain.gain
536
+ .setValueAtTime(1e-6, vibratoDelay) // exponentialRampToValueAtTime() requires a non-zero value
537
+ .exponentialRampToValueAtTime(channel.modulation, vibratoAttack);
538
+ channel.modulationEffect.lfo.connect(lfoGain);
539
+ lfoGain.connect(bufferSource.detune);
540
+ }
587
541
  bufferSource.connect(filterNode);
588
542
  filterNode.connect(gainNode);
589
543
  bufferSource.start(startTime, noteInfo.start / noteInfo.sampleRate);
590
- return { bufferSource, gainNode, filterNode };
544
+ return { bufferSource, gainNode, filterNode, lfoGain };
591
545
  }
592
546
  async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
593
547
  const channel = this.channels[channelNumber];
@@ -600,16 +554,17 @@ export class MidyGMLite {
600
554
  const noteInfo = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
601
555
  if (!noteInfo)
602
556
  return;
603
- const { bufferSource, gainNode, filterNode } = await this
557
+ const { bufferSource, gainNode, filterNode, lfoGain } = await this
604
558
  .createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3);
605
559
  this.connectNoteEffects(channel, gainNode);
606
560
  const scheduledNotes = channel.scheduledNotes;
607
561
  const scheduledNote = {
608
- gainNode,
609
- filterNode,
610
562
  bufferSource,
611
- noteNumber,
563
+ filterNode,
564
+ gainNode,
565
+ lfoGain,
612
566
  noteInfo,
567
+ noteNumber,
613
568
  startTime,
614
569
  };
615
570
  if (scheduledNotes.has(noteNumber)) {
@@ -636,7 +591,7 @@ export class MidyGMLite {
636
591
  continue;
637
592
  if (targetNote.ending)
638
593
  continue;
639
- const { bufferSource, filterNode, gainNode, noteInfo } = targetNote;
594
+ const { bufferSource, filterNode, gainNode, lfoGain, noteInfo } = targetNote;
640
595
  const velocityRate = (velocity + 127) / 127;
641
596
  const volEndTime = stopTime + noteInfo.volRelease * velocityRate;
642
597
  gainNode.gain.cancelScheduledValues(stopTime);
@@ -658,6 +613,8 @@ export class MidyGMLite {
658
613
  bufferSource.disconnect(0);
659
614
  filterNode.disconnect(0);
660
615
  gainNode.disconnect(0);
616
+ if (lfoGain)
617
+ lfoGain.disconnect(0);
661
618
  resolve();
662
619
  };
663
620
  bufferSource.stop(volEndTime);
@@ -668,28 +625,21 @@ export class MidyGMLite {
668
625
  const now = this.audioContext.currentTime;
669
626
  return this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now);
670
627
  }
671
- releaseSustainPedal(channelNumber) {
672
- const now = this.audioContext.currentTime;
628
+ releaseSustainPedal(channelNumber, halfVelocity) {
629
+ const velocity = halfVelocity * 2;
673
630
  const channel = this.channels[channelNumber];
631
+ const promises = [];
674
632
  channel.sustainPedal = false;
675
633
  channel.scheduledNotes.forEach((scheduledNotes) => {
676
634
  scheduledNotes.forEach((scheduledNote) => {
677
635
  if (scheduledNote) {
678
- const { bufferSource, gainNode, filterNode, noteInfo } = scheduledNote;
679
- const volEndTime = now + noteInfo.volRelease;
680
- gainNode.gain.cancelScheduledValues(now);
681
- gainNode.gain.linearRampToValueAtTime(0, volEndTime);
682
- const maxFreq = this.audioContext.sampleRate / 2;
683
- const baseFreq = this.centToHz(noteInfo.initialFilterFc);
684
- const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
685
- const modEndTime = now + noteInfo.modRelease;
686
- filterNode.frequency
687
- .cancelScheduledValues(stopTime)
688
- .linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
689
- bufferSource.stop(volEndTime);
636
+ const { noteNumber } = scheduledNote;
637
+ const promise = this.releaseNote(channelNumber, noteNumber, velocity);
638
+ promises.push(promise);
690
639
  }
691
640
  });
692
641
  });
642
+ return promises;
693
643
  }
694
644
  handleMIDIMessage(statusByte, data1, data2) {
695
645
  const channelNumber = statusByte & 0x0F;
@@ -771,13 +721,9 @@ export class MidyGMLite {
771
721
  }
772
722
  }
773
723
  setModulation(channelNumber, modulation) {
774
- const now = this.audioContext.currentTime;
775
724
  const channel = this.channels[channelNumber];
776
- channel.modulation = (modulation * 100 / 127) *
777
- channel.modulationDepthRange;
778
- const lfoGain = channel.modulationEffect.lfoGain;
779
- lfoGain.gain.cancelScheduledValues(now);
780
- lfoGain.gain.setValueAtTime(channel.modulation, now);
725
+ channel.modulation = (modulation / 127) *
726
+ (channel.modulationDepthRange * 100);
781
727
  }
782
728
  setVolume(channelNumber, volume) {
783
729
  const channel = this.channels[channelNumber];
@@ -806,7 +752,7 @@ export class MidyGMLite {
806
752
  const isOn = value >= 64;
807
753
  this.channels[channelNumber].sustainPedal = isOn;
808
754
  if (!isOn) {
809
- this.releaseSustainPedal(channelNumber);
755
+ this.releaseSustainPedal(channelNumber, value);
810
756
  }
811
757
  }
812
758
  setRPNMSB(channelNumber, value) {
@@ -940,7 +886,7 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
940
886
  configurable: true,
941
887
  writable: true,
942
888
  value: {
943
- volume: 1,
889
+ volume: 100 / 127,
944
890
  pan: 0,
945
891
  vibratoRate: 5,
946
892
  vibratoDepth: 0.5,
@@ -950,7 +896,7 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
950
896
  dataLSB: 0,
951
897
  program: 0,
952
898
  pitchBend: 0,
953
- modulationDepthRange: 2,
899
+ modulationDepthRange: 0.5,
954
900
  }
955
901
  });
956
902
  Object.defineProperty(MidyGMLite, "effectSettings", {
package/esm/midy.d.ts CHANGED
@@ -34,7 +34,6 @@ export class Midy {
34
34
  };
35
35
  constructor(audioContext: any);
36
36
  ticksPerBeat: number;
37
- secondsPerBeat: number;
38
37
  totalTime: number;
39
38
  reverbFactor: number;
40
39
  masterFineTuning: number;
@@ -65,7 +64,6 @@ export class Midy {
65
64
  pannerNode: any;
66
65
  modulationEffect: {
67
66
  lfo: any;
68
- lfoGain: any;
69
67
  };
70
68
  reverbEffect: {
71
69
  convolverNode: any;
@@ -117,7 +115,6 @@ export class Midy {
117
115
  pannerNode: any;
118
116
  modulationEffect: {
119
117
  lfo: any;
120
- lfoGain: any;
121
118
  };
122
119
  reverbEffect: {
123
120
  convolverNode: any;
@@ -138,7 +135,6 @@ export class Midy {
138
135
  pannerNode: any;
139
136
  modulationEffect: {
140
137
  lfo: any;
141
- lfoGain: any;
142
138
  };
143
139
  reverbEffect: {
144
140
  convolverNode: any;
@@ -205,7 +201,6 @@ export class Midy {
205
201
  getActiveChannelNotes(scheduledNotes: any): any;
206
202
  createModulationEffect(audioContext: any): {
207
203
  lfo: any;
208
- lfoGain: any;
209
204
  };
210
205
  createReverbEffect(audioContext: any, options?: {}): {
211
206
  convolverNode: any;
@@ -231,8 +226,8 @@ export class Midy {
231
226
  noteOn(channelNumber: any, noteNumber: any, velocity: any): Promise<void>;
232
227
  scheduleNoteRelease(channelNumber: any, noteNumber: any, velocity: any, stopTime: any, stopPedal?: boolean): Promise<any> | undefined;
233
228
  releaseNote(channelNumber: any, noteNumber: any, velocity: any): Promise<any> | undefined;
234
- releaseSustainPedal(channelNumber: any): void;
235
- releaseSostenuto(channelNumber: any): void;
229
+ releaseSustainPedal(channelNumber: any, halfVelocity: any): any[];
230
+ releaseSostenutoPedal(channelNumber: any, halfVelocity: any): any[];
236
231
  handleMIDIMessage(statusByte: any, data1: any, data2: any): any;
237
232
  handlePolyphonicKeyPressure(channelNumber: any, noteNumber: any, pressure: any): void;
238
233
  handleProgramChange(channelNumber: any, program: any): void;
package/esm/midy.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"midy.d.ts","sourceRoot":"","sources":["../src/midy.js"],"names":[],"mappings":"AAMA;IAyBE;;;;;;;;;;;;;;;;;;;;MAoBE;IAEF;;;;;;;;;;;MAWE;IAEF,+BAMC;IAjED,qBAAmB;IACnB,uBAAqB;IACrB,kBAAc;IACd,qBAAmB;IACnB,yBAAqB;IACrB,2BAAuB;IACvB,cAAa;IACb,cAAa;IACb,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IAsChB,kBAAgC;IAChC,gBAA4C;IAE5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAAiD;IAInD,4BAMC;IAED,mCASC;IAED,gDAMC;IAED,sCASC;IAED;;;;;;;;;;;;;;;;;;MAuBC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAWC;IAED,0DAyBC;IAED,8DAUC;IAED,qDAOC;IAED,2EA6CC;IAED,mCAQC;IAED,0BA+CC;IAED,uDAEC;IAED,wDAEC;IAED;;;MA4FC;IAED,4BAsBC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBA2BC;IAED,sBAGC;IAED,4CASC;IAED,gDAKC;IAED;;;MAUC;IAED;;;;MAoCC;IAED;;;;;MA2CC;IAED,sDA2BC;IAED,2BAEC;IAED,4BAEC;IAED;;;;OAiFC;IAED,gDAQC;IAED,kGA+CC;IAED,0EAGC;IAED,sIA2CC;IAED,0FAGC;IAED,8CAuBC;IAED,2CAYC;IAED,gEAqBC;IAED,sFAeC;IAED,4DAIC;IAED,+DAEC;IAED,8DAGC;IAED,0EAkEC;IAED,+CAEC;IAED,yDAQC;IAED,iEAEC;IAED,iDAIC;IAED,2CAMC;IAED,yDAIC;IAED,+CAEC;IAED,sCAKC;IAED,sDAMC;IAED,oDAEC;IAED,iDASC;IAED,iDAIC;IAED,wDAQC;IAED,uDAIC;IAED,2DAMC;IAED,6DAMC;IAED,6DASC;IAED,4CAkBC;IAED,4CAkBC;IAED,gDAEC;IAED,gDAEC;IAGD,+DAuBC;IAED,uCAoBC;IAED,8CAEC;IAED,uCAoBC;IAED,gBAEC;IAED,eAEC;IAED,eAEC;IAED,eAEC;IAED,4DAmBC;IAED,oBAQC;IAED,oBAQC;IAED,yDAgDC;IAED,yCAGC;IAED,sCAIC;IAED,6CAGC;IAED,8CAMC;IAED,+CAGC;IAED,kDAMC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF"}
1
+ {"version":3,"file":"midy.d.ts","sourceRoot":"","sources":["../src/midy.js"],"names":[],"mappings":"AAMA;IAwBE;;;;;;;;;;;;;;;;;;;;MAoBE;IAEF;;;;;;;;;;;MAWE;IAEF,+BAMC;IAhED,qBAAmB;IACnB,kBAAc;IACd,qBAAmB;IACnB,yBAAqB;IACrB,2BAAuB;IACvB,cAAa;IACb,cAAa;IACb,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IAsChB,kBAAgC;IAChC,gBAA4C;IAE5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAAiD;IAInD,4BAMC;IAED,mCASC;IAED,gDAMC;IAED,sCASC;IAED;;;;;;;;;;;;;;;;;MAuBC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAWC;IAED,0DAyBC;IAED,8DAUC;IAED,qDAOC;IAED,2EA4CC;IAED,mCAOC;IAED,0BA+CC;IAED,uDAEC;IAED,wDAEC;IAED;;;MA2GC;IAED,4BAsBC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,4CASC;IAED,gDAKC;IAED;;MAOC;IAED;;;;MAoCC;IAED;;;;;MA2CC;IAED,sDA2BC;IAED,2BAEC;IAED,4BAEC;IAED;;;;OAsFC;IAED,gDAQC;IAED,kGAgDC;IAED,0EAGC;IAED,sIA6CC;IAED,0FAGC;IAED,kEAeC;IAED,oEAYC;IAED,gEAqBC;IAED,sFAeC;IAED,4DAIC;IAED,+DAEC;IAED,8DAGC;IAED,0EAkEC;IAED,+CAEC;IAED,yDAIC;IAED,iEAEC;IAED,iDAIC;IAED,2CAMC;IAED,yDAIC;IAED,+CAEC;IAED,sCAKC;IAED,sDAMC;IAED,oDAEC;IAED,iDASC;IAED,iDAIC;IAED,wDAUC;IAED,uDAGC;IAED,2DAOC;IAED,6DAOC;IAED,6DASC;IAED,4CAkBC;IAED,4CAkBC;IAED,gDAEC;IAED,gDAEC;IAGD,+DAuBC;IAED,uCAoBC;IAED,8CAEC;IAED,uCAoBC;IAED,gBAEC;IAED,eAEC;IAED,eAEC;IAED,eAEC;IAED,4DAmBC;IAED,oBAQC;IAED,oBAQC;IAED,yDAgDC;IAED,yCAGC;IAED,sCAIC;IAED,6CAGC;IAED,8CAMC;IAED,+CAGC;IAED,kDAMC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF"}