@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.
- package/README.md +7 -1
- package/dist/{batch-ZNVWIGEL.js → batch-BS2AIZOR.js} +5 -4
- package/dist/batch-BS2AIZOR.js.map +1 -0
- package/dist/{chunk-A7R3VFIV.js → chunk-3ZBNANDF.js} +3 -3
- package/dist/{chunk-A7R3VFIV.js.map → chunk-3ZBNANDF.js.map} +1 -1
- package/dist/{chunk-ISNAL632.js → chunk-5N6HGPFH.js} +5 -165
- package/dist/chunk-5N6HGPFH.js.map +1 -0
- package/dist/chunk-BSZ3WQUP.js +164 -0
- package/dist/chunk-BSZ3WQUP.js.map +1 -0
- package/dist/{chunk-A6Z7X2LX.js → chunk-ZNJTMQIX.js} +227 -63
- package/dist/chunk-ZNJTMQIX.js.map +1 -0
- package/dist/cli.cjs +1 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/convert/index.cjs +1 -1
- package/dist/convert/index.cjs.map +1 -1
- package/dist/convert/index.js +3 -2
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/player/index.cjs +470 -122
- package/dist/player/index.cjs.map +1 -1
- package/dist/player/index.d.cts +12 -2
- package/dist/player/index.d.ts +12 -2
- package/dist/player/index.js +85 -60
- package/dist/player/index.js.map +1 -1
- package/dist/{schedule-DN-8X17d.d.ts → schedule-ClyON98C.d.ts} +22 -10
- package/dist/{schedule-CRpjr6JH.d.cts → schedule-V-fgepvP.d.cts} +22 -10
- package/dist/spec/index.cjs +1 -1
- package/dist/spec/index.cjs.map +1 -1
- package/dist/spec/index.d.cts +1 -1
- package/dist/spec/index.d.ts +1 -1
- package/dist/spec/index.js +1 -1
- package/dist/viewer/index.cjs +366 -61
- package/dist/viewer/index.cjs.map +1 -1
- package/dist/viewer/index.d.cts +1 -1
- package/dist/viewer/index.d.ts +1 -1
- package/dist/viewer/index.js +2 -1
- package/dist/viewer/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/batch-ZNVWIGEL.js.map +0 -1
- package/dist/chunk-A6Z7X2LX.js.map +0 -1
- package/dist/chunk-ISNAL632.js.map +0 -1
package/dist/viewer/index.cjs
CHANGED
|
@@ -286,7 +286,7 @@ var init_spec = __esm({
|
|
|
286
286
|
init_grace();
|
|
287
287
|
init_lay();
|
|
288
288
|
init_svara();
|
|
289
|
-
SPEC_VERSION = "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/
|
|
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
|
|
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(
|
|
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
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
-
|
|
474
|
-
|
|
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
|
|
570
|
-
var isIntegral = (x) => Math.abs(x - Math.round(x)) <
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
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 >
|
|
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
|
|
635
|
-
if (
|
|
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 (
|
|
943
|
+
if (edges2.close && prev?.kind === "group") {
|
|
639
944
|
prev.notes = [...prev.notes, ...cell.notes];
|
|
640
945
|
continue;
|
|
641
946
|
}
|
|
642
|
-
if (
|
|
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 +
|
|
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
|
|
1914
|
+
const EPS3 = 1e-6;
|
|
1610
1915
|
const advance = (dur) => {
|
|
1611
1916
|
barPos += dur;
|
|
1612
|
-
while (barPos >= beatsPerBar -
|
|
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 -
|
|
1924
|
+
while (m + 1 < meters.length && ev.beat >= meters[m + 1].beat - EPS3) {
|
|
1620
1925
|
m++;
|
|
1621
|
-
if (barPos >
|
|
1926
|
+
if (barPos > EPS3) {
|
|
1622
1927
|
tokens.push("|");
|
|
1623
1928
|
words.push("|");
|
|
1624
1929
|
}
|