@ugfoundation/swaralipi-js 0.12.0 → 0.13.0

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.
Files changed (43) hide show
  1. package/README.md +7 -1
  2. package/dist/{batch-ZNVWIGEL.js → batch-BS2AIZOR.js} +5 -4
  3. package/dist/batch-BS2AIZOR.js.map +1 -0
  4. package/dist/{chunk-A7R3VFIV.js → chunk-3ZBNANDF.js} +3 -3
  5. package/dist/{chunk-A7R3VFIV.js.map → chunk-3ZBNANDF.js.map} +1 -1
  6. package/dist/{chunk-ISNAL632.js → chunk-5N6HGPFH.js} +5 -165
  7. package/dist/chunk-5N6HGPFH.js.map +1 -0
  8. package/dist/chunk-BSZ3WQUP.js +164 -0
  9. package/dist/chunk-BSZ3WQUP.js.map +1 -0
  10. package/dist/{chunk-A6Z7X2LX.js → chunk-ZNJTMQIX.js} +227 -63
  11. package/dist/chunk-ZNJTMQIX.js.map +1 -0
  12. package/dist/cli.cjs +1 -1
  13. package/dist/cli.cjs.map +1 -1
  14. package/dist/cli.js +2 -2
  15. package/dist/convert/index.cjs +1 -1
  16. package/dist/convert/index.cjs.map +1 -1
  17. package/dist/convert/index.js +3 -2
  18. package/dist/index.cjs +1 -1
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.js +1 -1
  21. package/dist/player/index.cjs +470 -122
  22. package/dist/player/index.cjs.map +1 -1
  23. package/dist/player/index.d.cts +12 -2
  24. package/dist/player/index.d.ts +12 -2
  25. package/dist/player/index.js +85 -60
  26. package/dist/player/index.js.map +1 -1
  27. package/dist/{schedule-DN-8X17d.d.ts → schedule-ClyON98C.d.ts} +22 -10
  28. package/dist/{schedule-CRpjr6JH.d.cts → schedule-V-fgepvP.d.cts} +22 -10
  29. package/dist/spec/index.cjs +1 -1
  30. package/dist/spec/index.cjs.map +1 -1
  31. package/dist/spec/index.d.cts +1 -1
  32. package/dist/spec/index.d.ts +1 -1
  33. package/dist/spec/index.js +1 -1
  34. package/dist/viewer/index.cjs +366 -61
  35. package/dist/viewer/index.cjs.map +1 -1
  36. package/dist/viewer/index.d.cts +1 -1
  37. package/dist/viewer/index.d.ts +1 -1
  38. package/dist/viewer/index.js +2 -1
  39. package/dist/viewer/index.js.map +1 -1
  40. package/package.json +1 -1
  41. package/dist/batch-ZNVWIGEL.js.map +0 -1
  42. package/dist/chunk-A6Z7X2LX.js.map +0 -1
  43. package/dist/chunk-ISNAL632.js.map +0 -1
@@ -286,7 +286,7 @@ var init_spec = __esm({
286
286
  init_grace();
287
287
  init_lay();
288
288
  init_svara();
289
- SPEC_VERSION = "0.12.0";
289
+ SPEC_VERSION = "0.13.0";
290
290
  }
291
291
  });
292
292
 
@@ -368,9 +368,305 @@ function sampleFileName(midi) {
368
368
  return midiToSampleName(midi).toLowerCase().replace("#", "s");
369
369
  }
370
370
 
