@ugfoundation/swaralipi-js 0.13.1 → 0.14.1

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 (39) hide show
  1. package/README.md +25 -0
  2. package/dist/{batch-XVQRHLTN.js → batch-J72GGBG2.js} +4 -4
  3. package/dist/{batch-XVQRHLTN.js.map → batch-J72GGBG2.js.map} +1 -1
  4. package/dist/{chunk-MORX7G6E.js → chunk-4WDAVRET.js} +3 -3
  5. package/dist/{chunk-MORX7G6E.js.map → chunk-4WDAVRET.js.map} +1 -1
  6. package/dist/{chunk-L6YZ3GC6.js → chunk-GIOGYJMM.js} +180 -19
  7. package/dist/chunk-GIOGYJMM.js.map +1 -0
  8. package/dist/{chunk-GKS4WZZ6.js → chunk-KUZSW4FN.js} +3 -3
  9. package/dist/{chunk-GKS4WZZ6.js.map → chunk-KUZSW4FN.js.map} +1 -1
  10. package/dist/cli.cjs +1 -1
  11. package/dist/cli.cjs.map +1 -1
  12. package/dist/cli.js +2 -2
  13. package/dist/convert/index.cjs +1 -1
  14. package/dist/convert/index.cjs.map +1 -1
  15. package/dist/convert/index.js +2 -2
  16. package/dist/index.cjs +1 -1
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.js +1 -1
  19. package/dist/{schedule-V-fgepvP.d.cts → instruments-DTIuTlda.d.cts} +174 -140
  20. package/dist/{schedule-ClyON98C.d.ts → instruments-NcUb0ozc.d.ts} +174 -140
  21. package/dist/player/index.cjs +330 -84
  22. package/dist/player/index.cjs.map +1 -1
  23. package/dist/player/index.d.cts +40 -16
  24. package/dist/player/index.d.ts +40 -16
  25. package/dist/player/index.js +150 -69
  26. package/dist/player/index.js.map +1 -1
  27. package/dist/spec/index.cjs +1 -1
  28. package/dist/spec/index.cjs.map +1 -1
  29. package/dist/spec/index.d.cts +1 -1
  30. package/dist/spec/index.d.ts +1 -1
  31. package/dist/spec/index.js +1 -1
  32. package/dist/viewer/index.cjs +189 -31
  33. package/dist/viewer/index.cjs.map +1 -1
  34. package/dist/viewer/index.d.cts +62 -2
  35. package/dist/viewer/index.d.ts +62 -2
  36. package/dist/viewer/index.js +123 -4
  37. package/dist/viewer/index.js.map +1 -1
  38. package/package.json +1 -1
  39. package/dist/chunk-L6YZ3GC6.js.map +0 -1
package/README.md CHANGED
@@ -226,6 +226,8 @@ const page = renderPrintDocument(doc); // full HTML string (
226
226
 
227
227
  `document.querySelector('swaralipi-sheet').print()` is the element shortcut. PDF fidelity comes from the **browser's own engine** — the same Indic shaping (reph, conjuncts, matra) and CSS-drawn marks as the screen — so we deliberately avoid html2canvas/jsPDF, which can't shape Indic text.
228
228
 
