@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.
package/esm/midy.js CHANGED
@@ -8,12 +8,6 @@ export class Midy {
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,
@@ -174,10 +168,10 @@ export class Midy {
174
168
  const response = await fetch(midiUrl);
175
169
  const arrayBuffer = await response.arrayBuffer();
176
170
  const midi = parseMidi(new Uint8Array(arrayBuffer));
171
+ this.ticksPerBeat = midi.header.ticksPerBeat;
177
172
  const midiData = this.extractMidiData(midi);
178
173
  this.instruments = midiData.instruments;
179
174
  this.timeline = midiData.timeline;
180
- this.ticksPerBeat = midi.header.ticksPerBeat;
181
175
  this.totalTime = this.calcTotalTime();
182
176
  }
183
177
  setChannelAudioNodes(audioContext) {
@@ -263,18 +257,20 @@ export class Midy {
263
257
  async scheduleTimelineEvents(t, offset, queueIndex) {
264
258
  while (queueIndex < this.timeline.length) {
265
259
  const event = this.timeline[queueIndex];
266
- const time = this.ticksToSecond(event.ticks, this.secondsPerBeat);
267
- if (time > t + this.lookAhead)
260
+ if (event.startTime > t + this.lookAhead)
268
261
  break;
269
262
  switch (event.type) {
270
263
  case "controller":
271
264
  this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
272
265
  break;
273
266
  case "noteOn":
274
- await this.scheduleNoteOn(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, time + this.startDelay - offset);
275
- break;
267
+ if (event.velocity !== 0) {
268
+ await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
269
+ break;
270
+ }
271
+ /* falls through */
276
272
  case "noteOff": {
277
- const notePromise = this.scheduleNoteRelease(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, time + this.startDelay - offset);
273
+ const notePromise = this.scheduleNoteRelease(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
278
274
  if (notePromise) {
279
275
  this.notePromises.push(notePromise);
280
276
  }
@@ -283,9 +279,6 @@ export class Midy {
283
279
  case "programChange":
284
280
  this.handleProgramChange(event.channel, event.programNumber);
285
281
  break;
286
- case "setTempo":
287
- this.secondsPerBeat = event.microsecondsPerBeat / 1000000;
288
- break;
289
282
  case "sysEx":
290
283
  this.handleSysEx(event.data);
291
284
  }
@@ -294,9 +287,8 @@ export class Midy {
294
287
  return queueIndex;
295
288
  }
296
289
  getQueueIndex(second) {
297
- const ticks = this.secondToTicks(second, this.secondsPerBeat);
298
290
  for (let i = 0; i < this.timeline.length; i++) {
299
- if (ticks <= this.timeline[i].ticks) {
291
+ if (second <= this.timeline[i].startTime) {
300
292
  return i;
301
293
  }
302
294
  }
@@ -438,18 +430,28 @@ export class Midy {
438
430
  timeline.push(event);
439
431
  });
440
432
  });
433
+ const priority = {
434
+ setTempo: 0,
435
+ controller: 1,
436
+ };
441
437
  timeline.sort((a, b) => {
442
- if (a.ticks !== b.ticks) {
438
+ if (a.ticks !== b.ticks)
443
439
  return a.ticks - b.ticks;
444
- }
445
- if (a.type !== "controller" && b.type === "controller") {
446
- return -1;
447
- }
448
- if (a.type === "controller" && b.type !== "controller") {
449
- return 1;
450
- }
451
- return 0;
440
+ return (priority[a.type] || 2) - (priority[b.type] || 2);
452
441
  });
442
+ let prevTempoTime = 0;
443
+ let prevTempoTicks = 0;
444
+ let secondsPerBeat = 0.5;
445
+ for (let i = 0; i < timeline.length; i++) {
446
+ const event = timeline[i];
447
+ const timeFromPrevTempo = this.ticksToSecond(event.ticks - prevTempoTicks, secondsPerBeat);
448
+ event.startTime = prevTempoTime + timeFromPrevTempo;
449
+ if (event.type === "setTempo") {
450
+ prevTempoTime += this.ticksToSecond(event.ticks - prevTempoTicks, secondsPerBeat);
451
+ secondsPerBeat = event.microsecondsPerBeat / 1000000;
452
+ prevTempoTicks = event.ticks;
453
+ }
454
+ }
453
455
  return { instruments, timeline };
454
456
  }
455
457
  stopNotes() {
@@ -501,32 +503,12 @@ export class Midy {
501
503
  }
502
504
  }
503
505
  calcTotalTime() {
504
- const endOfTracks = [];
505
- let prevTicks = 0;
506
506
  let totalTime = 0;
507
- let secondsPerBeat = 0.5;
508
507
  for (let i = 0; i < this.timeline.length; i++) {
509
508
  const event = this.timeline[i];
510
- switch (event.type) {
511
- case "setTempo": {
512
- const durationTicks = event.ticks - prevTicks;
513
- totalTime += this.ticksToSecond(durationTicks, secondsPerBeat);
514
- secondsPerBeat = event.microsecondsPerBeat / 1000000;
515
- prevTicks = event.ticks;
516
- break;
517
- }
518
- case "endOfTrack":
519
- endOfTracks.push(event);
520
- }
521
- }
522
- let maxTicks = 0;
523
- for (let i = 0; i < endOfTracks.length; i++) {
524
- const event = endOfTracks[i];
525
- if (maxTicks < event.ticks)
526
- maxTicks = event.ticks;
509
+ if (totalTime < event.startTime)
510
+ totalTime = event.startTime;
527
511
  }
528
- const durationTicks = maxTicks - prevTicks;
529
- totalTime += this.ticksToSecond(durationTicks, secondsPerBeat);
530
512
  return totalTime;
531
513
  }
532
514
  currentTime() {
@@ -554,11 +536,8 @@ export class Midy {
554
536
  const lfo = new OscillatorNode(audioContext, {
555
537
  frequency: 5,
556
538
  });
557
- const lfoGain = new GainNode(audioContext);
558
- lfo.connect(lfoGain);
559
539
  return {
560
540
  lfo,
561
- lfoGain,
562
541
  };
563
542
  }
564
543
  createReverbEffect(audioContext, options = {}) {
@@ -690,12 +669,6 @@ export class Midy {
690
669
  .exponentialRampToValueAtTime(attackVolume, volAttack)
691
670
  .setValueAtTime(attackVolume, volHold)
692
671
  .linearRampToValueAtTime(sustainVolume, volDecay);
693
- if (channel.modulation > 0) {
694
- const lfoGain = channel.modulationEffect.lfoGain;
695
- lfoGain.connect(bufferSource.detune);
696
- lfoGain.gain.cancelScheduledValues(startTime + channel.vibratoDelay);
697
- lfoGain.gain.setValueAtTime(channel.modulation, startTime + channel.vibratoDelay);
698
- }
699
672
  // filter envelope
700
673
  const softPedalFactor = 1 -
701
674
  (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
@@ -721,6 +694,19 @@ export class Midy {
721
694
  .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
722
695
  .setValueAtTime(adjustedPeekFreq, modHold)
723
696
  .linearRampToValueAtTime(adjustedSustainFreq, modDecay);
697
+ let lfoGain;
698
+ if (channel.modulation > 0) {
699
+ const vibratoDelay = startTime + channel.vibratoDelay;
700
+ const vibratoAttack = vibratoDelay + 0.1;
701
+ lfoGain = new GainNode(this.audioContext, {
702
+ gain: 0,
703
+ });
704
+ lfoGain.gain
705
+ .setValueAtTime(1e-6, vibratoDelay) // exponentialRampToValueAtTime() requires a non-zero value
706
+ .exponentialRampToValueAtTime(channel.modulation, vibratoAttack);
707
+ channel.modulationEffect.lfo.connect(lfoGain);
708
+ lfoGain.connect(bufferSource.detune);
709
+ }
724
710
  bufferSource.connect(filterNode);
725
711
  filterNode.connect(gainNode);
726
712
  if (this.mono && channel.currentBufferSource) {
@@ -764,11 +750,12 @@ export class Midy {
764
750
  }
765
751
  const scheduledNotes = channel.scheduledNotes;
766
752
  const scheduledNote = {
767
- gainNode,
768
- filterNode,
769
753
  bufferSource,
770
- noteNumber,
754
+ filterNode,
755
+ gainNode,
756
+ lfoGain,
771
757
  noteInfo,
758
+ noteNumber,
772
759
  startTime,
773
760
  };
774
761
  if (scheduledNotes.has(noteNumber)) {
@@ -797,7 +784,7 @@ export class Midy {
797
784
  continue;
798
785
  if (targetNote.ending)
799
786
  continue;
800
- const { bufferSource, filterNode, gainNode, noteInfo } = targetNote;
787
+ const { bufferSource, filterNode, gainNode, lfoGain, noteInfo } = targetNote;
801
788
  const velocityRate = (velocity + 127) / 127;
802
789
  const volEndTime = stopTime + noteInfo.volRelease * velocityRate;
803
790
  gainNode.gain.cancelScheduledValues(stopTime);
@@ -819,6 +806,8 @@ export class Midy {
819
806
  bufferSource.disconnect(0);
820
807
  filterNode.disconnect(0);
821
808
  gainNode.disconnect(0);
809
+ if (lfoGain)
810
+ lfoGain.disconnect(0);
822
811
  resolve();
823
812
  };
824
813
  bufferSource.stop(volEndTime);
@@ -829,41 +818,34 @@ export class Midy {
829
818
  const now = this.audioContext.currentTime;
830
819
  return this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now);
831
820
  }
832
- releaseSustainPedal(channelNumber) {
833
- const now = this.audioContext.currentTime;
821
+ releaseSustainPedal(channelNumber, halfVelocity) {
822
+ const velocity = halfVelocity * 2;
834
823
  const channel = this.channels[channelNumber];
824
+ const promises = [];
835
825
  channel.sustainPedal = false;
836
826
  channel.scheduledNotes.forEach((scheduledNotes) => {
837
827
  scheduledNotes.forEach((scheduledNote) => {
838
828
  if (scheduledNote) {
839
- const { bufferSource, gainNode, filterNode, noteInfo } = scheduledNote;
840
- const volEndTime = now + noteInfo.volRelease;
841
- gainNode.gain.cancelScheduledValues(now);
842
- gainNode.gain.linearRampToValueAtTime(0, volEndTime);
843
- const maxFreq = this.audioContext.sampleRate / 2;
844
- const baseFreq = this.centToHz(noteInfo.initialFilterFc);
845
- const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
846
- const modEndTime = now + noteInfo.modRelease;
847
- filterNode.frequency
848
- .cancelScheduledValues(stopTime)
849
- .linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
850
- bufferSource.stop(volEndTime);
829
+ const { noteNumber } = scheduledNote;
830
+ const promise = this.releaseNote(channelNumber, noteNumber, velocity);
831
+ promises.push(promise);
851
832
  }
852
833
  });
853
834
  });
835
+ return promises;
854
836
  }
855
- releaseSostenuto(channelNumber) {
856
- const now = this.audioContext.currentTime;
837
+ releaseSostenutoPedal(channelNumber, halfVelocity) {
838
+ const velocity = halfVelocity * 2;
857
839
  const channel = this.channels[channelNumber];
840
+ const promises = [];
858
841
  channel.sostenutoPedal = false;
859
842
  channel.sostenutoNotes.forEach((activeNote) => {
860
- const { gainNode, bufferSource, noteInfo } = activeNote;
861
- const fadeTime = noteInfo.volRelease;
862
- gainNode.gain.cancelScheduledValues(now);
863
- gainNode.gain.linearRampToValueAtTime(0, now + fadeTime);
864
- bufferSource.stop(now + fadeTime);
843
+ const { noteNumber } = activeNote;
844
+ const promise = this.releaseNote(channelNumber, noteNumber, velocity);
845
+ promises.push(promise);
865
846
  });
866
847
  channel.sostenutoNotes.clear();
848
+ return promises;
867
849
  }
868
850
  handleMIDIMessage(statusByte, data1, data2) {
869
851
  const channelNumber = omni ? 0 : statusByte & 0x0F;
@@ -984,13 +966,9 @@ export class Midy {
984
966
  this.channels[channelNumber].bankMSB = msb;
985
967
  }
986
968
  setModulation(channelNumber, modulation) {
987
- const now = this.audioContext.currentTime;
988
969
  const channel = this.channels[channelNumber];
989
- channel.modulation = (modulation * 100 / 127) *
990
- channel.modulationDepthRange;
991
- const lfoGain = channel.modulationEffect.lfoGain;
992
- lfoGain.gain.cancelScheduledValues(now);
993
- lfoGain.gain.setValueAtTime(channel.modulation, now);
970
+ channel.modulation = (modulation / 127) *
971
+ (channel.modulationDepthRange * 100);
994
972
  }
995
973
  setPortamentoTime(channelNumber, portamentoTime) {
996
974
  this.channels[channelNumber].portamentoTime = portamentoTime / 127;
@@ -1025,7 +1003,7 @@ export class Midy {
1025
1003
  const isOn = value >= 64;
1026
1004
  this.channels[channelNumber].sustainPedal = isOn;
1027
1005
  if (!isOn) {
1028
- this.releaseSustainPedal(channelNumber);
1006
+ this.releaseSustainPedal(channelNumber, value);
1029
1007
  }
1030
1008
  }
1031
1009
  setPortamento(channelNumber, value) {
@@ -1054,25 +1032,29 @@ export class Midy {
1054
1032
  const activeNotes = this.getActiveNotes(channel);
1055
1033
  channel.sostenutoNotes = new Map(activeNotes);
1056
1034
  }
1035
+ else {
1036
+ this.releaseSostenutoPedal(channelNumber, value);
1037
+ }
1057
1038
  }
1058
1039
  setSoftPedal(channelNumber, softPedal) {
1059
1040
  const channel = this.channels[channelNumber];
1060
1041
  channel.softPedal = softPedal / 127;
1061
- this.updateChannelGain(channel);
1062
1042
  }
1063
1043
  setVibratoRate(channelNumber, vibratoRate) {
1064
1044
  const now = this.audioContext.currentTime;
1065
1045
  const channel = this.channels[channelNumber];
1066
1046
  channel.vibratoRate = vibratoRate / 127 * 4 + 3; // 3-7Hz
1067
- channel.modulationEffect.lfo.frequency.cancelScheduledValues(now);
1068
- channel.modulationEffect.lfo.frequency.setValueAtTime(depth, now);
1047
+ channel.modulationEffect.lfo.frequency
1048
+ .cancelScheduledValues(now)
1049
+ .setValueAtTime(channel.vibratoRate, now);
1069
1050
  }
1070
1051
  setVibratoDepth(channelNumber, vibratoDepth) {
1071
1052
  const now = this.audioContext.currentTime;
1072
1053
  const channel = this.channels[channelNumber];
1073
1054
  channel.vibratoDepth = vibratoDepth / 127;
1074
- channel.modulationEffect.lfoGain.gain.cancelScheduledValues(now);
1075
- channel.modulationEffect.lfoGain.gain.setValueAtTime(depth, now);
1055
+ channel.modulationEffect.lfoGain.gain
1056
+ .cancelScheduledValues(now)
1057
+ .setValueAtTime(channel.vibratoDepth, now);
1076
1058
  }
1077
1059
  setVibratoDelay(channelNumber, vibratoDelay) {
1078
1060
  // Access Virus: 0-10sec
@@ -1343,7 +1325,7 @@ Object.defineProperty(Midy, "channelSettings", {
1343
1325
  writable: true,
1344
1326
  value: {
1345
1327
  currentBufferSource: null,
1346
- volume: 1,
1328
+ volume: 100 / 127,
1347
1329
  pan: 0,
1348
1330
  portamentoTime: 0,
1349
1331
  reverb: 0,
@@ -1360,7 +1342,7 @@ Object.defineProperty(Midy, "channelSettings", {
1360
1342
  pitchBend: 0,
1361
1343
  fineTuning: 0,
1362
1344
  coarseTuning: 0,
1363
- modulationDepthRange: 2,
1345
+ modulationDepthRange: 0.5,
1364
1346
  }
1365
1347
  });
1366
1348
  Object.defineProperty(Midy, "effectSettings", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marmooo/midy",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "A MIDI player/synthesizer written in JavaScript that supports GM-Lite/GM1 and SF2/SF3.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -24,7 +24,6 @@ export class MidyGM1 {
24
24
  };
25
25
  constructor(audioContext: any);
26
26
  ticksPerBeat: number;
27
- secondsPerBeat: number;
28
27
  totalTime: number;
29
28
  noteCheckInterval: number;
30
29
  lookAhead: number;
@@ -50,7 +49,6 @@ export class MidyGM1 {
50
49
  pannerNode: any;
51
50
  modulationEffect: {
52
51
  lfo: any;
53
- lfoGain: any;
54
52
  };
55
53
  expression: number;
56
54
  modulation: number;
@@ -81,7 +79,6 @@ export class MidyGM1 {
81
79
  pannerNode: any;
82
80
  modulationEffect: {
83
81
  lfo: any;
84
- lfoGain: any;
85
82
  };
86
83
  };
87
84
  createChannels(audioContext: any): {
@@ -91,7 +88,6 @@ export class MidyGM1 {
91
88
  pannerNode: any;
92
89
  modulationEffect: {
93
90
  lfo: any;
94
- lfoGain: any;
95
91
  };
96
92
  expression: number;
97
93
  modulation: number;
@@ -137,12 +133,6 @@ export class MidyGM1 {
137
133
  getActiveChannelNotes(scheduledNotes: any): any;
138
134
  createModulationEffect(audioContext: any): {
139
135
  lfo: any;
140
- lfoGain: any;
141
- };
142
- createReverbEffect(audioContext: any, options?: {}): {
143
- convolverNode: any;
144
- dryGain: any;
145
- wetGain: any;
146
136
  };
147
137
  connectNoteEffects(channel: any, gainNode: any): void;
148
138
  cbToRatio(cb: any): number;
@@ -151,12 +141,13 @@ export class MidyGM1 {
151
141
  bufferSource: any;
152
142
  gainNode: any;
153
143
  filterNode: any;
144
+ lfoGain: any;
154
145
  }>;
155
146
  scheduleNoteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any): Promise<void>;
156
147
  noteOn(channelNumber: any, noteNumber: any, velocity: any): Promise<void>;
157
148
  scheduleNoteRelease(channelNumber: any, noteNumber: any, velocity: any, stopTime: any, stopPedal?: boolean): Promise<any> | undefined;
158
149
  releaseNote(channelNumber: any, noteNumber: any, velocity: any): Promise<any> | undefined;
159
- releaseSustainPedal(channelNumber: any): void;
150
+ releaseSustainPedal(channelNumber: any, halfVelocity: any): any[];
160
151
  handleMIDIMessage(statusByte: any, data1: any, data2: any): void | any[] | Promise<any>;
161
152
  handlePolyphonicKeyPressure(channelNumber: any, noteNumber: any, pressure: any): void;
162
153
  handleProgramChange(channelNumber: any, program: any): void;
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"AAMA;IAoBE;;;;;;;;;;;;;;MAcE;IAEF;;;;;;;MAOE;IAEF,+BAMC;IAlDD,qBAAmB;IACnB,uBAAqB;IACrB,kBAAc;IACd,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;IA4BhB,kBAAgC;IAChC,gBAA4C;IAE5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAAiD;IAInD,4BAMC;IAED,mCASC;IAED,gDAMC;IAED,sCASC;IAED;;;;;;;MAgBC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAWC;IAED,0DAyBC;IAED,8DAUC;IAED,qDAOC;IAED,2EA6CC;IAED,mCAQC;IAED,0BA+CC;IAED,uDAEC;IAED,wDAEC;IAED;;;MA0DC;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,sDAEC;IAED,2BAEC;IAED,4BAEC;IAED;;;;OAwEC;IAED,kGAsCC;IAED,0EAGC;IAED,sIA0CC;IAED,0FAGC;IAED,8CAuBC;IAED,wFAqBC;IAED,sFAeC;IAED,4DAGC;IAED,+DAEC;IAED,8DAGC;IAED,mFA+BC;IAED,yDAQC;IAED,iDAIC;IAED,2CAMC;IAED,yDAIC;IAED,sCAKC;IAED,sDAMC;IAED,gDAEC;IAED,gDAEC;IAED,+DAoBC;IAED,uCAoBC;IAED,8CAEC;IAED,uCAoBC;IAED,4DAgBC;IAED,oBAQC;IAED,yDAaC;IAED,yCAGC;IAED,sCAIC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF"}
1
+ {"version":3,"file":"midy-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"AAMA;IAmBE;;;;;;;;;;;;;;MAcE;IAEF;;;;;;;MAOE;IAEF,+BAMC;IAjDD,qBAAmB;IACnB,kBAAc;IACd,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;IA4BhB,kBAAgC;IAChC,gBAA4C;IAE5C;;;;;;;;;;;;;;;;;;;;;;;;;;;QAAiD;IAInD,4BAMC;IAED,mCASC;IAED,gDAMC;IAED,sCASC;IAED;;;;;;MAgBC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;QAWC;IAED,0DAyBC;IAED,8DAUC;IAED,qDAOC;IAED,2EA4CC;IAED,mCAOC;IAED,0BA+CC;IAED,uDAEC;IAED,wDAEC;IAED;;;MAyEC;IAED,4BAsBC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,4CASC;IAED,gDAKC;IAED;;MAOC;IAED,sDAEC;IAED,2BAEC;IAED,4BAEC;IAED;;;;;OA6EC;IAED,kGAuCC;IAED,0EAGC;IAED,sIA4CC;IAED,0FAGC;IAED,kEAeC;IAED,wFAqBC;IAED,sFAeC;IAED,4DAGC;IAED,+DAEC;IAED,8DAGC;IAED,mFA+BC;IAED,yDAIC;IAED,iDAIC;IAED,2CAMC;IAED,yDAIC;IAED,sCAKC;IAED,sDAMC;IAED,gDAEC;IAED,gDAEC;IAED,+DAoBC;IAED,uCAoBC;IAED,8CAEC;IAED,uCAoBC;IAED,4DAgBC;IAED,oBAQC;IAED,yDAaC;IAED,yCAGC;IAED,sCAIC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF"}