@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.
@@ -11,12 +11,6 @@ class MidyGM2 {
11
11
  writable: true,
12
12
  value: 120
13
13
  });
14
- Object.defineProperty(this, "secondsPerBeat", {
15
- enumerable: true,
16
- configurable: true,
17
- writable: true,
18
- value: 0.5
19
- });
20
14
  Object.defineProperty(this, "totalTime", {
21
15
  enumerable: true,
22
16
  configurable: true,
@@ -177,10 +171,10 @@ class MidyGM2 {
177
171
  const response = await fetch(midiUrl);
178
172
  const arrayBuffer = await response.arrayBuffer();
179
173
  const midi = (0, _esm_js_1.parseMidi)(new Uint8Array(arrayBuffer));
174
+ this.ticksPerBeat = midi.header.ticksPerBeat;
180
175
  const midiData = this.extractMidiData(midi);
181
176
  this.instruments = midiData.instruments;
182
177
  this.timeline = midiData.timeline;
183
- this.ticksPerBeat = midi.header.ticksPerBeat;
184
178
  this.totalTime = this.calcTotalTime();
185
179
  }
186
180
  setChannelAudioNodes(audioContext) {
@@ -266,18 +260,20 @@ class MidyGM2 {
266
260
  async scheduleTimelineEvents(t, offset, queueIndex) {
267
261
  while (queueIndex < this.timeline.length) {
268
262
  const event = this.timeline[queueIndex];
269
- const time = this.ticksToSecond(event.ticks, this.secondsPerBeat);
270
- if (time > t + this.lookAhead)
263
+ if (event.startTime > t + this.lookAhead)
271
264
  break;
272
265
  switch (event.type) {
273
266
  case "controller":
274
267
  this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
275
268
  break;
276
269
  case "noteOn":
277
- await this.scheduleNoteOn(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, time + this.startDelay - offset);
278
- break;
270
+ if (event.velocity !== 0) {
271
+ await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
272
+ break;
273
+ }
274
+ /* falls through */
279
275
  case "noteOff": {
280
- const notePromise = this.scheduleNoteRelease(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, time + this.startDelay - offset);
276
+ const notePromise = this.scheduleNoteRelease(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
281
277
  if (notePromise) {
282
278
  this.notePromises.push(notePromise);
283
279
  }
@@ -286,9 +282,6 @@ class MidyGM2 {
286
282
  case "programChange":
287
283
  this.handleProgramChange(event.channel, event.programNumber);
288
284
  break;
289
- case "setTempo":
290
- this.secondsPerBeat = event.microsecondsPerBeat / 1000000;
291
- break;
292
285
  case "sysEx":
293
286
  this.handleSysEx(event.data);
294
287
  }
@@ -297,9 +290,8 @@ class MidyGM2 {
297
290
  return queueIndex;
298
291
  }
299
292
  getQueueIndex(second) {
300
- const ticks = this.secondToTicks(second, this.secondsPerBeat);
301
293
  for (let i = 0; i < this.timeline.length; i++) {
302
- if (ticks <= this.timeline[i].ticks) {
294
+ if (second <= this.timeline[i].startTime) {
303
295
  return i;
304
296
  }
305
297
  }
@@ -441,18 +433,28 @@ class MidyGM2 {
441
433
  timeline.push(event);
442
434
  });
443
435
  });
436
+ const priority = {
437
+ setTempo: 0,
438
+ controller: 1,
439
+ };
444
440
  timeline.sort((a, b) => {
445
- if (a.ticks !== b.ticks) {
441
+ if (a.ticks !== b.ticks)
446
442
  return a.ticks - b.ticks;
447
- }
448
- if (a.type !== "controller" && b.type === "controller") {
449
- return -1;
450
- }
451
- if (a.type === "controller" && b.type !== "controller") {
452
- return 1;
453
- }
454
- return 0;
443
+ return (priority[a.type] || 2) - (priority[b.type] || 2);
455
444
  });
445
+ let prevTempoTime = 0;
446
+ let prevTempoTicks = 0;
447
+ let secondsPerBeat = 0.5;
448
+ for (let i = 0; i < timeline.length; i++) {
449
+ const event = timeline[i];
450
+ const timeFromPrevTempo = this.ticksToSecond(event.ticks - prevTempoTicks, secondsPerBeat);
451
+ event.startTime = prevTempoTime + timeFromPrevTempo;
452
+ if (event.type === "setTempo") {
453
+ prevTempoTime += this.ticksToSecond(event.ticks - prevTempoTicks, secondsPerBeat);
454
+ secondsPerBeat = event.microsecondsPerBeat / 1000000;
455
+ prevTempoTicks = event.ticks;
456
+ }
457
+ }
456
458
  return { instruments, timeline };
457
459
  }
458
460
  stopNotes() {
@@ -504,32 +506,12 @@ class MidyGM2 {
504
506
  }
505
507
  }
506
508
  calcTotalTime() {
507
- const endOfTracks = [];
508
- let prevTicks = 0;
509
509
  let totalTime = 0;
510
- let secondsPerBeat = 0.5;
511
510
  for (let i = 0; i < this.timeline.length; i++) {
512
511
  const event = this.timeline[i];
513
- switch (event.type) {
514
- case "setTempo": {
515
- const durationTicks = event.ticks - prevTicks;
516
- totalTime += this.ticksToSecond(durationTicks, secondsPerBeat);
517
- secondsPerBeat = event.microsecondsPerBeat / 1000000;
518
- prevTicks = event.ticks;
519
- break;
520
- }
521
- case "endOfTrack":
522
- endOfTracks.push(event);
523
- }
524
- }
525
- let maxTicks = 0;
526
- for (let i = 0; i < endOfTracks.length; i++) {
527
- const event = endOfTracks[i];
528
- if (maxTicks < event.ticks)
529
- maxTicks = event.ticks;
512
+ if (totalTime < event.startTime)
513
+ totalTime = event.startTime;
530
514
  }
531
- const durationTicks = maxTicks - prevTicks;
532
- totalTime += this.ticksToSecond(durationTicks, secondsPerBeat);
533
515
  return totalTime;
534
516
  }
535
517
  currentTime() {
@@ -557,11 +539,8 @@ class MidyGM2 {
557
539
  const lfo = new OscillatorNode(audioContext, {
558
540
  frequency: 5,
559
541
  });
560
- const lfoGain = new GainNode(audioContext);
561
- lfo.connect(lfoGain);
562
542
  return {
563
543
  lfo,
564
- lfoGain,
565
544
  };
566
545
  }
567
546
  createReverbEffect(audioContext, options = {}) {
@@ -693,12 +672,6 @@ class MidyGM2 {
693
672
  .exponentialRampToValueAtTime(attackVolume, volAttack)
694
673
  .setValueAtTime(attackVolume, volHold)
695
674
  .linearRampToValueAtTime(sustainVolume, volDecay);
696
- if (channel.modulation > 0) {
697
- const lfoGain = channel.modulationEffect.lfoGain;
698
- lfoGain.connect(bufferSource.detune);
699
- lfoGain.gain.cancelScheduledValues(startTime + channel.vibratoDelay);
700
- lfoGain.gain.setValueAtTime(channel.modulation, startTime + channel.vibratoDelay);
701
- }
702
675
  // filter envelope
703
676
  const softPedalFactor = 1 -
704
677
  (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
@@ -724,6 +697,19 @@ class MidyGM2 {
724
697
  .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
725
698
  .setValueAtTime(adjustedPeekFreq, modHold)
726
699
  .linearRampToValueAtTime(adjustedSustainFreq, modDecay);
700
+ let lfoGain;
701
+ if (channel.modulation > 0) {
702
+ const vibratoDelay = startTime + channel.vibratoDelay;
703
+ const vibratoAttack = vibratoDelay + 0.1;
704
+ lfoGain = new GainNode(this.audioContext, {
705
+ gain: 0,
706
+ });
707
+ lfoGain.gain
708
+ .setValueAtTime(1e-6, vibratoDelay) // exponentialRampToValueAtTime() requires a non-zero value
709
+ .exponentialRampToValueAtTime(channel.modulation, vibratoAttack);
710
+ channel.modulationEffect.lfo.connect(lfoGain);
711
+ lfoGain.connect(bufferSource.detune);
712
+ }
727
713
  bufferSource.connect(filterNode);
728
714
  filterNode.connect(gainNode);
729
715
  if (this.mono && channel.currentBufferSource) {
@@ -731,7 +717,7 @@ class MidyGM2 {
731
717
  channel.currentBufferSource = bufferSource;
732
718
  }
733
719
  bufferSource.start(startTime, noteInfo.start / noteInfo.sampleRate);
734
- return { bufferSource, gainNode, filterNode };
720
+ return { bufferSource, gainNode, filterNode, lfoGain };
735
721
  }
736
722
  calcBank(channel, channelNumber) {
737
723
  if (channel.bankMSB === 121) {
@@ -753,7 +739,7 @@ class MidyGM2 {
753
739
  const noteInfo = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
754
740
  if (!noteInfo)
755
741
  return;
756
- const { bufferSource, gainNode, filterNode } = await this
742
+ const { bufferSource, gainNode, filterNode, lfoGain } = await this
757
743
  .createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3);
758
744
  this.connectNoteEffects(channel, gainNode);
759
745
  if (channel.sostenutoPedal) {
@@ -767,11 +753,12 @@ class MidyGM2 {
767
753
  }
768
754
  const scheduledNotes = channel.scheduledNotes;
769
755
  const scheduledNote = {
770
- gainNode,
771
- filterNode,
772
756
  bufferSource,
773
- noteNumber,
757
+ filterNode,
758
+ gainNode,
759
+ lfoGain,
774
760
  noteInfo,
761
+ noteNumber,
775
762
  startTime,
776
763
  };
777
764
  if (scheduledNotes.has(noteNumber)) {
@@ -800,7 +787,7 @@ class MidyGM2 {
800
787
  continue;
801
788
  if (targetNote.ending)
802
789
  continue;
803
- const { bufferSource, filterNode, gainNode, noteInfo } = targetNote;
790
+ const { bufferSource, filterNode, gainNode, lfoGain, noteInfo } = targetNote;
804
791
  const velocityRate = (velocity + 127) / 127;
805
792
  const volEndTime = stopTime + noteInfo.volRelease * velocityRate;
806
793
  gainNode.gain.cancelScheduledValues(stopTime);
@@ -822,6 +809,8 @@ class MidyGM2 {
822
809
  bufferSource.disconnect(0);
823
810
  filterNode.disconnect(0);
824
811
  gainNode.disconnect(0);
812
+ if (lfoGain)
813
+ lfoGain.disconnect(0);
825
814
  resolve();
826
815
  };
827
816
  bufferSource.stop(volEndTime);
@@ -832,41 +821,34 @@ class MidyGM2 {
832
821
  const now = this.audioContext.currentTime;
833
822
  return this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now);
834
823
  }
835
- releaseSustainPedal(channelNumber) {
836
- const now = this.audioContext.currentTime;
824
+ releaseSustainPedal(channelNumber, halfVelocity) {
825
+ const velocity = halfVelocity * 2;
837
826
  const channel = this.channels[channelNumber];
827
+ const promises = [];
838
828
  channel.sustainPedal = false;
839
829
  channel.scheduledNotes.forEach((scheduledNotes) => {
840
830
  scheduledNotes.forEach((scheduledNote) => {
841
831
  if (scheduledNote) {
842
- const { bufferSource, gainNode, filterNode, noteInfo } = scheduledNote;
843
- const volEndTime = now + noteInfo.volRelease;
844
- gainNode.gain.cancelScheduledValues(now);
845
- gainNode.gain.linearRampToValueAtTime(0, volEndTime);
846
- const maxFreq = this.audioContext.sampleRate / 2;
847
- const baseFreq = this.centToHz(noteInfo.initialFilterFc);
848
- const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
849
- const modEndTime = now + noteInfo.modRelease;
850
- filterNode.frequency
851
- .cancelScheduledValues(stopTime)
852
- .linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
853
- bufferSource.stop(volEndTime);
832
+ const { noteNumber } = scheduledNote;
833
+ const promise = this.releaseNote(channelNumber, noteNumber, velocity);
834
+ promises.push(promise);
854
835
  }
855
836
  });
856
837
  });
838
+ return promises;
857
839
  }
858
- releaseSostenuto(channelNumber) {
859
- const now = this.audioContext.currentTime;
840
+ releaseSostenutoPedal(channelNumber, halfVelocity) {
841
+ const velocity = halfVelocity * 2;
860
842
  const channel = this.channels[channelNumber];
843
+ const promises = [];
861
844
  channel.sostenutoPedal = false;
862
845
  channel.sostenutoNotes.forEach((activeNote) => {
863
- const { gainNode, bufferSource, noteInfo } = activeNote;
864
- const fadeTime = noteInfo.volRelease;
865
- gainNode.gain.cancelScheduledValues(now);
866
- gainNode.gain.linearRampToValueAtTime(0, now + fadeTime);
867
- bufferSource.stop(now + fadeTime);
846
+ const { noteNumber } = activeNote;
847
+ const promise = this.releaseNote(channelNumber, noteNumber, velocity);
848
+ promises.push(promise);
868
849
  });
869
850
  channel.sostenutoNotes.clear();
851
+ return promises;
870
852
  }
871
853
  handleMIDIMessage(statusByte, data1, data2) {
872
854
  const channelNumber = omni ? 0 : statusByte & 0x0F;
@@ -976,13 +958,9 @@ class MidyGM2 {
976
958
  this.channels[channelNumber].bankMSB = msb;
977
959
  }
978
960
  setModulation(channelNumber, modulation) {
979
- const now = this.audioContext.currentTime;
980
961
  const channel = this.channels[channelNumber];
981
- channel.modulation = (modulation * 100 / 127) *
982
- channel.modulationDepthRange;
983
- const lfoGain = channel.modulationEffect.lfoGain;
984
- lfoGain.gain.cancelScheduledValues(now);
985
- lfoGain.gain.setValueAtTime(channel.modulation, now);
962
+ channel.modulation = (modulation / 127) *
963
+ (channel.modulationDepthRange * 100);
986
964
  }
987
965
  setPortamentoTime(channelNumber, portamentoTime) {
988
966
  this.channels[channelNumber].portamentoTime = portamentoTime / 127;
@@ -1017,7 +995,7 @@ class MidyGM2 {
1017
995
  const isOn = value >= 64;
1018
996
  this.channels[channelNumber].sustainPedal = isOn;
1019
997
  if (!isOn) {
1020
- this.releaseSustainPedal(channelNumber);
998
+ this.releaseSustainPedal(channelNumber, value);
1021
999
  }
1022
1000
  }
1023
1001
  setPortamento(channelNumber, value) {
@@ -1046,11 +1024,13 @@ class MidyGM2 {
1046
1024
  const activeNotes = this.getActiveNotes(channel);
1047
1025
  channel.sostenutoNotes = new Map(activeNotes);
1048
1026
  }
1027
+ else {
1028
+ this.releaseSostenutoPedal(channelNumber, value);
1029
+ }
1049
1030
  }
1050
1031
  setSoftPedal(channelNumber, softPedal) {
1051
1032
  const channel = this.channels[channelNumber];
1052
1033
  channel.softPedal = softPedal / 127;
1053
- this.updateChannelGain(channel);
1054
1034
  }
1055
1035
  setRPNMSB(channelNumber, value) {
1056
1036
  this.channels[channelNumber].rpnMSB = value;
@@ -1277,7 +1257,7 @@ Object.defineProperty(MidyGM2, "channelSettings", {
1277
1257
  writable: true,
1278
1258
  value: {
1279
1259
  currentBufferSource: null,
1280
- volume: 1,
1260
+ volume: 100 / 127,
1281
1261
  pan: 0,
1282
1262
  portamentoTime: 0,
1283
1263
  reverb: 0,
@@ -1294,7 +1274,7 @@ Object.defineProperty(MidyGM2, "channelSettings", {
1294
1274
  pitchBend: 0,
1295
1275
  fineTuning: 0,
1296
1276
  coarseTuning: 0,
1297
- modulationDepthRange: 2,
1277
+ modulationDepthRange: 0.5,
1298
1278
  }
1299
1279
  });
1300
1280
  Object.defineProperty(MidyGM2, "effectSettings", {
@@ -22,7 +22,6 @@ export class MidyGMLite {
22
22
  };
23
23
  constructor(audioContext: any);
24
24
  ticksPerBeat: number;
25
- secondsPerBeat: number;
26
25
  totalTime: number;
27
26
  noteCheckInterval: number;
28
27
  lookAhead: number;
@@ -48,7 +47,6 @@ export class MidyGMLite {
48
47
  pannerNode: any;
49
48
  modulationEffect: {
50
49
  lfo: any;
51
- lfoGain: any;
52
50
  };
53
51
  expression: number;
54
52
  modulation: number;
@@ -77,7 +75,6 @@ export class MidyGMLite {
77
75
  pannerNode: any;
78
76
  modulationEffect: {
79
77
  lfo: any;
80
- lfoGain: any;
81
78
  };
82
79
  };
83
80
  createChannels(audioContext: any): {
@@ -87,7 +84,6 @@ export class MidyGMLite {
87
84
  pannerNode: any;
88
85
  modulationEffect: {
89
86
  lfo: any;
90
- lfoGain: any;
91
87
  };
92
88
  expression: number;
93
89
  modulation: number;
@@ -131,12 +127,6 @@ export class MidyGMLite {
131
127
  getActiveChannelNotes(scheduledNotes: any): any;
132
128
  createModulationEffect(audioContext: any): {
133
129
  lfo: any;
134
- lfoGain: any;
135
- };
136
- createReverbEffect(audioContext: any, options?: {}): {
137
- convolverNode: any;
138
- dryGain: any;
139
- wetGain: any;
140
130
  };
141
131
  connectNoteEffects(channel: any, gainNode: any): void;
142
132
  cbToRatio(cb: any): number;
@@ -145,12 +135,13 @@ export class MidyGMLite {
145
135
  bufferSource: any;
146
136
  gainNode: any;
147
137
  filterNode: any;
138
+ lfoGain: any;
148
139
  }>;
149
140
  scheduleNoteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any): Promise<void>;
150
141
  noteOn(channelNumber: any, noteNumber: any, velocity: any): Promise<void>;
151
142
  scheduleNoteRelease(channelNumber: any, noteNumber: any, velocity: any, stopTime: any, stopPedal?: boolean): Promise<any> | undefined;
152
143
  releaseNote(channelNumber: any, noteNumber: any, velocity: any): Promise<any> | undefined;
153
- releaseSustainPedal(channelNumber: any): void;
144
+ releaseSustainPedal(channelNumber: any, halfVelocity: any): any[];
154
145
  handleMIDIMessage(statusByte: any, data1: any, data2: any): void | any[] | Promise<any>;
155
146
  handlePolyphonicKeyPressure(channelNumber: any, noteNumber: any, pressure: any): void;
156
147
  handleProgramChange(channelNumber: any, program: any): void;
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AAMA;IAoBE;;;;;;;;;;;;MAYE;IAEF;;;;;;;MAOE;IAEF,+BAMC;IAhDD,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;IA0BhB,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;;;;OAuEC;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,+DAcC;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-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AAMA;IAmBE;;;;;;;;;;;;MAYE;IAEF;;;;;;;MAOE;IAEF,+BAMC;IA/CD,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;IA0BhB,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;;;;;OA4EC;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,+DAcC;IAED,uCAoBC;IAED,8CAEC;IAED,uCAoBC;IAED,4DAgBC;IAED,oBAQC;IAED,yDAaC;IAED,yCAGC;IAED,sCAIC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF"}