229
+ **The watermark is on every page, in every engine.** WebKit (Safari, and every iOS browser) paints a `position: fixed` box once, centred on the whole document ([bug 6790](https://bugs.webkit.org/show_bug.cgi?id=6790)), so the print decides its own page breaks and pins one mark per page. A consumer printing its own document can do the same: `paginate(doc, { atoms, keep?, starts?, pageHeight, mark: 'sw-page-mark' })` (with `watermarkCss(text)` and `printPageHeight(widthPx, heightMm)`, which is shorter in WebKit, whose page size CSS cannot set).
230
+
229
231
  ## Play: `SwaralipiPlayer`
230
232
 
231
233
  A [Tone.js](https://tonejs.dev) player turns a document into audio — melody on **santoor / harmonium / violin / flute / grand piano**, a **tabla** loop (metronome for unrecorded taals), and a **tanpura** drone — and drives the viewer's highlight in sync. Tone is dynamically imported on first `play()`, so importing the module never touches `AudioContext` — safe in Node/SSR.
@@ -267,6 +269,27 @@ player.setEcho(true); // after each pass, one silent pass: tabla + tanp
267
269
 
268
270
  A line sung more than once (a `{ }`, a return to the asthayi) plays from the pass nearest the playhead, and `↑` `↓` walk the sung order. A repeat follows the playhead (tap or step to another line and it moves there), and a tempo or tonic change keeps both the repeat and the place. Wire buttons to the same calls and mirror them with `onLoop: ({ loop, echo, yourTurn }) => …`. A tap is a cancelable `swaralipi-seek` event on the sheet — `preventDefault()` it in a capturing listener to gate a play (a quota, say). `previewLimitSec` counts listening time, so repeats cannot run past it.
269
271
 
272
+ ### Follow the words, play an excerpt
273
+
274
+ `alignLyrics(doc, text)` places each note in the song's own text: `'part.row.note'` → `[start, end)` offsets into `text`. It reads the notation's phonetic syllables (`আ|বা|র্`, `চি|ৎ|ত`) and its written-out refrains. A held `৹` has no entry.
275
+
276
+ ```ts
277
+ import { SwaralipiPlayer, alignLyrics } from '@ugfoundation/swaralipi-js/player';
278
+
279
+ const lyrics = alignLyrics(doc, songText);
280
+ const player = new SwaralipiPlayer({
281
+ onHighlight: (path) => path && lyrics.has(path) && markWord(lyrics.get(path)), // a repeat maps to the same words
282
+ });
283
+
284
+ // A dance drama quotes lines 1–4, then line 13: play only those, in order.
285
+ player.load(doc, { excerpt: { lyrics, ranges: [[line1Start, line4End], [line13Start, line13End]] } });
286
+ ```
287
+
288
+ - Each range plays the song's form while its words stay inside it: `{ }` twice, a return to the asthayi included.
289
+ - Between ranges, a short rest lands the next one on its own beat. The tabla never breaks.
290
+ - Words the notation does not sing play nothing.
291
+ - `durationSec`, seeks and repeats keep to the excerpt. `buildSchedule(doc, { repeats: true, excerpt })` gives its timeline.
292
+
270
293
  ### Export audio (WAV / MP3)
271
294
 
272
295
  Render a document to a downloadable audio file **entirely in the browser** — an `OfflineAudioContext` bounces the same melody / tabla / tanpura arrangement faster than real time, so nothing hits a server (works on Chrome, Firefox, Safari incl. iOS, Edge, Android, PWAs).
@@ -286,6 +309,8 @@ Object.assign(document.createElement('a'), { href: URL.createObjectURL(blob), do
286
309
 
287
310
  Also honours `instrument`, `tonic`, `bpm`, `mp3Kbps` (192), `channels` (mono by default — the content is monophonic), `sampleRate`, and `soundsBaseUrl`. **WAV** is 16-bit PCM and needs no dependency; **MP3** needs the optional peer dependency `@breezystack/lamejs` — see [License](#license).
288
311
 
312
+ `excerpt` plays only some lines (as the player's does); `lyrics: songText` returns `cues` — each sung line and word in seconds on the file's own timeline, ready for an LRC sidecar. `mp3Stream()` encodes PCM fed in pieces as one MP3, and `exportMidiSet(items, name)` writes several songs back to back into one `.mid` (a tempo and a marker at each song).
313
+
289
314
  ## Convert: archival HTML → JSON
290
315
 
291
316
  Digitised swaralipi is commonly published as HTML grids typeset in the ASCII-encoded **Swarabitan** webfont, where each cell is a glyph code rather than text. The converter decodes that encoding into spec JSON, losslessly — every cell keeps its original code alongside the decoded note, so nothing is lost even where a cell can't be interpreted.
@@ -1,5 +1,5 @@
1
- import { convertNltrHtml, aggregate, formatSummary } from './chunk-MORX7G6E.js';
2
- import './chunk-GKS4WZZ6.js';
1
+ import { convertNltrHtml, aggregate, formatSummary } from './chunk-4WDAVRET.js';
2
+ import './chunk-KUZSW4FN.js';
3
3
  import './chunk-BSZ3WQUP.js';
4
4
  import './chunk-KOHSKHXI.js';
5
5
  import { stat, readdir, mkdir, readFile, writeFile } from 'fs/promises';
@@ -44,5 +44,5 @@ async function runConvert(input, opts = {}) {
44
44
  }
45
45
 
46
46
  export { runConvert };
47
- //# sourceMappingURL=batch-XVQRHLTN.js.map
48
- //# sourceMappingURL=batch-XVQRHLTN.js.map
47
+ //# sourceMappingURL=batch-J72GGBG2.js.map
48
+ //# sourceMappingURL=batch-J72GGBG2.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/convert/batch.ts"],"names":["report"],"mappings":"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","file":"batch-XVQRHLTN.js","sourcesContent":["/**\n * File/dir conversion driver for the CLI (Node-only; not part of the\n * public `/convert` export). Continues past per-file failures and reports\n * them in the aggregate.\n */\nimport { mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises';\nimport { basename, join } from 'node:path';\nimport { convertNltrHtml } from './index.js';\nimport { aggregate, formatSummary, type BatchFailure, type BatchReport, type FileReport } from './report.js';\n\nexport interface BatchOptions {\n /** Output file (single input) or directory (directory input). */\n out?: string;\n /** Write the aggregate report JSON here. */\n reportPath?: string;\n compact?: boolean;\n quiet?: boolean;\n /** Progress/summary sink (default stderr). */\n log?: (line: string) => void;\n}\n\nconst nltrIdOf = (file: string): number | undefined => {\n const m = basename(file).match(/^(\\d+)\\.html$/);\n return m ? Number(m[1]) : undefined;\n};\n\nconst serialize = (doc: unknown, compact: boolean | undefined): string =>\n (compact ? JSON.stringify(doc) : JSON.stringify(doc, null, 2)) + '\\n';\n\nexport async function runConvert(\n input: string,\n opts: BatchOptions = {},\n): Promise<{ exitCode: 0 | 1; report: BatchReport }> {\n const log = opts.log ?? ((line: string) => process.stderr.write(line + '\\n'));\n const stats = await stat(input);\n const files = stats.isDirectory()\n ? (await readdir(input))\n .filter((f) => f.endsWith('.html'))\n .sort((a, b) => (nltrIdOf(a) ?? 0) - (nltrIdOf(b) ?? 0) || a.localeCompare(b))\n .map((f) => join(input, f))\n : [input];\n\n const isDir = stats.isDirectory();\n if (isDir && !opts.out) throw new Error('directory input requires -o <outDir>');\n if (isDir && opts.out) await mkdir(opts.out, { recursive: true });\n\n const reports: FileReport[] = [];\n const failures: BatchFailure[] = [];\n for (const file of files) {\n try {\n const html = await readFile(file, 'utf8');\n const { doc, report } = convertNltrHtml(html, { nltrId: nltrIdOf(file) });\n reports.push(report);\n const json = serialize(doc, opts.compact);\n if (isDir) {\n const name = `${nltrIdOf(file) ?? basename(file, '.html')}.json`;\n await writeFile(join(opts.out!, name), json);\n } else if (opts.out) {\n await writeFile(opts.out, json);\n } else {\n process.stdout.write(json);\n }\n } catch (err) {\n failures.push({ file: basename(file), error: err instanceof Error ? err.message : String(err) });\n }\n }\n\n const report = aggregate(reports, failures);\n if (opts.reportPath) await writeFile(opts.reportPath, serialize(report, false));\n if (!opts.quiet) log(formatSummary(report));\n return { exitCode: failures.length > 0 ? 1 : 0, report };\n}\n"]}
1
+ {"version":3,"sources":["../src/convert/batch.ts"],"names":["report"],"mappings":"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","file":"batch-J72GGBG2.js","sourcesContent":["/**\n * File/dir conversion driver for the CLI (Node-only; not part of the\n * public `/convert` export). Continues past per-file failures and reports\n * them in the aggregate.\n */\nimport { mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises';\nimport { basename, join } from 'node:path';\nimport { convertNltrHtml } from './index.js';\nimport { aggregate, formatSummary, type BatchFailure, type BatchReport, type FileReport } from './report.js';\n\nexport interface BatchOptions {\n /** Output file (single input) or directory (directory input). */\n out?: string;\n /** Write the aggregate report JSON here. */\n reportPath?: string;\n compact?: boolean;\n quiet?: boolean;\n /** Progress/summary sink (default stderr). */\n log?: (line: string) => void;\n}\n\nconst nltrIdOf = (file: string): number | undefined => {\n const m = basename(file).match(/^(\\d+)\\.html$/);\n return m ? Number(m[1]) : undefined;\n};\n\nconst serialize = (doc: unknown, compact: boolean | undefined): string =>\n (compact ? JSON.stringify(doc) : JSON.stringify(doc, null, 2)) + '\\n';\n\nexport async function runConvert(\n input: string,\n opts: BatchOptions = {},\n): Promise<{ exitCode: 0 | 1; report: BatchReport }> {\n const log = opts.log ?? ((line: string) => process.stderr.write(line + '\\n'));\n const stats = await stat(input);\n const files = stats.isDirectory()\n ? (await readdir(input))\n .filter((f) => f.endsWith('.html'))\n .sort((a, b) => (nltrIdOf(a) ?? 0) - (nltrIdOf(b) ?? 0) || a.localeCompare(b))\n .map((f) => join(input, f))\n : [input];\n\n const isDir = stats.isDirectory();\n if (isDir && !opts.out) throw new Error('directory input requires -o <outDir>');\n if (isDir && opts.out) await mkdir(opts.out, { recursive: true });\n\n const reports: FileReport[] = [];\n const failures: BatchFailure[] = [];\n for (const file of files) {\n try {\n const html = await readFile(file, 'utf8');\n const { doc, report } = convertNltrHtml(html, { nltrId: nltrIdOf(file) });\n reports.push(report);\n const json = serialize(doc, opts.compact);\n if (isDir) {\n const name = `${nltrIdOf(file) ?? basename(file, '.html')}.json`;\n await writeFile(join(opts.out!, name), json);\n } else if (opts.out) {\n await writeFile(opts.out, json);\n } else {\n process.stdout.write(json);\n }\n } catch (err) {\n failures.push({ file: basename(file), error: err instanceof Error ? err.message : String(err) });\n }\n }\n\n const report = aggregate(reports, failures);\n if (opts.reportPath) await writeFile(opts.reportPath, serialize(report, false));\n if (!opts.quiet) log(formatSummary(report));\n return { exitCode: failures.length > 0 ? 1 : 0, report };\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { swaralipiSchema, SPEC_VERSION } from './chunk-GKS4WZZ6.js';
1
+ import { swaralipiSchema, SPEC_VERSION } from './chunk-KUZSW4FN.js';
2
2
  import { decodeCell } from './chunk-BSZ3WQUP.js';
3
3
  import { isTaalFerta, TAAL_FERTA, TAALS } from './chunk-KOHSKHXI.js';
4
4
 
@@ -431,5 +431,5 @@ function convertNltrHtml(html, opts = {}) {
431
431
  }
432
432
 
433
433
  export { ExtractError, aggregate, assemble, bnInt, buildSystems, convertNltrHtml, deriveGeometry, extractPage, formatSummary, resolveTaal, splitLyric };
434
- //# sourceMappingURL=chunk-MORX7G6E.js.map
435
- //# sourceMappingURL=chunk-MORX7G6E.js.map
434
+ //# sourceMappingURL=chunk-4WDAVRET.js.map
435
+ //# sourceMappingURL=chunk-4WDAVRET.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/convert/grid.ts","../src/convert/taal-map.ts","../src/convert/assemble.ts","../src/convert/extract.ts","../src/convert/report.ts","../src/convert/index.ts"],"names":[],"mappings":"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","file":"chunk-MORX7G6E.js","sourcesContent":["/**\n * Grid → systems.\n *\n * Rows group into 4-row \"systems\", role given by `row % 4` (verified with\n * zero exceptions across the whole corpus):\n * 0 → annotation row rendered ABOVE the staff (alt-on-repeat passages,\n * stop marks, beat numbers — sparse),\n * 1 → notation row (style1, Swarabitan codes),\n * 2 → annotation row rendered BELOW the staff (sparse),\n * 3 → lyric row (style2, Bengali).\n * Cells align across the four rows strictly by column id (rows can be\n * ragged, so never align by index).\n */\nimport type { NltrPage } from './extract.js';\n\nexport interface System {\n /** System index (source rows 4k..4k+3). */\n index: number;\n above: Map<number, string>;\n notation: Map<number, string>;\n below: Map<number, string>;\n lyric: Map<number, string>;\n maxCol: number;\n}\n\nconst ROLES = ['above', 'notation', 'below', 'lyric'] as const;\n\nexport function buildSystems(page: NltrPage): { systems: System[]; warnings: string[] } {\n const warnings: string[] = [];\n const byIndex = new Map<number, System>();\n let styleMismatches = 0;\n\n for (const cell of page.cells) {\n if (cell.text === '') continue;\n const role = ROLES[cell.row % 4]!;\n // Notation/annotation rows are style1, lyric rows style2 — corpus-wide\n // invariant. A mismatch is recorded but the row role (from row % 4) wins.\n const expected = role === 'lyric' ? 'style2' : 'style1';\n if (cell.style !== expected) styleMismatches++;\n\n const index = Math.floor(cell.row / 4);\n let system = byIndex.get(index);\n if (!system) {\n system = {\n index,\n above: new Map(),\n notation: new Map(),\n below: new Map(),\n lyric: new Map(),\n maxCol: 0,\n };\n byIndex.set(index, system);\n }\n system[role].set(cell.col, cell.text);\n if (cell.col > system.maxCol) system.maxCol = cell.col;\n }\n\n if (styleMismatches > 0) {\n warnings.push(`${styleMismatches} cell(s) had an unexpected style class for their row role`);\n }\n\n // Keep source order; drop systems with no content at all (trailing blank\n // systems are common).\n const systems = [...byIndex.values()].sort((a, b) => a.index - b.index);\n return { systems, warnings };\n}\n","/**\n * Resolve the printed Bengali তাল name (+ the grid's own geometry) into a\n * spec `Taal`.\n *\n * Evidence chain, strongest first:\n * 1. bhaag hints in the name itself — `ধামার(৩/২/২/৩/৪)` → [3,2,2,3,4];\n * 2. the name table below (the 7 player-sampled taals from TAALS, plus\n * well-attested Rabindrik taals with stable definitions);\n * 3. the grid geometry: bar/dari columns partition each cycle into bhaags\n * (`numCols = avartan × (numBeats + nDaris) + 1` held on every checked\n * song), which both derives unknown taals and cross-checks named ones.\n * তালমুক্ত/মুক্তছন্দ (free rhythm) and unresolvable names yield no `taal`; the\n * name lands in `meta.taalName` verbatim, except free rhythm which is\n * normalised to মুক্তছন্দ.\n */\nimport { isTaalFerta, TAAL_FERTA, TAALS, type Taal } from '../spec/index.js';\nimport type { System } from './grid.js';\n\nconst BN_DIGIT_MAP: Record<string, string> = {\n '০': '0',\n '১': '1',\n '২': '2',\n '৩': '3',\n '৪': '4',\n '৫': '5',\n '৬': '6',\n '৭': '7',\n '৮': '8',\n '৯': '9',\n};\n\n/** Parse an integer written with Bengali or ASCII digits. */\nexport function bnInt(s: string | undefined): number | undefined {\n if (s === undefined) return undefined;\n const ascii = s.trim().replace(/[০-৯]/g, (d) => BN_DIGIT_MAP[d] ?? d);\n return /^\\d+$/.test(ascii) ? Number(ascii) : undefined;\n}\n\n/** Names mapping straight onto player-sampled TAALS ids. */\nconst BN_TAAL_MAP: Record<string, keyof typeof TAALS> = {\n দাদরা: 'dadra',\n কাহারবা: 'kehrwa',\n একতালা: 'ektaal',\n একতাল: 'ektaal',\n তিনতালা: 'teentaal',\n ত্রিতাল: 'teentaal',\n ঝাঁপতাল: 'jhaptaal',\n ঝাঁপ: 'jhaptaal',\n রুপক: 'rupak',\n রূপক: 'rupak',\n আড়াচৌতাল: 'adachautaal',\n ঝম্পক: 'jhampak',\n রুপকড়া: 'rupakra',\n রূপকড়া: 'rupakra',\n খেমটা: 'khemta',\n};\n\n/** Long-tail taals with stable textbook definitions (ids transliterated). */\nconst BN_TAAL_DEFS: Record<string, Taal> = {\n তেওড়া: { id: 'teora', numBeats: 7, bhaags: [3, 2, 2], sam: 0 },\n তেওরা: { id: 'teora', numBeats: 7, bhaags: [3, 2, 2], sam: 0 },\n ষষ্ঠী: { id: 'shashthi', numBeats: 6, bhaags: [2, 4], sam: 0 },\n নবতাল: { id: 'nabataal', numBeats: 9, bhaags: [5, 4], sam: 0 },\n একাদশী: { id: 'ekadashi', numBeats: 11, bhaags: [3, 4, 4], sam: 0 },\n নবপঞ্চ: { id: 'nabapancha', numBeats: 18, bhaags: [2, 4, 4, 4, 4], sam: 0 },\n ধামার: { id: 'dhamar', numBeats: 14, bhaags: [3, 2, 2, 3, 4], sam: 0 },\n চৌতাল: { id: 'choutaal', numBeats: 12, bhaags: [2, 2, 2, 2, 2, 2], sam: 0 },\n সুরফাঁক: { id: 'surphank', numBeats: 10, sam: 0 },\n};\n\n/**\n * Readable ids for the remaining census names whose beat count varies by\n * song (or isn't well-attested) — numBeats comes from hints/geometry only.\n */\nconst BN_TAAL_IDS: Record<string, string> = {\n কাওয়ালি: 'kawali',\n আড়াঠেকা: 'araatheka',\n কাশ্মীরি: 'kashmiri',\n যৎ: 'yat',\n মধ্যমান: 'madhyaman',\n অর্দ্ধঝাঁপ: 'ardhajhanp',\n অর্ধঝাঁপ: 'ardhajhanp',\n 'বিলম্বিত ত্রিতাল': 'vilambit-teentaal',\n};\n\n/**\n * Free-rhythm markers — never resolve to a taal. The sources print তালমুক্ত;\n * the emitted `taalName` is normalised to the canonical মুক্তছন্দ.\n */\nconst TAAL_FREE = ['তালমুক্ত', 'মুক্তছন্দ'];\nconst TAAL_FREE_CANON = 'মুক্তছন্দ';\n\nexport interface GridGeometry {\n /** Total columns of the widest full notation row. */\n numCols: number;\n /** Matra count of one cycle (notes between cycle bars, daris excluded). */\n numBeats?: number;\n /** Bhaag division of one cycle, from dari positions. */\n bhaags?: number[];\n}\n\n/** Bar-class tokens (cycle/section dandas) and the vibhag dari. */\nconst BAR_TOKENS = new Set(['l', 'll', 'lll', 'llll', 'L', 'LL', '|']);\nconst DARI_TOKEN = 'A';\n\n/**\n * Derive cycle geometry from the first notation row shaped `bar … bar`.\n * Returns per-cycle numBeats/bhaags when at least one full cycle is visible.\n */\nexport function deriveGeometry(systems: System[]): GridGeometry | undefined {\n let numCols = 0;\n for (const system of systems) {\n if (system.notation.size === 0) continue;\n const cols = [...system.notation.keys()].sort((a, b) => a - b);\n numCols = Math.max(numCols, cols.length);\n const barCols = cols.filter((c) => BAR_TOKENS.has(system.notation.get(c)!));\n if (barCols.length < 2) continue;\n // First full cycle: between the first two bar columns.\n const from = barCols[0]!;\n const to = barCols[1]!;\n const inner = cols.filter((c) => c > from && c < to);\n if (inner.length === 0) continue;\n const bhaags: number[] = [];\n let run = 0;\n for (const c of inner) {\n if (system.notation.get(c) === DARI_TOKEN) {\n if (run > 0) bhaags.push(run);\n run = 0;\n } else {\n run++;\n }\n }\n if (run > 0) bhaags.push(run);\n const numBeats = bhaags.reduce((a, b) => a + b, 0);\n if (numBeats === 0) continue;\n return { numCols: cols.length, numBeats, bhaags: bhaags.length > 1 ? bhaags : undefined };\n }\n return numCols > 0 ? { numCols } : undefined;\n}\n\nexport interface ResolvedTaal {\n taal?: Taal;\n taalName?: string;\n avartan?: number;\n /** Which evidence resolved the taal. */\n matched: 'taals' | 'table' | 'geometry' | 'none';\n warnings: string[];\n}\n\nexport function resolveTaal(\n nameRaw: string | undefined,\n avartanRaw: string | undefined,\n geometry: GridGeometry | undefined,\n): ResolvedTaal {\n const warnings: string[] = [];\n const avartan = bnInt(avartanRaw);\n const taalName = nameRaw?.trim() || undefined;\n\n if (!taalName || TAAL_FREE.includes(taalName)) {\n return { taalName: taalName ? TAAL_FREE_CANON : undefined, avartan, matched: 'none', warnings };\n }\n // তালফেরতা is a heading, not a taal: the song changes taal row by row\n // (`row.taal`), so there is no single one to resolve — and nothing to warn.\n if (isTaalFerta(taalName)) return { taalName: TAAL_FERTA, avartan, matched: 'none', warnings };\n\n // 1. Split `name(৩/২/…)` into base name + bhaag hint.\n const parenMatch = taalName.match(/^(.*?)\\s*\\(([০-৯\\d/\\s]+)\\)\\s*$/);\n const base = (parenMatch ? parenMatch[1]! : taalName).trim();\n let hintBhaags: number[] | undefined;\n if (parenMatch) {\n const parts = parenMatch[2]!.split('/').map((p) => bnInt(p));\n if (parts.every((p): p is number => p !== undefined) && parts.length > 0) {\n hintBhaags = parts;\n }\n }\n // `অখন্ড ৬ মাত্রা` — \"unbroken N matra\".\n const akhandaMatch = base.match(/^অখন্ড\\s+([০-৯\\d]+)\\s*মাত্রা$/);\n\n // 2. Name lookup.\n let taal: Taal | undefined;\n let matched: ResolvedTaal['matched'] = 'none';\n const mappedId = BN_TAAL_MAP[base];\n if (mappedId) {\n taal = { ...TAALS[mappedId] };\n matched = 'taals';\n } else if (BN_TAAL_DEFS[base]) {\n taal = { ...BN_TAAL_DEFS[base] };\n matched = 'table';\n } else if (akhandaMatch) {\n const beats = bnInt(akhandaMatch[1]);\n if (beats) {\n taal = { id: `akhanda-${beats}`, numBeats: beats, sam: 0 };\n matched = 'table';\n }\n }\n\n // 3. Reconcile with the source's own declaration. A parens hint is part\n // of the printed taal name, so it overrides the table; grid geometry is\n // our own derivation and only ever warns against a name-resolved taal.\n const hintBeats = hintBhaags?.reduce((a, b) => a + b, 0);\n if (taal) {\n if (hintBeats !== undefined && hintBeats !== taal.numBeats) {\n warnings.push(\n `taal ${taal.id}: name declares ${hintBeats} beats (${hintBhaags!.join('/')}), table says ${taal.numBeats} — using the name's`,\n );\n taal = { id: taal.id, numBeats: hintBeats, sam: 0 };\n if (hintBhaags!.length > 1) taal.bhaags = hintBhaags;\n } else if (hintBhaags && hintBhaags.length > 1) {\n const tableBhaags = taal.bhaags?.join('/');\n if (tableBhaags && tableBhaags !== hintBhaags.join('/')) {\n warnings.push(\n `taal ${taal.id}: name declares bhaags ${hintBhaags.join('/')}, table says ${tableBhaags} — using the name's`,\n );\n const { khaali: _khaali, ...rest } = taal;\n taal = { ...rest, bhaags: hintBhaags };\n }\n }\n } else if (hintBeats !== undefined || geometry?.numBeats !== undefined) {\n const evidenceBeats = (hintBeats ?? geometry?.numBeats)!;\n const evidenceBhaags = hintBhaags ?? geometry?.bhaags;\n // 4. Unknown name — derive entirely from evidence; keep a readable id\n // (transliteration table, else the Bengali name itself — ids are free-form).\n taal = { id: BN_TAAL_IDS[base] ?? base, numBeats: evidenceBeats, sam: 0 };\n if (evidenceBhaags && evidenceBhaags.length > 1) taal.bhaags = evidenceBhaags;\n matched = 'geometry';\n } else {\n warnings.push(`unresolved taal name: ${taalName}`);\n }\n\n // Geometry cross-check for table-resolved taals.\n if (taal && geometry?.numBeats !== undefined && geometry.numBeats !== taal.numBeats) {\n warnings.push(\n `grid geometry says ${geometry.numBeats} beats per cycle but taal ${taal.id} has ${taal.numBeats}`,\n );\n }\n\n return { taal, taalName, avartan, matched, warnings };\n}\n","/**\n * Assembly: systems → spec document.\n *\n * Each system becomes one `Row`. Within a row, columns are walked in order;\n * a notation cell's decoded notes join the row, with the column-aligned\n * lyric / above / below content attached to the cell's first note. Columns\n * that have lyric or annotation content but no notation emit a carrier note\n * (legal per the spec refine). The whole song is one unnamed part: NLTR\n * prints no section labels, and `ll` cannot be split on safely because it\n * also marks mid-song asthayi returns.\n */\nimport { SPEC_VERSION, swaralipiSchema, type Meta, type Note, type Row, type Swaralipi } from '../spec/index.js';\nimport { decodeCell } from './codec.js';\nimport type { NltrPage } from './extract.js';\nimport { buildSystems } from './grid.js';\nimport { deriveGeometry, resolveTaal } from './taal-map.js';\nimport type { FileReport } from './report.js';\n\nexport interface ConvertOptions {\n /** NLTR song/node id (usually the source filename). */\n nltrId?: number;\n /** Override the provenance URL stored in `meta.source`. */\n source?: string;\n}\n\nexport interface ConvertResult {\n doc: Swaralipi;\n report: FileReport;\n}\n\nconst segmenter = new Intl.Segmenter('bn', { granularity: 'grapheme' });\n\n/** Split a lyric cell into grapheme clusters (ক+া = 1, র+্ = 1, ৹ = 1). */\nexport function splitLyric(text: string): string[] {\n return [...segmenter.segment(text)].map((s) => s.segment);\n}\n\nexport function assemble(page: NltrPage, opts: ConvertOptions = {}): ConvertResult {\n const { systems, warnings } = buildSystems(page);\n const geometry = deriveGeometry(systems);\n const resolved = resolveTaal(page.header.taalNameRaw, page.header.avartanRaw, geometry);\n warnings.push(...resolved.warnings);\n\n const report: FileReport = {\n nltrId: opts.nltrId,\n title: page.header.title ?? '',\n taalName: resolved.taalName,\n taalId: resolved.taal?.id,\n taalMatched: resolved.matched,\n systems: 0,\n notationCells: 0,\n lyricCells: 0,\n annotationCells: 0,\n decodedCells: 0,\n knownRawCells: 0,\n unknownCells: 0,\n unknownTokens: {},\n lyricAlignMismatches: 0,\n warnings,\n };\n const countUnknown = (tokens: string[]) => {\n for (const t of tokens) report.unknownTokens[t] = (report.unknownTokens[t] ?? 0) + 1;\n };\n\n const rows: Row[] = [];\n for (const system of systems) {\n const notes: Note[] = [];\n for (let col = 0; col <= system.maxCol; col++) {\n const notationText = system.notation.get(col);\n const lyricText = system.lyric.get(col);\n const aboveText = system.above.get(col);\n const belowText = system.below.get(col);\n if (\n notationText === undefined &&\n lyricText === undefined &&\n aboveText === undefined &&\n belowText === undefined\n ) {\n continue;\n }\n\n let cellNotes: Note[];\n if (notationText !== undefined) {\n report.notationCells++;\n const decode = decodeCell(notationText);\n countUnknown(decode.unknownTokens);\n if (decode.unknownTokens.length > 0) report.unknownCells++;\n else if (decode.decoded) report.decodedCells++;\n else report.knownRawCells++;\n cellNotes = decode.notes;\n } else {\n cellNotes = [{}]; // carrier for lyric/annotation on an empty column\n }\n\n if (lyricText !== undefined) {\n report.lyricCells++;\n attachLyric(cellNotes, lyricText, report);\n }\n if (aboveText !== undefined) {\n report.annotationCells++;\n const decode = decodeCell(aboveText);\n countUnknown(decode.unknownTokens);\n cellNotes[0]!.above = decode.notes;\n }\n if (belowText !== undefined) {\n report.annotationCells++;\n const decode = decodeCell(belowText);\n countUnknown(decode.unknownTokens);\n cellNotes[0]!.below = decode.notes;\n }\n notes.push(...cellNotes);\n }\n if (notes.length > 0) {\n rows.push({ notes });\n report.systems++;\n }\n }\n\n const meta: Meta = { title: page.header.title ?? `NLTR ${opts.nltrId ?? 'song'}` };\n if (opts.nltrId !== undefined) meta.nltrId = opts.nltrId;\n if (resolved.taal) meta.taal = resolved.taal;\n if (resolved.taalName) meta.taalName = resolved.taalName;\n if (resolved.avartan !== undefined) meta.avartan = resolved.avartan;\n const source =\n opts.source ??\n (opts.nltrId !== undefined\n ? `https://rabindra-rachanabali.nltr.org/node/${opts.nltrId}`\n : page.header.printId !== undefined\n ? `https://rabindra-rachanabali.nltr.org/node/${page.header.printId}`\n : undefined);\n if (source) meta.source = source;\n\n // Validate against the spec before returning — an assembler bug must fail\n // the file, never emit an invalid document.\n const doc = swaralipiSchema.parse({ version: SPEC_VERSION, meta, parts: [{ rows }] });\n return { doc, report };\n}\n\n/**\n * Attach a lyric cell to its notation cell's notes. When the grapheme\n * cluster count matches the cell's note count the sub-syllables map 1:1\n * (e.g. `অ৹` onto the two notes of `nsfa`); otherwise the whole string\n * lands on the first note (never invent splits).\n */\nfunction attachLyric(cellNotes: Note[], lyricText: string, report: FileReport): void {\n if (cellNotes.length > 1) {\n const clusters = splitLyric(lyricText);\n if (clusters.length === cellNotes.length) {\n clusters.forEach((cluster, i) => {\n cellNotes[i]!.lyric = cluster;\n });\n return;\n }\n report.lyricAlignMismatches++;\n }\n cellNotes[0]!.lyric = lyricText;\n}\n","/**\n * NLTR HTML extraction — the lowest layer of the converter.\n *\n * The 1,738 cached NLTR pages are machine-generated and 100% structurally\n * uniform (verified across all 2.79M cells), so extraction is deliberately\n * a strict regex parse with a count assertion rather than a lenient DOM\n * walk: any structural surprise fails the file loudly (`ExtractError`)\n * instead of silently mis-treeing.\n *\n * Page anatomy:\n * - a metadata table of `style3` triples: label (নাম / তাল / আবর্তন), ':',\n * value;\n * - one grid of cells `<td width='auto' id=R.C> <span class='styleN'>\n * <div align='center'>TOKEN</div></span></td>` where `id` is the\n * `row.col` coordinate (often unquoted) and `&nbsp;` marks an empty cell.\n */\n\n/** One grid cell. `text` is entity-decoded and whitespace-normalized ('' = empty). */\nexport interface RawCell {\n row: number;\n col: number;\n /** `style1` = Swarabitan notation, `style2` = Bengali lyric. */\n style: string;\n text: string;\n}\n\n/** The style3 metadata header values, verbatim (Bengali). */\nexport interface NltrHeader {\n title?: string;\n taalNameRaw?: string;\n avartanRaw?: string;\n /** NLTR's internal print node id (`/print/<id>`), for provenance. */\n printId?: number;\n}\n\nexport interface NltrPage {\n header: NltrHeader;\n cells: RawCell[];\n maxRow: number;\n}\n\n/** Structural surprise in the source HTML (not a decode problem). */\nexport class ExtractError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ExtractError';\n }\n}\n\nconst NAMED_ENTITIES: Record<string, string> = {\n nbsp: ' ',\n amp: '&',\n lt: '<',\n gt: '>',\n quot: '\"',\n apos: \"'\",\n};\n\n/** Decode the handful of entities NLTR emits (named + numeric). */\nexport function decodeEntities(s: string): string {\n return s.replace(/&(?:#x([0-9a-fA-F]+)|#(\\d+)|([a-zA-Z]+));/g, (m, hex, dec, name) => {\n if (hex) return String.fromCodePoint(parseInt(hex, 16));\n if (dec) return String.fromCodePoint(parseInt(dec, 10));\n return NAMED_ENTITIES[name] ?? m;\n });\n}\n\n/**\n * Normalize cell text: decode entities, drop format/control characters\n * (U+200E etc. occur in lyric cells), collapse whitespace (incl. NBSP),\n * and canonicalize to NFC (NLTR mixes precomposed/decomposed forms of\n * ড়/ঢ়/য়; NFC settles both on the decomposed form since these are\n * composition exclusions).\n */\nfunction normalizeCellText(s: string): string {\n return decodeEntities(s)\n .replace(/\\p{Cf}/gu, '')\n .replace(/[\\s ]+/gu, ' ')\n .trim()\n .normalize('NFC');\n}\n\n// Cell alignment is center except for arc end-pieces (w/x), which NLTR\n// emits with align='right'/'left' so they visually join the neighbor cell.\nconst CELL_RE =\n /<td width='auto' id=[\"']?(\\d+)\\.(\\d+)[\"']?\\s*>\\s*<span class='(style\\d)'><div align='(?:center|left|right)'>([\\s\\S]*?)<\\/div><\\/span><\\/td>/g;\n\nconst META_ROW_RE =\n /<tr><td><div align='left'><span class='style3'>([^<]*)<\\/span><\\/div><\\/td><td><div align='left'><span class='style3'>:<\\/span><\\/div><\\/td><td><div align='left'><span class='style3'>([^<]*)<\\/span><\\/div><\\/td><\\/tr>/g;\n\n/** Parse one NLTR swaralipi page. Throws `ExtractError` on structural surprise. */\nexport function extractPage(html: string): NltrPage {\n const header: NltrHeader = {};\n for (const m of html.matchAll(META_ROW_RE)) {\n const label = normalizeCellText(m[1]!);\n const value = normalizeCellText(m[2]!);\n if (label === 'নাম') header.title = value;\n else if (label === 'তাল') header.taalNameRaw = value;\n else if (label === 'আবর্তন') header.avartanRaw = value;\n }\n const printMatch = html.match(/\\/print\\/(\\d+)/);\n if (printMatch) header.printId = Number(printMatch[1]);\n\n const cells: RawCell[] = [];\n let maxRow = 0;\n for (const m of html.matchAll(CELL_RE)) {\n const row = Number(m[1]);\n const col = Number(m[2]);\n cells.push({ row, col, style: m[3]!, text: normalizeCellText(m[4]!) });\n if (row > maxRow) maxRow = row;\n }\n\n // Strict-parse assertion: every grid <td> must have been captured.\n const tdCount = (html.match(/<td width='auto' id=/g) ?? []).length;\n if (cells.length !== tdCount) {\n throw new ExtractError(\n `grid cell mismatch: matched ${cells.length} cells but found ${tdCount} '<td width=' openers`,\n );\n }\n if (cells.length === 0) throw new ExtractError('no swaralipi grid cells found');\n if (header.title === undefined) throw new ExtractError('no নাম metadata row found');\n\n return { header, cells, maxRow };\n}\n","/**\n * Conversion reporting: per-file decode statistics and the batch aggregate.\n * The unknown-token histogram is the feedback loop for growing the codec.\n */\n\nexport interface FileReport {\n nltrId?: number;\n title: string;\n taalName?: string;\n taalId?: string;\n taalMatched: 'taals' | 'table' | 'geometry' | 'none';\n systems: number;\n notationCells: number;\n lyricCells: number;\n annotationCells: number;\n /** Notation cells with ≥1 semantic note and no unknown chunk. */\n decodedCells: number;\n /** Notation cells consisting only of understood-but-raw marks. */\n knownRawCells: number;\n /** Notation cells containing ≥1 chunk outside the grammar. */\n unknownCells: number;\n unknownTokens: Record<string, number>;\n lyricAlignMismatches: number;\n warnings: string[];\n}\n\nexport interface BatchFailure {\n file: string;\n error: string;\n}\n\nexport interface BatchReport {\n files: number;\n ok: number;\n failed: number;\n failures: BatchFailure[];\n systems: number;\n notationCells: number;\n lyricCells: number;\n annotationCells: number;\n decodedCells: number;\n knownRawCells: number;\n unknownCells: number;\n /** decoded / (decoded + knownRaw + unknown) over notation cells. */\n decodeRate: number;\n unknownRate: number;\n /** All unknown tokens, sorted by frequency (descending). */\n unknownTokens: Record<string, number>;\n lyricAlignMismatches: number;\n /** taalName → count + resolution outcome. */\n taals: Record<string, { count: number; taalId?: string; matched: string }>;\n warningCounts: Record<string, number>;\n}\n\nexport function aggregate(reports: FileReport[], failures: BatchFailure[]): BatchReport {\n const out: BatchReport = {\n files: reports.length + failures.length,\n ok: reports.length,\n failed: failures.length,\n failures,\n systems: 0,\n notationCells: 0,\n lyricCells: 0,\n annotationCells: 0,\n decodedCells: 0,\n knownRawCells: 0,\n unknownCells: 0,\n decodeRate: 0,\n unknownRate: 0,\n unknownTokens: {},\n lyricAlignMismatches: 0,\n taals: {},\n warningCounts: {},\n };\n const unknown = new Map<string, number>();\n for (const r of reports) {\n out.systems += r.systems;\n out.notationCells += r.notationCells;\n out.lyricCells += r.lyricCells;\n out.annotationCells += r.annotationCells;\n out.decodedCells += r.decodedCells;\n out.knownRawCells += r.knownRawCells;\n out.unknownCells += r.unknownCells;\n out.lyricAlignMismatches += r.lyricAlignMismatches;\n for (const [tok, n] of Object.entries(r.unknownTokens)) {\n unknown.set(tok, (unknown.get(tok) ?? 0) + n);\n }\n const taalKey = r.taalName ?? '<none>';\n const taalEntry = (out.taals[taalKey] ??= { count: 0, taalId: r.taalId, matched: r.taalMatched });\n taalEntry.count++;\n for (const w of r.warnings) {\n // Collapse per-file numbers so identical warning shapes aggregate.\n const shape = w.replace(/\\d+/g, 'N');\n out.warningCounts[shape] = (out.warningCounts[shape] ?? 0) + 1;\n }\n }\n out.unknownTokens = Object.fromEntries([...unknown.entries()].sort((a, b) => b[1] - a[1]));\n if (out.notationCells > 0) {\n out.decodeRate = out.decodedCells / out.notationCells;\n out.unknownRate = out.unknownCells / out.notationCells;\n }\n return out;\n}\n\nexport function formatSummary(r: BatchReport): string {\n const pct = (x: number) => `${(x * 100).toFixed(2)}%`;\n const lines = [\n `files: ${r.ok}/${r.files} converted${r.failed ? ` (${r.failed} FAILED)` : ''}`,\n `systems: ${r.systems} notation cells: ${r.notationCells} lyric cells: ${r.lyricCells} annotation cells: ${r.annotationCells}`,\n `decoded: ${r.decodedCells} (${pct(r.decodeRate)}) known-raw: ${r.knownRawCells} unknown: ${r.unknownCells} (${pct(r.unknownRate)})`,\n `lyric alignment fallbacks: ${r.lyricAlignMismatches}`,\n ];\n const unknowns = Object.entries(r.unknownTokens);\n if (unknowns.length > 0) {\n lines.push(`unknown tokens (${unknowns.length} distinct, top 20):`);\n for (const [tok, n] of unknowns.slice(0, 20)) lines.push(` ${String(n).padStart(6)} ${JSON.stringify(tok)}`);\n }\n const taalLines = Object.entries(r.taals).sort((a, b) => b[1].count - a[1].count);\n lines.push(`taals (${taalLines.length} distinct):`);\n for (const [name, info] of taalLines.slice(0, 15)) {\n lines.push(` ${String(info.count).padStart(5)} ${name} → ${info.taalId ?? '—'} [${info.matched}]`);\n }\n if (r.failed > 0) {\n lines.push('failures:');\n for (const f of r.failures.slice(0, 10)) lines.push(` ${f.file}: ${f.error}`);\n }\n return lines.join('\\n');\n}\n","/**\n * @ugfoundation/swaralipi-js/convert\n *\n * NLTR HTML → swaralipi JSON converter (Node-only subpath export).\n * `convertNltrHtml` itself is pure (string in, document out); file/dir\n * batch helpers live in the CLI (`swaralipi convert`).\n */\nimport { assemble, type ConvertOptions, type ConvertResult } from './assemble.js';\nimport { extractPage } from './extract.js';\n\nexport { ExtractError, extractPage } from './extract.js';\nexport type { NltrHeader, NltrPage, RawCell } from './extract.js';\nexport { buildSystems } from './grid.js';\nexport type { System } from './grid.js';\nexport { decodeCell } from './codec.js';\nexport type { CellDecode } from './codec.js';\nexport { bnInt, deriveGeometry, resolveTaal } from './taal-map.js';\nexport type { GridGeometry, ResolvedTaal } from './taal-map.js';\nexport { assemble, splitLyric } from './assemble.js';\nexport type { ConvertOptions, ConvertResult } from './assemble.js';\nexport { aggregate, formatSummary } from './report.js';\nexport type { BatchFailure, BatchReport, FileReport } from './report.js';\n\n/**\n * Convert one NLTR swaralipi page (HTML source text) into a validated\n * spec document plus a decode report.\n *\n * Throws `ExtractError` when the HTML does not look like an NLTR swaralipi\n * grid; cell-level decode problems never throw (they degrade to `raw`).\n */\nexport function convertNltrHtml(html: string, opts: ConvertOptions = {}): ConvertResult {\n return assemble(extractPage(html), opts);\n}\n"]}
1
+ {"version":3,"sources":["../src/convert/grid.ts","../src/convert/taal-map.ts","../src/convert/assemble.ts","../src/convert/extract.ts","../src/convert/report.ts","../src/convert/index.ts"],"names":[],"mappings":";;;;;AAyBA,IAAM,KAAA,GAAQ,CAAC,OAAA,EAAS,UAAA,EAAY,SAAS,OAAO,CAAA;AAE7C,SAAS,aAAa,IAAA,EAA2D;AACtF,EAAA,MAAM,WAAqB,EAAC;AAC5B,EAAA,MAAM,OAAA,uBAAc,GAAA,EAAoB;AACxC,EAAA,IAAI,eAAA,GAAkB,CAAA;AAEtB,EAAA,KAAA,MAAW,IAAA,IAAQ,KAAK,KAAA,EAAO;AAC7B,IAAA,IAAI,IAAA,CAAK,SAAS,EAAA,EAAI;AACtB,IAAA,MAAM,IAAA,GAAO,KAAA,CAAM,IAAA,CAAK,GAAA,GAAM,CAAC,CAAA;AAG/B,IAAA,MAAM,QAAA,GAAW,IAAA,KAAS,OAAA,GAAU,QAAA,GAAW,QAAA;AAC/C,IAAA,IAAI,IAAA,CAAK,UAAU,QAAA,EAAU,eAAA,EAAA;AAE7B,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,MAAM,CAAC,CAAA;AACrC,IAAA,IAAI,MAAA,GAAS,OAAA,CAAQ,GAAA,CAAI,KAAK,CAAA;AAC9B,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA,MAAA,GAAS;AAAA,QACP,KAAA;AAAA,QACA,KAAA,sBAAW,GAAA,EAAI;AAAA,QACf,QAAA,sBAAc,GAAA,EAAI;AAAA,QAClB,KAAA,sBAAW,GAAA,EAAI;AAAA,QACf,KAAA,sBAAW,GAAA,EAAI;AAAA,QACf,MAAA,EAAQ;AAAA,OACV;AACA,MAAA,OAAA,CAAQ,GAAA,CAAI,OAAO,MAAM,CAAA;AAAA,IAC3B;AACA,IAAA,MAAA,CAAO,IAAI,CAAA,CAAE,GAAA,CAAI,IAAA,CAAK,GAAA,EAAK,KAAK,IAAI,CAAA;AACpC,IAAA,IAAI,KAAK,GAAA,GAAM,MAAA,CAAO,MAAA,EAAQ,MAAA,CAAO,SAAS,IAAA,CAAK,GAAA;AAAA,EACrD;AAEA,EAAA,IAAI,kBAAkB,CAAA,EAAG;AACvB,IAAA,QAAA,CAAS,IAAA,CAAK,CAAA,EAAG,eAAe,CAAA,yDAAA,CAA2D,CAAA;AAAA,EAC7F;AAIA,EAAA,MAAM,OAAA,GAAU,CAAC,GAAG,OAAA,CAAQ,QAAQ,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,CAAE,KAAA,GAAQ,EAAE,KAAK,CAAA;AACtE,EAAA,OAAO,EAAE,SAAS,QAAA,EAAS;AAC7B;;;AC/CA,IAAM,YAAA,GAAuC;AAAA,EAC3C,QAAA,EAAK,GAAA;AAAA,EACL,QAAA,EAAK,GAAA;AAAA,EACL,QAAA,EAAK,GAAA;AAAA,EACL,QAAA,EAAK,GAAA;AAAA,EACL,QAAA,EAAK,GAAA;AAAA,EACL,QAAA,EAAK,GAAA;AAAA,EACL,QAAA,EAAK,GAAA;AAAA,EACL,QAAA,EAAK,GAAA;AAAA,EACL,QAAA,EAAK,GAAA;AAAA,EACL,QAAA,EAAK;AACP,CAAA;AAGO,SAAS,MAAM,CAAA,EAA2C;AAC/D,EAAA,IAAI,CAAA,KAAM,QAAW,OAAO,MAAA;AAC5B,EAAA,MAAM,KAAA,GAAQ,CAAA,CAAE,IAAA,EAAK,CAAE,OAAA,CAAQ,QAAA,EAAU,CAAC,CAAA,KAAM,YAAA,CAAa,CAAC,CAAA,IAAK,CAAC,CAAA;AACpE,EAAA,OAAO,QAAQ,IAAA,CAAK,KAAK,CAAA,GAAI,MAAA,CAAO,KAAK,CAAA,GAAI,MAAA;AAC/C;AAGA,IAAM,WAAA,GAAkD;AAAA,EACtD,8BAAA,EAAO,OAAA;AAAA,EACP,0CAAA,EAAS,QAAA;AAAA,EACT,oCAAA,EAAQ,QAAA;AAAA,EACR,8BAAA,EAAO,QAAA;AAAA,EACP,0CAAA,EAAS,UAAA;AAAA,EACT,0CAAA,EAAS,UAAA;AAAA,EACT,0CAAA,EAAS,UAAA;AAAA,EACT,wBAAA,EAAM,UAAA;AAAA,EACN,wBAAA,EAAM,OAAA;AAAA,EACN,wBAAA,EAAM,OAAA;AAAA,EACN,sDAAA,EAAW,aAAA;AAAA,EACX,8BAAA,EAAO,SAAA;AAAA,EACP,0CAAA,EAAS,SAAA;AAAA,EACT,0CAAA,EAAS,SAAA;AAAA,EACT,8BAAA,EAAO;AACT,CAAA;AAGA,IAAM,YAAA,GAAqC;AAAA,EACzC,oCAAA,EAAQ,EAAE,EAAA,EAAI,OAAA,EAAS,QAAA,EAAU,CAAA,EAAG,MAAA,EAAQ,CAAC,CAAA,EAAG,CAAA,EAAG,CAAC,CAAA,EAAG,KAAK,CAAA,EAAE;AAAA,EAC9D,8BAAA,EAAO,EAAE,EAAA,EAAI,OAAA,EAAS,QAAA,EAAU,CAAA,EAAG,MAAA,EAAQ,CAAC,CAAA,EAAG,CAAA,EAAG,CAAC,CAAA,EAAG,KAAK,CAAA,EAAE;AAAA,EAC7D,8BAAA,EAAO,EAAE,EAAA,EAAI,UAAA,EAAY,QAAA,EAAU,CAAA,EAAG,MAAA,EAAQ,CAAC,CAAA,EAAG,CAAC,CAAA,EAAG,GAAA,EAAK,CAAA,EAAE;AAAA,EAC7D,8BAAA,EAAO,EAAE,EAAA,EAAI,UAAA,EAAY,QAAA,EAAU,CAAA,EAAG,MAAA,EAAQ,CAAC,CAAA,EAAG,CAAC,CAAA,EAAG,GAAA,EAAK,CAAA,EAAE;AAAA,EAC7D,oCAAA,EAAQ,EAAE,EAAA,EAAI,UAAA,EAAY,QAAA,EAAU,EAAA,EAAI,MAAA,EAAQ,CAAC,CAAA,EAAG,CAAA,EAAG,CAAC,CAAA,EAAG,KAAK,CAAA,EAAE;AAAA,EAClE,oCAAA,EAAQ,EAAE,EAAA,EAAI,YAAA,EAAc,UAAU,EAAA,EAAI,MAAA,EAAQ,CAAC,CAAA,EAAG,GAAG,CAAA,EAAG,CAAA,EAAG,CAAC,CAAA,EAAG,KAAK,CAAA,EAAE;AAAA,EAC1E,8BAAA,EAAO,EAAE,EAAA,EAAI,QAAA,EAAU,UAAU,EAAA,EAAI,MAAA,EAAQ,CAAC,CAAA,EAAG,GAAG,CAAA,EAAG,CAAA,EAAG,CAAC,CAAA,EAAG,KAAK,CAAA,EAAE;AAAA,EACrE,gCAAO,EAAE,EAAA,EAAI,UAAA,EAAY,QAAA,EAAU,IAAI,MAAA,EAAQ,CAAC,CAAA,EAAG,CAAA,EAAG,GAAG,CAAA,EAAG,CAAA,EAAG,CAAC,CAAA,EAAG,KAAK,CAAA,EAAE;AAAA,EAC1E,4CAAS,EAAE,EAAA,EAAI,YAAY,QAAA,EAAU,EAAA,EAAI,KAAK,CAAA;AAChD,CAAA;AAMA,IAAM,WAAA,GAAsC;AAAA,EAC1C,gDAAA,EAAU,QAAA;AAAA,EACV,gDAAA,EAAU,WAAA;AAAA,EACV,gDAAA,EAAU,UAAA;AAAA,EACV,cAAA,EAAI,KAAA;AAAA,EACJ,0CAAA,EAAS,WAAA;AAAA,EACT,4DAAA,EAAY,YAAA;AAAA,EACZ,gDAAA,EAAU,YAAA;AAAA,EACV,6FAAA,EAAoB;AACtB,CAAA;AAMA,IAAM,SAAA,GAAY,CAAC,kDAAA,EAAY,wDAAW,CAAA;AAC1C,IAAM,eAAA,GAAkB,wDAAA;AAYxB,IAAM,UAAA,mBAAa,IAAI,GAAA,CAAI,CAAC,GAAA,EAAK,IAAA,EAAM,KAAA,EAAO,MAAA,EAAQ,GAAA,EAAK,IAAA,EAAM,GAAG,CAAC,CAAA;AACrE,IAAM,UAAA,GAAa,GAAA;AAMZ,SAAS,eAAe,OAAA,EAA6C;AAC1E,EAAA,IAAI,OAAA,GAAU,CAAA;AACd,EAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,IAAA,IAAI,MAAA,CAAO,QAAA,CAAS,IAAA,KAAS,CAAA,EAAG;AAChC,IAAA,MAAM,IAAA,GAAO,CAAC,GAAG,MAAA,CAAO,QAAA,CAAS,IAAA,EAAM,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAM,IAAI,CAAC,CAAA;AAC7D,IAAA,OAAA,GAAU,IAAA,CAAK,GAAA,CAAI,OAAA,EAAS,IAAA,CAAK,MAAM,CAAA;AACvC,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,MAAA,CAAO,CAAC,CAAA,KAAM,UAAA,CAAW,GAAA,CAAI,MAAA,CAAO,QAAA,CAAS,GAAA,CAAI,CAAC,CAAE,CAAC,CAAA;AAC1E,IAAA,IAAI,OAAA,CAAQ,SAAS,CAAA,EAAG;AAExB,IAAA,MAAM,IAAA,GAAO,QAAQ,CAAC,CAAA;AACtB,IAAA,MAAM,EAAA,GAAK,QAAQ,CAAC,CAAA;AACpB,IAAA,MAAM,KAAA,GAAQ,KAAK,MAAA,CAAO,CAAC,MAAM,CAAA,GAAI,IAAA,IAAQ,IAAI,EAAE,CAAA;AACnD,IAAA,IAAI,KAAA,CAAM,WAAW,CAAA,EAAG;AACxB,IAAA,MAAM,SAAmB,EAAC;AAC1B,IAAA,IAAI,GAAA,GAAM,CAAA;AACV,IAAA,KAAA,MAAW,KAAK,KAAA,EAAO;AACrB,MAAA,IAAI,MAAA,CAAO,QAAA,CAAS,GAAA,CAAI,CAAC,MAAM,UAAA,EAAY;AACzC,QAAA,IAAI,GAAA,GAAM,CAAA,EAAG,MAAA,CAAO,IAAA,CAAK,GAAG,CAAA;AAC5B,QAAA,GAAA,GAAM,CAAA;AAAA,MACR,CAAA,MAAO;AACL,QAAA,GAAA,EAAA;AAAA,MACF;AAAA,IACF;AACA,IAAA,IAAI,GAAA,GAAM,CAAA,EAAG,MAAA,CAAO,IAAA,CAAK,GAAG,CAAA;AAC5B,IAAA,MAAM,QAAA,GAAW,OAAO,MAAA,CAAO,CAAC,GAAG,CAAA,KAAM,CAAA,GAAI,GAAG,CAAC,CAAA;AACjD,IAAA,IAAI,aAAa,CAAA,EAAG;AACpB,IAAA,OAAO,EAAE,OAAA,EAAS,IAAA,CAAK,MAAA,EAAQ,QAAA,EAAU,QAAQ,MAAA,CAAO,MAAA,GAAS,CAAA,GAAI,MAAA,GAAS,MAAA,EAAU;AAAA,EAC1F;AACA,EAAA,OAAO,OAAA,GAAU,CAAA,GAAI,EAAE,OAAA,EAAQ,GAAI,MAAA;AACrC;AAWO,SAAS,WAAA,CACd,OAAA,EACA,UAAA,EACA,QAAA,EACc;AACd,EAAA,MAAM,WAAqB,EAAC;AAC5B,EAAA,MAAM,OAAA,GAAU,MAAM,UAAU,CAAA;AAChC,EAAA,MAAM,QAAA,GAAW,OAAA,EAAS,IAAA,EAAK,IAAK,MAAA;AAEpC,EAAA,IAAI,CAAC,QAAA,IAAY,SAAA,CAAU,QAAA,CAAS,QAAQ,CAAA,EAAG;AAC7C,IAAA,OAAO,EAAE,UAAU,QAAA,GAAW,eAAA,GAAkB,QAAW,OAAA,EAAS,OAAA,EAAS,QAAQ,QAAA,EAAS;AAAA,EAChG;AAGA,EAAA,IAAI,WAAA,CAAY,QAAQ,CAAA,EAAG,OAAO,EAAE,UAAU,UAAA,EAAY,OAAA,EAAS,OAAA,EAAS,MAAA,EAAQ,QAAA,EAAS;AAG7F,EAAA,MAAM,UAAA,GAAa,QAAA,CAAS,KAAA,CAAM,gCAAgC,CAAA;AAClE,EAAA,MAAM,QAAQ,UAAA,GAAa,UAAA,CAAW,CAAC,CAAA,GAAK,UAAU,IAAA,EAAK;AAC3D,EAAA,IAAI,UAAA;AACJ,EAAA,IAAI,UAAA,EAAY;AACd,IAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,CAAC,CAAA,CAAG,KAAA,CAAM,GAAG,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,KAAM,KAAA,CAAM,CAAC,CAAC,CAAA;AAC3D,IAAA,IAAI,KAAA,CAAM,MAAM,CAAC,CAAA,KAAmB,MAAM,MAAS,CAAA,IAAK,KAAA,CAAM,MAAA,GAAS,CAAA,EAAG;AACxE,MAAA,UAAA,GAAa,KAAA;AAAA,IACf;AAAA,EACF;AAEA,EAAA,MAAM,YAAA,GAAe,IAAA,CAAK,KAAA,CAAM,+BAA+B,CAAA;AAG/D,EAAA,IAAI,IAAA;AACJ,EAAA,IAAI,OAAA,GAAmC,MAAA;AACvC,EAAA,MAAM,QAAA,GAAW,YAAY,IAAI,CAAA;AACjC,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,IAAA,GAAO,EAAE,GAAG,KAAA,CAAM,QAAQ,CAAA,EAAE;AAC5B,IAAA,OAAA,GAAU,OAAA;AAAA,EACZ,CAAA,MAAA,IAAW,YAAA,CAAa,IAAI,CAAA,EAAG;AAC7B,IAAA,IAAA,GAAO,EAAE,GAAG,YAAA,CAAa,IAAI,CAAA,EAAE;AAC/B,IAAA,OAAA,GAAU,OAAA;AAAA,EACZ,WAAW,YAAA,EAAc;AACvB,IAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,YAAA,CAAa,CAAC,CAAC,CAAA;AACnC,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,IAAA,GAAO,EAAE,IAAI,CAAA,QAAA,EAAW,KAAK,IAAI,QAAA,EAAU,KAAA,EAAO,KAAK,CAAA,EAAE;AACzD,MAAA,OAAA,GAAU,OAAA;AAAA,IACZ;AAAA,EACF;AAKA,EAAA,MAAM,SAAA,GAAY,YAAY,MAAA,CAAO,CAAC,GAAG,CAAA,KAAM,CAAA,GAAI,GAAG,CAAC,CAAA;AACvD,EAAA,IAAI,IAAA,EAAM;AACR,IAAA,IAAI,SAAA,KAAc,MAAA,IAAa,SAAA,KAAc,IAAA,CAAK,QAAA,EAAU;AAC1D,MAAA,QAAA,CAAS,IAAA;AAAA,QACP,CAAA,KAAA,EAAQ,IAAA,CAAK,EAAE,CAAA,gBAAA,EAAmB,SAAS,CAAA,QAAA,EAAW,UAAA,CAAY,IAAA,CAAK,GAAG,CAAC,CAAA,cAAA,EAAiB,IAAA,CAAK,QAAQ,CAAA,wBAAA;AAAA,OAC3G;AACA,MAAA,IAAA,GAAO,EAAE,EAAA,EAAI,IAAA,CAAK,IAAI,QAAA,EAAU,SAAA,EAAW,KAAK,CAAA,EAAE;AAClD,MAAA,IAAI,UAAA,CAAY,MAAA,GAAS,CAAA,EAAG,IAAA,CAAK,MAAA,GAAS,UAAA;AAAA,IAC5C,CAAA,MAAA,IAAW,UAAA,IAAc,UAAA,CAAW,MAAA,GAAS,CAAA,EAAG;AAC9C,MAAA,MAAM,WAAA,GAAc,IAAA,CAAK,MAAA,EAAQ,IAAA,CAAK,GAAG,CAAA;AACzC,MAAA,IAAI,WAAA,IAAe,WAAA,KAAgB,UAAA,CAAW,IAAA,CAAK,GAAG,CAAA,EAAG;AACvD,QAAA,QAAA,CAAS,IAAA;AAAA,UACP,CAAA,KAAA,EAAQ,KAAK,EAAE,CAAA,uBAAA,EAA0B,WAAW,IAAA,CAAK,GAAG,CAAC,CAAA,aAAA,EAAgB,WAAW,CAAA,wBAAA;AAAA,SAC1F;AACA,QAAA,MAAM,EAAE,MAAA,EAAQ,OAAA,EAAS,GAAG,MAAK,GAAI,IAAA;AACrC,QAAA,IAAA,GAAO,EAAE,GAAG,IAAA,EAAM,MAAA,EAAQ,UAAA,EAAW;AAAA,MACvC;AAAA,IACF;AAAA,EACF,CAAA,MAAA,IAAW,SAAA,KAAc,MAAA,IAAa,QAAA,EAAU,aAAa,MAAA,EAAW;AACtE,IAAA,MAAM,aAAA,GAAiB,aAAa,QAAA,EAAU,QAAA;AAC9C,IAAA,MAAM,cAAA,GAAiB,cAAc,QAAA,EAAU,MAAA;AAG/C,IAAA,IAAA,GAAO,EAAE,IAAI,WAAA,CAAY,IAAI,KAAK,IAAA,EAAM,QAAA,EAAU,aAAA,EAAe,GAAA,EAAK,CAAA,EAAE;AACxE,IAAA,IAAI,cAAA,IAAkB,cAAA,CAAe,MAAA,GAAS,CAAA,OAAQ,MAAA,GAAS,cAAA;AAC/D,IAAA,OAAA,GAAU,UAAA;AAAA,EACZ,CAAA,MAAO;AACL,IAAA,QAAA,CAAS,IAAA,CAAK,CAAA,sBAAA,EAAyB,QAAQ,CAAA,CAAE,CAAA;AAAA,EACnD;AAGA,EAAA,IAAI,QAAQ,QAAA,EAAU,QAAA,KAAa,UAAa,QAAA,CAAS,QAAA,KAAa,KAAK,QAAA,EAAU;AACnF,IAAA,QAAA,CAAS,IAAA;AAAA,MACP,CAAA,mBAAA,EAAsB,SAAS,QAAQ,CAAA,0BAAA,EAA6B,KAAK,EAAE,CAAA,KAAA,EAAQ,KAAK,QAAQ,CAAA;AAAA,KAClG;AAAA,EACF;AAEA,EAAA,OAAO,EAAE,IAAA,EAAM,QAAA,EAAU,OAAA,EAAS,SAAS,QAAA,EAAS;AACtD;;;AC/MA,IAAM,SAAA,GAAY,IAAI,IAAA,CAAK,SAAA,CAAU,MAAM,EAAE,WAAA,EAAa,YAAY,CAAA;AAG/D,SAAS,WAAW,IAAA,EAAwB;AACjD,EAAA,OAAO,CAAC,GAAG,SAAA,CAAU,OAAA,CAAQ,IAAI,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,OAAO,CAAA;AAC1D;AAEO,SAAS,QAAA,CAAS,IAAA,EAAgB,IAAA,GAAuB,EAAC,EAAkB;AACjF,EAAA,MAAM,EAAE,OAAA,EAAS,QAAA,EAAS,GAAI,aAAa,IAAI,CAAA;AAC/C,EAAA,MAAM,QAAA,GAAW,eAAe,OAAO,CAAA;AACvC,EAAA,MAAM,QAAA,GAAW,YAAY,IAAA,CAAK,MAAA,CAAO,aAAa,IAAA,CAAK,MAAA,CAAO,YAAY,QAAQ,CAAA;AACtF,EAAA,QAAA,CAAS,IAAA,CAAK,GAAG,QAAA,CAAS,QAAQ,CAAA;AAElC,EAAA,MAAM,MAAA,GAAqB;AAAA,IACzB,QAAQ,IAAA,CAAK,MAAA;AAAA,IACb,KAAA,EAAO,IAAA,CAAK,MAAA,CAAO,KAAA,IAAS,EAAA;AAAA,IAC5B,UAAU,QAAA,CAAS,QAAA;AAAA,IACnB,MAAA,EAAQ,SAAS,IAAA,EAAM,EAAA;AAAA,IACvB,aAAa,QAAA,CAAS,OAAA;AAAA,IACtB,OAAA,EAAS,CAAA;AAAA,IACT,aAAA,EAAe,CAAA;AAAA,IACf,UAAA,EAAY,CAAA;AAAA,IACZ,eAAA,EAAiB,CAAA;AAAA,IACjB,YAAA,EAAc,CAAA;AAAA,IACd,aAAA,EAAe,CAAA;AAAA,IACf,YAAA,EAAc,CAAA;AAAA,IACd,eAAe,EAAC;AAAA,IAChB,oBAAA,EAAsB,CAAA;AAAA,IACtB;AAAA,GACF;AACA,EAAA,MAAM,YAAA,GAAe,CAAC,MAAA,KAAqB;AACzC,IAAA,KAAA,MAAW,CAAA,IAAK,MAAA,EAAQ,MAAA,CAAO,aAAA,CAAc,CAAC,KAAK,MAAA,CAAO,aAAA,CAAc,CAAC,CAAA,IAAK,CAAA,IAAK,CAAA;AAAA,EACrF,CAAA;AAEA,EAAA,MAAM,OAAc,EAAC;AACrB,EAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,IAAA,MAAM,QAAgB,EAAC;AACvB,IAAA,KAAA,IAAS,GAAA,GAAM,CAAA,EAAG,GAAA,IAAO,MAAA,CAAO,QAAQ,GAAA,EAAA,EAAO;AAC7C,MAAA,MAAM,YAAA,GAAe,MAAA,CAAO,QAAA,CAAS,GAAA,CAAI,GAAG,CAAA;AAC5C,MAAA,MAAM,SAAA,GAAY,MAAA,CAAO,KAAA,CAAM,GAAA,CAAI,GAAG,CAAA;AACtC,MAAA,MAAM,SAAA,GAAY,MAAA,CAAO,KAAA,CAAM,GAAA,CAAI,GAAG,CAAA;AACtC,MAAA,MAAM,SAAA,GAAY,MAAA,CAAO,KAAA,CAAM,GAAA,CAAI,GAAG,CAAA;AACtC,MAAA,IACE,iBAAiB,MAAA,IACjB,SAAA,KAAc,UACd,SAAA,KAAc,MAAA,IACd,cAAc,MAAA,EACd;AACA,QAAA;AAAA,MACF;AAEA,MAAA,IAAI,SAAA;AACJ,MAAA,IAAI,iBAAiB,MAAA,EAAW;AAC9B,QAAA,MAAA,CAAO,aAAA,EAAA;AACP,QAAA,MAAM,MAAA,GAAS,WAAW,YAAY,CAAA;AACtC,QAAA,YAAA,CAAa,OAAO,aAAa,CAAA;AACjC,QAAA,IAAI,MAAA,CAAO,aAAA,CAAc,MAAA,GAAS,CAAA,EAAG,MAAA,CAAO,YAAA,EAAA;AAAA,aAAA,IACnC,MAAA,CAAO,SAAS,MAAA,CAAO,YAAA,EAAA;AAAA,aAC3B,MAAA,CAAO,aAAA,EAAA;AACZ,QAAA,SAAA,GAAY,MAAA,CAAO,KAAA;AAAA,MACrB,CAAA,MAAO;AACL,QAAA,SAAA,GAAY,CAAC,EAAE,CAAA;AAAA,MACjB;AAEA,MAAA,IAAI,cAAc,MAAA,EAAW;AAC3B,QAAA,MAAA,CAAO,UAAA,EAAA;AACP,QAAA,WAAA,CAAY,SAAA,EAAW,WAAW,MAAM,CAAA;AAAA,MAC1C;AACA,MAAA,IAAI,cAAc,MAAA,EAAW;AAC3B,QAAA,MAAA,CAAO,eAAA,EAAA;AACP,QAAA,MAAM,MAAA,GAAS,WAAW,SAAS,CAAA;AACnC,QAAA,YAAA,CAAa,OAAO,aAAa,CAAA;AACjC,QAAA,SAAA,CAAU,CAAC,CAAA,CAAG,KAAA,GAAQ,MAAA,CAAO,KAAA;AAAA,MAC/B;AACA,MAAA,IAAI,cAAc,MAAA,EAAW;AAC3B,QAAA,MAAA,CAAO,eAAA,EAAA;AACP,QAAA,MAAM,MAAA,GAAS,WAAW,SAAS,CAAA;AACnC,QAAA,YAAA,CAAa,OAAO,aAAa,CAAA;AACjC,QAAA,SAAA,CAAU,CAAC,CAAA,CAAG,KAAA,GAAQ,MAAA,CAAO,KAAA;AAAA,MAC/B;AACA,MAAA,KAAA,CAAM,IAAA,CAAK,GAAG,SAAS,CAAA;AAAA,IACzB;AACA,IAAA,IAAI,KAAA,CAAM,SAAS,CAAA,EAAG;AACpB,MAAA,IAAA,CAAK,IAAA,CAAK,EAAE,KAAA,EAAO,CAAA;AACnB,MAAA,MAAA,CAAO,OAAA,EAAA;AAAA,IACT;AAAA,EACF;AAEA,EAAA,MAAM,IAAA,GAAa,EAAE,KAAA,EAAO,IAAA,CAAK,MAAA,CAAO,SAAS,CAAA,KAAA,EAAQ,IAAA,CAAK,MAAA,IAAU,MAAM,CAAA,CAAA,EAAG;AACjF,EAAA,IAAI,IAAA,CAAK,MAAA,KAAW,MAAA,EAAW,IAAA,CAAK,SAAS,IAAA,CAAK,MAAA;AAClD,EAAA,IAAI,QAAA,CAAS,IAAA,EAAM,IAAA,CAAK,IAAA,GAAO,QAAA,CAAS,IAAA;AACxC,EAAA,IAAI,QAAA,CAAS,QAAA,EAAU,IAAA,CAAK,QAAA,GAAW,QAAA,CAAS,QAAA;AAChD,EAAA,IAAI,QAAA,CAAS,OAAA,KAAY,MAAA,EAAW,IAAA,CAAK,UAAU,QAAA,CAAS,OAAA;AAC5D,EAAA,MAAM,SACJ,IAAA,CAAK,MAAA,KACJ,KAAK,MAAA,KAAW,MAAA,GACb,8CAA8C,IAAA,CAAK,MAAM,CAAA,CAAA,GACzD,IAAA,CAAK,OAAO,OAAA,KAAY,MAAA,GACtB,8CAA8C,IAAA,CAAK,MAAA,CAAO,OAAO,CAAA,CAAA,GACjE,MAAA,CAAA;AACR,EAAA,IAAI,MAAA,OAAa,MAAA,GAAS,MAAA;AAI1B,EAAA,MAAM,GAAA,GAAM,eAAA,CAAgB,KAAA,CAAM,EAAE,OAAA,EAAS,YAAA,EAAc,IAAA,EAAM,KAAA,EAAO,CAAC,EAAE,IAAA,EAAM,GAAG,CAAA;AACpF,EAAA,OAAO,EAAE,KAAK,MAAA,EAAO;AACvB;AAQA,SAAS,WAAA,CAAY,SAAA,EAAmB,SAAA,EAAmB,MAAA,EAA0B;AACnF,EAAA,IAAI,SAAA,CAAU,SAAS,CAAA,EAAG;AACxB,IAAA,MAAM,QAAA,GAAW,WAAW,SAAS,CAAA;AACrC,IAAA,IAAI,QAAA,CAAS,MAAA,KAAW,SAAA,CAAU,MAAA,EAAQ;AACxC,MAAA,QAAA,CAAS,OAAA,CAAQ,CAAC,OAAA,EAAS,CAAA,KAAM;AAC/B,QAAA,SAAA,CAAU,CAAC,EAAG,KAAA,GAAQ,OAAA;AAAA,MACxB,CAAC,CAAA;AACD,MAAA;AAAA,IACF;AACA,IAAA,MAAA,CAAO,oBAAA,EAAA;AAAA,EACT;AACA,EAAA,SAAA,CAAU,CAAC,EAAG,KAAA,GAAQ,SAAA;AACxB;;;AClHO,IAAM,YAAA,GAAN,cAA2B,KAAA,CAAM;AAAA,EACtC,YAAY,OAAA,EAAiB;AAC3B,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,cAAA;AAAA,EACd;AACF;AAEA,IAAM,cAAA,GAAyC;AAAA,EAC7C,IAAA,EAAM,MAAA;AAAA,EACN,GAAA,EAAK,GAAA;AAAA,EACL,EAAA,EAAI,GAAA;AAAA,EACJ,EAAA,EAAI,GAAA;AAAA,EACJ,IAAA,EAAM,GAAA;AAAA,EACN,IAAA,EAAM;AACR,CAAA;AAGO,SAAS,eAAe,CAAA,EAAmB;AAChD,EAAA,OAAO,EAAE,OAAA,CAAQ,4CAAA,EAA8C,CAAC,CAAA,EAAG,GAAA,EAAK,KAAK,IAAA,KAAS;AACpF,IAAA,IAAI,KAAK,OAAO,MAAA,CAAO,cAAc,QAAA,CAAS,GAAA,EAAK,EAAE,CAAC,CAAA;AACtD,IAAA,IAAI,KAAK,OAAO,MAAA,CAAO,cAAc,QAAA,CAAS,GAAA,EAAK,EAAE,CAAC,CAAA;AACtD,IAAA,OAAO,cAAA,CAAe,IAAI,CAAA,IAAK,CAAA;AAAA,EACjC,CAAC,CAAA;AACH;AASA,SAAS,kBAAkB,CAAA,EAAmB;AAC5C,EAAA,OAAO,cAAA,CAAe,CAAC,CAAA,CACpB,OAAA,CAAQ,YAAY,EAAE,CAAA,CACtB,OAAA,CAAQ,UAAA,EAAY,GAAG,CAAA,CACvB,IAAA,EAAK,CACL,UAAU,KAAK,CAAA;AACpB;AAIA,IAAM,OAAA,GACJ,8IAAA;AAEF,IAAM,WAAA,GACJ,4NAAA;AAGK,SAAS,YAAY,IAAA,EAAwB;AAClD,EAAA,MAAM,SAAqB,EAAC;AAC5B,EAAA,KAAA,MAAW,CAAA,IAAK,IAAA,CAAK,QAAA,CAAS,WAAW,CAAA,EAAG;AAC1C,IAAA,MAAM,KAAA,GAAQ,iBAAA,CAAkB,CAAA,CAAE,CAAC,CAAE,CAAA;AACrC,IAAA,MAAM,KAAA,GAAQ,iBAAA,CAAkB,CAAA,CAAE,CAAC,CAAE,CAAA;AACrC,IAAA,IAAI,KAAA,KAAU,oBAAA,EAAO,MAAA,CAAO,KAAA,GAAQ,KAAA;AAAA,SAAA,IAC3B,KAAA,KAAU,oBAAA,EAAO,MAAA,CAAO,WAAA,GAAc,KAAA;AAAA,SAAA,IACtC,KAAA,KAAU,sCAAA,EAAU,MAAA,CAAO,UAAA,GAAa,KAAA;AAAA,EACnD;AACA,EAAA,MAAM,UAAA,GAAa,IAAA,CAAK,KAAA,CAAM,gBAAgB,CAAA;AAC9C,EAAA,IAAI,YAAY,MAAA,CAAO,OAAA,GAAU,MAAA,CAAO,UAAA,CAAW,CAAC,CAAC,CAAA;AAErD,EAAA,MAAM,QAAmB,EAAC;AAC1B,EAAA,IAAI,MAAA,GAAS,CAAA;AACb,EAAA,KAAA,MAAW,CAAA,IAAK,IAAA,CAAK,QAAA,CAAS,OAAO,CAAA,EAAG;AACtC,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,CAAA,CAAE,CAAC,CAAC,CAAA;AACvB,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,CAAA,CAAE,CAAC,CAAC,CAAA;AACvB,IAAA,KAAA,CAAM,IAAA,CAAK,EAAE,GAAA,EAAK,GAAA,EAAK,OAAO,CAAA,CAAE,CAAC,CAAA,EAAI,IAAA,EAAM,iBAAA,CAAkB,CAAA,CAAE,CAAC,CAAE,GAAG,CAAA;AACrE,IAAA,IAAI,GAAA,GAAM,QAAQ,MAAA,GAAS,GAAA;AAAA,EAC7B;AAGA,EAAA,MAAM,WAAW,IAAA,CAAK,KAAA,CAAM,uBAAuB,CAAA,IAAK,EAAC,EAAG,MAAA;AAC5D,EAAA,IAAI,KAAA,CAAM,WAAW,OAAA,EAAS;AAC5B,IAAA,MAAM,IAAI,YAAA;AAAA,MACR,CAAA,4BAAA,EAA+B,KAAA,CAAM,MAAM,CAAA,iBAAA,EAAoB,OAAO,CAAA,qBAAA;AAAA,KACxE;AAAA,EACF;AACA,EAAA,IAAI,MAAM,MAAA,KAAW,CAAA,EAAG,MAAM,IAAI,aAAa,+BAA+B,CAAA;AAC9E,EAAA,IAAI,OAAO,KAAA,KAAU,MAAA,EAAW,MAAM,IAAI,aAAa,0CAA2B,CAAA;AAElF,EAAA,OAAO,EAAE,MAAA,EAAQ,KAAA,EAAO,MAAA,EAAO;AACjC;;;ACrEO,SAAS,SAAA,CAAU,SAAuB,QAAA,EAAuC;AACtF,EAAA,MAAM,GAAA,GAAmB;AAAA,IACvB,KAAA,EAAO,OAAA,CAAQ,MAAA,GAAS,QAAA,CAAS,MAAA;AAAA,IACjC,IAAI,OAAA,CAAQ,MAAA;AAAA,IACZ,QAAQ,QAAA,CAAS,MAAA;AAAA,IACjB,QAAA;AAAA,IACA,OAAA,EAAS,CAAA;AAAA,IACT,aAAA,EAAe,CAAA;AAAA,IACf,UAAA,EAAY,CAAA;AAAA,IACZ,eAAA,EAAiB,CAAA;AAAA,IACjB,YAAA,EAAc,CAAA;AAAA,IACd,aAAA,EAAe,CAAA;AAAA,IACf,YAAA,EAAc,CAAA;AAAA,IACd,UAAA,EAAY,CAAA;AAAA,IACZ,WAAA,EAAa,CAAA;AAAA,IACb,eAAe,EAAC;AAAA,IAChB,oBAAA,EAAsB,CAAA;AAAA,IACtB,OAAO,EAAC;AAAA,IACR,eAAe;AAAC,GAClB;AACA,EAAA,MAAM,OAAA,uBAAc,GAAA,EAAoB;AACxC,EAAA,KAAA,MAAW,KAAK,OAAA,EAAS;AACvB,IAAA,GAAA,CAAI,WAAW,CAAA,CAAE,OAAA;AACjB,IAAA,GAAA,CAAI,iBAAiB,CAAA,CAAE,aAAA;AACvB,IAAA,GAAA,CAAI,cAAc,CAAA,CAAE,UAAA;AACpB,IAAA,GAAA,CAAI,mBAAmB,CAAA,CAAE,eAAA;AACzB,IAAA,GAAA,CAAI,gBAAgB,CAAA,CAAE,YAAA;AACtB,IAAA,GAAA,CAAI,iBAAiB,CAAA,CAAE,aAAA;AACvB,IAAA,GAAA,CAAI,gBAAgB,CAAA,CAAE,YAAA;AACtB,IAAA,GAAA,CAAI,wBAAwB,CAAA,CAAE,oBAAA;AAC9B,IAAA,KAAA,MAAW,CAAC,KAAK,CAAC,CAAA,IAAK,OAAO,OAAA,CAAQ,CAAA,CAAE,aAAa,CAAA,EAAG;AACtD,MAAA,OAAA,CAAQ,IAAI,GAAA,EAAA,CAAM,OAAA,CAAQ,IAAI,GAAG,CAAA,IAAK,KAAK,CAAC,CAAA;AAAA,IAC9C;AACA,IAAA,MAAM,OAAA,GAAU,EAAE,QAAA,IAAY,QAAA;AAC9B,IAAA,MAAM,SAAA,GAAa,GAAA,CAAI,KAAA,CAAM,OAAO,CAAA,KAAM,EAAE,KAAA,EAAO,CAAA,EAAG,MAAA,EAAQ,CAAA,CAAE,MAAA,EAAQ,OAAA,EAAS,EAAE,WAAA,EAAY;AAC/F,IAAA,SAAA,CAAU,KAAA,EAAA;AACV,IAAA,KAAA,MAAW,CAAA,IAAK,EAAE,QAAA,EAAU;AAE1B,MAAA,MAAM,KAAA,GAAQ,CAAA,CAAE,OAAA,CAAQ,MAAA,EAAQ,GAAG,CAAA;AACnC,MAAA,GAAA,CAAI,cAAc,KAAK,CAAA,GAAA,CAAK,IAAI,aAAA,CAAc,KAAK,KAAK,CAAA,IAAK,CAAA;AAAA,IAC/D;AAAA,EACF;AACA,EAAA,GAAA,CAAI,gBAAgB,MAAA,CAAO,WAAA,CAAY,CAAC,GAAG,OAAA,CAAQ,SAAS,CAAA,CAAE,KAAK,CAAC,CAAA,EAAG,MAAM,CAAA,CAAE,CAAC,IAAI,CAAA,CAAE,CAAC,CAAC,CAAC,CAAA;AACzF,EAAA,IAAI,GAAA,CAAI,gBAAgB,CAAA,EAAG;AACzB,IAAA,GAAA,CAAI,UAAA,GAAa,GAAA,CAAI,YAAA,GAAe,GAAA,CAAI,aAAA;AACxC,IAAA,GAAA,CAAI,WAAA,GAAc,GAAA,CAAI,YAAA,GAAe,GAAA,CAAI,aAAA;AAAA,EAC3C;AACA,EAAA,OAAO,GAAA;AACT;AAEO,SAAS,cAAc,CAAA,EAAwB;AACpD,EAAA,MAAM,GAAA,GAAM,CAAC,CAAA,KAAc,CAAA,EAAA,CAAI,IAAI,GAAA,EAAK,OAAA,CAAQ,CAAC,CAAC,CAAA,CAAA,CAAA;AAClD,EAAA,MAAM,KAAA,GAAQ;AAAA,IACZ,CAAA,OAAA,EAAU,CAAA,CAAE,EAAE,CAAA,CAAA,EAAI,CAAA,CAAE,KAAK,CAAA,UAAA,EAAa,CAAA,CAAE,MAAA,GAAS,CAAA,EAAA,EAAK,CAAA,CAAE,MAAM,aAAa,EAAE,CAAA,CAAA;AAAA,IAC7E,CAAA,SAAA,EAAY,CAAA,CAAE,OAAO,CAAA,kBAAA,EAAqB,CAAA,CAAE,aAAa,CAAA,eAAA,EAAkB,CAAA,CAAE,UAAU,CAAA,oBAAA,EAAuB,CAAA,CAAE,eAAe,CAAA,CAAA;AAAA,IAC/H,YAAY,CAAA,CAAE,YAAY,KAAK,GAAA,CAAI,CAAA,CAAE,UAAU,CAAC,CAAA,cAAA,EAAiB,CAAA,CAAE,aAAa,cAAc,CAAA,CAAE,YAAY,KAAK,GAAA,CAAI,CAAA,CAAE,WAAW,CAAC,CAAA,CAAA,CAAA;AAAA,IACnI,CAAA,2BAAA,EAA8B,EAAE,oBAAoB,CAAA;AAAA,GACtD;AACA,EAAA,MAAM,QAAA,GAAW,MAAA,CAAO,OAAA,CAAQ,CAAA,CAAE,aAAa,CAAA;AAC/C,EAAA,IAAI,QAAA,CAAS,SAAS,CAAA,EAAG;AACvB,IAAA,KAAA,CAAM,IAAA,CAAK,CAAA,gBAAA,EAAmB,QAAA,CAAS,MAAM,CAAA,mBAAA,CAAqB,CAAA;AAClE,IAAA,KAAA,MAAW,CAAC,KAAK,CAAC,CAAA,IAAK,SAAS,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA,EAAG,KAAA,CAAM,IAAA,CAAK,KAAK,MAAA,CAAO,CAAC,CAAA,CAAE,QAAA,CAAS,CAAC,CAAC,KAAK,IAAA,CAAK,SAAA,CAAU,GAAG,CAAC,CAAA,CAAE,CAAA;AAAA,EAC/G;AACA,EAAA,MAAM,YAAY,MAAA,CAAO,OAAA,CAAQ,CAAA,CAAE,KAAK,EAAE,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAM,EAAE,CAAC,CAAA,CAAE,QAAQ,CAAA,CAAE,CAAC,EAAE,KAAK,CAAA;AAChF,EAAA,KAAA,CAAM,IAAA,CAAK,CAAA,OAAA,EAAU,SAAA,CAAU,MAAM,CAAA,WAAA,CAAa,CAAA;AAClD,EAAA,KAAA,MAAW,CAAC,MAAM,IAAI,CAAA,IAAK,UAAU,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA,EAAG;AACjD,IAAA,KAAA,CAAM,KAAK,CAAA,EAAA,EAAK,MAAA,CAAO,KAAK,KAAK,CAAA,CAAE,SAAS,CAAC,CAAC,CAAA,EAAA,EAAK,IAAI,WAAM,IAAA,CAAK,MAAA,IAAU,QAAG,CAAA,EAAA,EAAK,IAAA,CAAK,OAAO,CAAA,CAAA,CAAG,CAAA;AAAA,EACrG;AACA,EAAA,IAAI,CAAA,CAAE,SAAS,CAAA,EAAG;AAChB,IAAA,KAAA,CAAM,KAAK,WAAW,CAAA;AACtB,IAAA,KAAA,MAAW,CAAA,IAAK,CAAA,CAAE,QAAA,CAAS,KAAA,CAAM,GAAG,EAAE,CAAA,EAAG,KAAA,CAAM,IAAA,CAAK,KAAK,CAAA,CAAE,IAAI,CAAA,EAAA,EAAK,CAAA,CAAE,KAAK,CAAA,CAAE,CAAA;AAAA,EAC/E;AACA,EAAA,OAAO,KAAA,CAAM,KAAK,IAAI,CAAA;AACxB;;;ACjGO,SAAS,eAAA,CAAgB,IAAA,EAAc,IAAA,GAAuB,EAAC,EAAkB;AACtF,EAAA,OAAO,QAAA,CAAS,WAAA,CAAY,IAAI,CAAA,EAAG,IAAI,CAAA;AACzC","file":"chunk-4WDAVRET.js","sourcesContent":["/**\n * Grid → systems.\n *\n * Rows group into 4-row \"systems\", role given by `row % 4` (verified with\n * zero exceptions across the whole corpus):\n * 0 → annotation row rendered ABOVE the staff (alt-on-repeat passages,\n * stop marks, beat numbers — sparse),\n * 1 → notation row (style1, Swarabitan codes),\n * 2 → annotation row rendered BELOW the staff (sparse),\n * 3 → lyric row (style2, Bengali).\n * Cells align across the four rows strictly by column id (rows can be\n * ragged, so never align by index).\n */\nimport type { NltrPage } from './extract.js';\n\nexport interface System {\n /** System index (source rows 4k..4k+3). */\n index: number;\n above: Map<number, string>;\n notation: Map<number, string>;\n below: Map<number, string>;\n lyric: Map<number, string>;\n maxCol: number;\n}\n\nconst ROLES = ['above', 'notation', 'below', 'lyric'] as const;\n\nexport function buildSystems(page: NltrPage): { systems: System[]; warnings: string[] } {\n const warnings: string[] = [];\n const byIndex = new Map<number, System>();\n let styleMismatches = 0;\n\n for (const cell of page.cells) {\n if (cell.text === '') continue;\n const role = ROLES[cell.row % 4]!;\n // Notation/annotation rows are style1, lyric rows style2 — corpus-wide\n // invariant. A mismatch is recorded but the row role (from row % 4) wins.\n const expected = role === 'lyric' ? 'style2' : 'style1';\n if (cell.style !== expected) styleMismatches++;\n\n const index = Math.floor(cell.row / 4);\n let system = byIndex.get(index);\n if (!system) {\n system = {\n index,\n above: new Map(),\n notation: new Map(),\n below: new Map(),\n lyric: new Map(),\n maxCol: 0,\n };\n byIndex.set(index, system);\n }\n system[role].set(cell.col, cell.text);\n if (cell.col > system.maxCol) system.maxCol = cell.col;\n }\n\n if (styleMismatches > 0) {\n warnings.push(`${styleMismatches} cell(s) had an unexpected style class for their row role`);\n }\n\n // Keep source order; drop systems with no content at all (trailing blank\n // systems are common).\n const systems = [...byIndex.values()].sort((a, b) => a.index - b.index);\n return { systems, warnings };\n}\n","/**\n * Resolve the printed Bengali তাল name (+ the grid's own geometry) into a\n * spec `Taal`.\n *\n * Evidence chain, strongest first:\n * 1. bhaag hints in the name itself — `ধামার(৩/২/২/৩/৪)` → [3,2,2,3,4];\n * 2. the name table below (the 7 player-sampled taals from TAALS, plus\n * well-attested Rabindrik taals with stable definitions);\n * 3. the grid geometry: bar/dari columns partition each cycle into bhaags\n * (`numCols = avartan × (numBeats + nDaris) + 1` held on every checked\n * song), which both derives unknown taals and cross-checks named ones.\n * তালমুক্ত/মুক্তছন্দ (free rhythm) and unresolvable names yield no `taal`; the\n * name lands in `meta.taalName` verbatim, except free rhythm which is\n * normalised to মুক্তছন্দ.\n */\nimport { isTaalFerta, TAAL_FERTA, TAALS, type Taal } from '../spec/index.js';\nimport type { System } from './grid.js';\n\nconst BN_DIGIT_MAP: Record<string, string> = {\n '০': '0',\n '১': '1',\n '২': '2',\n '৩': '3',\n '৪': '4',\n '৫': '5',\n '৬': '6',\n '৭': '7',\n '৮': '8',\n '৯': '9',\n};\n\n/** Parse an integer written with Bengali or ASCII digits. */\nexport function bnInt(s: string | undefined): number | undefined {\n if (s === undefined) return undefined;\n const ascii = s.trim().replace(/[০-৯]/g, (d) => BN_DIGIT_MAP[d] ?? d);\n return /^\\d+$/.test(ascii) ? Number(ascii) : undefined;\n}\n\n/** Names mapping straight onto player-sampled TAALS ids. */\nconst BN_TAAL_MAP: Record<string, keyof typeof TAALS> = {\n দাদরা: 'dadra',\n কাহারবা: 'kehrwa',\n একতালা: 'ektaal',\n একতাল: 'ektaal',\n তিনতালা: 'teentaal',\n ত্রিতাল: 'teentaal',\n ঝাঁপতাল: 'jhaptaal',\n ঝাঁপ: 'jhaptaal',\n রুপক: 'rupak',\n রূপক: 'rupak',\n আড়াচৌতাল: 'adachautaal',\n ঝম্পক: 'jhampak',\n রুপকড়া: 'rupakra',\n রূপকড়া: 'rupakra',\n খেমটা: 'khemta',\n};\n\n/** Long-tail taals with stable textbook definitions (ids transliterated). */\nconst BN_TAAL_DEFS: Record<string, Taal> = {\n তেওড়া: { id: 'teora', numBeats: 7, bhaags: [3, 2, 2], sam: 0 },\n তেওরা: { id: 'teora', numBeats: 7, bhaags: [3, 2, 2], sam: 0 },\n ষষ্ঠী: { id: 'shashthi', numBeats: 6, bhaags: [2, 4], sam: 0 },\n নবতাল: { id: 'nabataal', numBeats: 9, bhaags: [5, 4], sam: 0 },\n একাদশী: { id: 'ekadashi', numBeats: 11, bhaags: [3, 4, 4], sam: 0 },\n নবপঞ্চ: { id: 'nabapancha', numBeats: 18, bhaags: [2, 4, 4, 4, 4], sam: 0 },\n ধামার: { id: 'dhamar', numBeats: 14, bhaags: [3, 2, 2, 3, 4], sam: 0 },\n চৌতাল: { id: 'choutaal', numBeats: 12, bhaags: [2, 2, 2, 2, 2, 2], sam: 0 },\n সুরফাঁক: { id: 'surphank', numBeats: 10, sam: 0 },\n};\n\n/**\n * Readable ids for the remaining census names whose beat count varies by\n * song (or isn't well-attested) — numBeats comes from hints/geometry only.\n */\nconst BN_TAAL_IDS: Record<string, string> = {\n কাওয়ালি: 'kawali',\n আড়াঠেকা: 'araatheka',\n কাশ্মীরি: 'kashmiri',\n যৎ: 'yat',\n মধ্যমান: 'madhyaman',\n অর্দ্ধঝাঁপ: 'ardhajhanp',\n অর্ধঝাঁপ: 'ardhajhanp',\n 'বিলম্বিত ত্রিতাল': 'vilambit-teentaal',\n};\n\n/**\n * Free-rhythm markers — never resolve to a taal. The sources print তালমুক্ত;\n * the emitted `taalName` is normalised to the canonical মুক্তছন্দ.\n */\nconst TAAL_FREE = ['তালমুক্ত', 'মুক্তছন্দ'];\nconst TAAL_FREE_CANON = 'মুক্তছন্দ';\n\nexport interface GridGeometry {\n /** Total columns of the widest full notation row. */\n numCols: number;\n /** Matra count of one cycle (notes between cycle bars, daris excluded). */\n numBeats?: number;\n /** Bhaag division of one cycle, from dari positions. */\n bhaags?: number[];\n}\n\n/** Bar-class tokens (cycle/section dandas) and the vibhag dari. */\nconst BAR_TOKENS = new Set(['l', 'll', 'lll', 'llll', 'L', 'LL', '|']);\nconst DARI_TOKEN = 'A';\n\n/**\n * Derive cycle geometry from the first notation row shaped `bar … bar`.\n * Returns per-cycle numBeats/bhaags when at least one full cycle is visible.\n */\nexport function deriveGeometry(systems: System[]): GridGeometry | undefined {\n let numCols = 0;\n for (const system of systems) {\n if (system.notation.size === 0) continue;\n const cols = [...system.notation.keys()].sort((a, b) => a - b);\n numCols = Math.max(numCols, cols.length);\n const barCols = cols.filter((c) => BAR_TOKENS.has(system.notation.get(c)!));\n if (barCols.length < 2) continue;\n // First full cycle: between the first two bar columns.\n const from = barCols[0]!;\n const to = barCols[1]!;\n const inner = cols.filter((c) => c > from && c < to);\n if (inner.length === 0) continue;\n const bhaags: number[] = [];\n let run = 0;\n for (const c of inner) {\n if (system.notation.get(c) === DARI_TOKEN) {\n if (run > 0) bhaags.push(run);\n run = 0;\n } else {\n run++;\n }\n }\n if (run > 0) bhaags.push(run);\n const numBeats = bhaags.reduce((a, b) => a + b, 0);\n if (numBeats === 0) continue;\n return { numCols: cols.length, numBeats, bhaags: bhaags.length > 1 ? bhaags : undefined };\n }\n return numCols > 0 ? { numCols } : undefined;\n}\n\nexport interface ResolvedTaal {\n taal?: Taal;\n taalName?: string;\n avartan?: number;\n /** Which evidence resolved the taal. */\n matched: 'taals' | 'table' | 'geometry' | 'none';\n warnings: string[];\n}\n\nexport function resolveTaal(\n nameRaw: string | undefined,\n avartanRaw: string | undefined,\n geometry: GridGeometry | undefined,\n): ResolvedTaal {\n const warnings: string[] = [];\n const avartan = bnInt(avartanRaw);\n const taalName = nameRaw?.trim() || undefined;\n\n if (!taalName || TAAL_FREE.includes(taalName)) {\n return { taalName: taalName ? TAAL_FREE_CANON : undefined, avartan, matched: 'none', warnings };\n }\n // তালফেরতা is a heading, not a taal: the song changes taal row by row\n // (`row.taal`), so there is no single one to resolve — and nothing to warn.\n if (isTaalFerta(taalName)) return { taalName: TAAL_FERTA, avartan, matched: 'none', warnings };\n\n // 1. Split `name(৩/২/…)` into base name + bhaag hint.\n const parenMatch = taalName.match(/^(.*?)\\s*\\(([০-৯\\d/\\s]+)\\)\\s*$/);\n const base = (parenMatch ? parenMatch[1]! : taalName).trim();\n let hintBhaags: number[] | undefined;\n if (parenMatch) {\n const parts = parenMatch[2]!.split('/').map((p) => bnInt(p));\n if (parts.every((p): p is number => p !== undefined) && parts.length > 0) {\n hintBhaags = parts;\n }\n }\n // `অখন্ড ৬ মাত্রা` — \"unbroken N matra\".\n const akhandaMatch = base.match(/^অখন্ড\\s+([০-৯\\d]+)\\s*মাত্রা$/);\n\n // 2. Name lookup.\n let taal: Taal | undefined;\n let matched: ResolvedTaal['matched'] = 'none';\n const mappedId = BN_TAAL_MAP[base];\n if (mappedId) {\n taal = { ...TAALS[mappedId] };\n matched = 'taals';\n } else if (BN_TAAL_DEFS[base]) {\n taal = { ...BN_TAAL_DEFS[base] };\n matched = 'table';\n } else if (akhandaMatch) {\n const beats = bnInt(akhandaMatch[1]);\n if (beats) {\n taal = { id: `akhanda-${beats}`, numBeats: beats, sam: 0 };\n matched = 'table';\n }\n }\n\n // 3. Reconcile with the source's own declaration. A parens hint is part\n // of the printed taal name, so it overrides the table; grid geometry is\n // our own derivation and only ever warns against a name-resolved taal.\n const hintBeats = hintBhaags?.reduce((a, b) => a + b, 0);\n if (taal) {\n if (hintBeats !== undefined && hintBeats !== taal.numBeats) {\n warnings.push(\n `taal ${taal.id}: name declares ${hintBeats} beats (${hintBhaags!.join('/')}), table says ${taal.numBeats} — using the name's`,\n );\n taal = { id: taal.id, numBeats: hintBeats, sam: 0 };\n if (hintBhaags!.length > 1) taal.bhaags = hintBhaags;\n } else if (hintBhaags && hintBhaags.length > 1) {\n const tableBhaags = taal.bhaags?.join('/');\n if (tableBhaags && tableBhaags !== hintBhaags.join('/')) {\n warnings.push(\n `taal ${taal.id}: name declares bhaags ${hintBhaags.join('/')}, table says ${tableBhaags} — using the name's`,\n );\n const { khaali: _khaali, ...rest } = taal;\n taal = { ...rest, bhaags: hintBhaags };\n }\n }\n } else if (hintBeats !== undefined || geometry?.numBeats !== undefined) {\n const evidenceBeats = (hintBeats ?? geometry?.numBeats)!;\n const evidenceBhaags = hintBhaags ?? geometry?.bhaags;\n // 4. Unknown name — derive entirely from evidence; keep a readable id\n // (transliteration table, else the Bengali name itself — ids are free-form).\n taal = { id: BN_TAAL_IDS[base] ?? base, numBeats: evidenceBeats, sam: 0 };\n if (evidenceBhaags && evidenceBhaags.length > 1) taal.bhaags = evidenceBhaags;\n matched = 'geometry';\n } else {\n warnings.push(`unresolved taal name: ${taalName}`);\n }\n\n // Geometry cross-check for table-resolved taals.\n if (taal && geometry?.numBeats !== undefined && geometry.numBeats !== taal.numBeats) {\n warnings.push(\n `grid geometry says ${geometry.numBeats} beats per cycle but taal ${taal.id} has ${taal.numBeats}`,\n );\n }\n\n return { taal, taalName, avartan, matched, warnings };\n}\n","/**\n * Assembly: systems → spec document.\n *\n * Each system becomes one `Row`. Within a row, columns are walked in order;\n * a notation cell's decoded notes join the row, with the column-aligned\n * lyric / above / below content attached to the cell's first note. Columns\n * that have lyric or annotation content but no notation emit a carrier note\n * (legal per the spec refine). The whole song is one unnamed part: NLTR\n * prints no section labels, and `ll` cannot be split on safely because it\n * also marks mid-song asthayi returns.\n */\nimport { SPEC_VERSION, swaralipiSchema, type Meta, type Note, type Row, type Swaralipi } from '../spec/index.js';\nimport { decodeCell } from './codec.js';\nimport type { NltrPage } from './extract.js';\nimport { buildSystems } from './grid.js';\nimport { deriveGeometry, resolveTaal } from './taal-map.js';\nimport type { FileReport } from './report.js';\n\nexport interface ConvertOptions {\n /** NLTR song/node id (usually the source filename). */\n nltrId?: number;\n /** Override the provenance URL stored in `meta.source`. */\n source?: string;\n}\n\nexport interface ConvertResult {\n doc: Swaralipi;\n report: FileReport;\n}\n\nconst segmenter = new Intl.Segmenter('bn', { granularity: 'grapheme' });\n\n/** Split a lyric cell into grapheme clusters (ক+া = 1, র+্ = 1, ৹ = 1). */\nexport function splitLyric(text: string): string[] {\n return [...segmenter.segment(text)].map((s) => s.segment);\n}\n\nexport function assemble(page: NltrPage, opts: ConvertOptions = {}): ConvertResult {\n const { systems, warnings } = buildSystems(page);\n const geometry = deriveGeometry(systems);\n const resolved = resolveTaal(page.header.taalNameRaw, page.header.avartanRaw, geometry);\n warnings.push(...resolved.warnings);\n\n const report: FileReport = {\n nltrId: opts.nltrId,\n title: page.header.title ?? '',\n taalName: resolved.taalName,\n taalId: resolved.taal?.id,\n taalMatched: resolved.matched,\n systems: 0,\n notationCells: 0,\n lyricCells: 0,\n annotationCells: 0,\n decodedCells: 0,\n knownRawCells: 0,\n unknownCells: 0,\n unknownTokens: {},\n lyricAlignMismatches: 0,\n warnings,\n };\n const countUnknown = (tokens: string[]) => {\n for (const t of tokens) report.unknownTokens[t] = (report.unknownTokens[t] ?? 0) + 1;\n };\n\n const rows: Row[] = [];\n for (const system of systems) {\n const notes: Note[] = [];\n for (let col = 0; col <= system.maxCol; col++) {\n const notationText = system.notation.get(col);\n const lyricText = system.lyric.get(col);\n const aboveText = system.above.get(col);\n const belowText = system.below.get(col);\n if (\n notationText === undefined &&\n lyricText === undefined &&\n aboveText === undefined &&\n belowText === undefined\n ) {\n continue;\n }\n\n let cellNotes: Note[];\n if (notationText !== undefined) {\n report.notationCells++;\n const decode = decodeCell(notationText);\n countUnknown(decode.unknownTokens);\n if (decode.unknownTokens.length > 0) report.unknownCells++;\n else if (decode.decoded) report.decodedCells++;\n else report.knownRawCells++;\n cellNotes = decode.notes;\n } else {\n cellNotes = [{}]; // carrier for lyric/annotation on an empty column\n }\n\n if (lyricText !== undefined) {\n report.lyricCells++;\n attachLyric(cellNotes, lyricText, report);\n }\n if (aboveText !== undefined) {\n report.annotationCells++;\n const decode = decodeCell(aboveText);\n countUnknown(decode.unknownTokens);\n cellNotes[0]!.above = decode.notes;\n }\n if (belowText !== undefined) {\n report.annotationCells++;\n const decode = decodeCell(belowText);\n countUnknown(decode.unknownTokens);\n cellNotes[0]!.below = decode.notes;\n }\n notes.push(...cellNotes);\n }\n if (notes.length > 0) {\n rows.push({ notes });\n report.systems++;\n }\n }\n\n const meta: Meta = { title: page.header.title ?? `NLTR ${opts.nltrId ?? 'song'}` };\n if (opts.nltrId !== undefined) meta.nltrId = opts.nltrId;\n if (resolved.taal) meta.taal = resolved.taal;\n if (resolved.taalName) meta.taalName = resolved.taalName;\n if (resolved.avartan !== undefined) meta.avartan = resolved.avartan;\n const source =\n opts.source ??\n (opts.nltrId !== undefined\n ? `https://rabindra-rachanabali.nltr.org/node/${opts.nltrId}`\n : page.header.printId !== undefined\n ? `https://rabindra-rachanabali.nltr.org/node/${page.header.printId}`\n : undefined);\n if (source) meta.source = source;\n\n // Validate against the spec before returning — an assembler bug must fail\n // the file, never emit an invalid document.\n const doc = swaralipiSchema.parse({ version: SPEC_VERSION, meta, parts: [{ rows }] });\n return { doc, report };\n}\n\n/**\n * Attach a lyric cell to its notation cell's notes. When the grapheme\n * cluster count matches the cell's note count the sub-syllables map 1:1\n * (e.g. `অ৹` onto the two notes of `nsfa`); otherwise the whole string\n * lands on the first note (never invent splits).\n */\nfunction attachLyric(cellNotes: Note[], lyricText: string, report: FileReport): void {\n if (cellNotes.length > 1) {\n const clusters = splitLyric(lyricText);\n if (clusters.length === cellNotes.length) {\n clusters.forEach((cluster, i) => {\n cellNotes[i]!.lyric = cluster;\n });\n return;\n }\n report.lyricAlignMismatches++;\n }\n cellNotes[0]!.lyric = lyricText;\n}\n","/**\n * NLTR HTML extraction — the lowest layer of the converter.\n *\n * The 1,738 cached NLTR pages are machine-generated and 100% structurally\n * uniform (verified across all 2.79M cells), so extraction is deliberately\n * a strict regex parse with a count assertion rather than a lenient DOM\n * walk: any structural surprise fails the file loudly (`ExtractError`)\n * instead of silently mis-treeing.\n *\n * Page anatomy:\n * - a metadata table of `style3` triples: label (নাম / তাল / আবর্তন), ':',\n * value;\n * - one grid of cells `<td width='auto' id=R.C> <span class='styleN'>\n * <div align='center'>TOKEN</div></span></td>` where `id` is the\n * `row.col` coordinate (often unquoted) and `&nbsp;` marks an empty cell.\n */\n\n/** One grid cell. `text` is entity-decoded and whitespace-normalized ('' = empty). */\nexport interface RawCell {\n row: number;\n col: number;\n /** `style1` = Swarabitan notation, `style2` = Bengali lyric. */\n style: string;\n text: string;\n}\n\n/** The style3 metadata header values, verbatim (Bengali). */\nexport interface NltrHeader {\n title?: string;\n taalNameRaw?: string;\n avartanRaw?: string;\n /** NLTR's internal print node id (`/print/<id>`), for provenance. */\n printId?: number;\n}\n\nexport interface NltrPage {\n header: NltrHeader;\n cells: RawCell[];\n maxRow: number;\n}\n\n/** Structural surprise in the source HTML (not a decode problem). */\nexport class ExtractError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ExtractError';\n }\n}\n\nconst NAMED_ENTITIES: Record<string, string> = {\n nbsp: ' ',\n amp: '&',\n lt: '<',\n gt: '>',\n quot: '\"',\n apos: \"'\",\n};\n\n/** Decode the handful of entities NLTR emits (named + numeric). */\nexport function decodeEntities(s: string): string {\n return s.replace(/&(?:#x([0-9a-fA-F]+)|#(\\d+)|([a-zA-Z]+));/g, (m, hex, dec, name) => {\n if (hex) return String.fromCodePoint(parseInt(hex, 16));\n if (dec) return String.fromCodePoint(parseInt(dec, 10));\n return NAMED_ENTITIES[name] ?? m;\n });\n}\n\n/**\n * Normalize cell text: decode entities, drop format/control characters\n * (U+200E etc. occur in lyric cells), collapse whitespace (incl. NBSP),\n * and canonicalize to NFC (NLTR mixes precomposed/decomposed forms of\n * ড়/ঢ়/য়; NFC settles both on the decomposed form since these are\n * composition exclusions).\n */\nfunction normalizeCellText(s: string): string {\n return decodeEntities(s)\n .replace(/\\p{Cf}/gu, '')\n .replace(/[\\s ]+/gu, ' ')\n .trim()\n .normalize('NFC');\n}\n\n// Cell alignment is center except for arc end-pieces (w/x), which NLTR\n// emits with align='right'/'left' so they visually join the neighbor cell.\nconst CELL_RE =\n /<td width='auto' id=[\"']?(\\d+)\\.(\\d+)[\"']?\\s*>\\s*<span class='(style\\d)'><div align='(?:center|left|right)'>([\\s\\S]*?)<\\/div><\\/span><\\/td>/g;\n\nconst META_ROW_RE =\n /<tr><td><div align='left'><span class='style3'>([^<]*)<\\/span><\\/div><\\/td><td><div align='left'><span class='style3'>:<\\/span><\\/div><\\/td><td><div align='left'><span class='style3'>([^<]*)<\\/span><\\/div><\\/td><\\/tr>/g;\n\n/** Parse one NLTR swaralipi page. Throws `ExtractError` on structural surprise. */\nexport function extractPage(html: string): NltrPage {\n const header: NltrHeader = {};\n for (const m of html.matchAll(META_ROW_RE)) {\n const label = normalizeCellText(m[1]!);\n const value = normalizeCellText(m[2]!);\n if (label === 'নাম') header.title = value;\n else if (label === 'তাল') header.taalNameRaw = value;\n else if (label === 'আবর্তন') header.avartanRaw = value;\n }\n const printMatch = html.match(/\\/print\\/(\\d+)/);\n if (printMatch) header.printId = Number(printMatch[1]);\n\n const cells: RawCell[] = [];\n let maxRow = 0;\n for (const m of html.matchAll(CELL_RE)) {\n const row = Number(m[1]);\n const col = Number(m[2]);\n cells.push({ row, col, style: m[3]!, text: normalizeCellText(m[4]!) });\n if (row > maxRow) maxRow = row;\n }\n\n // Strict-parse assertion: every grid <td> must have been captured.\n const tdCount = (html.match(/<td width='auto' id=/g) ?? []).length;\n if (cells.length !== tdCount) {\n throw new ExtractError(\n `grid cell mismatch: matched ${cells.length} cells but found ${tdCount} '<td width=' openers`,\n );\n }\n if (cells.length === 0) throw new ExtractError('no swaralipi grid cells found');\n if (header.title === undefined) throw new ExtractError('no নাম metadata row found');\n\n return { header, cells, maxRow };\n}\n","/**\n * Conversion reporting: per-file decode statistics and the batch aggregate.\n * The unknown-token histogram is the feedback loop for growing the codec.\n */\n\nexport interface FileReport {\n nltrId?: number;\n title: string;\n taalName?: string;\n taalId?: string;\n taalMatched: 'taals' | 'table' | 'geometry' | 'none';\n systems: number;\n notationCells: number;\n lyricCells: number;\n annotationCells: number;\n /** Notation cells with ≥1 semantic note and no unknown chunk. */\n decodedCells: number;\n /** Notation cells consisting only of understood-but-raw marks. */\n knownRawCells: number;\n /** Notation cells containing ≥1 chunk outside the grammar. */\n unknownCells: number;\n unknownTokens: Record<string, number>;\n lyricAlignMismatches: number;\n warnings: string[];\n}\n\nexport interface BatchFailure {\n file: string;\n error: string;\n}\n\nexport interface BatchReport {\n files: number;\n ok: number;\n failed: number;\n failures: BatchFailure[];\n systems: number;\n notationCells: number;\n lyricCells: number;\n annotationCells: number;\n decodedCells: number;\n knownRawCells: number;\n unknownCells: number;\n /** decoded / (decoded + knownRaw + unknown) over notation cells. */\n decodeRate: number;\n unknownRate: number;\n /** All unknown tokens, sorted by frequency (descending). */\n unknownTokens: Record<string, number>;\n lyricAlignMismatches: number;\n /** taalName → count + resolution outcome. */\n taals: Record<string, { count: number; taalId?: string; matched: string }>;\n warningCounts: Record<string, number>;\n}\n\nexport function aggregate(reports: FileReport[], failures: BatchFailure[]): BatchReport {\n const out: BatchReport = {\n files: reports.length + failures.length,\n ok: reports.length,\n failed: failures.length,\n failures,\n systems: 0,\n notationCells: 0,\n lyricCells: 0,\n annotationCells: 0,\n decodedCells: 0,\n knownRawCells: 0,\n unknownCells: 0,\n decodeRate: 0,\n unknownRate: 0,\n unknownTokens: {},\n lyricAlignMismatches: 0,\n taals: {},\n warningCounts: {},\n };\n const unknown = new Map<string, number>();\n for (const r of reports) {\n out.systems += r.systems;\n out.notationCells += r.notationCells;\n out.lyricCells += r.lyricCells;\n out.annotationCells += r.annotationCells;\n out.decodedCells += r.decodedCells;\n out.knownRawCells += r.knownRawCells;\n out.unknownCells += r.unknownCells;\n out.lyricAlignMismatches += r.lyricAlignMismatches;\n for (const [tok, n] of Object.entries(r.unknownTokens)) {\n unknown.set(tok, (unknown.get(tok) ?? 0) + n);\n }\n const taalKey = r.taalName ?? '<none>';\n const taalEntry = (out.taals[taalKey] ??= { count: 0, taalId: r.taalId, matched: r.taalMatched });\n taalEntry.count++;\n for (const w of r.warnings) {\n // Collapse per-file numbers so identical warning shapes aggregate.\n const shape = w.replace(/\\d+/g, 'N');\n out.warningCounts[shape] = (out.warningCounts[shape] ?? 0) + 1;\n }\n }\n out.unknownTokens = Object.fromEntries([...unknown.entries()].sort((a, b) => b[1] - a[1]));\n if (out.notationCells > 0) {\n out.decodeRate = out.decodedCells / out.notationCells;\n out.unknownRate = out.unknownCells / out.notationCells;\n }\n return out;\n}\n\nexport function formatSummary(r: BatchReport): string {\n const pct = (x: number) => `${(x * 100).toFixed(2)}%`;\n const lines = [\n `files: ${r.ok}/${r.files} converted${r.failed ? ` (${r.failed} FAILED)` : ''}`,\n `systems: ${r.systems} notation cells: ${r.notationCells} lyric cells: ${r.lyricCells} annotation cells: ${r.annotationCells}`,\n `decoded: ${r.decodedCells} (${pct(r.decodeRate)}) known-raw: ${r.knownRawCells} unknown: ${r.unknownCells} (${pct(r.unknownRate)})`,\n `lyric alignment fallbacks: ${r.lyricAlignMismatches}`,\n ];\n const unknowns = Object.entries(r.unknownTokens);\n if (unknowns.length > 0) {\n lines.push(`unknown tokens (${unknowns.length} distinct, top 20):`);\n for (const [tok, n] of unknowns.slice(0, 20)) lines.push(` ${String(n).padStart(6)} ${JSON.stringify(tok)}`);\n }\n const taalLines = Object.entries(r.taals).sort((a, b) => b[1].count - a[1].count);\n lines.push(`taals (${taalLines.length} distinct):`);\n for (const [name, info] of taalLines.slice(0, 15)) {\n lines.push(` ${String(info.count).padStart(5)} ${name} → ${info.taalId ?? '—'} [${info.matched}]`);\n }\n if (r.failed > 0) {\n lines.push('failures:');\n for (const f of r.failures.slice(0, 10)) lines.push(` ${f.file}: ${f.error}`);\n }\n return lines.join('\\n');\n}\n","/**\n * @ugfoundation/swaralipi-js/convert\n *\n * NLTR HTML → swaralipi JSON converter (Node-only subpath export).\n * `convertNltrHtml` itself is pure (string in, document out); file/dir\n * batch helpers live in the CLI (`swaralipi convert`).\n */\nimport { assemble, type ConvertOptions, type ConvertResult } from './assemble.js';\nimport { extractPage } from './extract.js';\n\nexport { ExtractError, extractPage } from './extract.js';\nexport type { NltrHeader, NltrPage, RawCell } from './extract.js';\nexport { buildSystems } from './grid.js';\nexport type { System } from './grid.js';\nexport { decodeCell } from './codec.js';\nexport type { CellDecode } from './codec.js';\nexport { bnInt, deriveGeometry, resolveTaal } from './taal-map.js';\nexport type { GridGeometry, ResolvedTaal } from './taal-map.js';\nexport { assemble, splitLyric } from './assemble.js';\nexport type { ConvertOptions, ConvertResult } from './assemble.js';\nexport { aggregate, formatSummary } from './report.js';\nexport type { BatchFailure, BatchReport, FileReport } from './report.js';\n\n/**\n * Convert one NLTR swaralipi page (HTML source text) into a validated\n * spec document plus a decode report.\n *\n * Throws `ExtractError` when the HTML does not look like an NLTR swaralipi\n * grid; cell-level decode problems never throw (they degrade to `raw`).\n */\nexport function convertNltrHtml(html: string, opts: ConvertOptions = {}): ConvertResult {\n return assemble(extractPage(html), opts);\n}\n"]}
@@ -79,11 +79,175 @@ function sCurve(x, steepness = 8) {
79
79
  return (logistic(t) - lo) / (logistic(1) - lo);
80
80
  }
81
81
 
82
+ // src/player/lyrics.ts
83
+ function lyricCues(events, map, text) {
84
+ const tokens = [...text.matchAll(/\S+/g)].map((m) => ({
85
+ text: m[0],
86
+ start: m.index,
87
+ end: m.index + m[0].length,
88
+ line: text.slice(0, m.index).split("\n").length - 1
89
+ }));
90
+ const cues = [];
91
+ for (const ev of events) {
92
+ const span = map.get(ev.path);
93
+ const index = span ? tokens.findIndex((t) => t.start <= span[0] && span[0] < t.end) : -1;
94
+ if (index < 0) continue;
95
+ const token = tokens[index];
96
+ let cue = cues.at(-1);
97
+ if (!cue || cue.line !== token.line || index < cue.last) {
98
+ cue = { beat: ev.beat, line: token.line, last: -1, words: [] };
99
+ cues.push(cue);
100
+ }
101
+ if (index !== cue.last) {
102
+ cue.words.push({ beat: ev.beat, text: token.text });
103
+ cue.last = index;
104
+ }
105
+ }
106
+ return cues.map(({ beat, words }) => ({ beat, words }));
107
+ }
108
+ var MATCH = 2;
109
+ var GAP = 1;
110
+ var JUMP = 4;
111
+ var RUN = 3;
112
+ var STRAY = 8;
113
+ var EPS = 1e-6;
114
+ var [DIAG, UP, LEFT, JUMPED] = [1, 2, 3, 4];
115
+ var letters = (s) => s.normalize("NFD").replace(/ৎ/g, "\u09A4").replace(/[^\p{L}\p{M}]|[়্]/gu, "").toLowerCase();
116
+ function alignLyrics(doc, text) {
117
+ const sung = [];
118
+ doc.parts.forEach(
119
+ (part, p) => part.rows.forEach(
120
+ (row, r) => row.notes.forEach((note, i) => {
121
+ for (const letter of letters(note.lyric ?? "")) sung.push({ letter, path: `${p}.${r}.${i}` });
122
+ })
123
+ )
124
+ );
125
+ const said = [];
126
+ const starts = [];
127
+ let at = 0;
128
+ let gap = true;
129
+ for (const ch of text) {
130
+ const ls = letters(ch);
131
+ if (ls === "") {
132
+ gap ||= /[\s\p{P}]/u.test(ch);
133
+ if (/[\p{M}\p{Cf}]/u.test(ch) && said.at(-1)?.to === at) said.at(-1).to += ch.length;
134
+ } else {
135
+ if (gap) starts.push(said.length);
136
+ gap = false;
137
+ }
138
+ for (const letter of ls) said.push({ letter, from: at, to: at + ch.length });
139
+ at += ch.length;
140
+ }
141
+ const n = sung.length;
142
+ const m = said.length;
143
+ const w = m + 1;
144
+ const from = new Uint8Array((n + 1) * w);
145
+ const best = new Int32Array(n + 1);
146
+ let prev = new Int32Array(w).fill(-1073741824);
147
+ prev[0] = 0;
148
+ let cur = new Int32Array(w);
149
+ const settle = (i, row) => {
150
+ for (let j2 = 1; j2 < w; j2++) if (row[j2] > row[best[i]]) best[i] = j2;
151
+ for (const s of starts) {
152
+ if (row[best[i]] - JUMP > row[s]) {
153
+ row[s] = row[best[i]] - JUMP;
154
+ from[i * w + s] = JUMPED;
155
+ }
156
+ }
157
+ for (let j2 = 1; j2 < w; j2++) {
158
+ if (row[j2 - 1] - GAP > row[j2]) {
159
+ row[j2] = row[j2 - 1] - GAP;
160
+ from[i * w + j2] = LEFT;
161
+ }
162
+ }
163
+ };
164
+ settle(0, prev);
165
+ for (let i = 1; i <= n; i++) {
166
+ cur[0] = prev[0] - GAP;
167
+ from[i * w] = UP;
168
+ for (let j2 = 1; j2 < w; j2++) {
169
+ const diag = prev[j2 - 1] + (sung[i - 1].letter === said[j2 - 1].letter ? MATCH : -GAP);
170
+ const up = prev[j2] - GAP;
171
+ cur[j2] = Math.max(diag, up);
172
+ from[i * w + j2] = diag >= up ? DIAG : UP;
173
+ }
174
+ best[i] = 0;
175
+ settle(i, cur);
176
+ [prev, cur] = [cur, prev];
177
+ }
178
+ const pairs = [];
179
+ let j = 0;
180
+ for (let k = 1; k < w; k++) if (prev[k] > prev[j]) j = k;
181
+ for (let i = n; i > 0; ) {
182
+ const step = from[i * w + j];
183
+ if (step === JUMPED) j = best[i];
184
+ else if (step === LEFT) j--;
185
+ else if (step === UP) i--;
186
+ else {
187
+ if (sung[i - 1].letter === said[j - 1].letter) pairs.push([i - 1, j - 1]);
188
+ i--;
189
+ j--;
190
+ }
191
+ }
192
+ pairs.reverse();
193
+ const wordStart = new Set(starts);
194
+ const follows = (k) => pairs[k][0] === pairs[k - 1][0] + 1 && (pairs[k][1] === pairs[k - 1][1] + 1 || wordStart.has(pairs[k][1]));
195
+ const out = /* @__PURE__ */ new Map();
196
+ const last = /* @__PURE__ */ new Map();
197
+ for (let a = 0, b = 1; a < pairs.length; a = b++) {
198
+ while (b < pairs.length && follows(b)) b++;
199
+ if (b - a < RUN) continue;
200
+ for (const [s, t] of pairs.slice(a, b)) {
201
+ const { path } = sung[s];
202
+ const { from: lo, to: hi } = said[t];
203
+ const span = out.get(path);
204
+ if (span === void 0) out.set(path, [lo, hi]);
205
+ else if (t === last.get(path) + 1) span[1] = hi;
206
+ else continue;
207
+ last.set(path, t);
208
+ }
209
+ }
210
+ return out;
211
+ }
212
+ var mod = (a, n) => (a % n + n) % n;
213
+ var sings = (v) => v.note.svara !== void 0 || v.note.sustain === true;
214
+ function excerptVisits(order, at, { lyrics, ranges }, cycle) {
215
+ const pos = order.map((v) => lyrics.get(`${v.p}.${v.r}.${v.i}`)?.[0]);
216
+ for (let a = 0; a < order.length; a++) {
217
+ if (pos[a] !== void 0 || letters(order[a].note.lyric ?? "") === "") continue;
218
+ let b = a;
219
+ let stray = 0;
220
+ while (b < order.length && pos[b] === void 0) stray += letters(order[b++].note.lyric ?? "").length;
221
+ if (stray >= STRAY) pos.fill(-1, a, b);
222
+ a = b;
223
+ }
224
+ const visits = [];
225
+ let first;
226
+ let end = 0;
227
+ for (const [lo, hi] of ranges) {
228
+ const inside = (k) => pos[k] === void 0 ? void 0 : pos[k] >= lo && pos[k] < hi;
229
+ const a = order.findIndex((_, k) => inside(k) === true);
230
+ if (a < 0) continue;
231
+ let b = a + 1;
232
+ while (b < order.length && inside(b) !== false) b++;
233
+ while (b - 1 > a && !sings(order[b - 1])) b--;
234
+ const n = cycle(order[a]);
235
+ if (first === void 0) first = a;
236
+ else if (n !== void 0) {
237
+ const gap = mod(at[a] - at[end], n);
238
+ if (gap > EPS) visits.push({ ...order[end - 1], note: { rest: true, durationMatra: gap } });
239
+ }
240
+ visits.push(...order.slice(a, b));
241
+ end = b;
242
+ }
243
+ return { visits, matra: first === void 0 ? 0 : at[first] };
244
+ }
245
+
82
246
  // src/player/repeats.ts
83
247
  var UNDECODED_CELL = /[srgmpqnvtduka]/i;
84
248
  var isSilent = (n) => n.svara === void 0 && n.rest !== true && n.sustain !== true && !(n.raw !== void 0 && UNDECODED_CELL.test(n.raw));
85
249
  var STOP_MARK = /^\|+$/;
86
- var EPS = 1e-6;
250
+ var EPS2 = 1e-6;
87
251
  var edges = (n, kind, edge) => n.brackets?.filter((b) => b.kind === kind && b.edge === edge).length ?? 0;
88
252
  var sounds = (n) => n.svara !== void 0 || n.rest === true || n.sustain === true;
89
253
  var matra = (notes) => notes.reduce((sum, n) => sum + (n.durationMatra ?? 1), 0);
@@ -112,10 +276,10 @@ function playOrder(doc) {
112
276
  owed = 0;
113
277
  continue;
114
278
  }
115
- if (owed > EPS && t.timed) owed -= matra([t.note]);
279
+ if (owed > EPS2 && t.timed) owed -= matra([t.note]);
116
280
  else {
117
281
  const sub = useChanged || again ? changed.get(k) : void 0;
118
- const fits = sub !== void 0 && matra(sub) >= matra([t.note]) - EPS;
282
+ const fits = sub !== void 0 && matra(sub) >= matra([t.note]) - EPS2;
119
283
  for (const note of fits ? sub : [t.note]) out.push({ p: t.p, r: t.r, i: t.i, note });
120
284
  if (t.timed) last = k;
121
285
  owed = fits ? matra(sub) - matra([t.note]) : 0;
@@ -308,8 +472,6 @@ function buildSchedule(doc, opts = {}) {
308
472
  const events = [];
309
473
  let cursor = 0;
310
474
  let lastPitched = -1;
311
- let samMatra;
312
- let matra2 = 0;
313
475
  const speeds = resolveLaySpeeds(doc.meta.laySpeeds, opts.laySpeeds);
314
476
  const withLay = opts.lay !== false;
315
477
  const tempo = [{ beat: 0, endBeat: 0, scale: 1, lay: "drut", taal: doc.meta.taal }];
@@ -328,7 +490,15 @@ function buildSchedule(doc, opts = {}) {
328
490
  rows.set(`${p}.${r}`, { scale, lay: layNow, taal, glides: meendContinuations(row.notes) });
329
491
  })
330
492
  );
331
- 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 }))));
493
+ const order = opts.repeats === true ? playOrder(doc) : doc.parts.flatMap((part, p) => part.rows.flatMap((row, r) => row.notes.map((note, i) => ({ p, r, i, note }))));
494
+ const at = [0];
495
+ let samMatra;
496
+ for (const { note } of order) {
497
+ const m = at[at.length - 1];
498
+ if (samMatra === void 0 && note.above?.some((a) => a.raw !== void 0 && SAM_TICK.test(a.raw))) samMatra = m;
499
+ at.push(note.bar === void 0 && !isSilent(note) ? m + (note.durationMatra ?? 1) : m);
500
+ }
501
+ const { visits, matra: matra2 } = opts.excerpt ? excerptVisits(order, at, opts.excerpt, (v) => rows.get(`${v.p}.${v.r}`).taal?.numBeats) : { visits: order, matra: 0 };
332
502
  let rowNow = "";
333
503
  for (const { p, r, i, note } of visits) {
334
504
  const row = rows.get(`${p}.${r}`);
@@ -344,14 +514,6 @@ function buildSchedule(doc, opts = {}) {
344
514
  }
345
515
  }
346
516
  }
347
- if (samMatra === void 0 && note.above !== void 0) {
348
- for (const a of note.above) {
349
- if (a.raw !== void 0 && SAM_TICK.test(a.raw)) {
350
- samMatra = matra2;
351
- break;
352
- }
353
- }
354
- }
355
517
  if (note.bar !== void 0 || isSilent(note)) continue;
356
518
  const path = `${p}.${r}.${i}`;
357
519
  const written = note.durationMatra ?? 1;
@@ -377,10 +539,9 @@ function buildSchedule(doc, opts = {}) {
377
539
  events.push({ beat: cursor, durationBeats: dur, midi: null, kan, resh, path, kind: "rest" });
378
540
  }
379
541
  cursor += dur;
380
- matra2 += written;
381
542
  }
382
543
  const numBeats = tempo[0].taal?.numBeats;
383
- const pickupMatra = samMatra !== void 0 && numBeats !== void 0 && numBeats > 0 ? (numBeats - samMatra % numBeats) % numBeats : 0;
544
+ const pickupMatra = numBeats !== void 0 && numBeats > 0 ? ((matra2 - (samMatra ?? 0)) % numBeats + numBeats) % numBeats : 0;
384
545
  tempo[tempo.length - 1].endBeat = cursor;
385
546
  return { events, totalBeats: cursor, pickupBeats: pickupMatra / tempo[0].scale, tempo };
386
547
  }
@@ -678,6 +839,6 @@ function createTanpura(Tone, base, stems = TANPURA_PCS) {
678
839
  return new Tone.Players({ urls, baseUrl: `${normalizeBase(base)}tanpura/` });
679
840
  }
680
841
 
681
- export { ARC_RAW_RE, DEFAULT_SOUNDS_BASE, DRUM_KIT_BASE, GRACE_STEP, INSTRUMENTS, MEEND_HOLD, OCTAVE_OFFSET, SALAMANDER_BASE, SAMPLED_TAALS, SHUDDHA, TABLA_BPMS, TABLA_GRAIN_OVERLAP_SEC, TABLA_GRAIN_SEC, TABLA_VARIATIONS, TANPURA_PCS, TONEJS_INSTRUMENTS_BASE, buildSchedule, byRow, createBuffers, createDrumKit, createMetronome, createSampler, createTanpura, drumPattern, eventAt, glideTargets, graceTouches, instrumentSampleUrl, isArcRaw, lineAt, lineFor, meendContinuations, midiToSampleName, nearestSampleKey, nearestTablaBpm, normalizeBase, parseTonic, probeUrl, quantizeLoopEnd, sCurve, sampleFileName, santoorUrls, scheduleLines, subsetUrls, svaraSemitone, svaraToMidi, tablaBpms, tablaShiftSemitones, tablaUrl, triggerGlide };
682
- //# sourceMappingURL=chunk-L6YZ3GC6.js.map
683
- //# sourceMappingURL=chunk-L6YZ3GC6.js.map
842
+ export { ARC_RAW_RE, DEFAULT_SOUNDS_BASE, DRUM_KIT_BASE, GRACE_STEP, INSTRUMENTS, MEEND_HOLD, OCTAVE_OFFSET, SALAMANDER_BASE, SAMPLED_TAALS, SHUDDHA, TABLA_BPMS, TABLA_GRAIN_OVERLAP_SEC, TABLA_GRAIN_SEC, TABLA_VARIATIONS, TANPURA_PCS, TONEJS_INSTRUMENTS_BASE, alignLyrics, buildSchedule, byRow, createBuffers, createDrumKit, createMetronome, createSampler, createTanpura, drumPattern, eventAt, glideTargets, graceTouches, instrumentSampleUrl, isArcRaw, lineAt, lineFor, lyricCues, meendContinuations, midiToSampleName, nearestSampleKey, nearestTablaBpm, normalizeBase, parseTonic, probeUrl, quantizeLoopEnd, sCurve, sampleFileName, santoorUrls, scheduleLines, subsetUrls, svaraSemitone, svaraToMidi, tablaBpms, tablaShiftSemitones, tablaUrl, triggerGlide };
843
+ //# sourceMappingURL=chunk-GIOGYJMM.js.map
844
+ //# sourceMappingURL=chunk-GIOGYJMM.js.map