371
- // src/player/schedule.ts
371
+ // src/convert/codec.ts
372
+ var LETTERS = {
373
+ s: { letter: "s" },
374
+ r: { letter: "r" },
375
+ g: { letter: "g" },
376
+ m: { letter: "m" },
377
+ p: { letter: "p" },
378
+ q: { letter: "d" },
379
+ // ধ — shuddha dha
380
+ n: { letter: "n" },
381
+ d: { letter: "d", komal: true },
382
+ // দ
383
+ t: { letter: "g", komal: true },
384
+ // জ্ঞ
385
+ k: { letter: "m", tivra: true },
386
+ // ক্ষ
387
+ u: { letter: "n", komal: true },
388
+ // ণ
389
+ v: { letter: "r", komal: true }
390
+ // ঋ
391
+ };
392
+ var STRUCTURALS = {
393
+ "%": () => ({ svara: ["madhyam", "s"] }),
394
+ // precomposed সা
395
+ "&": () => ({ svara: ["madhyam", "n"] })
396
+ // precomposed নি
397
+ };
398
+ var BAR_RUN_RE = /^(-)?([lLA[\]]+)$/;
399
+ function decodeBarRun(body) {
400
+ const m = body.match(BAR_RUN_RE);
401
+ if (!m || !/[lLA]/.test(m[2])) return void 0;
402
+ const notes = [];
403
+ if (m[1]) notes.push({ sustain: true });
404
+ const run = m[2];
405
+ let i = 0;
406
+ while (i < run.length) {
407
+ const ch = run[i];
408
+ if (ch === "l" || ch === "L") {
409
+ let j = i;
410
+ while (j < run.length && run[j] === ch) j++;
411
+ const strokes = (j - i) * (ch === "L" ? 2 : 1);
412
+ notes.push({ bar: strokes >= 4 ? "quad" : strokes >= 2 ? "double" : "single" });
413
+ i = j;
414
+ } else if (ch === "A") {
415
+ notes.push({ bar: "dari" });
416
+ i++;
417
+ } else {
418
+ notes.push({ brackets: [{ kind: "alt", edge: ch === "[" ? "open" : "close" }] });
419
+ i++;
420
+ }
421
+ }
422
+ return notes;
423
+ }
424
+ var KNOWN_RAW_RE = [
425
+ /^[wxyjEJ$<>=_]+$/,
426
+ // arc & line drawing segments
427
+ /^\|+$/,
428
+ // thin yugal-dari stop mark
429
+ /^[0-9!@#]{1,2}[fF]?:?$/,
430
+ // beat-number labels incl. 10–16 (sam tick = f/F, e.g. 1f / 1F / 2:)
431
+ /^[oOe`+,.;:iI~^*bcBCWXYFHZz'"?]$/
432
+ // dots, ticks, duration marks, legacy forms
433
+ ];
434
+ var OPEN_BRACKETS = {
435
+ "{": "repeat",
436
+ "(": "skip",
437
+ "[": "alt"
438
+ };
439
+ var CLOSE_BRACKETS = {
440
+ "}": "repeat",
441
+ ")": "skip",
442
+ "]": "alt"
443
+ };
444
+ function octaveOf(mark) {
445
+ if (mark === "f" || mark === "F") return "taar";
446
+ if (mark === "h" || mark === "H") return "mandra";
447
+ return "madhyam";
448
+ }
449
+ var BODY_RE = /^([0-9!@][fF]?)?([zj]*)(-)?([i:])?(?:([SRGMPQNDTKUV])([fFhH])?)?((?:[srgmpqndtkuv][fh]?[i:]?)+)?(a)?([fh])?([i:])?(?:([SRGMPQNDTKUV])([fFhH])?)?([0-9!@][fF]?)?(-)?$/;
450
+ function decodeChunk(chunk) {
451
+ const opens = [];
452
+ const closes = [];
453
+ let body = chunk;
454
+ while (body.length > 0) {
455
+ const kind = OPEN_BRACKETS[body[0]];
456
+ if (kind === void 0) break;
457
+ opens.push(kind);
458
+ body = body.slice(1);
459
+ }
460
+ while (body.length > 0) {
461
+ const kind = CLOSE_BRACKETS[body[body.length - 1]];
462
+ if (kind === void 0) break;
463
+ closes.unshift(kind);
464
+ body = body.slice(0, -1);
465
+ }
466
+ const finish = (notes, kind) => {
467
+ if (notes.length === 0) notes.push({});
468
+ const first = notes[0];
469
+ const last = notes[notes.length - 1];
470
+ if (opens.length > 0) first.brackets = [...opens.map((kind_) => ({ kind: kind_, edge: "open" })), ...first.brackets ?? []];
471
+ if (closes.length > 0) last.brackets = [...last.brackets ?? [], ...closes.map((kind_) => ({ kind: kind_, edge: "close" }))];
472
+ return { notes, kind };
473
+ };
474
+ if (body === "") return finish([{}], "decoded");
475
+ const barRun = decodeBarRun(body);
476
+ if (barRun) return finish(barRun, "decoded");
477
+ const structural = STRUCTURALS[body];
478
+ if (structural) return finish([structural()], "decoded");
479
+ if (KNOWN_RAW_RE.some((re) => re.test(body))) return finish([{}], "known");
480
+ const m = body.match(BODY_RE);
481
+ if (m) {
482
+ const [, , , hyphen, , kanLetter, kanOct, unitsStr, aakar, postOct, , trailKan, trailKanOct] = m;
483
+ const attachKan = (note, letter, oct) => {
484
+ const def = LETTERS[letter.toLowerCase()];
485
+ note.kan = [octaveOf(oct?.toLowerCase()), def.letter];
486
+ };
487
+ if (unitsStr) {
488
+ const units = unitsStr.match(/[srgmpqndtkuv][fh]?/g) ?? [];
489
+ const notes = units.map((unit) => {
490
+ const def = LETTERS[unit[0]];
491
+ const note = { svara: [octaveOf(unit[1]), def.letter] };
492
+ if (def.komal) note.komal = true;
493
+ if (def.tivra) note.tivra = true;
494
+ if (units.length > 1) note.durationMatra = 1 / units.length;
495
+ return note;
496
+ });
497
+ const last = notes[notes.length - 1];
498
+ if (postOct && last.svara[0] === "madhyam") {
499
+ last.svara = [octaveOf(postOct), last.svara[1]];
500
+ }
501
+ if (hyphen) notes[0].sustain = true;
502
+ if (kanLetter) attachKan(notes[0], kanLetter, kanOct);
503
+ if (trailKan && last.kan === void 0) attachKan(last, trailKan, trailKanOct);
504
+ return finish(notes, "decoded");
505
+ }
506
+ if (aakar) {
507
+ const note = hyphen ? { sustain: true } : { rest: true };
508
+ if (trailKan) attachKan(note, trailKan, trailKanOct);
509
+ else if (kanLetter) attachKan(note, kanLetter, kanOct);
510
+ return finish([note], "decoded");
511
+ }
512
+ if (hyphen && !kanLetter && !trailKan) {
513
+ return finish([{ sustain: true }], "decoded");
514
+ }
515
+ }
516
+ return finish([{}], "unknown");
517
+ }
518
+ function decodeCell(text) {
519
+ const chunks = text.split(/\s+/).filter((c) => c.length > 0);
520
+ const out = { notes: [], unknownTokens: [], knownRawTokens: [], decoded: false };
521
+ for (const chunk of chunks) {
522
+ const { notes, kind } = decodeChunk(chunk);
523
+ notes[0].raw = chunk;
524
+ if (kind === "decoded") out.decoded = true;
525
+ else if (kind === "known") out.knownRawTokens.push(chunk);
526
+ else out.unknownTokens.push(chunk);
527
+ out.notes.push(...notes);
528
+ }
529
+ return out;
530
+ }
531
+
532
+ // src/player/repeats.ts
372
533
  var UNDECODED_CELL = /[srgmpqnvtduka]/i;
373
534
  var isSilent = (n) => n.svara === void 0 && n.rest !== true && n.sustain !== true && !(n.raw !== void 0 && UNDECODED_CELL.test(n.raw));
535
+ var STOP_MARK = /^\|+$/;
536
+ var EPS = 1e-6;
537
+ var edges = (n, kind, edge) => n.brackets?.filter((b) => b.kind === kind && b.edge === edge).length ?? 0;
538
+ var sounds = (n) => n.svara !== void 0 || n.rest === true || n.sustain === true;
539
+ var matra = (notes) => notes.reduce((sum, n) => sum + (n.durationMatra ?? 1), 0);
540
+ function playOrder(doc) {
541
+ const tokens = [];
542
+ doc.parts.forEach(
543
+ (part, p) => part.rows.forEach(
544
+ (row, r) => row.notes.forEach((note, i) => tokens.push({ p, r, i, note, timed: note.bar === void 0 && !isSilent(note) }))
545
+ )
546
+ );
547
+ const { asthayi, stop, returns } = form(tokens);
548
+ const changed = changedNotes(tokens);
549
+ const out = [];
550
+ const sing = (lo, hi, useChanged) => {
551
+ const repeats = [];
552
+ let owed = 0;
553
+ for (let k = lo; k < hi; ) {
554
+ const t = tokens[k];
555
+ if (edges(t.note, "repeat", "open") > 0 && repeats.at(-1)?.open !== k) repeats.push({ open: k, again: false });
556
+ const again = repeats.at(-1)?.again === true;
557
+ const skipTo = again && edges(t.note, "skip", "open") > 0 ? skipEnd(tokens, k, hi) : -1;
558
+ if (skipTo >= 0) {
559
+ if (edges(tokens[skipTo].note, "repeat", "close") > 0) repeats.pop();
560
+ k = skipTo + 1;
561
+ owed = 0;
562
+ continue;
563
+ }
564
+ if (owed > EPS && t.timed) owed -= matra([t.note]);
565
+ else {
566
+ const sub = useChanged || again ? changed.get(k) : void 0;
567
+ const fits = sub !== void 0 && matra(sub) >= matra([t.note]) - EPS;
568
+ for (const note of fits ? sub : [t.note]) out.push({ p: t.p, r: t.r, i: t.i, note });
569
+ owed = fits ? matra(sub) - matra([t.note]) : 0;
570
+ }
571
+ const loop = edges(t.note, "repeat", "close") > 0 ? repeats.at(-1) : void 0;
572
+ if (loop !== void 0 && !loop.again) {
573
+ loop.again = true;
574
+ k = loop.open;
575
+ owed = 0;
576
+ continue;
577
+ }
578
+ if (loop !== void 0) repeats.pop();
579
+ k++;
580
+ }
581
+ };
582
+ let from = 0;
583
+ for (const ret of returns) {
584
+ sing(from, ret.resume, false);
585
+ sing(asthayi, stop + 1, ret.changed);
586
+ from = ret.resume;
587
+ }
588
+ sing(from, tokens.length, false);
589
+ return out;
590
+ }
591
+ function form(tokens) {
592
+ const first = /* @__PURE__ */ new Map();
593
+ const last = /* @__PURE__ */ new Map();
594
+ tokens.forEach((t, k) => {
595
+ if (!t.timed) return;
596
+ const row = `${t.p}.${t.r}`;
597
+ if (!first.has(row)) first.set(row, k);
598
+ last.set(row, k);
599
+ });
600
+ const opens = [];
601
+ let opening = false;
602
+ tokens.forEach((t, k) => {
603
+ if (t.note.bar !== "double") return;
604
+ const row = `${t.p}.${t.r}`;
605
+ const before = (first.get(row) ?? Infinity) < k;
606
+ const after = (last.get(row) ?? -1) > k;
607
+ opening = before !== after ? after : !opening;
608
+ if (opening) opens.push(k);
609
+ });
610
+ const returns = [];
611
+ for (let k = 0; k < tokens.length; k++) {
612
+ let at = -1;
613
+ let open = false;
614
+ let changed = false;
615
+ let end = false;
616
+ for (; k < tokens.length && !tokens[k].timed; k++) {
617
+ const n = tokens[k].note;
618
+ if (n.bar === "quad") end = true;
619
+ if (n.bar === "double" && !opens.includes(k)) at = k;
620
+ if (edges(n, "alt", "open") > 0) open = true;
621
+ if (open && edges(n, "alt", "close") > 0) {
622
+ at = k;
623
+ changed = true;
624
+ }
625
+ }
626
+ if (at >= 0 && !end) returns.push({ resume: at + 1, changed });
627
+ }
628
+ const top = opens[0];
629
+ const asthayi = top !== void 0 && top < (returns[0]?.resume ?? Infinity) ? top : 0;
630
+ const asthayiEnd = Math.min(returns[0]?.resume ?? Infinity, opens.find((k) => k > asthayi) ?? Infinity, tokens.length);
631
+ const lastSung = tokens.reduce((last2, t, k) => t.timed ? k : last2, -1);
632
+ const stop = tokens.findIndex(
633
+ (t, k) => k >= asthayi && k < asthayiEnd && k < lastSung && t.note.above?.some((a) => STOP_MARK.test(a.raw ?? "")) === true
634
+ );
635
+ if (stop < 0) return { asthayi, stop, returns: [] };
636
+ const atClose = returns[0]?.resume === asthayiEnd && !tokens.slice(stop + 1, asthayiEnd).some((t) => t.timed);
637
+ return { asthayi, stop, returns: atClose ? returns.slice(1) : returns };
638
+ }
639
+ function changedNotes(tokens) {
640
+ const out = /* @__PURE__ */ new Map();
641
+ let row = "";
642
+ let inside = false;
643
+ tokens.forEach((t, k) => {
644
+ if (row !== `${t.p}.${t.r}`) {
645
+ row = `${t.p}.${t.r}`;
646
+ inside = false;
647
+ }
648
+ const notes = [];
649
+ for (const above of t.note.above ?? []) {
650
+ const read = above.raw !== void 0 && !sounds(above) && above.brackets === void 0;
651
+ for (const a of read ? decodeCell(above.raw).notes : [above]) {
652
+ if (edges(a, "alt", "open") > 0) inside = true;
653
+ if (inside && sounds(a)) notes.push(a);
654
+ if (edges(a, "alt", "close") > 0) inside = false;
655
+ }
656
+ }
657
+ if (notes.length > 0) out.set(k, notes);
658
+ });
659
+ return out;
660
+ }
661
+ function skipEnd(tokens, k, hi) {
662
+ for (let depth = 0, j = k; j < hi; j++) {
663
+ depth += edges(tokens[j].note, "skip", "open") - edges(tokens[j].note, "skip", "close");
664
+ if (depth <= 0) return j;
665
+ }
666
+ return -1;
667
+ }
668
+
669
+ // src/player/schedule.ts
374
670
  var ARC_RAW_RE = /^[wWxXyYjJeEzZ$=_<>`\\]+$/;
375
671
  var isArcRaw = (raw) => raw !== void 0 && ARC_RAW_RE.test(raw);
376
672
  var MEEND_MARKS = { start: "w", end: "x", both: "xw" };
@@ -414,67 +710,76 @@ function buildSchedule(doc, opts = {}) {
414
710
  let cursor = 0;
415
711
  let lastPitched = -1;
416
712
  let samMatra;
417
- let matra = 0;
713
+ let matra2 = 0;
418
714
  const speeds = resolveLaySpeeds(doc.meta.laySpeeds, opts.laySpeeds);
419
715
  const withLay = opts.lay !== false;
420
716
  const tempo = [{ beat: 0, endBeat: 0, scale: 1, lay: "drut", taal: doc.meta.taal }];
717
+ const rows = /* @__PURE__ */ new Map();
421
718
  let scale = 1;
719
+ let layNow = "drut";
422
720
  let taal = doc.meta.taal;
423
- doc.parts.forEach((part, p) => {
424
- part.rows.forEach((row, r) => {
721
+ doc.parts.forEach(
722
+ (part, p) => part.rows.forEach((row, r) => {
425
723
  const lay = withLay ? rowLay(row) : void 0;
426
- const layChanged = lay !== void 0 && speeds[lay] !== scale;
427
- const nextTaal = row.taal !== void 0 ? row.taal : taal;
428
- if (layChanged || !sameTaal(nextTaal, taal)) {
429
- if (layChanged) scale = speeds[lay];
430
- taal = nextTaal;
431
- const last = tempo[tempo.length - 1];
432
- const seg = { beat: cursor, endBeat: cursor, scale, lay: layChanged ? lay : last.lay, taal };
724
+ if (lay !== void 0 && speeds[lay] !== scale) {
725
+ scale = speeds[lay];
726
+ layNow = lay;
727
+ }
728
+ if (row.taal !== void 0) taal = row.taal;
729
+ rows.set(`${p}.${r}`, { scale, lay: layNow, taal, glides: meendContinuations(row.notes) });
730
+ })
731
+ );
732
+ const visits = opts.repeats === true ? playOrder(doc) : doc.parts.flatMap((part, p) => part.rows.flatMap((row, r) => row.notes.map((note, i) => ({ p, r, i, note }))));
733
+ let rowNow = "";
734
+ for (const { p, r, i, note } of visits) {
735
+ const row = rows.get(`${p}.${r}`);
736
+ if (rowNow !== `${p}.${r}`) {
737
+ rowNow = `${p}.${r}`;
738
+ const last = tempo[tempo.length - 1];
739
+ if (row.scale !== last.scale || !sameTaal(row.taal, last.taal)) {
740
+ const seg = { beat: cursor, endBeat: cursor, scale: row.scale, lay: row.lay, taal: row.taal };
433
741
  if (last.beat === cursor) tempo[tempo.length - 1] = seg;
434
742
  else {
435
743
  last.endBeat = cursor;
436
744
  tempo.push(seg);
437
745
  }
438
746
  }
439
- const glides = meendContinuations(row.notes);
440
- row.notes.forEach((note, i) => {
441
- if (samMatra === void 0 && note.above !== void 0) {
442
- for (const a of note.above) {
443
- if (a.raw !== void 0 && SAM_TICK.test(a.raw)) {
444
- samMatra = matra;
445
- break;
446
- }
447
- }
448
- }
449
- if (note.bar !== void 0 || isSilent(note)) return;
450
- const path = `${p}.${r}.${i}`;
451
- const written = note.durationMatra ?? 1;
452
- const dur = written / scale;
453
- const kan = graceMidis(note.kan, tonicPc, baseOctave);
454
- const resh = graceMidis(note.resh, tonicPc, baseOctave);
455
- if (note.svara !== void 0) {
456
- events.push({
457
- beat: cursor,
458
- durationBeats: dur,
459
- midi: svaraToMidi(note, tonicPc, baseOctave),
460
- kan,
461
- resh,
462
- path,
463
- kind: note.sustain === true ? "sustain" : "note",
464
- ...glides.has(i) && lastPitched >= 0 ? { glide: true } : {}
465
- });
466
- lastPitched = events.length - 1;
467
- } else if (note.sustain === true) {
468
- if (lastPitched >= 0) events[lastPitched].durationBeats += dur;
469
- events.push({ beat: cursor, durationBeats: dur, midi: null, kan, resh, path, kind: "sustain" });
470
- } else {
471
- events.push({ beat: cursor, durationBeats: dur, midi: null, kan, resh, path, kind: "rest" });
747
+ }
748
+ if (samMatra === void 0 && note.above !== void 0) {
749
+ for (const a of note.above) {
750
+ if (a.raw !== void 0 && SAM_TICK.test(a.raw)) {
751
+ samMatra = matra2;
752
+ break;
472
753
  }
473
- cursor += dur;
474
- matra += written;
754
+ }
755
+ }
756
+ if (note.bar !== void 0 || isSilent(note)) continue;
757
+ const path = `${p}.${r}.${i}`;
758
+ const written = note.durationMatra ?? 1;
759
+ const dur = written / row.scale;
760
+ const kan = graceMidis(note.kan, tonicPc, baseOctave);
761
+ const resh = graceMidis(note.resh, tonicPc, baseOctave);
762
+ if (note.svara !== void 0) {
763
+ events.push({
764
+ beat: cursor,
765
+ durationBeats: dur,
766
+ midi: svaraToMidi(note, tonicPc, baseOctave),
767
+ kan,
768
+ resh,
769
+ path,
770
+ kind: note.sustain === true ? "sustain" : "note",
771
+ ...row.glides.has(i) && lastPitched >= 0 ? { glide: true } : {}
475
772
  });
476
- });
477
- });
773
+ lastPitched = events.length - 1;
774
+ } else if (note.sustain === true) {
775
+ if (lastPitched >= 0) events[lastPitched].durationBeats += dur;
776
+ events.push({ beat: cursor, durationBeats: dur, midi: null, kan, resh, path, kind: "sustain" });
777
+ } else {
778
+ events.push({ beat: cursor, durationBeats: dur, midi: null, kan, resh, path, kind: "rest" });
779
+ }
780
+ cursor += dur;
781
+ matra2 += written;
782
+ }
478
783
  const numBeats = tempo[0].taal?.numBeats;
479
784
  const pickupMatra = samMatra !== void 0 && numBeats !== void 0 && numBeats > 0 ? (numBeats - samMatra % numBeats) % numBeats : 0;
480
785
  tempo[tempo.length - 1].endBeat = cursor;
@@ -566,8 +871,8 @@ function localizeDigits(s, lang) {
566
871
  }
567
872
 
568
873
  // src/viewer/render.ts
569
- var EPS = 1e-6;
570
- var isIntegral = (x) => Math.abs(x - Math.round(x)) < EPS;
874
+ var EPS2 = 1e-6;
875
+ var isIntegral = (x) => Math.abs(x - Math.round(x)) < EPS2;
571
876
  function escapeHtml(s) {
572
877
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
573
878
  }
@@ -608,7 +913,7 @@ function groupRowIntoCells(notes) {
608
913
  cur ??= { kind: "group", start: i, notes: [] };
609
914
  cur.notes.push(note);
610
915
  acc += d;
611
- if (note.halfMatra === true || acc > EPS && isIntegral(acc)) close();
916
+ if (note.halfMatra === true || acc > EPS2 && isIntegral(acc)) close();
612
917
  });
613
918
  close();
614
919
  return mergeStructural(absorbBrackets(cells));
@@ -631,15 +936,15 @@ function absorbBrackets(cells) {
631
936
  const out = [];
632
937
  for (let i = 0; i < cells.length; i++) {
633
938
  const cell = cells[i];
634
- const edges = bracketEdges(cell);
635
- if (edges !== void 0 && edges.open !== edges.close) {
939
+ const edges2 = bracketEdges(cell);
940
+ if (edges2 !== void 0 && edges2.open !== edges2.close) {
636
941
  const prev = out[out.length - 1];
637
942
  const next = cells[i + 1];
638
- if (edges.close && prev?.kind === "group") {
943
+ if (edges2.close && prev?.kind === "group") {
639
944
  prev.notes = [...prev.notes, ...cell.notes];
640
945
  continue;
641
946
  }
642
- if (edges.open && next?.kind === "group") {
947
+ if (edges2.open && next?.kind === "group") {
643
948
  next.notes = [...cell.notes, ...next.notes];
644
949
  next.start = cell.start;
645
950
  continue;
@@ -840,7 +1145,7 @@ function cellMatra(cell) {
840
1145
  const pitched = cell.notes.filter(isPitched);
841
1146
  const total = pitched.reduce((a, n) => a + (n.durationMatra ?? 1), 0);
842
1147
  const half = pitched.some((n) => n.halfMatra === true);
843
- const whole = Math.floor(total + EPS);
1148
+ const whole = Math.floor(total + EPS2);
844
1149
  return { matras: half ? whole : Math.max(1, whole), half };
845
1150
  }
846
1151
  function cellPitchedOpts(cell) {
@@ -1606,19 +1911,19 @@ function buildAbc(schedule, lyricOf, opts) {
1606
1911
  const tokens = [];
1607
1912
  const words = [];
1608
1913
  let barPos = 0;
1609
- const EPS2 = 1e-6;
1914
+ const EPS3 = 1e-6;
1610
1915
  const advance = (dur) => {
1611
1916
  barPos += dur;
1612
- while (barPos >= beatsPerBar - EPS2) {
1917
+ while (barPos >= beatsPerBar - EPS3) {
1613
1918
  tokens.push("|");
1614
1919
  words.push("|");
1615
1920
  barPos -= beatsPerBar;
1616
1921
  }
1617
1922
  };
1618
1923
  schedule.events.forEach((ev, i) => {
1619
- while (m + 1 < meters.length && ev.beat >= meters[m + 1].beat - EPS2) {
1924
+ while (m + 1 < meters.length && ev.beat >= meters[m + 1].beat - EPS3) {
1620
1925
  m++;
1621
- if (barPos > EPS2) {
1926
+ if (barPos > EPS3) {
1622
1927
  tokens.push("|");
1623
1928
  words.push("|");
1624
1929
  }