@k-l-lambda/lilylet 0.1.73 → 0.1.74

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.
@@ -333,8 +333,10 @@ const extractMarkOptions = (marks) => {
333
333
  beamStart: false,
334
334
  beamEnd: false,
335
335
  dynamic: undefined,
336
+ dynamics: [],
336
337
  hairpin: undefined,
337
338
  pedal: undefined,
339
+ pedals: [],
338
340
  tremolo: undefined,
339
341
  fingerings: [],
340
342
  navigation: undefined,
@@ -380,7 +382,8 @@ const extractMarkOptions = (marks) => {
380
382
  case 'dynamic': {
381
383
  const dynStr = DYNAMIC_MAP[mark.type];
382
384
  if (dynStr) {
383
- result.dynamic = dynStr;
385
+ result.dynamic = dynStr; // kept for back-compat (first dynamic)
386
+ result.dynamics.push(dynStr);
384
387
  }
385
388
  break;
386
389
  }
@@ -391,16 +394,22 @@ const extractMarkOptions = (marks) => {
391
394
  else if (mark.type === HairpinType.diminuendoStart) {
392
395
  result.hairpin = 'dimStart';
393
396
  }
394
- else if (mark.type === HairpinType.crescendoEnd || mark.type === HairpinType.diminuendoEnd) {
395
- result.hairpin = 'end';
397
+ else if (mark.type === HairpinType.crescendoEnd) {
398
+ result.hairpin = 'crescEnd';
399
+ }
400
+ else if (mark.type === HairpinType.diminuendoEnd) {
401
+ result.hairpin = 'dimEnd';
396
402
  }
397
403
  break;
398
404
  case 'pedal':
405
+ // A note can carry more than one pedal mark (a pedal "bounce": an up
406
+ // to release the previous pedal and an immediate down to re-pedal on
407
+ // the same note), so collect ALL of them rather than keep one scalar.
399
408
  if (mark.type === PedalType.sustainOn) {
400
- result.pedal = 'down';
409
+ result.pedals.push('down');
401
410
  }
402
411
  else if (mark.type === PedalType.sustainOff) {
403
- result.pedal = 'up';
412
+ result.pedals.push('up');
404
413
  }
405
414
  break;
406
415
  case 'tie':
@@ -489,6 +498,7 @@ const noteEventToMEI = (event, indent, layerStaff, tieEnd, contextStemDir, keyFi
489
498
  elementId: noteId,
490
499
  hairpin: markOptions.hairpin,
491
500
  pedal: markOptions.pedal,
501
+ pedals: markOptions.pedals,
492
502
  hasTieStart: markOptions.tieStart,
493
503
  pitches: event.pitches,
494
504
  arpeggio: markOptions.arpeggio,
@@ -497,6 +507,7 @@ const noteEventToMEI = (event, indent, layerStaff, tieEnd, contextStemDir, keyFi
497
507
  mordent: markOptions.mordent,
498
508
  turn: markOptions.turn,
499
509
  dynamic: markOptions.dynamic,
510
+ dynamics: markOptions.dynamics,
500
511
  slurStart: markOptions.slurStart,
501
512
  slurEnd: markOptions.slurEnd,
502
513
  fingerings: markOptions.fingerings,
@@ -549,6 +560,7 @@ const noteEventToMEI = (event, indent, layerStaff, tieEnd, contextStemDir, keyFi
549
560
  elementId: chordId,
550
561
  hairpin: markOptions.hairpin,
551
562
  pedal: markOptions.pedal,
563
+ pedals: markOptions.pedals,
552
564
  hasTieStart: markOptions.tieStart,
553
565
  pitches: event.pitches,
554
566
  arpeggio: markOptions.arpeggio,
@@ -557,6 +569,7 @@ const noteEventToMEI = (event, indent, layerStaff, tieEnd, contextStemDir, keyFi
557
569
  mordent: markOptions.mordent,
558
570
  turn: markOptions.turn,
559
571
  dynamic: markOptions.dynamic,
572
+ dynamics: markOptions.dynamics,
560
573
  slurStart: markOptions.slurStart,
561
574
  slurEnd: markOptions.slurEnd,
562
575
  fingerings: markOptions.fingerings,
@@ -574,6 +587,15 @@ const restEventToMEI = (event, indent, keyFifths = 0, ottavaShift = 0, measureAc
574
587
  // Cross-staff attribute
575
588
  if (crossStaff)
576
589
  attrs += ` staff="${crossStaff}"`;
590
+ // A rest may carry a fermata (held silence). Surface it so the layer loop can
591
+ // emit a <fermata> control event referencing this rest's id.
592
+ let fermata;
593
+ if (event.marks) {
594
+ for (const mk of event.marks) {
595
+ if (mk.markType === 'ornament' && mk.type === 'fermata')
596
+ fermata = 'normal';
597
+ }
598
+ }
577
599
  // Pitched rest (positioned at specific pitch)
578
600
  if (event.pitch) {
579
601
  const pitch = encodePitch(event.pitch, keyFifths, ottavaShift);
@@ -581,14 +603,14 @@ const restEventToMEI = (event, indent, keyFifths = 0, ottavaShift = 0, measureAc
581
603
  }
582
604
  // Space rest (invisible)
583
605
  if (event.invisible) {
584
- return { xml: `${indent}<space ${attrs} />\n`, elementId: restId };
606
+ return { xml: `${indent}<space ${attrs} />\n`, elementId: restId, fermata };
585
607
  }
586
608
  // Full measure rest
587
609
  if (event.fullMeasure) {
588
610
  const mRestId = generateId('mrest');
589
- return { xml: `${indent}<mRest xml:id="${mRestId}" />\n`, elementId: mRestId };
611
+ return { xml: `${indent}<mRest xml:id="${mRestId}" />\n`, elementId: mRestId, fermata };
590
612
  }
591
- return { xml: `${indent}<rest ${attrs} />\n`, elementId: restId };
613
+ return { xml: `${indent}<rest ${attrs} />\n`, elementId: restId, fermata };
592
614
  };
593
615
  // Check if a tuplet has balanced internal beam groups (both [ and ] inside the same tuplet)
594
616
  // Returns false for cross-tuplet beams where [ is in one tuplet and ] in another
@@ -627,6 +649,9 @@ const tupletEventToMEI = (event, indent, layerStaff, keyFifths = 0, currentStaff
627
649
  const mordents = [];
628
650
  const turns = [];
629
651
  const arpeggios = [];
652
+ const pedals = [];
653
+ const fingerings = [];
654
+ const markups = [];
630
655
  // Handle internal beam groups: if notes have manual beam marks, respect them
631
656
  const hasInternalBeams = !inParentBeam && tupletHasInternalBeams(event);
632
657
  let beamOpen = false;
@@ -656,8 +681,9 @@ const tupletEventToMEI = (event, indent, layerStaff, keyFifths = 0, currentStaff
656
681
  if (result.slurEnd)
657
682
  slurEnds.push(result.elementId);
658
683
  // Collect other control events
659
- if (result.dynamic)
660
- dynamics.push({ startid: result.elementId, label: result.dynamic });
684
+ if (result.dynamics)
685
+ for (const label of result.dynamics)
686
+ dynamics.push({ startid: result.elementId, label });
661
687
  if (result.fermata)
662
688
  fermatas.push({ startid: result.elementId, shape: result.fermata === 'short' ? 'angular' : undefined });
663
689
  if (result.trill)
@@ -668,6 +694,16 @@ const tupletEventToMEI = (event, indent, layerStaff, keyFifths = 0, currentStaff
668
694
  turns.push({ startid: result.elementId });
669
695
  if (result.arpeggio)
670
696
  arpeggios.push({ plist: result.elementId });
697
+ if (result.pedals)
698
+ for (const dir of result.pedals)
699
+ pedals.push({ startId: result.elementId, dir });
700
+ // Fingerings and text directions (markup) on inner notes are control
701
+ // events that attach by id — collect them so the layer loop can emit
702
+ // <fing>/<dir> for them (previously silently dropped inside tuplets).
703
+ for (const fing of result.fingerings)
704
+ fingerings.push({ startid: result.elementId, finger: fing.finger, placement: fing.placement });
705
+ for (const mkup of result.markups)
706
+ markups.push({ startid: result.elementId, content: mkup.content, placement: mkup.placement });
671
707
  // Close beam if this note ends a beam group
672
708
  if (hasInternalBeams && markOptions.beamEnd && beamOpen) {
673
709
  xml += `${baseIndent}</beam>\n`;
@@ -676,7 +712,10 @@ const tupletEventToMEI = (event, indent, layerStaff, keyFifths = 0, currentStaff
676
712
  }
677
713
  else if (e.type === 'rest') {
678
714
  const restIndent = beamOpen ? baseIndent + ' ' : baseIndent;
679
- xml += restEventToMEI(e, restIndent, keyFifths, ottavaShift, measureAccidentals).xml;
715
+ const restResult = restEventToMEI(e, restIndent, keyFifths, ottavaShift, measureAccidentals);
716
+ xml += restResult.xml;
717
+ if (restResult.fermata)
718
+ fermatas.push({ startid: restResult.elementId, shape: restResult.fermata === 'short' ? 'angular' : undefined });
680
719
  }
681
720
  else if (e.type === 'context') {
682
721
  const ctx = e;
@@ -697,7 +736,7 @@ const tupletEventToMEI = (event, indent, layerStaff, keyFifths = 0, currentStaff
697
736
  xml += `${baseIndent}</beam>\n`;
698
737
  }
699
738
  xml += `${indent}</tuplet>\n`;
700
- return { xml, firstNoteId, slurStarts, slurEnds, dynamics, fermatas, trills, mordents, turns, arpeggios, endingClef };
739
+ return { xml, firstNoteId, slurStarts, slurEnds, dynamics, fermatas, trills, mordents, turns, arpeggios, pedals, fingerings, markups, endingClef };
701
740
  };
702
741
  // Convert TremoloEvent to MEI (fingered tremolo - alternating between two notes)
703
742
  const tremoloEventToMEI = (event, indent, keyFifths = 0, ottavaShift = 0, measureAccidentals) => {
@@ -803,16 +842,20 @@ const getEventBeamMarks = (event) => {
803
842
  return { beamStart: false, beamEnd: false };
804
843
  };
805
844
  // Encode a layer (voice)
806
- const encodeLayer = (voice, layerN, indent, initialTiePitches = [], keyFifths = 0, initialClef, initialSlur = null, initialHairpin = null, initialOctave = null) => {
845
+ const encodeLayer = (voice, layerN, indent, initialTiePitches = [], keyFifths = 0, initialClef, initialSlurs = [], initialHairpin = null, initialOctave = null) => {
807
846
  const layerId = generateId("layer");
808
847
  let xml = `${indent}<layer xml:id="${layerId}" n="${layerN}">\n`;
809
848
  let beamElementOpen = false; // Whether actual <beam> element is open (passed to tuplets)
810
849
  const baseIndent = indent + ' ';
811
850
  // Track current clef to only emit changes
812
851
  let currentClef = initialClef;
813
- // Track hairpin spans
852
+ // Track hairpin spans. Use a stack of open hairpins (not a single slot): the
853
+ // flattened layer stream can interleave overlapping/cross-staff hairpins
854
+ // (e.g. a crescendo starting before the previous one ended), and a single slot
855
+ // would silently overwrite the earlier one. Seed with any hairpin still open
856
+ // from the previous measure (cross-measure carry).
814
857
  const hairpins = [];
815
- let currentHairpin = initialHairpin;
858
+ const openHairpins = initialHairpin ? [initialHairpin] : [];
816
859
  // Track pedal marks (each is independent, not paired spans)
817
860
  const pedals = [];
818
861
  // Track octave spans - initialize from previous measure if continuing
@@ -823,9 +866,15 @@ const encodeLayer = (voice, layerN, indent, initialTiePitches = [], keyFifths =
823
866
  let currentOttavaShift = initialOctave?.shift || 0; // Track current ottava shift for pitch encoding
824
867
  let lastNoteId = null; // Track last note id for ending ottava spans
825
868
  let ottavaExplicitlyClosed = false; // Track if ottava was explicitly closed by \ottava #0
826
- // Track slur spans - slurs must be encoded as control events in MEI
869
+ // Track slur spans - slurs must be encoded as control events in MEI.
870
+ // A single slot can't hold the overlapping/concurrent slurs that piano writing
871
+ // produces (measured up to 3 open at once per voice); a new start while one was
872
+ // open overwrote it and the span was lost. Use a STACK and pair each end to the
873
+ // most-recent open slur (LIFO), matching the hairpin fix. `currentSlur` is kept
874
+ // as a view of the stack top for the existing cross-measure carry plumbing.
827
875
  const slurs = [];
828
- let currentSlur = initialSlur ? { startId: initialSlur } : null;
876
+ const openSlurs = initialSlurs.map(startId => ({ startId }));
877
+ let currentSlur = openSlurs.length ? openSlurs[openSlurs.length - 1] : null;
829
878
  // Track arpeggio refs
830
879
  const arpeggios = [];
831
880
  // Track ornament refs
@@ -962,38 +1011,52 @@ const encodeLayer = (voice, layerN, indent, initialTiePitches = [], keyFifths =
962
1011
  }
963
1012
  // Track hairpin spans
964
1013
  if (result.hairpin === 'crescStart') {
965
- currentHairpin = { form: 'cres', startId: result.elementId };
1014
+ openHairpins.push({ form: 'cres', startId: result.elementId });
966
1015
  }
967
1016
  else if (result.hairpin === 'dimStart') {
968
- currentHairpin = { form: 'dim', startId: result.elementId };
969
- }
970
- else if (result.hairpin === 'end' && currentHairpin) {
1017
+ openHairpins.push({ form: 'dim', startId: result.elementId });
1018
+ }
1019
+ else if ((result.hairpin === 'crescEnd' || result.hairpin === 'dimEnd') && openHairpins.length > 0) {
1020
+ const endForm = result.hairpin === 'crescEnd' ? 'cres' : 'dim';
1021
+ // Close the most-recent open hairpin of the matching form; if none
1022
+ // matches (interleaved/malformed input), fall back to the newest open.
1023
+ let idx = -1;
1024
+ for (let i = openHairpins.length - 1; i >= 0; i--) {
1025
+ if (openHairpins[i].form === endForm) {
1026
+ idx = i;
1027
+ break;
1028
+ }
1029
+ }
1030
+ if (idx < 0)
1031
+ idx = openHairpins.length - 1;
1032
+ const open = openHairpins.splice(idx, 1)[0];
971
1033
  hairpins.push({
972
- form: currentHairpin.form,
973
- startId: currentHairpin.startId,
1034
+ form: open.form,
1035
+ startId: open.startId,
974
1036
  endId: result.elementId,
975
1037
  });
976
- currentHairpin = null;
977
1038
  }
978
- // Track pedal marks (each is independent)
979
- if (result.pedal === 'down' || result.pedal === 'up') {
980
- pedals.push({
981
- startId: result.elementId,
982
- dir: result.pedal,
983
- });
1039
+ // Track pedal marks (each is independent; a note may carry several,
1040
+ // e.g. an up+down pedal bounce at the same beat).
1041
+ if (result.pedals) {
1042
+ for (const dir of result.pedals) {
1043
+ pedals.push({ startId: result.elementId, dir });
1044
+ }
984
1045
  }
985
1046
  // Track slur spans - end must be processed before start
986
- // in case a note ends one slur and starts another
987
- if (result.slurEnd && currentSlur) {
1047
+ // in case a note ends one slur and starts another.
1048
+ // Pair an end to the most-recent open slur (LIFO).
1049
+ if (result.slurEnd && openSlurs.length > 0) {
1050
+ const open = openSlurs.pop();
988
1051
  slurs.push({
989
- startId: currentSlur.startId,
1052
+ startId: open.startId,
990
1053
  endId: result.elementId,
991
1054
  });
992
- currentSlur = null;
993
1055
  }
994
1056
  if (result.slurStart) {
995
- currentSlur = { startId: result.elementId };
1057
+ openSlurs.push({ startId: result.elementId });
996
1058
  }
1059
+ currentSlur = openSlurs.length ? openSlurs[openSlurs.length - 1] : null;
997
1060
  // Track arpeggio refs
998
1061
  if (result.arpeggio) {
999
1062
  arpeggios.push({ plist: result.elementId });
@@ -1017,8 +1080,9 @@ const encodeLayer = (voice, layerN, indent, initialTiePitches = [], keyFifths =
1017
1080
  if (result.turn) {
1018
1081
  turns.push({ startid: result.elementId });
1019
1082
  }
1020
- if (result.dynamic) {
1021
- dynamics.push({ startid: result.elementId, label: result.dynamic });
1083
+ if (result.dynamics) {
1084
+ for (const label of result.dynamics)
1085
+ dynamics.push({ startid: result.elementId, label });
1022
1086
  }
1023
1087
  // Track fingerings
1024
1088
  for (const fing of result.fingerings) {
@@ -1039,6 +1103,9 @@ const encodeLayer = (voice, layerN, indent, initialTiePitches = [], keyFifths =
1039
1103
  const restCrossStaff = currentStaff !== (voice.staff || 1) ? currentStaff : undefined;
1040
1104
  const restResult = restEventToMEI(event, currentIndent, keyFifths, currentOttavaShift, measureAccidentals, restCrossStaff);
1041
1105
  xml += restResult.xml;
1106
+ // Fermata over a rest (held silence) — emit as a control event on the rest.
1107
+ if (restResult.fermata)
1108
+ fermatas.push({ startid: restResult.elementId, shape: restResult.fermata === 'short' ? 'angular' : undefined });
1042
1109
  // A leading dynamic/markup attaches to the next event, which may be this rest
1043
1110
  flushPendingMarkups(restResult.elementId);
1044
1111
  flushPendingDynamics(restResult.elementId);
@@ -1061,20 +1128,21 @@ const encodeLayer = (voice, layerN, indent, initialTiePitches = [], keyFifths =
1061
1128
  flushPendingDynamics(tupletResult.firstNoteId);
1062
1129
  lastNoteId = tupletResult.firstNoteId;
1063
1130
  }
1064
- // Process slur ends first (to close any pending slurs from before this tuplet)
1131
+ // Process slur ends first (to close open slurs, LIFO), then starts.
1065
1132
  for (const endId of tupletResult.slurEnds) {
1066
- if (currentSlur) {
1133
+ if (openSlurs.length > 0) {
1134
+ const open = openSlurs.pop();
1067
1135
  slurs.push({
1068
- startId: currentSlur.startId,
1136
+ startId: open.startId,
1069
1137
  endId: endId,
1070
1138
  });
1071
- currentSlur = null;
1072
1139
  }
1073
1140
  }
1074
1141
  // Then process slur starts (to open new slurs)
1075
1142
  for (const startId of tupletResult.slurStarts) {
1076
- currentSlur = { startId };
1143
+ openSlurs.push({ startId });
1077
1144
  }
1145
+ currentSlur = openSlurs.length ? openSlurs[openSlurs.length - 1] : null;
1078
1146
  // Collect other control events from tuplet
1079
1147
  dynamics.push(...tupletResult.dynamics);
1080
1148
  fermatas.push(...tupletResult.fermatas);
@@ -1082,6 +1150,9 @@ const encodeLayer = (voice, layerN, indent, initialTiePitches = [], keyFifths =
1082
1150
  mordents.push(...tupletResult.mordents);
1083
1151
  turns.push(...tupletResult.turns);
1084
1152
  arpeggios.push(...tupletResult.arpeggios);
1153
+ pedals.push(...tupletResult.pedals);
1154
+ fingerings.push(...tupletResult.fingerings);
1155
+ markups.push(...tupletResult.markups);
1085
1156
  break;
1086
1157
  }
1087
1158
  case 'tremolo':
@@ -1222,8 +1293,23 @@ const encodeLayer = (voice, layerN, indent, initialTiePitches = [], keyFifths =
1222
1293
  const pendingOctave = currentOctave
1223
1294
  ? { dis: currentOctave.dis, disPlace: currentOctave.disPlace, startId: currentOctave.startId, shift: currentOttavaShift, continued: true, emitted: currentOctave.emitted, endToken: currentOctave.endToken, endFallbackId: currentOctave.endFallbackId }
1224
1295
  : null;
1296
+ // Resolve hairpins still open at layer end. The cross-measure carry supports a
1297
+ // single pending hairpin, so keep the OLDEST open span (bottom of stack — most
1298
+ // likely to legitimately continue into the next measure) as pendingHairpin, and
1299
+ // flush the rest here, ending them at the last note so they aren't dropped
1300
+ // (MusicXML files routinely leave wedges unclosed). Without a last note id there
1301
+ // is nothing to attach to, so those are unavoidably dropped.
1302
+ let pendingHairpin = null;
1303
+ if (openHairpins.length > 0) {
1304
+ pendingHairpin = openHairpins[0];
1305
+ for (let i = 1; i < openHairpins.length; i++) {
1306
+ if (lastNoteId) {
1307
+ hairpins.push({ form: openHairpins[i].form, startId: openHairpins[i].startId, endId: lastNoteId });
1308
+ }
1309
+ }
1310
+ }
1225
1311
  xml += `${indent}</layer>\n`;
1226
- return { xml, hairpins, pedals, octaves, slurs, arpeggios, fermatas, trills, mordents, turns, dynamics, fingerings, navigations, harmonies, barlines, markups, pendingTiePitches, pendingSlur: currentSlur?.startId || null, pendingHairpin: currentHairpin, pendingOctave, ottavaExplicitlyClosed, endingClef: currentClef, lastNoteId, currentOttavaShift, octaveEndReplacements };
1312
+ return { xml, hairpins, pedals, octaves, slurs, arpeggios, fermatas, trills, mordents, turns, dynamics, fingerings, navigations, harmonies, barlines, markups, pendingTiePitches, pendingSlur: openSlurs.map(s => s.startId), pendingHairpin, pendingOctave, ottavaExplicitlyClosed, endingClef: currentClef, lastNoteId, currentOttavaShift, octaveEndReplacements };
1227
1313
  };
1228
1314
  // Encode a staff
1229
1315
  const encodeStaff = (voices, staffN, indent, tieState = {}, slurState = {}, hairpinState = {}, ottavaState = {}, keyFifths = 0, initialClef) => {
@@ -1260,7 +1346,7 @@ const encodeStaff = (voices, staffN, indent, tieState = {}, slurState = {}, hair
1260
1346
  const layerN = vi + 1;
1261
1347
  const tieKey = `${staffN}-${layerN}`;
1262
1348
  const initialTies = tieState[tieKey] || [];
1263
- const initialSlur = slurState[tieKey] || null;
1349
+ const initialSlur = slurState[tieKey] || [];
1264
1350
  const initialHairpin = hairpinState[tieKey] || null;
1265
1351
  const initialOctave = ottavaState[tieKey] || null;
1266
1352
  const result = encodeLayer(voice, layerN, indent + ' ', initialTies, keyFifths, endingClef, initialSlur, initialHairpin, initialOctave);
@@ -1286,9 +1372,9 @@ const encodeStaff = (voices, staffN, indent, tieState = {}, slurState = {}, hair
1286
1372
  pendingTies[tieKey] = result.pendingTiePitches;
1287
1373
  }
1288
1374
  // Track pending slurs for this layer
1289
- if (result.pendingSlur) {
1290
- pendingSlurs[tieKey] = result.pendingSlur;
1291
- }
1375
+ // Always record (even empty) so a measure that closed all its slurs
1376
+ // clears the carried stack rather than leaving a stale open slur.
1377
+ pendingSlurs[tieKey] = result.pendingSlur;
1292
1378
  // Track pending hairpins for this layer
1293
1379
  if (result.pendingHairpin) {
1294
1380
  pendingHairpins[tieKey] = result.pendingHairpin;
@@ -8,13 +8,23 @@ import { LilyletDoc } from './types';
8
8
  /**
9
9
  * Decode MusicXML string to LilyletDoc
10
10
  */
11
- export declare const decode: (xmlString: string) => LilyletDoc;
11
+ /**
12
+ * Decode raw MusicXML bytes (or a string) into a clean UTF-8/UTF-16-correct
13
+ * JS string. MuseScore/Finale/Sibelius frequently export `.xml` as UTF-16 LE
14
+ * with a BOM; reading those as UTF-8 yields mojibake and a failed parse.
15
+ *
16
+ * Detection order: byte-order mark → declared `encoding="..."` in the XML
17
+ * prolog → default UTF-8. A leading BOM is always stripped (xmldom chokes on a
18
+ * U+FEFF before `<?xml`).
19
+ */
20
+ export declare const readXmlString: (input: string | Uint8Array) => string;
21
+ export declare const decode: (input: string | Uint8Array) => LilyletDoc;
12
22
  /**
13
23
  * Decode MusicXML file to LilyletDoc
14
24
  */
15
25
  export declare const decodeFile: (filePath: string) => Promise<LilyletDoc>;
16
26
  declare const _default: {
17
- decode: (xmlString: string) => LilyletDoc;
27
+ decode: (input: string | Uint8Array) => LilyletDoc;
18
28
  decodeFile: (filePath: string) => Promise<LilyletDoc>;
19
29
  };
20
30
  export default _default;