@ugfoundation/swaralipi-js 0.8.2 → 0.9.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 (52) hide show
  1. package/README.md +1 -1
  2. package/dist/{batch-6FPBT5DP.js → batch-NEM7JXHV.js} +5 -5
  3. package/dist/{batch-6FPBT5DP.js.map → batch-NEM7JXHV.js.map} +1 -1
  4. package/dist/{chunk-UMBKIGVQ.js → chunk-DOEFSS66.js} +5 -4
  5. package/dist/chunk-DOEFSS66.js.map +1 -0
  6. package/dist/{chunk-4HNT6OAT.js → chunk-MYVIQU4O.js} +12 -3
  7. package/dist/chunk-MYVIQU4O.js.map +1 -0
  8. package/dist/{chunk-4IVUPMRK.js → chunk-SEN266BY.js} +28 -13
  9. package/dist/chunk-SEN266BY.js.map +1 -0
  10. package/dist/{chunk-FULC22MN.js → chunk-Z6A3CP5V.js} +28 -19
  11. package/dist/chunk-Z6A3CP5V.js.map +1 -0
  12. package/dist/cli.cjs +33 -19
  13. package/dist/cli.cjs.map +1 -1
  14. package/dist/cli.js +3 -3
  15. package/dist/convert/index.cjs +31 -17
  16. package/dist/convert/index.cjs.map +1 -1
  17. package/dist/convert/index.d.cts +1 -1
  18. package/dist/convert/index.d.ts +1 -1
  19. package/dist/convert/index.js +3 -3
  20. package/dist/index.cjs +38 -17
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.cts +2 -2
  23. package/dist/index.d.ts +2 -2
  24. package/dist/index.js +2 -2
  25. package/dist/{lay-Csa4CD46.d.cts → lay-CZIzR9cM.d.ts} +16 -2
  26. package/dist/{lay-Ct9ky_EE.d.ts → lay-CfDlPhH2.d.cts} +16 -2
  27. package/dist/player/index.cjs +116 -71
  28. package/dist/player/index.cjs.map +1 -1
  29. package/dist/player/index.d.cts +11 -9
  30. package/dist/player/index.d.ts +11 -9
  31. package/dist/player/index.js +89 -66
  32. package/dist/player/index.js.map +1 -1
  33. package/dist/{schedule-B_UaluBi.d.cts → schedule-BaovCtmf.d.ts} +31 -12
  34. package/dist/{schedule-CdILTNsk.d.ts → schedule-CU-VECDH.d.cts} +31 -12
  35. package/dist/{schema-DGA_bNVZ.d.ts → schema-Cf_epUIC.d.cts} +333 -63
  36. package/dist/{schema-DGA_bNVZ.d.cts → schema-Cf_epUIC.d.ts} +333 -63
  37. package/dist/spec/index.cjs +38 -17
  38. package/dist/spec/index.cjs.map +1 -1
  39. package/dist/spec/index.d.cts +26 -8
  40. package/dist/spec/index.d.ts +26 -8
  41. package/dist/spec/index.js +2 -2
  42. package/dist/viewer/index.cjs +156 -73
  43. package/dist/viewer/index.cjs.map +1 -1
  44. package/dist/viewer/index.d.cts +25 -7
  45. package/dist/viewer/index.d.ts +25 -7
  46. package/dist/viewer/index.js +78 -27
  47. package/dist/viewer/index.js.map +1 -1
  48. package/package.json +1 -1
  49. package/dist/chunk-4HNT6OAT.js.map +0 -1
  50. package/dist/chunk-4IVUPMRK.js.map +0 -1
  51. package/dist/chunk-FULC22MN.js.map +0 -1
  52. package/dist/chunk-UMBKIGVQ.js.map +0 -1
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # swaralipi-js
1
+ <img src="assets/banner.svg" alt="swaralipi-js" width="100%">
2
2
 
3
3
  `@ugfoundation/swaralipi-js` — a JSON spec, a Unicode-Bengali viewer, a Tone.js player, and an archival-HTML converter for **Rabindrasangeet swaralipi** (Tagore's আকারমাত্রিক / *akarmatrik* notation).
4
4
 
@@ -1,6 +1,6 @@
1
- import { convertNltrHtml, aggregate, formatSummary } from './chunk-UMBKIGVQ.js';
2
- import './chunk-FULC22MN.js';
3
- import './chunk-4HNT6OAT.js';
1
+ import { convertNltrHtml, aggregate, formatSummary } from './chunk-DOEFSS66.js';
2
+ import './chunk-Z6A3CP5V.js';
3
+ import './chunk-MYVIQU4O.js';
4
4
  import { stat, readdir, mkdir, readFile, writeFile } from 'fs/promises';
5
5
  import { join, basename } from 'path';
6
6
 
@@ -43,5 +43,5 @@ async function runConvert(input, opts = {}) {
43
43
  }
44
44
 
45
45
  export { runConvert };
46
- //# sourceMappingURL=batch-6FPBT5DP.js.map
47
- //# sourceMappingURL=batch-6FPBT5DP.js.map
46
+ //# sourceMappingURL=batch-NEM7JXHV.js.map
47
+ //# sourceMappingURL=batch-NEM7JXHV.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/convert/batch.ts"],"names":["report"],"mappings":"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","file":"batch-6FPBT5DP.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-NEM7JXHV.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,5 +1,5 @@
1
- import { swaralipiSchema, SPEC_VERSION } from './chunk-FULC22MN.js';
2
- import { TAALS } from './chunk-4HNT6OAT.js';
1
+ import { swaralipiSchema, SPEC_VERSION } from './chunk-Z6A3CP5V.js';
2
+ import { isTaalFerta, TAAL_FERTA, TAALS } from './chunk-MYVIQU4O.js';
3
3
 
4
4
  // src/convert/codec.ts
5
5
  var LETTERS = {
@@ -292,6 +292,7 @@ function resolveTaal(nameRaw, avartanRaw, geometry) {
292
292
  if (!taalName || TAAL_FREE.includes(taalName)) {
293
293
  return { taalName: taalName ? TAAL_FREE_CANON : void 0, avartan, matched: "none", warnings };
294
294
  }
295
+ if (isTaalFerta(taalName)) return { taalName: TAAL_FERTA, avartan, matched: "none", warnings };
295
296
  const parenMatch = taalName.match(/^(.*?)\s*\(([০-৯\d/\s]+)\)\s*$/);
296
297
  const base = (parenMatch ? parenMatch[1] : taalName).trim();
297
298
  let hintBhaags;
@@ -590,5 +591,5 @@ function convertNltrHtml(html, opts = {}) {
590
591
  }
591
592
 
592
593
  export { ExtractError, aggregate, assemble, bnInt, buildSystems, convertNltrHtml, decodeCell, deriveGeometry, extractPage, formatSummary, resolveTaal, splitLyric };
593
- //# sourceMappingURL=chunk-UMBKIGVQ.js.map
594
- //# sourceMappingURL=chunk-UMBKIGVQ.js.map
594
+ //# sourceMappingURL=chunk-DOEFSS66.js.map
595
+ //# sourceMappingURL=chunk-DOEFSS66.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/convert/codec.ts","../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-DOEFSS66.js","sourcesContent":["/**\n * The Swarabitan ASCII codec: decode one grid-cell text into spec `Note`s.\n *\n * NLTR renders notation through the legacy \"Swarabitan\" webfont, which maps\n * ASCII to akarmatrik glyphs. The table below was derived by dumping every\n * glyph of `Swarabitan.woff` (tools/font-dump/) and cross-checking against\n * the Swarabitan legend scans, the rendered reference screenshot of song\n * 4270, and corpus-wide token statistics. Evidence per assignment:\n *\n * Letters (full-size glyphs):\n * s=স r=র g=গ m=ম p=প q=ধ n=ন — the seven shuddha letters\n * d=দ (komal dha) t=জ্ঞ (komal ga) — legend: কোমল র—ঋ, কোমল গ—জ্ঞ,\n * k=ক্ষ (tivra ma) u=ণ (komal ni) — কড়ি ম—ক্ষ, কোমল ধ—দ, কোমল ন—ণ\n * v=ঋ (komal re)\n * Modifiers:\n * f = রেফ above → taar octave (zero-width high combining mark)\n * h = হসন্ত below → mandra octave (low combining mark; corpus nh/uh/dh/ph)\n * leading '-' = no new vowel attack (melisma; lyric row shows ৹)\n * trailing 'a' = the আ-কার matra carrier; k letter-units in one cell\n * share it → k notes of 1/k matra each\n * UPPERCASE letter = small raised form = কণ (kan/grace) prefix; takes its\n * own f/h (corpus: Sfua, NHsa, QHpha); width ratios ~0.65\n * confirm uppercase↔lowercase pairing\n * Structurals (own cells):\n * A = thin full-height bar → দাঁড়ি vibhag separator (fixed columns)\n * l = serif danda I → cycle end; ll / single-glyph L = section\n * %=সা &=নি — precomposed one-matra forms (rare)\n * Known-but-kept-raw marks (drawn pieces the spec doesn't model yet):\n * w x = left/right ends of a long under-arc; E J j y $ < > = _ = arc/line\n * fill segments; | = thin yugal-dari stop mark (mostly annotation rows);\n * digits/!/@ (+f/F sam tick) = taal beat labels; b=× o O e ` + , . i : ;\n * = dots/ticks/duration marks; B C * ^ ~ c = rare precomposed/legacy forms.\n *\n * Decoding never throws: any chunk outside the grammar becomes a raw-only\n * note and is reported as unknown — zero data loss by construction.\n */\nimport type { Note, Octave, Svara, SvaraLetter } from '../spec/index.js';\n\nexport interface CellDecode {\n notes: Note[];\n /** Chunks that failed the grammar (reported, kept as raw notes). */\n unknownTokens: string[];\n /** Chunks understood but intentionally left raw (marks, beat labels …). */\n knownRawTokens: string[];\n /** True if at least one chunk decoded to a semantic note. */\n decoded: boolean;\n}\n\ninterface LetterDef {\n letter: SvaraLetter;\n komal?: true;\n tivra?: true;\n}\n\n/** ASCII → svara letter (+ komal/tivra). The five non-shuddha glyph letters\n * are distinct characters in akarmatrik, so the flags live here. */\nconst LETTERS: Record<string, LetterDef> = {\n s: { letter: 's' },\n r: { letter: 'r' },\n g: { letter: 'g' },\n m: { letter: 'm' },\n p: { letter: 'p' },\n q: { letter: 'd' }, // ধ — shuddha dha\n n: { letter: 'n' },\n d: { letter: 'd', komal: true }, // দ\n t: { letter: 'g', komal: true }, // জ্ঞ\n k: { letter: 'm', tivra: true }, // ক্ষ\n u: { letter: 'n', komal: true }, // ণ\n v: { letter: 'r', komal: true }, // ঋ\n};\n\n/** Standalone structural tokens (bars are handled by the bar-run rule). */\nconst STRUCTURALS: Record<string, () => Note> = {\n '%': () => ({ svara: ['madhyam', 's'] }), // precomposed সা\n '&': () => ({ svara: ['madhyam', 'n'] }), // precomposed নি\n};\n\n/**\n * Bar/bracket runs: `l`(I danda) `ll`/`L`(II) `llll`/`LL`(IIII) `A`(দাঁড়ি),\n * plus composites the corpus writes into one cell — `l[]l` / `L[]L` (the\n * legend's \"I [ ] I\" return-to-asthayi-with-changed-notes marker), `AA`,\n * and `-A` (the previous note's continuation dash sharing the dari's cell).\n */\nconst BAR_RUN_RE = /^(-)?([lLA[\\]]+)$/;\n\nfunction decodeBarRun(body: string): Note[] | undefined {\n const m = body.match(BAR_RUN_RE);\n if (!m || !/[lLA]/.test(m[2]!)) return undefined;\n const notes: Note[] = [];\n if (m[1]) notes.push({ sustain: true });\n const run = m[2]!;\n let i = 0;\n while (i < run.length) {\n const ch = run[i]!;\n if (ch === 'l' || ch === 'L') {\n let j = i;\n while (j < run.length && run[j] === ch) j++;\n const strokes = (j - i) * (ch === 'L' ? 2 : 1);\n notes.push({ bar: strokes >= 4 ? 'quad' : strokes >= 2 ? 'double' : 'single' });\n i = j;\n } else if (ch === 'A') {\n notes.push({ bar: 'dari' });\n i++;\n } else {\n notes.push({ brackets: [{ kind: 'alt', edge: ch === '[' ? 'open' : 'close' }] });\n i++;\n }\n }\n return notes;\n}\n\n/** Marks/labels that are understood but deliberately preserved raw-only. */\nconst KNOWN_RAW_RE = [\n /^[wxyjEJ$<>=_]+$/, // arc & line drawing segments\n /^\\|+$/, // thin yugal-dari stop mark\n /^[0-9!@#]{1,2}[fF]?:?$/, // beat-number labels incl. 10–16 (sam tick = f/F, e.g. 1f / 1F / 2:)\n /^[oOe`+,.;:iI~^*bcBCWXYFHZz'\"?]$/, // dots, ticks, duration marks, legacy forms\n];\n\nconst OPEN_BRACKETS: Record<string, 'repeat' | 'skip' | 'alt'> = {\n '{': 'repeat',\n '(': 'skip',\n '[': 'alt',\n};\nconst CLOSE_BRACKETS: Record<string, 'repeat' | 'skip' | 'alt'> = {\n '}': 'repeat',\n ')': 'skip',\n ']': 'alt',\n};\n\nfunction octaveOf(mark: string | undefined): Octave {\n if (mark === 'f' || mark === 'F') return 'taar';\n if (mark === 'h' || mark === 'H') return 'mandra';\n return 'madhyam';\n}\n\n/**\n * Body grammar of one chunk (brackets already peeled):\n *\n * [beat-digit] ['-'] [KAN] unit* ['a'] [fh] [i:] [KAN] [beat-digit]\n *\n * where unit = letter [fh] and KAN = uppercase letter [fFhH]. Corpus\n * realities beyond the canonical `[-]KAN unit+ a` shape:\n * - octave mark typed after the আ-কার: `saf` ≡ `sfa` (taar sa) — the reph\n * applies to the last unit;\n * - trailing `i` (two stacked rings, ঃ-shaped) or `:` (two lozenges — a\n * different glyph) after the matra: a **matra sign worth ½**, the আ-কার's\n * half-brother (legend `রাঃ` = 1½, `সঃ` = ½; corpus rows fit their taal\n * cycle 114:19 in favour of that reading over \"articulation only\"). The\n * durations below still ignore it — they split the cell evenly among its\n * units, so `qinqi` decodes ⅓⅓⅓ where the sheet says ½ ¼ ¼. The viewer\n * reads the marks off `raw` and draws them right (viewer/render.ts\n * `rawMarks`); playback is what the even split still gets wrong;\n * - trailing KAN (`-aR`): an anticipatory grace drawn at the cell's right\n * edge, leading into the next note — attached to this cell's last note;\n * - the cut mark may also lead or sit between units (`-iqi`, `mpimi`);\n * - leading `z` = blank spacer glyph (empty outline in the font) — skipped.\n * A standalone detached `a` (no hyphen) is a 1-matra rest per the legend\n * (\"হাইফেন-বর্জিত … বিরামের মাত্রা\").\n */\nconst BODY_RE =\n /^([0-9!@][fF]?)?([zj]*)(-)?([i:])?(?:([SRGMPQNDTKUV])([fFhH])?)?((?:[srgmpqndtkuv][fh]?[i:]?)+)?(a)?([fh])?([i:])?(?:([SRGMPQNDTKUV])([fFhH])?)?([0-9!@][fF]?)?(-)?$/;\n\nfunction decodeChunk(chunk: string): { notes: Note[]; kind: 'decoded' | 'known' | 'unknown' } {\n // 1. Peel brackets.\n const opens: ('repeat' | 'skip' | 'alt')[] = [];\n const closes: ('repeat' | 'skip' | 'alt')[] = [];\n let body = chunk;\n while (body.length > 0) {\n const kind = OPEN_BRACKETS[body[0]!];\n if (kind === undefined) break;\n opens.push(kind);\n body = body.slice(1);\n }\n while (body.length > 0) {\n const kind = CLOSE_BRACKETS[body[body.length - 1]!];\n if (kind === undefined) break;\n closes.unshift(kind);\n body = body.slice(0, -1);\n }\n\n const finish = (notes: Note[], kind: 'decoded' | 'known' | 'unknown') => {\n if (notes.length === 0) notes.push({});\n const first = notes[0]!;\n const last = notes[notes.length - 1]!;\n if (opens.length > 0) first.brackets = [...opens.map((kind_) => ({ kind: kind_, edge: 'open' as const })), ...(first.brackets ?? [])];\n if (closes.length > 0) last.brackets = [...(last.brackets ?? []), ...closes.map((kind_) => ({ kind: kind_, edge: 'close' as const }))];\n return { notes, kind };\n };\n\n // 2. Bracket-only chunk → raw carrier for the bracket edges.\n if (body === '') return finish([{}], 'decoded');\n\n // 3. Bar/bracket runs, then the remaining standalone structurals.\n const barRun = decodeBarRun(body);\n if (barRun) return finish(barRun, 'decoded');\n const structural = STRUCTURALS[body];\n if (structural) return finish([structural()], 'decoded');\n\n // 4. Known raw marks.\n if (KNOWN_RAW_RE.some((re) => re.test(body))) return finish([{}], 'known');\n\n // 5. Note grammar.\n const m = body.match(BODY_RE);\n if (m) {\n const [, , , hyphen, , kanLetter, kanOct, unitsStr, aakar, postOct, , trailKan, trailKanOct] = m;\n const attachKan = (note: Note, letter: string, oct: string | undefined) => {\n const def = LETTERS[letter.toLowerCase()]!;\n // NOTE: the spec kan is a plain [octave, letter] tuple, so a komal\n // kan letter (e.g. D = small দ) is projected onto its shuddha\n // letter; the exact glyph stays recoverable from `raw`.\n note.kan = [octaveOf(oct?.toLowerCase()), def.letter] as Svara;\n };\n if (unitsStr) {\n const units = unitsStr.match(/[srgmpqndtkuv][fh]?/g) ?? [];\n const notes: Note[] = units.map((unit) => {\n const def = LETTERS[unit[0]!]!;\n const note: Note = { svara: [octaveOf(unit[1]), def.letter] as Svara };\n if (def.komal) note.komal = true;\n if (def.tivra) note.tivra = true;\n if (units.length > 1) note.durationMatra = 1 / units.length;\n return note;\n });\n const last = notes[notes.length - 1]!;\n // Post-আকার octave mark (`saf`): applies to the last unit unless it\n // already carries its own mark.\n if (postOct && last.svara![0] === 'madhyam') {\n last.svara = [octaveOf(postOct), last.svara![1]] as Svara;\n }\n if (hyphen) notes[0]!.sustain = true;\n if (kanLetter) attachKan(notes[0]!, kanLetter, kanOct);\n if (trailKan && last.kan === undefined) attachKan(last, trailKan, trailKanOct);\n return finish(notes, 'decoded');\n }\n if (aakar) {\n // '-a' = sustain (hold previous pitch, no new attack);\n // bare detached 'a' = one-matra rest (legend: hyphen-less matra sign).\n const note: Note = hyphen ? { sustain: true } : { rest: true };\n if (trailKan) attachKan(note, trailKan, trailKanOct);\n else if (kanLetter) attachKan(note, kanLetter, kanOct);\n return finish([note], 'decoded');\n }\n if (hyphen && !kanLetter && !trailKan) {\n // A bare continuation dash (`-`, `-i`) — the আ-কার is implied.\n return finish([{ sustain: true }], 'decoded');\n }\n // Kan without units, or digit-edges with no note between — keep raw.\n }\n\n return finish([{}], 'unknown');\n}\n\n/**\n * Decode one cell's text. Multi-chunk cells (whitespace-separated) emit\n * notes in order; the first note of each chunk carries that chunk's source\n * text in `raw`, so joining a cell's `raw` values with spaces reproduces\n * the normalized cell text (the lossless property tests rely on this).\n */\nexport function decodeCell(text: string): CellDecode {\n const chunks = text.split(/\\s+/).filter((c) => c.length > 0);\n const out: CellDecode = { notes: [], unknownTokens: [], knownRawTokens: [], decoded: false };\n for (const chunk of chunks) {\n const { notes, kind } = decodeChunk(chunk);\n notes[0]!.raw = chunk;\n if (kind === 'decoded') out.decoded = true;\n else if (kind === 'known') out.knownRawTokens.push(chunk);\n else out.unknownTokens.push(chunk);\n out.notes.push(...notes);\n }\n return out;\n}\n","/**\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};\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: 'rupakda', numBeats: 8, bhaags: [3, 2, 3], sam: 0 },\n রূপকড়া: { id: 'rupakda', numBeats: 8, bhaags: [3, 2, 3], 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 খেমটা: 'khemta',\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"]}
@@ -20,6 +20,15 @@ var TAALS = {
20
20
  dhamaar: { id: "dhamaar", numBeats: 14, bhaags: [5, 2, 3, 4], sam: 0, khaali: [7] }
21
21
  // ধামার
22
22
  };
23
+ var TAAL_FERTA = "\u09A4\u09BE\u09B2\u09AB\u09C7\u09B0\u09A4\u09BE";
24
+ function isTaalFerta(name) {
25
+ return name !== void 0 && name.normalize("NFC").replace(/[\s-]/g, "") === TAAL_FERTA;
26
+ }
27
+ function sameTaal(a, b) {
28
+ if (a === b) return true;
29
+ if (!a || !b) return false;
30
+ return a.id === b.id && a.numBeats === b.numBeats && a.variation === b.variation && String(a.bhaags) === String(b.bhaags);
31
+ }
23
32
 
24
33
  // src/spec/grace.ts
25
34
  function graceNotes(grace) {
@@ -56,6 +65,6 @@ function hasTivra(letter) {
56
65
  return letter === "m";
57
66
  }
58
67
 
59
- export { LAYS, LAY_SPEEDS, TAALS, graceNotes, hasKomal, hasTivra, printedLay, resolveLaySpeeds, rowLay };
60
- //# sourceMappingURL=chunk-4HNT6OAT.js.map
61
- //# sourceMappingURL=chunk-4HNT6OAT.js.map
68
+ export { LAYS, LAY_SPEEDS, TAALS, TAAL_FERTA, graceNotes, hasKomal, hasTivra, isTaalFerta, printedLay, resolveLaySpeeds, rowLay, sameTaal };
69
+ //# sourceMappingURL=chunk-MYVIQU4O.js.map
70
+ //# sourceMappingURL=chunk-MYVIQU4O.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/spec/taals.ts","../src/spec/grace.ts","../src/spec/lay.ts","../src/spec/svara.ts"],"names":[],"mappings":"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","file":"chunk-MYVIQU4O.js","sourcesContent":["import type { Taal } from './schema.js';\n\n/**\n * Reference definitions for the taals the player will sample:\n * adachautaal, dadra, ektaal, jhaptaal, kehrwa, rupak, shashthi, teentaal,\n * jhampak, dhamaar.\n *\n * `numBeats` and the `bhaags` given below are standard Hindustani taal theory.\n * `bhaags` is omitted where a single canonical division isn't well-agreed\n * (adachautaal) — we assert only what's uncontested. `sam` is beat 0 for all.\n *\n * Tabla loops live under `tabla/<id>/` by default; per-taal variation and\n * skip-bpm overrides (e.g. shashthi → `tabla/shashthi-pro/`) are in\n * instruments.ts's `TABLA_VARIATIONS`.\n */\nexport const TAALS = {\n dadra: { id: 'dadra', numBeats: 6, bhaags: [3, 3], sam: 0, khaali: [3] },\n kehrwa: { id: 'kehrwa', numBeats: 8, bhaags: [4, 4], sam: 0, khaali: [4] },\n rupak: { id: 'rupak', numBeats: 7, bhaags: [3, 2, 2], sam: 0, khaali: [0] },\n jhaptaal: { id: 'jhaptaal', numBeats: 10, bhaags: [2, 3, 2, 3], sam: 0, khaali: [5] },\n ektaal: { id: 'ektaal', numBeats: 12, bhaags: [2, 2, 2, 2, 2, 2], sam: 0, khaali: [2, 6] },\n adachautaal: { id: 'adachautaal', numBeats: 14, sam: 0 },\n choutaal: { id: 'choutaal', numBeats: 12, bhaags: [2, 2, 2, 2, 2, 2], sam: 0, khaali: [2, 6] }, // চৌতাল\n shashthi: { id: 'shashthi', numBeats: 6, bhaags: [2, 4], sam: 0 }, // ষষ্ঠী\n teora: { id: 'teora', numBeats: 7, bhaags: [3, 2, 2], sam: 0 }, // টেওরা\n teentaal: { id: 'teentaal', numBeats: 16, bhaags: [4, 4, 4, 4], sam: 0, khaali: [8] },\n aratheka: { id: 'aratheka', numBeats: 16, bhaags: [4, 4, 4, 4], sam: 0, khaali: [8] }, // আড়াঠেকা\n jhampak: { id: 'jhampak', numBeats: 5, bhaags: [3, 2], sam: 0 }, // ঝম্পক\n dhamaar: { id: 'dhamaar', numBeats: 14, bhaags: [5, 2, 3, 4], sam: 0, khaali: [7] }, // ধামার\n} as const satisfies Record<string, Taal>;\n\nexport type TaalId = keyof typeof TAALS;\n\n/**\n * The name a তালফেরতা song's header prints instead of one taal (Swarabitan /\n * Gitabitan's own word; gitabitan.io's taxonomy spells it তাল-ফেরতা). The\n * taals themselves live on the rows (`row.taal`).\n */\nexport const TAAL_FERTA = 'তালফেরতা';\n\n/** Whether a printed taal name is the তালফেরতা heading (either spelling). */\nexport function isTaalFerta(name: string | undefined): boolean {\n return name !== undefined && name.normalize('NFC').replace(/[\\s-]/g, '') === TAAL_FERTA;\n}\n\n/**\n * Whether two taal marks name the same accompaniment — the তালফেরতা test.\n * `null` (মুক্তছন্দ) and `undefined` (no taal) are only equal to themselves.\n * Compared by value: a row restating the taal in force is not a change.\n */\nexport function sameTaal(a: Taal | null | undefined, b: Taal | null | undefined): boolean {\n if (a === b) return true;\n if (!a || !b) return false;\n return a.id === b.id && a.numBeats === b.numBeats && a.variation === b.variation && String(a.bhaags) === String(b.bhaags);\n}\n","/**\n * কণ / রেশ readers. Zod-free on purpose: the viewer and the player import\n * this directly (the `spec/index.js` barrel would drag zod into their\n * browser bundles — see `taals.ts` for the same rule).\n */\nimport type { Grace, Note, Svara } from './schema.js';\n\n/** Normalise a grace payload to its note list; the legacy tuple is one note. */\nexport function graceNotes(grace: Grace | undefined): Note[] {\n if (grace === undefined) return [];\n return typeof grace[0] === 'string' ? [{ svara: grace as Svara }] : (grace as Note[]);\n}\n","/**\n * লয় (laya) — the tempo tier a passage is sung at.\n *\n * Hindustani names the three tiers বিলম্বিত / মধ্য / দ্রুত (vilambit = slow,\n * madhya = medium, drut = fast), and Rabindrasangeet swaralipi uses the same\n * words: NLTR prints \"বিলম্বিত লয়:\" / \"দ্রুত লয়:\" at the head of the line\n * where the tempo changes (see `printedLay`).\n *\n * `meta.bpm` is the **দ্রুত** tempo — the fast one — so the tiers are\n * multipliers ≤ 1 on it and a document with no লয় marks plays exactly as it\n * did before this field existed.\n *\n * Zod-free on purpose: the viewer, player and creator import this directly\n * (the `spec/index.js` barrel would drag zod into their browser bundles — the\n * same rule as `taals.ts` and `grace.ts`).\n */\nimport type { Note, Row } from './schema.js';\n\nexport type Lay = 'drut' | 'madhya' | 'vilambit';\n\nexport const LAYS: readonly Lay[] = ['drut', 'madhya', 'vilambit'];\n\n/**\n * ### The tempo factors — change them HERE.\n *\n * Fraction of `meta.bpm` each tier is played at. Consumers override per\n * document (`meta.laySpeeds`) or per player/export call (`laySpeeds`), so\n * these are only the fallback.\n */\nexport const LAY_SPEEDS: Record<Lay, number> = {\n drut: 1,\n madhya: 0.75,\n vilambit: 0.5,\n};\n\nexport type LaySpeeds = Partial<Record<Lay, number>>;\n\n/** Resolve the factors actually in force: call option → document → default. */\nexport function resolveLaySpeeds(doc?: LaySpeeds, opt?: LaySpeeds): Record<Lay, number> {\n return { ...LAY_SPEEDS, ...doc, ...opt };\n}\n\n/**\n * The printed লয় mark: the leading lyric-only cells NLTR writes a tempo\n * change as (\"বিলম্বিত\" \"লয়:\"). Reading it means the 1738 already-converted\n * songs change tempo without being reconverted or edited.\n *\n * ponytail: matches the first word only, and only in the leading annotation\n * position where real lyrics never sit — so 'মধ্যমান' (a taal) can't trip it.\n * If a source ever writes the mark elsewhere in the row, give it `row.lay`.\n */\nconst PRINTED: Record<string, Lay> = { দ্রুত: 'drut', মধ্য: 'madhya', বিলম্বিত: 'vilambit' };\n\nexport function printedLay(notes: Note[]): Lay | undefined {\n const first = notes[0];\n if (first === undefined || first.lyric === undefined) return undefined;\n for (const k of Object.keys(first)) if (k !== 'lyric') return undefined;\n return PRINTED[first.lyric.normalize('NFC')];\n}\n\n/** The লয় this row switches to, if any: the explicit field wins over the\n * source's printed mark. `undefined` = carry on at the previous tier. */\nexport function rowLay(row: Row): Lay | undefined {\n return row.lay ?? printedLay(row.notes);\n}\n","/**\n * Which svaras admit which alteration — স and প are অচল (immovable), ম has\n * only its তীব্র (কড়ি) form, and র গ ধ ন only their কোমল form. The notation\n * fonts encode exactly this (no glyph exists for a কোমল প), so a flag the\n * sheet cannot draw must not move the pitch either.\n *\n * Zod-free on purpose: the viewer and the player import this directly (the\n * `spec/index.js` barrel would drag zod into their browser bundles — see\n * `taals.ts` and `grace.ts` for the same rule).\n */\nimport type { SvaraLetter } from './schema.js';\n\nexport function hasKomal(letter: SvaraLetter): boolean {\n return letter === 'r' || letter === 'g' || letter === 'd' || letter === 'n';\n}\n\nexport function hasTivra(letter: SvaraLetter): boolean {\n return letter === 'm';\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { TAALS, hasKomal, hasTivra, resolveLaySpeeds, rowLay, graceNotes } from './chunk-4HNT6OAT.js';
1
+ import { TAALS, hasKomal, hasTivra, resolveLaySpeeds, rowLay, sameTaal, graceNotes } from './chunk-MYVIQU4O.js';
2
2
 
3
3
  // src/player/pitch.ts
4
4
  var OCTAVE_OFFSET = {
@@ -115,6 +115,13 @@ function glideTargets(events, i) {
115
115
  }
116
116
  return out;
117
117
  }
118
+ var GRACE_STEP = 0.12;
119
+ function graceTouches(ev) {
120
+ return [
121
+ ...ev.kan.map((midi, g) => ({ midi, beat: ev.beat - GRACE_STEP * (ev.kan.length - g) })),
122
+ ...ev.resh.map((midi, g) => ({ midi, beat: ev.beat + GRACE_STEP * (g + 1) }))
123
+ ];
124
+ }
118
125
  var SAM_TICK = /^\d+[fF]$/;
119
126
  function buildSchedule(doc, opts = {}) {
120
127
  const tonicPc = parseTonic(opts.tonic ?? doc.meta.tonic);
@@ -126,18 +133,23 @@ function buildSchedule(doc, opts = {}) {
126
133
  let matra = 0;
127
134
  const speeds = resolveLaySpeeds(doc.meta.laySpeeds, opts.laySpeeds);
128
135
  const withLay = opts.lay !== false;
129
- const tempo = [{ beat: 0, endBeat: 0, scale: 1, lay: "drut" }];
136
+ const tempo = [{ beat: 0, endBeat: 0, scale: 1, lay: "drut", taal: doc.meta.taal }];
130
137
  let scale = 1;
138
+ let taal = doc.meta.taal;
131
139
  doc.parts.forEach((part, p) => {
132
140
  part.rows.forEach((row, r) => {
133
141
  const lay = withLay ? rowLay(row) : void 0;
134
- if (lay !== void 0 && speeds[lay] !== scale) {
135
- scale = speeds[lay];
142
+ const layChanged = lay !== void 0 && speeds[lay] !== scale;
143
+ const nextTaal = row.taal !== void 0 ? row.taal : taal;
144
+ if (layChanged || !sameTaal(nextTaal, taal)) {
145
+ if (layChanged) scale = speeds[lay];
146
+ taal = nextTaal;
136
147
  const last = tempo[tempo.length - 1];
137
- if (last.beat === cursor) tempo[tempo.length - 1] = { beat: cursor, endBeat: cursor, scale, lay };
148
+ const seg = { beat: cursor, endBeat: cursor, scale, lay: layChanged ? lay : last.lay, taal };
149
+ if (last.beat === cursor) tempo[tempo.length - 1] = seg;
138
150
  else {
139
151
  last.endBeat = cursor;
140
- tempo.push({ beat: cursor, endBeat: cursor, scale, lay });
152
+ tempo.push(seg);
141
153
  }
142
154
  }
143
155
  const glides = meendContinuations(row.notes);
@@ -154,12 +166,15 @@ function buildSchedule(doc, opts = {}) {
154
166
  const path = `${p}.${r}.${i}`;
155
167
  const written = note.durationMatra ?? 1;
156
168
  const dur = written / scale;
169
+ const kan = graceMidis(note.kan, tonicPc, baseOctave);
170
+ const resh = graceMidis(note.resh, tonicPc, baseOctave);
157
171
  if (note.svara !== void 0) {
158
172
  events.push({
159
173
  beat: cursor,
160
174
  durationBeats: dur,
161
175
  midi: svaraToMidi(note, tonicPc, baseOctave),
162
- kan: graceMidis(note.kan, tonicPc, baseOctave),
176
+ kan,
177
+ resh,
163
178
  path,
164
179
  kind: note.sustain === true ? "sustain" : "note",
165
180
  ...glides.has(i) && lastPitched >= 0 ? { glide: true } : {}
@@ -167,16 +182,16 @@ function buildSchedule(doc, opts = {}) {
167
182
  lastPitched = events.length - 1;
168
183
  } else if (note.sustain === true) {
169
184
  if (lastPitched >= 0) events[lastPitched].durationBeats += dur;
170
- events.push({ beat: cursor, durationBeats: dur, midi: null, kan: [], path, kind: "sustain" });
185
+ events.push({ beat: cursor, durationBeats: dur, midi: null, kan, resh, path, kind: "sustain" });
171
186
  } else {
172
- events.push({ beat: cursor, durationBeats: dur, midi: null, kan: [], path, kind: "rest" });
187
+ events.push({ beat: cursor, durationBeats: dur, midi: null, kan, resh, path, kind: "rest" });
173
188
  }
174
189
  cursor += dur;
175
190
  matra += written;
176
191
  });
177
192
  });
178
193
  });
179
- const numBeats = doc.meta.taal?.numBeats;
194
+ const numBeats = tempo[0].taal?.numBeats;
180
195
  const pickupMatra = samMatra !== void 0 && numBeats !== void 0 && numBeats > 0 ? (numBeats - samMatra % numBeats) % numBeats : 0;
181
196
  tempo[tempo.length - 1].endBeat = cursor;
182
197
  return { events, totalBeats: cursor, pickupBeats: pickupMatra / tempo[0].scale, tempo };
@@ -476,6 +491,6 @@ function createTanpura(Tone, base, stems = TANPURA_PCS) {
476
491
  return new Tone.Players({ urls, baseUrl: `${normalizeBase(base)}tanpura/` });
477
492
  }
478
493
 
479
- export { ARC_RAW_RE, DEFAULT_SOUNDS_BASE, DRUM_KIT_BASE, 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, createBuffers, createDrumKit, createMetronome, createSampler, createTanpura, drumPattern, glideTargets, instrumentSampleUrl, isArcRaw, meendContinuations, midiToSampleName, nearestSampleKey, nearestTablaBpm, normalizeBase, parseTonic, probeUrl, quantizeLoopEnd, sCurve, sampleFileName, santoorUrls, subsetUrls, svaraSemitone, svaraToMidi, tablaBpms, tablaShiftSemitones, tablaUrl, triggerGlide };
480
- //# sourceMappingURL=chunk-4IVUPMRK.js.map
481
- //# sourceMappingURL=chunk-4IVUPMRK.js.map
494
+ 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, createBuffers, createDrumKit, createMetronome, createSampler, createTanpura, drumPattern, glideTargets, graceTouches, instrumentSampleUrl, isArcRaw, meendContinuations, midiToSampleName, nearestSampleKey, nearestTablaBpm, normalizeBase, parseTonic, probeUrl, quantizeLoopEnd, sCurve, sampleFileName, santoorUrls, subsetUrls, svaraSemitone, svaraToMidi, tablaBpms, tablaShiftSemitones, tablaUrl, triggerGlide };
495
+ //# sourceMappingURL=chunk-SEN266BY.js.map
496
+ //# sourceMappingURL=chunk-SEN266BY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/player/pitch.ts","../src/player/schedule.ts","../src/player/instruments.ts"],"names":[],"mappings":";;;AAaO,IAAM,aAAA,GAAwC;AAAA,EACnD,YAAA,EAAc,EAAA;AAAA,EACd,MAAA,EAAQ,EAAA;AAAA,EACR,OAAA,EAAS,CAAA;AAAA,EACT,IAAA,EAAM,CAAA;AAAA,EACN,UAAA,EAAY;AACd;AAOO,IAAM,OAAA,GAAuC;AAAA,EAClD,CAAA,EAAG,CAAA;AAAA,EACH,CAAA,EAAG,CAAA;AAAA,EACH,CAAA,EAAG,CAAA;AAAA,EACH,CAAA,EAAG,CAAA;AAAA,EACH,CAAA,EAAG,CAAA;AAAA,EACH,CAAA,EAAG,CAAA;AAAA,EACH,CAAA,EAAG;AACL;AAGA,IAAM,WAAA,GAAc,CAAC,GAAA,EAAK,IAAA,EAAM,GAAA,EAAK,IAAA,EAAM,GAAA,EAAK,GAAA,EAAK,IAAA,EAAM,GAAA,EAAK,IAAA,EAAM,GAAA,EAAK,MAAM,GAAG,CAAA;AAGpF,IAAM,WAAA,GAAsC;AAAA,EAC1C,CAAA,EAAG,CAAA;AAAA,EACH,IAAA,EAAM,CAAA;AAAA,EACN,EAAA,EAAI,CAAA;AAAA,EACJ,CAAA,EAAG,CAAA;AAAA,EACH,IAAA,EAAM,CAAA;AAAA,EACN,EAAA,EAAI,CAAA;AAAA,EACJ,CAAA,EAAG,CAAA;AAAA,EACH,IAAA,EAAM,CAAA;AAAA,EACN,EAAA,EAAI,CAAA;AAAA,EACJ,CAAA,EAAG,CAAA;AAAA,EACH,IAAA,EAAM,CAAA;AAAA,EACN,EAAA,EAAI,CAAA;AAAA,EACJ,CAAA,EAAG,CAAA;AAAA,EACH,IAAA,EAAM,CAAA;AAAA,EACN,EAAA,EAAI,CAAA;AAAA,EACJ,CAAA,EAAG,CAAA;AAAA,EACH,IAAA,EAAM,EAAA;AAAA,EACN,EAAA,EAAI,EAAA;AAAA,EACJ,CAAA,EAAG,EAAA;AAAA,EACH,IAAA,EAAM,CAAA;AAAA,EACN,EAAA,EAAI;AACN,CAAA;AAOO,SAAS,WAAW,KAAA,EAAmC;AAC5D,EAAA,IAAI,CAAC,OAAO,OAAO,CAAA;AACnB,EAAA,MAAM,GAAA,GAAM,MAAM,IAAA,EAAK,CAAE,aAAY,CAAE,OAAA,CAAQ,MAAM,GAAG,CAAA;AACxD,EAAA,OAAO,WAAA,CAAY,GAAG,CAAA,IAAK,CAAA;AAC7B;AAUO,SAAS,cAAc,IAAA,EAA2B;AACvD,EAAA,IAAI,IAAA,CAAK,KAAA,KAAU,MAAA,EAAW,OAAO,IAAA;AACrC,EAAA,MAAM,GAAG,MAAM,CAAA,GAAI,IAAA,CAAK,KAAA;AACxB,EAAA,IAAI,IAAA,GAAO,QAAQ,MAAM,CAAA;AAIzB,EAAA,IAAA,CAAK,IAAA,CAAK,UAAU,IAAA,IAAQ,IAAA,CAAK,cAAc,MAAA,KAAc,QAAA,CAAS,MAAM,CAAA,EAAG,IAAA,IAAQ,CAAA;AACvF,EAAA,IAAI,KAAK,KAAA,KAAU,IAAA,IAAQ,QAAA,CAAS,MAAM,GAAG,IAAA,IAAQ,CAAA;AACrD,EAAA,OAAO,IAAA;AACT;AAOO,SAAS,WAAA,CAAY,IAAA,EAAY,OAAA,EAAiB,UAAA,GAAa,CAAA,EAAkB;AACtF,EAAA,MAAM,IAAA,GAAO,cAAc,IAAI,CAAA;AAC/B,EAAA,IAAI,IAAA,KAAS,IAAA,IAAQ,IAAA,CAAK,KAAA,KAAU,QAAW,OAAO,IAAA;AACtD,EAAA,MAAM,CAAC,MAAM,CAAA,GAAI,IAAA,CAAK,KAAA;AACtB,EAAA,OAAA,CAAQ,aAAa,CAAA,GAAI,aAAA,CAAc,MAAM,CAAA,IAAK,KAAK,OAAA,GAAU,IAAA;AACnE;AAKO,SAAS,UAAA,CACd,KAAA,EACA,OAAA,EACA,UAAA,GAAa,CAAA,EACH;AACV,EAAA,OAAO,WAAW,KAAK,CAAA,CACpB,GAAA,CAAI,CAAC,MAAM,WAAA,CAAY,CAAA,EAAG,OAAA,EAAS,UAAU,CAAC,CAAA,CAC9C,MAAA,CAAO,CAAC,CAAA,KAAmB,MAAM,IAAI,CAAA;AAC1C;AAGO,SAAS,iBAAiB,IAAA,EAAsB;AACrD,EAAA,MAAM,EAAA,GAAA,CAAO,IAAA,GAAO,EAAA,GAAM,EAAA,IAAM,EAAA;AAChC,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,IAAA,GAAO,EAAE,CAAA,GAAI,CAAA;AACvC,EAAA,OAAO,CAAA,EAAG,WAAA,CAAY,EAAE,CAAC,GAAG,MAAM,CAAA,CAAA;AACpC;AAGO,SAAS,eAAe,IAAA,EAAsB;AACnD,EAAA,OAAO,iBAAiB,IAAI,CAAA,CAAE,aAAY,CAAE,OAAA,CAAQ,KAAK,GAAG,CAAA;AAC9D;AAaO,SAAS,MAAA,CAAO,CAAA,EAAW,SAAA,GAAY,CAAA,EAAW;AACvD,EAAA,MAAM,CAAA,GAAI,KAAK,GAAA,CAAI,IAAA,CAAK,IAAI,CAAA,EAAG,CAAC,GAAG,CAAC,CAAA;AACpC,EAAA,MAAM,QAAA,GAAW,CAAC,CAAA,KAAc,CAAA,IAAK,CAAA,GAAI,KAAK,GAAA,CAAI,CAAC,SAAA,IAAa,CAAA,GAAI,GAAA,CAAI,CAAA,CAAA;AACxE,EAAA,MAAM,EAAA,GAAK,SAAS,CAAC,CAAA;AACrB,EAAA,OAAA,CAAQ,SAAS,CAAC,CAAA,GAAI,EAAA,KAAO,QAAA,CAAS,CAAC,CAAA,GAAI,EAAA,CAAA;AAC7C;;;AC9CA,IAAM,cAAA,GAAiB,kBAAA;AAKvB,IAAM,WAAW,CAAC,CAAA,KAChB,EAAE,KAAA,KAAU,MAAA,IACZ,EAAE,IAAA,KAAS,IAAA,IACX,EAAE,OAAA,KAAY,IAAA,IACd,EAAE,CAAA,CAAE,GAAA,KAAQ,UAAa,cAAA,CAAe,IAAA,CAAK,EAAE,GAAG,CAAA,CAAA;AAQ7C,IAAM,UAAA,GAAa;AACnB,IAAM,WAAW,CAAC,GAAA,KACvB,QAAQ,MAAA,IAAa,UAAA,CAAW,KAAK,GAAG;AAqBnC,SAAS,mBAAmB,KAAA,EAA4B;AAC7D,EAAA,MAAM,GAAA,uBAAU,GAAA,EAAY;AAE5B,EAAA,IAAI,OAAA;AACJ,EAAA,KAAA,CAAM,OAAA,CAAQ,CAAC,IAAA,EAAM,CAAA,KAAM;AACzB,IAAA,IAAI,KAAA,GAAQ,KAAK,KAAA,KAAU,OAAA,GAAU,MAAM,IAAA,CAAK,KAAA,KAAU,QAAQ,GAAA,GAAM,EAAA;AACxE,IAAA,KAAA,MAAW,CAAA,IAAK,IAAA,CAAK,KAAA,IAAS,EAAC,EAAG,IAAI,QAAA,CAAS,CAAA,CAAE,GAAG,CAAA,EAAG,KAAA,IAAS,CAAA,CAAE,GAAA;AAClE,IAAA,IAAI,MAAA,GAAS,KAAA;AACb,IAAA,KAAA,MAAW,MAAM,KAAA,EAAO;AACtB,MAAA,IAAI,EAAA,KAAO,GAAA,IAAO,EAAA,KAAO,GAAA,EAAK;AAE5B,QAAA,IAAI,OAAA,KAAY,MAAA,EAAW,KAAA,MAAW,CAAA,IAAK,CAAC,GAAG,OAAA,EAAS,CAAC,CAAA,EAAG,GAAA,CAAI,GAAA,CAAI,CAAC,CAAA;AACrE,QAAA,OAAA,GAAU,MAAA;AACV,QAAA,MAAA,GAAS,IAAA;AAAA,MACX,CAAA,MAAA,IAAW,EAAA,KAAO,GAAA,IAAO,EAAA,KAAO,GAAA,EAAK;AACnC,QAAA,OAAA,GAAU,EAAC;AACX,QAAA,MAAA,GAAS,IAAA;AAAA,MACX;AAAA,IACF;AACA,IAAA,IAAI,CAAC,MAAA,IAAU,OAAA,KAAY,MAAA,EAAW,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,EACtD,CAAC,CAAA;AAID,EAAA,OAAO,GAAA;AACT;AAQO,SAAS,YAAA,CAAa,QAAqB,CAAA,EAAwB;AACxE,EAAA,MAAM,MAAmB,EAAC;AAC1B,EAAA,KAAA,IAAS,IAAI,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,MAAA,CAAO,QAAQ,CAAA,EAAA,EAAK;AAC1C,IAAA,MAAM,EAAA,GAAK,OAAO,CAAC,CAAA;AACnB,IAAA,IAAI,EAAA,CAAG,SAAS,MAAA,EAAQ;AACxB,IAAA,IAAI,EAAA,CAAG,SAAS,IAAA,EAAM;AACtB,IAAA,IAAI,EAAA,CAAG,UAAU,IAAA,EAAM;AACvB,IAAA,GAAA,CAAI,KAAK,EAAE,CAAA;AAAA,EACb;AACA,EAAA,OAAO,GAAA;AACT;AAGO,IAAM,UAAA,GAAa;AASnB,SAAS,aAAa,EAAA,EAAiD;AAG5E,EAAA,OAAO;AAAA,IACL,GAAG,EAAA,CAAG,GAAA,CAAI,GAAA,CAAI,CAAC,MAAM,CAAA,MAAO,EAAE,IAAA,EAAM,IAAA,EAAM,GAAG,IAAA,GAAO,UAAA,IAAc,GAAG,GAAA,CAAI,MAAA,GAAS,IAAG,CAAE,CAAA;AAAA,IACvF,GAAG,EAAA,CAAG,IAAA,CAAK,GAAA,CAAI,CAAC,IAAA,EAAM,CAAA,MAAO,EAAE,IAAA,EAAM,MAAM,EAAA,CAAG,IAAA,GAAO,UAAA,IAAc,CAAA,GAAI,IAAG,CAAE;AAAA,GAC9E;AACF;AAMA,IAAM,QAAA,GAAW,WAAA;AAmBV,SAAS,aAAA,CAAc,GAAA,EAAgB,IAAA,GAAwB,EAAC,EAAa;AAClF,EAAA,MAAM,UAAU,UAAA,CAAW,IAAA,CAAK,KAAA,IAAS,GAAA,CAAI,KAAK,KAAK,CAAA;AACvD,EAAA,MAAM,UAAA,GAAa,KAAK,UAAA,IAAc,CAAA;AACtC,EAAA,MAAM,SAAsB,EAAC;AAC7B,EAAA,IAAI,MAAA,GAAS,CAAA;AAEb,EAAA,IAAI,WAAA,GAAc,EAAA;AAGlB,EAAA,IAAI,QAAA;AACJ,EAAA,IAAI,KAAA,GAAQ,CAAA;AAKZ,EAAA,MAAM,SAAS,gBAAA,CAAiB,GAAA,CAAI,IAAA,CAAK,SAAA,EAAW,KAAK,SAAS,CAAA;AAClE,EAAA,MAAM,OAAA,GAAU,KAAK,GAAA,KAAQ,KAAA;AAC7B,EAAA,MAAM,KAAA,GAAwB,CAAC,EAAE,IAAA,EAAM,GAAG,OAAA,EAAS,CAAA,EAAG,KAAA,EAAO,CAAA,EAAG,KAAK,MAAA,EAAQ,IAAA,EAAM,GAAA,CAAI,IAAA,CAAK,MAAM,CAAA;AAClG,EAAA,IAAI,KAAA,GAAQ,CAAA;AAGZ,EAAA,IAAI,IAAA,GAAgC,IAAI,IAAA,CAAK,IAAA;AAE7C,EAAA,GAAA,CAAI,KAAA,CAAM,OAAA,CAAQ,CAAC,IAAA,EAAM,CAAA,KAAM;AAC7B,IAAA,IAAA,CAAK,IAAA,CAAK,OAAA,CAAQ,CAAC,GAAA,EAAK,CAAA,KAAM;AAC5B,MAAA,MAAM,GAAA,GAAM,OAAA,GAAU,MAAA,CAAO,GAAG,CAAA,GAAI,MAAA;AACpC,MAAA,MAAM,UAAA,GAAa,GAAA,KAAQ,MAAA,IAAa,MAAA,CAAO,GAAG,CAAA,KAAM,KAAA;AACxD,MAAA,MAAM,QAAA,GAAW,GAAA,CAAI,IAAA,KAAS,MAAA,GAAY,IAAI,IAAA,GAAO,IAAA;AACrD,MAAA,IAAI,UAAA,IAAc,CAAC,QAAA,CAAS,QAAA,EAAU,IAAI,CAAA,EAAG;AAC3C,QAAA,IAAI,UAAA,EAAY,KAAA,GAAQ,MAAA,CAAO,GAAI,CAAA;AACnC,QAAA,IAAA,GAAO,QAAA;AACP,QAAA,MAAM,IAAA,GAAO,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,CAAC,CAAA;AACnC,QAAA,MAAM,GAAA,GAAoB,EAAE,IAAA,EAAM,MAAA,EAAQ,OAAA,EAAS,MAAA,EAAQ,KAAA,EAAO,GAAA,EAAK,UAAA,GAAa,GAAA,GAAO,IAAA,CAAK,GAAA,EAAK,IAAA,EAAK;AAE1G,QAAA,IAAI,KAAK,IAAA,KAAS,MAAA,QAAc,KAAA,CAAM,MAAA,GAAS,CAAC,CAAA,GAAI,GAAA;AAAA,aAC/C;AACH,UAAA,IAAA,CAAK,OAAA,GAAU,MAAA;AACf,UAAA,KAAA,CAAM,KAAK,GAAG,CAAA;AAAA,QAChB;AAAA,MACF;AACA,MAAA,MAAM,MAAA,GAAS,kBAAA,CAAmB,GAAA,CAAI,KAAK,CAAA;AAC3C,MAAA,GAAA,CAAI,KAAA,CAAM,OAAA,CAAQ,CAAC,IAAA,EAAM,CAAA,KAAM;AAG7B,QAAA,IAAI,QAAA,KAAa,MAAA,IAAa,IAAA,CAAK,KAAA,KAAU,MAAA,EAAW;AACtD,UAAA,KAAA,MAAW,CAAA,IAAK,KAAK,KAAA,EAAO;AAC1B,YAAA,IAAI,EAAE,GAAA,KAAQ,MAAA,IAAa,SAAS,IAAA,CAAK,CAAA,CAAE,GAAG,CAAA,EAAG;AAC/C,cAAA,QAAA,GAAW,KAAA;AACX,cAAA;AAAA,YACF;AAAA,UACF;AAAA,QACF;AACA,QAAA,IAAI,IAAA,CAAK,GAAA,KAAQ,MAAA,IAAa,QAAA,CAAS,IAAI,CAAA,EAAG;AAC9C,QAAA,MAAM,OAAO,CAAA,EAAG,CAAC,CAAA,CAAA,EAAI,CAAC,IAAI,CAAC,CAAA,CAAA;AAC3B,QAAA,MAAM,OAAA,GAAU,KAAK,aAAA,IAAiB,CAAA;AACtC,QAAA,MAAM,MAAM,OAAA,GAAU,KAAA;AAEtB,QAAA,MAAM,GAAA,GAAM,UAAA,CAAW,IAAA,CAAK,GAAA,EAAK,SAAS,UAAU,CAAA;AACpD,QAAA,MAAM,IAAA,GAAO,UAAA,CAAW,IAAA,CAAK,IAAA,EAAM,SAAS,UAAU,CAAA;AAEtD,QAAA,IAAI,IAAA,CAAK,UAAU,MAAA,EAAW;AAE5B,UAAA,MAAA,CAAO,IAAA,CAAK;AAAA,YACV,IAAA,EAAM,MAAA;AAAA,YACN,aAAA,EAAe,GAAA;AAAA,YACf,IAAA,EAAM,WAAA,CAAY,IAAA,EAAM,OAAA,EAAS,UAAU,CAAA;AAAA,YAC3C,GAAA;AAAA,YACA,IAAA;AAAA,YACA,IAAA;AAAA,YACA,IAAA,EAAM,IAAA,CAAK,OAAA,KAAY,IAAA,GAAO,SAAA,GAAY,MAAA;AAAA,YAC1C,GAAI,MAAA,CAAO,GAAA,CAAI,CAAC,CAAA,IAAK,WAAA,IAAe,CAAA,GAAI,EAAE,KAAA,EAAO,IAAA,EAAc,GAAI;AAAC,WACrE,CAAA;AACD,UAAA,WAAA,GAAc,OAAO,MAAA,GAAS,CAAA;AAAA,QAChC,CAAA,MAAA,IAAW,IAAA,CAAK,OAAA,KAAY,IAAA,EAAM;AAEhC,UAAA,IAAI,WAAA,IAAe,CAAA,EAAG,MAAA,CAAO,WAAW,EAAG,aAAA,IAAiB,GAAA;AAC5D,UAAA,MAAA,CAAO,IAAA,CAAK,EAAE,IAAA,EAAM,MAAA,EAAQ,aAAA,EAAe,GAAA,EAAK,IAAA,EAAM,IAAA,EAAM,GAAA,EAAK,IAAA,EAAM,IAAA,EAAM,IAAA,EAAM,WAAW,CAAA;AAAA,QAChG,CAAA,MAAO;AAEL,UAAA,MAAA,CAAO,IAAA,CAAK,EAAE,IAAA,EAAM,MAAA,EAAQ,aAAA,EAAe,GAAA,EAAK,IAAA,EAAM,IAAA,EAAM,GAAA,EAAK,IAAA,EAAM,IAAA,EAAM,IAAA,EAAM,QAAQ,CAAA;AAAA,QAC7F;AACA,QAAA,MAAA,IAAU,GAAA;AACV,QAAA,KAAA,IAAS,OAAA;AAAA,MACX,CAAC,CAAA;AAAA,IACH,CAAC,CAAA;AAAA,EACH,CAAC,CAAA;AAMD,EAAA,MAAM,QAAA,GAAW,KAAA,CAAM,CAAC,CAAA,CAAG,IAAA,EAAM,QAAA;AACjC,EAAA,MAAM,WAAA,GACJ,QAAA,KAAa,MAAA,IAAa,QAAA,KAAa,MAAA,IAAa,WAAW,CAAA,GAAA,CAC1D,QAAA,GAAY,QAAA,GAAW,QAAA,IAAa,QAAA,GACrC,CAAA;AACN,EAAA,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,CAAC,CAAA,CAAG,OAAA,GAAU,MAAA;AAGnC,EAAA,OAAO,EAAE,MAAA,EAAQ,UAAA,EAAY,MAAA,EAAQ,WAAA,EAAa,cAAc,KAAA,CAAM,CAAC,CAAA,CAAG,KAAA,EAAO,KAAA,EAAM;AACzF;;;AClRO,IAAM,mBAAA,GAAsB;AAE5B,IAAM,eAAA,GAAkB;AAExB,IAAM,uBAAA,GAA0B;AAGvC,SAAS,OAAO,IAAA,EAAiD;AAC/D,EAAA,MAAM,IAA4B,EAAC;AACnC,EAAA,KAAA,MAAW,CAAA,IAAK,IAAA,EAAM,CAAA,CAAE,CAAC,CAAA,GAAI,GAAG,CAAA,CAAE,OAAA,CAAQ,GAAA,EAAK,GAAG,CAAC,CAAA,IAAA,CAAA;AACnD,EAAA,OAAO,CAAA;AACT;AAEA,IAAM,UAAA,GAAa;AAAA,EACjB,IAAA;AAAA,EAAM,IAAA;AAAA,EAAM,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,IAAA;AAAA,EAAM,IAAA;AAAA,EAAM,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,IAAA;AAAA,EAAM,IAAA;AAAA,EAAM,KAAA;AAAA,EAAO,KAAA;AAAA,EACvE,IAAA;AAAA,EAAM,IAAA;AAAA,EAAM,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,IAAA;AAAA,EAAM,IAAA;AAAA,EAAM,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,IAAA;AAAA,EAAM,IAAA;AAAA,EAAM,KAAA;AAAA,EAAO,KAAA;AAAA,EACvE,IAAA;AAAA,EAAM,IAAA;AAAA,EAAM,KAAA;AAAA,EAAO,KAAA;AAAA,EAAO,IAAA;AAAA,EAAM;AAClC,CAAA;AAEA,IAAM,cAAA,GAAiB;AAAA,EACrB,IAAA;AAAA,EAAM,KAAA;AAAA,EAAO,IAAA;AAAA,EAAM,KAAA;AAAA,EAAO,IAAA;AAAA,EAAM,IAAA;AAAA,EAAM,KAAA;AAAA,EAAO,IAAA;AAAA,EAAM,KAAA;AAAA,EAAO,IAAA;AAAA,EAAM,KAAA;AAAA,EAChE,IAAA;AAAA,EAAM,KAAA;AAAA,EAAO,IAAA;AAAA,EAAM,KAAA;AAAA,EAAO,IAAA;AAAA,EAAM,IAAA;AAAA,EAAM,KAAA;AAAA,EAAO,IAAA;AAAA,EAAM,KAAA;AAAA,EAAO,IAAA;AAAA,EAAM,KAAA;AAAA,EAChE,IAAA;AAAA,EAAM,KAAA;AAAA,EAAO,IAAA;AAAA,EAAM,KAAA;AAAA,EAAO,IAAA;AAAA,EAAM,IAAA;AAAA,EAAM,KAAA;AAAA,EAAO,IAAA;AAAA,EAAM,KAAA;AAAA,EAAO,IAAA;AAAA,EAAM,KAAA;AAAA,EAChE,IAAA;AAAA,EAAM,KAAA;AAAA,EAAO;AACf,CAAA;AACA,IAAM,WAAA,GAAc,CAAC,IAAA,EAAM,IAAA,EAAM,MAAM,IAAA,EAAM,IAAA,EAAM,IAAA,EAAM,IAAA,EAAM,MAAM,IAAA,EAAM,IAAA,EAAM,IAAA,EAAM,IAAA,EAAM,MAAM,IAAI,CAAA;AACvG,IAAM,UAAA,GAAa,CAAC,IAAA,EAAM,IAAA,EAAM,IAAA,EAAM,IAAA,EAAM,IAAA,EAAM,IAAA,EAAM,IAAA,EAAM,IAAA,EAAM,IAAA,EAAM,IAAI,CAAA;AAKvE,IAAM,WAAA,GAA6C;AAAA,EACxD,KAAA,EAAO,EAAE,KAAA,EAAO,aAAA,EAAe,IAAA,EAAM,MAAA,CAAO,UAAU,CAAA,EAAG,OAAA,EAAS,eAAA,EAAiB,OAAA,EAAS,CAAA,EAAE;AAAA,EAC9F,OAAA,EAAS,EAAE,KAAA,EAAO,SAAA,EAAW,IAAA,EAAM,WAAA,EAAY,EAAG,OAAA,EAAS,UAAA,EAAY,KAAA,EAAO,IAAA,EAAM,OAAA,EAAS,GAAA,EAAI;AAAA,EACjG,SAAA,EAAW,EAAE,KAAA,EAAO,WAAA,EAAa,IAAA,EAAM,MAAA,CAAO,cAAc,CAAA,EAAG,OAAA,EAAS,CAAA,EAAG,uBAAuB,CAAA,UAAA,CAAA,EAAc,SAAS,GAAA,EAAI;AAAA,EAC7H,MAAA,EAAQ,EAAE,KAAA,EAAO,QAAA,EAAU,IAAA,EAAM,MAAA,CAAO,WAAW,CAAA,EAAG,OAAA,EAAS,CAAA,EAAG,uBAAuB,CAAA,OAAA,CAAA,EAAW,SAAS,CAAA,EAAE;AAAA,EAC/G,KAAA,EAAO,EAAE,KAAA,EAAO,OAAA,EAAS,IAAA,EAAM,MAAA,CAAO,UAAU,CAAA,EAAG,OAAA,EAAS,CAAA,EAAG,uBAAuB,CAAA,MAAA,CAAA,EAAU,SAAS,CAAA;AAC3G;AAGO,IAAM,gBAAqC,IAAI,GAAA;AAAA,EACpD,MAAA,CAAO,KAAK,KAAK;AACnB;AAGO,IAAM,UAAA,GAAgC,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,EAAA,EAAG,EAAG,CAAC,CAAA,EAAG,CAAA,KAAM,EAAA,GAAK,CAAA,GAAI,EAAE;AActF,IAAM,gBAAA,GAGT;AAAA;AAAA,EAEF,QAAA,EAAU,EAAE,UAAA,EAAY,CAAC,KAAK,CAAA,EAAG,QAAA,EAAU,CAAC,GAAG,CAAA,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMjD,QAAQ,EAAE,UAAA,EAAY,CAAC,EAAA,EAAI,SAAS,CAAA;AACtC;AAGO,SAAS,UAAU,MAAA,EAAmC;AAC3D,EAAA,MAAM,IAAA,GAAO,gBAAA,CAAiB,MAAM,CAAA,EAAG,QAAA;AACvC,EAAA,OAAO,IAAA,IAAQ,IAAA,CAAK,MAAA,GAAS,UAAA,CAAW,MAAA,CAAO,CAAC,CAAA,KAAM,CAAC,IAAA,CAAK,QAAA,CAAS,CAAC,CAAC,CAAA,GAAI,UAAA;AAC7E;AAIA,SAAS,QAAA,CAAS,QAAgB,SAAA,EAA4B;AAC5D,EAAA,MAAM,IAAI,SAAA,IAAa,gBAAA,CAAiB,MAAM,CAAA,EAAG,WAAW,CAAC,CAAA;AAC7D,EAAA,OAAO,CAAA,GAAI,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,CAAC,CAAA,CAAA,GAAK,MAAA;AAChC;AAGO,SAAS,cAAc,GAAA,EAAqB;AACjD,EAAA,OAAO,IAAI,QAAA,CAAS,GAAG,CAAA,GAAI,GAAA,GAAM,GAAG,GAAG,CAAA,CAAA,CAAA;AACzC;AAGO,SAAS,WAAA,GAAsC;AACpD,EAAA,MAAM,OAA+B,EAAC;AACtC,EAAA,MAAM,GAAA,GAAM,CAAC,IAAA,KAAiB;AAC5B,IAAA,MAAM,IAAA,GAAO,eAAe,IAAI,CAAA;AAChC,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,GAAA,EAAK,GAAG,CAAA,CAAE,OAAA,CAAQ,MAAA,EAAQ,CAAC,CAAA,KAAM,CAAA,CAAE,WAAA,EAAa,CAAA;AAC1E,IAAA,IAAA,CAAK,IAAI,CAAA,GAAI,CAAA,EAAG,IAAI,CAAA,IAAA,CAAA;AAAA,EACtB,CAAA;AACA,EAAA,KAAA,IAAS,OAAO,EAAA,EAAI,IAAA,IAAQ,EAAA,EAAI,IAAA,EAAA,MAAY,IAAI,CAAA;AAChD,EAAA,GAAA,CAAI,EAAE,CAAA;AACN,EAAA,OAAO,IAAA;AACT;AAEA,IAAM,OAAA,GAAkC,EAAE,CAAA,EAAG,CAAA,EAAG,GAAG,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,GAAG,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,GAAG,EAAA,EAAG;AAG7E,SAAS,WAAW,IAAA,EAAsB;AAC/C,EAAA,MAAM,CAAA,GAAI,2BAAA,CAA4B,IAAA,CAAK,IAAA,CAAK,MAAM,CAAA;AACtD,EAAA,IAAI,CAAC,GAAG,OAAO,GAAA;AACf,EAAA,MAAM,OAAO,OAAA,CAAQ,CAAA,CAAE,CAAC,CAAA,CAAG,aAAa,CAAA;AACxC,EAAA,MAAM,GAAA,GAAM,CAAA,CAAE,CAAC,CAAA,KAAM,GAAA,GAAM,KAAK,CAAA,CAAE,CAAC,CAAA,KAAM,EAAA,GAAK,CAAA,GAAI,CAAA;AAClD,EAAA,OAAA,CAAQ,OAAO,CAAA,CAAE,CAAC,CAAC,CAAA,GAAI,CAAA,IAAK,KAAK,IAAA,GAAO,GAAA;AAC1C;AAUO,SAAS,UAAA,CACd,IAAA,EACA,EAAA,EACA,EAAA,EACwB;AACxB,EAAA,MAAM,UAAU,MAAA,CAAO,OAAA,CAAQ,IAAI,CAAA,CAChC,IAAI,CAAC,CAAC,CAAA,EAAG,CAAC,OAAO,EAAE,CAAA,EAAG,CAAA,EAAG,IAAA,EAAM,WAAW,CAAC,CAAA,EAAE,CAAE,CAAA,CAC/C,OAAO,CAAC,CAAA,KAAM,MAAA,CAAO,QAAA,CAAS,EAAE,IAAI,CAAC,CAAA,CACrC,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,CAAE,IAAA,GAAO,EAAE,IAAI,CAAA;AACjC,EAAA,IAAI,QAAQ,MAAA,KAAW,CAAA,EAAG,OAAO,EAAE,GAAG,IAAA,EAAK;AAE3C,EAAA,MAAM,IAAA,uBAAW,GAAA,EAAY;AAC7B,EAAA,KAAA,MAAW,CAAA,IAAK,OAAA,EAAS,IAAI,CAAA,CAAE,IAAA,IAAQ,EAAA,IAAM,CAAA,CAAE,IAAA,IAAQ,EAAA,EAAI,IAAA,CAAK,GAAA,CAAI,CAAA,CAAE,CAAC,CAAA;AAEvE,EAAA,IAAI,KAAA;AACJ,EAAA,IAAI,IAAA;AACJ,EAAA,KAAA,MAAW,KAAK,OAAA,EAAS,IAAI,EAAE,IAAA,IAAQ,EAAA,UAAY,CAAA,CAAE,CAAA;AACrD,EAAA,KAAA,MAAW,CAAA,IAAK,OAAA,EAAS,IAAI,CAAA,CAAE,QAAQ,EAAA,EAAI;AAAE,IAAA,IAAA,GAAO,CAAA,CAAE,CAAA;AAAG,IAAA;AAAA,EAAO;AAChE,EAAA,IAAI,KAAA,EAAO,IAAA,CAAK,GAAA,CAAI,KAAK,CAAA;AACzB,EAAA,IAAI,IAAA,EAAM,IAAA,CAAK,GAAA,CAAI,IAAI,CAAA;AACvB,EAAA,IAAI,IAAA,CAAK,SAAS,CAAA,EAAG;AAEnB,IAAA,MAAM,GAAA,GAAA,CAAO,KAAK,EAAA,IAAM,CAAA;AACxB,IAAA,MAAM,UAAU,OAAA,CAAQ,MAAA,CAAO,CAAC,CAAA,EAAG,CAAA,KAAO,KAAK,GAAA,CAAI,CAAA,CAAE,OAAO,GAAG,CAAA,GAAI,KAAK,GAAA,CAAI,CAAA,CAAE,OAAO,GAAG,CAAA,GAAI,IAAI,CAAE,CAAA;AAClG,IAAA,IAAA,CAAK,GAAA,CAAI,QAAQ,CAAC,CAAA;AAAA,EACpB;AAEA,EAAA,MAAM,MAA8B,EAAC;AACrC,EAAA,KAAA,MAAW,CAAA,IAAK,OAAA,EAAS,IAAI,IAAA,CAAK,GAAA,CAAI,CAAA,CAAE,CAAC,CAAA,EAAG,GAAA,CAAI,CAAA,CAAE,CAAC,CAAA,GAAI,CAAA,CAAE,CAAA;AACzD,EAAA,OAAO,GAAA;AACT;AAKO,SAAS,mBAAA,CAAoB,KAAoB,aAAA,EAA4C;AAClG,EAAA,IAAI,GAAA,CAAI,KAAA,IAAS,aAAA,KAAkB,MAAA,EAAW,OAAO,MAAA;AACrD,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,KAAA,GAAQ,CAAA,EAAG,aAAA,CAAc,aAAA,IAAiB,EAAE,CAAC,CAAA,EAAG,GAAA,CAAI,OAAO,CAAA,CAAA,GAAK,GAAA,CAAI,OAAA;AACrF,EAAA,MAAM,OAAO,MAAA,CAAO,MAAA,CAAO,GAAA,CAAI,IAAI,EAAE,CAAC,CAAA;AACtC,EAAA,OAAO,IAAA,KAAS,SAAY,MAAA,GAAY,CAAA,EAAG,cAAc,IAAI,CAAC,GAAG,IAAI,CAAA,CAAA;AACvE;AASA,eAAsB,QAAA,CAAS,GAAA,EAAa,SAAA,GAAY,GAAA,EAAwB;AAC9E,EAAA,IAAI,OAAO,KAAA,KAAU,WAAA,EAAa,OAAO,KAAA;AACzC,EAAA,MAAM,OAAO,OAAO,eAAA,KAAoB,WAAA,GAAc,IAAI,iBAAgB,GAAI,MAAA;AAC9E,EAAA,MAAM,KAAA,GAAQ,OAAO,UAAA,CAAW,MAAM,KAAK,KAAA,EAAM,EAAG,SAAS,CAAA,GAAI,MAAA;AACjE,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,MAC3B,MAAA,EAAQ,KAAA;AAAA,MACR,OAAA,EAAS,EAAE,KAAA,EAAO,WAAA,EAAY;AAAA,MAC9B,KAAA,EAAO,UAAA;AAAA,MACP,QAAQ,IAAA,EAAM;AAAA,KACf,CAAA;AACD,IAAA,OAAO,GAAA,CAAI,EAAA;AAAA,EACb,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,KAAA;AAAA,EACT,CAAA,SAAE;AACA,IAAA,IAAI,KAAA,KAAU,MAAA,EAAW,YAAA,CAAa,KAAK,CAAA;AAAA,EAC7C;AACF;AAYO,SAAS,eAAA,CAAgB,aAAqB,GAAA,EAAqB;AACxE,EAAA,IAAI,GAAA,IAAO,GAAG,OAAO,WAAA;AACrB,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,WAAA,GAAc,GAAG,CAAA;AAC1C,EAAA,IAAI,KAAA,GAAQ,GAAG,OAAO,WAAA;AACtB,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,KAAA,GAAQ,GAAA,EAAK,WAAW,CAAA;AAC1C;AAIO,SAAS,eAAA,CAAgB,GAAA,EAAa,IAAA,GAA0B,UAAA,EAAoB;AACzF,EAAA,OAAO,KAAK,MAAA,CAAO,CAAC,MAAM,CAAA,KAAO,IAAA,CAAK,IAAI,CAAA,GAAI,GAAG,IAAI,IAAA,CAAK,GAAA,CAAI,OAAO,GAAG,CAAA,GAAI,IAAI,IAAA,EAAO,IAAA,CAAK,CAAC,CAAE,CAAA;AACjG;AAIO,SAAS,QAAA,CAAS,IAAA,EAAc,MAAA,EAAgB,GAAA,EAAa,SAAA,EAA4B;AAC9F,EAAA,MAAM,GAAA,GAAM,QAAA,CAAS,MAAA,EAAQ,SAAS,CAAA;AACtC,EAAA,OAAO,CAAA,EAAG,cAAc,IAAI,CAAC,SAAS,GAAG,CAAA,CAAA,EAAI,GAAG,CAAA,EAAG,GAAG,CAAA,OAAA,CAAA;AACxD;AAUO,IAAM,oBAAA,GAAuB,CAAA;AAO7B,SAAS,oBAAoB,aAAA,EAA+B;AACjE,EAAA,MAAM,CAAA,GAAA,CAAA,CAAO,aAAA,GAAgB,oBAAA,IAAwB,EAAA,GAAM,EAAA,IAAM,EAAA;AACjE,EAAA,OAAO,CAAA,GAAI,CAAA,GAAI,CAAA,GAAI,EAAA,GAAK,CAAA;AAC1B;AAcO,IAAM,eAAA,GAAkB;AACxB,IAAM,uBAAA,GAA0B;AAGhC,SAAS,aAAa,IAAA,EAAsB;AACjD,EAAA,OAAO,CAAA,EAAG,aAAA,CAAc,IAAI,CAAC,CAAA,oBAAA,CAAA;AAC/B;AAGO,SAAS,eAAA,CAAgB,MAAkB,IAAA,EAAc;AAC9D,EAAA,OAAO,IAAI,IAAA,CAAK,MAAA,CAAO,YAAA,CAAa,IAAI,CAAC,CAAA;AAC3C;AAGO,IAAM,WAAA,GAAc,CAAC,GAAA,EAAK,IAAA,EAAM,GAAA,EAAK,IAAA,EAAM,GAAA,EAAK,GAAA,EAAK,IAAA,EAAM,GAAA,EAAK,IAAA,EAAM,GAAA,EAAK,MAAM,GAAG;AAYpF,IAAM,aAAA,GAAgB;AAG7B,IAAM,UAAA,GAAwC;AAAA,EAC5C,IAAA,EAAM,UAAA;AAAA,EACN,KAAA,EAAO,WAAA;AAAA,EACP,KAAA,EAAO,WAAA;AAAA,EACP,IAAA,EAAM,UAAA;AAAA,EACN,IAAA,EAAM,UAAA;AAAA,EACN,IAAA,EAAM;AACR,CAAA;AAGO,SAAS,aAAA,CAAc,IAAA,EAAkB,IAAA,GAAe,aAAA,EAAe;AAC5E,EAAA,MAAM,OAA+B,EAAC;AACtC,EAAA,KAAA,MAAW,CAAC,KAAA,EAAO,IAAI,CAAA,IAAK,MAAA,CAAO,QAAQ,UAAU,CAAA,EAAG,IAAA,CAAK,KAAK,CAAA,GAAI,IAAA;AACtE,EAAA,OAAO,IAAI,KAAK,OAAA,CAAQ,EAAE,MAAM,OAAA,EAAS,aAAA,CAAc,IAAI,CAAA,EAAG,CAAA;AAChE;AASO,SAAS,YAAY,IAAA,EAAyE;AACnG,EAAA,MAAM,IAAI,IAAA,CAAK,QAAA;AACf,EAAA,MAAM,GAAA,GAAM,KAAK,GAAA,IAAO,CAAA;AACxB,EAAA,MAAM,SAAS,IAAI,GAAA,CAAI,IAAA,CAAK,MAAA,IAAU,EAAE,CAAA;AACxC,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,MAAA,IAAU,CAAC,CAAC,CAAA;AAChC,EAAA,MAAM,UAAuB,IAAI,KAAA,CAAM,CAAC,CAAA,CAAE,KAAK,OAAO,CAAA;AAEtD,EAAA,IAAI,GAAA,GAAM,CAAA;AACV,EAAA,KAAA,MAAW,OAAO,MAAA,EAAQ;AACxB,IAAA,OAAA,CAAQ,GAAG,CAAA,GAAI,MAAA,CAAO,GAAA,CAAI,GAAG,IAAI,OAAA,GAAU,OAAA;AAC3C,IAAA,GAAA,IAAO,GAAA;AAAA,EACT;AACA,EAAA,OAAA,CAAQ,GAAG,CAAA,GAAI,MAAA;AACf,EAAA,OAAO,OAAA;AACT;AAOO,SAAS,aAAA,CACd,IAAA,EACA,GAAA,EACA,aAAA,EACA,KAAA,EACA;AACA,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,KAAA,GAAQ,CAAA,EAAG,aAAA,CAAc,aAAA,IAAiB,EAAE,CAAC,CAAA,EAAG,GAAA,CAAI,OAAO,CAAA,CAAA,GAAK,GAAA,CAAI,OAAA;AACrF,EAAA,MAAM,IAAA,GAAO,KAAA,GAAQ,UAAA,CAAW,GAAA,CAAI,IAAA,EAAM,MAAM,EAAA,EAAI,KAAA,CAAM,EAAE,CAAA,GAAI,GAAA,CAAI,IAAA;AACpE,EAAA,OAAO,IAAI,IAAA,CAAK,OAAA,CAAQ,EAAE,IAAA,EAAM,OAAA,EAAS,aAAA,CAAc,IAAI,CAAA,EAAG,OAAA,EAAS,GAAA,CAAI,OAAA,IAAW,GAAG,CAAA;AAC3F;AA2BO,IAAM,UAAA,GAAa;AAG1B,IAAM,WAAA,GAAc,EAAA;AAGb,SAAS,gBAAA,CAAiB,MAA8B,IAAA,EAAsB;AACnF,EAAA,IAAI,IAAA,GAAO,EAAA;AACX,EAAA,IAAI,QAAA,GAAW,QAAA;AACf,EAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,EAAG;AACnC,IAAA,MAAM,OAAO,IAAA,CAAK,GAAA,CAAI,UAAA,CAAW,GAAG,IAAI,IAAI,CAAA;AAC5C,IAAA,IAAI,OAAO,QAAA,EAAU;AACnB,MAAA,QAAA,GAAW,IAAA;AACX,MAAA,IAAA,GAAO,GAAA;AAAA,IACT;AAAA,EACF;AACA,EAAA,OAAO,IAAA;AACT;AAGO,SAAS,aAAA,CACd,IAAA,EACA,GAAA,EACA,aAAA,EACA,KAAA,EACA;AACA,EAAA,MAAM,IAAA,GAAO,GAAA,CAAI,KAAA,GAAQ,CAAA,EAAG,aAAA,CAAc,aAAA,IAAiB,EAAE,CAAC,CAAA,EAAG,GAAA,CAAI,OAAO,CAAA,CAAA,GAAK,GAAA,CAAI,OAAA;AACrF,EAAA,MAAM,IAAA,GAAO,KAAA,GAAQ,UAAA,CAAW,GAAA,CAAI,IAAA,EAAM,MAAM,EAAA,EAAI,KAAA,CAAM,EAAE,CAAA,GAAI,GAAA,CAAI,IAAA;AACpE,EAAA,OAAO,EAAE,OAAA,EAAS,IAAI,IAAA,CAAK,gBAAA,CAAiB,EAAE,IAAA,EAAM,OAAA,EAAS,aAAA,CAAc,IAAI,CAAA,EAAG,GAAG,IAAA,EAAK;AAC5F;AAcO,SAAS,YAAA,CACd,IAAA,EACA,OAAA,EACA,IAAA,EACA,IAAA,EACA,MACA,MAAA,EACA,OAAA,GAAU,CAAA,EACV,IAAA,GAAO,UAAA,EACD;AACN,EAAA,MAAM,MAAM,gBAAA,CAAiB,IAAA,EAAM,IAAA,CAAK,CAAC,EAAG,IAAI,CAAA;AAChD,EAAA,IAAI,QAAQ,EAAA,IAAM,CAAC,OAAA,CAAQ,GAAA,CAAI,GAAG,CAAA,EAAG;AACrC,EAAA,MAAM,UAAA,GAAa,WAAW,GAAG,CAAA;AACjC,EAAA,MAAM,IAAA,GAAO,CAAC,IAAA,KAAiB,IAAA,CAAK,IAAI,CAAA,EAAA,CAAI,IAAA,GAAO,cAAc,EAAE,CAAA;AAEnE,EAAA,MAAM,MAAA,GAAS,IAAI,IAAA,CAAK,gBAAA,CAAiB;AAAA,IACvC,GAAA,EAAK,OAAA,CAAQ,GAAA,CAAI,GAAG,CAAA;AAAA,IACpB,OAAA,EAAS,OAAA;AAAA,IACT,KAAA,EAAO,aAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAKP,SAAS,IAAA,CAAK;AAAA,GACf,CAAA,CAAE,OAAA,CAAQ,IAAI,CAAA;AAKf,EAAA,IAAI,CAAC,KAAK,OAAA,CAAQ,SAAA,SAAkB,OAAA,GAAU,MAAM,OAAO,OAAA,EAAQ;AAEnE,EAAA,MAAM,QAAQ,MAAA,CAAO,YAAA;AACrB,EAAA,KAAA,CAAM,cAAA,CAAe,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA,CAAG,IAAI,CAAA,EAAG,IAAA,CAAK,CAAC,CAAA,CAAG,EAAE,CAAA;AACrD,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,IAAA,CAAK,QAAQ,CAAA,EAAA,EAAK;AACpC,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,CAAA,GAAI,CAAC,CAAA;AACvB,IAAA,MAAM,EAAA,GAAK,KAAK,CAAC,CAAA;AACjB,IAAA,MAAM,QAAQ,IAAA,CAAK,EAAA,GAAA,CAAM,EAAA,CAAG,EAAA,GAAK,KAAK,EAAA,IAAM,IAAA;AAC5C,IAAA,MAAM,SAAmB,EAAC;AAC1B,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,IAAK,WAAA,EAAa,CAAA,EAAA,EAAK;AACrC,MAAA,MAAA,CAAO,IAAA,CAAK,IAAA,CAAK,IAAA,CAAK,IAAA,GAAA,CAAQ,EAAA,CAAG,IAAA,GAAO,IAAA,CAAK,IAAA,IAAQ,MAAA,CAAO,CAAA,GAAI,WAAW,CAAC,CAAC,CAAA;AAAA,IAC/E;AACA,IAAA,KAAA,CAAM,mBAAA,CAAoB,QAAQ,KAAA,EAAO,IAAA,CAAK,IAAI,EAAA,CAAG,EAAA,GAAK,KAAA,EAAO,IAAI,CAAC,CAAA;AAAA,EACxE;AACA,EAAA,MAAA,CAAO,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA,CAAG,IAAI,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,MAAA,GAAS,IAAA,CAAK,CAAC,CAAA,CAAG,EAAA,EAAI,IAAI,CAAC,CAAA;AACnE;AAIO,SAAS,aAAA,CAAc,IAAA,EAAkB,IAAA,EAAc,KAAA,GAA2B,WAAA,EAAa;AACpG,EAAA,MAAM,OAA+B,EAAC;AACtC,EAAA,KAAA,MAAW,KAAK,KAAA,EAAO,IAAA,CAAK,CAAC,CAAA,GAAI,GAAG,CAAC,CAAA,IAAA,CAAA;AACrC,EAAA,OAAO,IAAI,IAAA,CAAK,OAAA,CAAQ,EAAE,IAAA,EAAM,OAAA,EAAS,CAAA,EAAG,aAAA,CAAc,IAAI,CAAC,CAAA,QAAA,CAAA,EAAY,CAAA;AAC7E","file":"chunk-SEN266BY.js","sourcesContent":["/**\n * Pure svara → pitch math for the player. No Tone, no audio — unit-testable\n * in plain Node and shared by the scheduler.\n *\n * The model: a tonic (Sa) pitch-class + a per-note scale degree + octave\n * register + komal/tivra/microtone accidentals → an absolute MIDI number,\n * which the Tone.Sampler pitch-shifts from the nearest santoor sample.\n */\nimport { graceNotes } from '../spec/grace.js';\nimport { hasKomal, hasTivra } from '../spec/svara.js';\nimport type { Note, Octave, SvaraLetter } from '../spec/index.js';\n\n/** Octave register offset in octaves, relative to madhyam (middle). */\nexport const OCTAVE_OFFSET: Record<Octave, number> = {\n 'ati-mandra': -2,\n mandra: -1,\n madhyam: 0,\n taar: 1,\n 'ati-taar': 2,\n};\n\n/**\n * Shuddha (natural) scale degrees in semitones above Sa — the Bilawal/major\n * thaat that Rabindrasangeet swaralipi notates against. `d` = dha, `n` = ni\n * (standard sargam letter convention).\n */\nexport const SHUDDHA: Record<SvaraLetter, number> = {\n s: 0,\n r: 2,\n g: 4,\n m: 5,\n p: 7,\n d: 9,\n n: 11,\n};\n\n/** Sharp-spelled pitch-class names, index = semitone 0–11. */\nconst SHARP_NAMES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'] as const;\n\n/** Pitch-class lookup. Accepts sharps (`C#`, `Cs`, `cs`) and flats (`Db`). */\nconst PITCH_CLASS: Record<string, number> = {\n c: 0,\n 'c#': 1,\n db: 1,\n d: 2,\n 'd#': 3,\n eb: 3,\n e: 4,\n 'e#': 5,\n fb: 4,\n f: 5,\n 'f#': 6,\n gb: 6,\n g: 7,\n 'g#': 8,\n ab: 8,\n a: 9,\n 'a#': 10,\n bb: 10,\n b: 11,\n 'b#': 0,\n cb: 11,\n};\n\n/**\n * Parse a tonic string to a pitch-class 0–11. Accepts `C`, `C#`, `Db`, and\n * the player/sample lowercase-`s` sharp form (`cs`, `Cs`); case-insensitive.\n * Unknown input falls back to 0 (C).\n */\nexport function parseTonic(tonic: string | undefined): number {\n if (!tonic) return 0;\n const key = tonic.trim().toLowerCase().replace(/s/g, '#');\n return PITCH_CLASS[key] ?? 0;\n}\n\n/**\n * Semitone of a note relative to Sa (degree + accidentals), ignoring octave.\n * Returns null when the note carries no pitch (rest/sustain-hold/bar).\n *\n * Microtones (অল্পকোমল/অতিকোমল) are approximated to the komal pitch — a\n * documented v1 limitation (the sampler can't bend a sub-komal microtone\n * cleanly per note); matches the viewer's placeholder treatment.\n */\nexport function svaraSemitone(note: Note): number | null {\n if (note.svara === undefined) return null;\n const [, letter] = note.svara;\n let semi = SHUDDHA[letter];\n // Only alter what the notation can actually draw: স/প are অচল and ম has no\n // কোমল form, so a stray flag on one of those must sound as written (the\n // viewer falls back to the shuddha glyph — see glyphs.ts).\n if ((note.komal === true || note.microtone !== undefined) && hasKomal(letter)) semi -= 1;\n if (note.tivra === true && hasTivra(letter)) semi += 1;\n return semi;\n}\n\n/**\n * Absolute MIDI for a note, or null when it has no pitch. madhyam Sa lands on\n * the tonic in octave `baseOctave` (C4 = MIDI 60). Out-of-sample registers\n * are produced by the Sampler pitch-shifting from the nearest recorded note.\n */\nexport function svaraToMidi(note: Note, tonicPc: number, baseOctave = 4): number | null {\n const semi = svaraSemitone(note);\n if (semi === null || note.svara === undefined) return null;\n const [octave] = note.svara;\n return (baseOctave + 1 + OCTAVE_OFFSET[octave]) * 12 + tonicPc + semi;\n}\n\n/** MIDI of a কণ/রেশ grace: one pitch per grace svara, in written order.\n * Goes through `svaraToMidi`, so a komal/tivra grace sounds as written (the\n * legacy tuple form carries no accidentals and is shuddha by construction). */\nexport function graceMidis(\n grace: Note['kan'],\n tonicPc: number,\n baseOctave = 4,\n): number[] {\n return graceNotes(grace)\n .map((n) => svaraToMidi(n, tonicPc, baseOctave))\n .filter((m): m is number => m !== null);\n}\n\n/** MIDI → Tone note name (`C#4`). */\nexport function midiToSampleName(midi: number): string {\n const pc = ((midi % 12) + 12) % 12;\n const octave = Math.floor(midi / 12) - 1;\n return `${SHARP_NAMES[pc]}${octave}`;\n}\n\n/** MIDI → santoor sample filename stem (`cs4`): lowercase, `#` → `s`. */\nexport function sampleFileName(midi: number): string {\n return midiToSampleName(midi).toLowerCase().replace('#', 's');\n}\n\n/**\n * Logistic S-curve on [0, 1], normalised so f(0) = 0 and f(1) = 1.\n *\n * A মীড় is **not** a linear ramp. The hand (or the voice) accelerates away\n * from the source pitch and decelerates into the target — \"if the pull is too\n * fast, the melody loses its vocal quality; if it is too slow, the note dies\n * before reaching its destination\". That S shape is the established model for\n * portamento pitch contours (Yang & Chew's logistic fit, ISMIR 2016), and its\n * coefficient has a direct musical meaning: `steepness` is how abruptly the\n * pitch leaves one svara for the next.\n */\nexport function sCurve(x: number, steepness = 8): number {\n const t = Math.min(Math.max(x, 0), 1);\n const logistic = (u: number) => 1 / (1 + Math.exp(-steepness * (u - 0.5)));\n const lo = logistic(0);\n return (logistic(t) - lo) / (logistic(1) - lo);\n}\n","/**\n * Pure spec-doc → playback timeline. No Tone, no audio — the player turns\n * this `Schedule` into Transport events, but it is fully unit-testable on its\n * own and mirrors the viewer's spec-driven (not raw-driven) reading.\n *\n * Timing unit is the মাত্রা (matra / beat). One global beat cursor runs across\n * every part and row: NLTR \"systems\" (rows) are wraps of one continuous line,\n * and parts (asthayi → antara …) play back-to-back, so the music never pauses\n * at a row or part boundary.\n *\n * Each audible column carries the same `data-note` path the viewer emits\n * (`part.row.note`, note = absolute index into `row.notes`; see\n * src/viewer/render.ts), so `SwaralipiPlayer` can drive `highlight()` directly.\n */\nimport type { Note, Swaralipi, Taal } from '../spec/index.js';\nimport { resolveLaySpeeds, rowLay, type Lay, type LaySpeeds } from '../spec/lay.js';\nimport { sameTaal } from '../spec/taals.js';\nimport { graceMidis, parseTonic, svaraToMidi } from './pitch.js';\n\nexport interface PlayEvent {\n /** Onset in matra from the start of the document. */\n beat: number;\n /** Ring length in matra (extended by following আ-কার holds). */\n durationBeats: number;\n /** Pitch as MIDI, or null for a rest / আ-কার hold column. */\n midi: number | null;\n /** MIDI of each কণ grace on this column, in written order (empty = none). */\n kan: number[];\n /** MIDI of each রেশ grace on this column, in written order (empty = none). */\n resh: number[];\n /** `part.row.note` highlight path. */\n path: string;\n kind: 'note' | 'rest' | 'sustain';\n /**\n * মীড়: this pitch is reached by **gliding** from the previous pitched event,\n * not by a new attack. The two (or more) notes are one continuous sound whose\n * pitch travels — see `meendContinuations`. Consumers pair an attack with its\n * targets via `glideTargets`.\n */\n glide?: true;\n}\n\n/**\n * One accompaniment stretch of the timeline: a লয় and a তাল held constant.\n * A new segment starts wherever either changes — they are independent marks.\n * `scale` is the fraction of the base bpm the passage sounds at, and it is\n * exactly the factor a looping accompaniment must change its own tempo by —\n * `beat` × secondsPerBeat gives its start in seconds. Always at least one\n * segment, the first at beat 0.\n */\nexport interface TempoSegment {\n /** Start, in schedule beats (see PlayEvent.beat). */\n beat: number;\n /** End, in schedule beats (= the next segment's start, or totalBeats). */\n endBeat: number;\n scale: number;\n lay: Lay;\n /** তাল in force: `meta.taal` until a row's `taal` changes it. `null` = an\n * explicit মুক্তছন্দ passage; `undefined` = the document names no taal. */\n taal?: Taal | null;\n}\n\nexport interface Schedule {\n events: PlayEvent[];\n /** Total length in matra (the cursor's final value). */\n totalBeats: number;\n /**\n * অনাগত/anacrusis offset: beats the melody must be pushed later so the song's\n * সম (sam) lands on a cyclic accompaniment's sam. Many songs begin part-way\n * through the তাল cycle (the notation's first beat-label is `2`/`3`… and the\n * সম tick `1f`/`2f`… appears a few beats in) — without a shift the pickup note\n * would sound on the tabla's sam. Derived purely from the notation's own সম\n * tick position and `meta.taal.numBeats`; 0 when the song starts on sam or has\n * no sam tick. The player/exporter apply it only when a rhythm actually loops.\n */\n pickupBeats: number;\n /** লয় / তাল stretches, in order and gapless. One entry (drut, scale 1,\n * `meta.taal`) for a document that never changes either — i.e. every\n * document written before `row.lay` / `row.taal` existed. */\n tempo: TempoSegment[];\n}\n\nexport interface ScheduleOptions {\n /** Tonic; accepts `C`, `C#`, `cs`… (see parseTonic). Default C. */\n tonic?: string;\n /** Octave of madhyam Sa (C4 = MIDI 60). Default 4. */\n baseOctave?: number;\n /** Override the লয় tempo factors (see LAY_SPEEDS / meta.laySpeeds). */\n laySpeeds?: LaySpeeds;\n /** Apply লয় at all. Default true. The staff view sets it false: it reads\n * `durationBeats` as written note values, where a 0.75 stretch is not a\n * writable duration. */\n lay?: boolean;\n}\n\n/**\n * A raw the codec could not decode but which still **holds a matra** in the\n * source grid: it carries svara letters or the আ-কার carrier `a` (e.g. a\n * mistyped `` `-a `` / `frsfa` / `PUda`). Bracket, bar-glyph and beat-label\n * carriers contain none of these, so they stay timeless. Dropping these\n * columns outright ran the melody ahead of the তাল from that point on.\n */\nconst UNDECODED_CELL = /[srgmpqnvtduka]/i;\n\n/** A column that produces no sound and no time advance (bar, bracket-only,\n * carrier, structural raw). Mirrors the viewer's non-pitched, non-rest\n * classes. */\nconst isSilent = (n: Note): boolean =>\n n.svara === undefined &&\n n.rest !== true &&\n n.sustain !== true &&\n !(n.raw !== undefined && UNDECODED_CELL.test(n.raw));\n\n/**\n * Below-annotation raws that are মীড় arc drawing segments (codec known-raw):\n * `w`/`x` are the left/right curled ends, the rest are straight fill pieces.\n * Lives here rather than in the viewer because **both** need it and the viewer\n * may import the player (never the reverse — that would pull Tone into it).\n */\nexport const ARC_RAW_RE = /^[wWxXyYjJeEzZ$=_<>`\\\\]+$/;\nexport const isArcRaw = (raw: string | undefined): raw is string =>\n raw !== undefined && ARC_RAW_RE.test(raw);\n\n/**\n * Note indices in one row that lie under a মীড় arc **without opening it** —\n * i.e. every note the pitch glides *to*, as opposed to the one it glides\n * *from*.\n *\n * Swarabitan's legend defines the under-arc as \"কোনো এক স্বর যখন আর এক স্বরে\n * বিশেষরূপে গড়াইয়া যায়\" — when one svara *rolls into* another (example:\n * ণা‿পা). It is one continuous phonation, which is why 90 % of the corpus's\n * 7 419 arcs end on a melisma (a note under the *same* held syllable, lyric ৹).\n * So the notes under an arc must not be re-articulated: only the first sounds.\n *\n * Two encodings feed the same scan — the converter's below-row `w`/`x` raws\n * (every meend in the 1738-song corpus) and the spec's own `meend: start|end`\n * (what the creator writes). A cell may carry both ends (`xw` =\n * close then re-open, `wx` = an arc drawn inside the one cell), so marks are\n * read **in order** — reading them as an unordered set left a `wx` arc open and\n * slurred the next eighteen notes into a single slide. Arcs are resolved per row — exactly one arc in the\n * whole corpus wraps across a row, so cross-row state would not pay for itself.\n */\nexport function meendContinuations(notes: Note[]): Set<number> {\n const out = new Set<number>();\n /** Notes seen since the arc opened — committed only once it closes. */\n let pending: number[] | undefined;\n notes.forEach((note, i) => {\n let marks = note.meend === 'start' ? 'w' : note.meend === 'end' ? 'x' : '';\n for (const b of note.below ?? []) if (isArcRaw(b.raw)) marks += b.raw;\n let marked = false;\n for (const ch of marks) {\n if (ch === 'x' || ch === 'X') {\n // Closes here: this note and everything since the open are targets.\n if (pending !== undefined) for (const k of [...pending, i]) out.add(k);\n pending = undefined;\n marked = true;\n } else if (ch === 'w' || ch === 'W') {\n pending = []; // opens here: this note is the source, not a target\n marked = true;\n }\n }\n if (!marked && pending !== undefined) pending.push(i);\n });\n // An arc left open at the row's end is a notation slip (5 in 1738 songs), not\n // a glide: committing it would slur the whole rest of the line into one\n // 16-beat slide. The viewer still draws it as a system-wrap continuation.\n return out;\n}\n\n/**\n * The glide targets belonging to the attack at `events[i]`: the pitched events\n * that follow it flagged `glide`. Silent columns (আ-কার holds) are transparent\n * — the note is still ringing through them — but a rest ends the phrase.\n * Consumers skip events that are themselves `glide` (already consumed here).\n */\nexport function glideTargets(events: PlayEvent[], i: number): PlayEvent[] {\n const out: PlayEvent[] = [];\n for (let j = i + 1; j < events.length; j++) {\n const ev = events[j]!;\n if (ev.kind === 'rest') break;\n if (ev.midi === null) continue;\n if (ev.glide !== true) break;\n out.push(ev);\n }\n return out;\n}\n\n/** Schedule beats between a grace and its column's start; consecutive graces step by it. */\nexport const GRACE_STEP = 0.12;\n\n/**\n * When each grace of `ev` sounds, in written order, mirrored around the\n * column's start: a কণ (written left of its svara) touches just before it, a\n * রেশ (written right) just after — so on a hold or rest column they touch under\n * whatever is still ringing. Not at the column's end: every grace is a fresh\n * strike, and one struck just before the next column is heard as its কণ.\n */\nexport function graceTouches(ev: PlayEvent): { midi: number; beat: number }[] {\n // ponytail: a fixed step, so several graces on a very short column spill past\n // it (two রেশ on a 1/5-matra note); scale the step by column length if needed.\n return [\n ...ev.kan.map((midi, g) => ({ midi, beat: ev.beat - GRACE_STEP * (ev.kan.length - g) })),\n ...ev.resh.map((midi, g) => ({ midi, beat: ev.beat + GRACE_STEP * (g + 1) })),\n ];\n}\n\n/** A সম beat-tick: a beat-label digit carrying the reph tick (`1f`/`2F`…). It\n * marks the sam wherever it lands; the digit is a within-line vibhag number, so\n * only the trailing f/F (not the value) identifies sam. Labels live on a note's\n * `above` annotations (see the converter's beat-label handling). */\nconst SAM_TICK = /^\\d+[fF]$/;\n\n/**\n * Walk a document into a flat, time-ordered list of play events.\n *\n * - svara note → an attack at the cursor; advances by `durationMatra ?? 1`.\n * - rest → a silent column that still advances the cursor.\n * - sustain **with** svara (melisma) → a fresh attack (the pitch moves under\n * a held vowel); advances.\n * - sustain **without** svara (আ-কার hold) → no attack, but the previous\n * audible note's ring is extended; still emitted (midi null) so the lyric\n * column highlights; advances.\n * - a svara under a মীড় arc (but not the note the arc opened on) → still its\n * own event, flagged `glide`: the pitch is reached by sliding from the\n * previous attack rather than by striking again (see meendContinuations).\n * - bar / bracket-only / carrier / structural raw → ignored (no event, no\n * advance); an *undecoded* notation cell (see UNDECODED_CELL) instead\n * plays as a rest, so it keeps its matra and the তাল stays aligned.\n */\nexport function buildSchedule(doc: Swaralipi, opts: ScheduleOptions = {}): Schedule {\n const tonicPc = parseTonic(opts.tonic ?? doc.meta.tonic);\n const baseOctave = opts.baseOctave ?? 4;\n const events: PlayEvent[] = [];\n let cursor = 0;\n /** Index in `events` of the last pitched attack, for hold-extension. */\n let lastPitched = -1;\n /** Matra position of the first সম tick (see SAM_TICK), if any. Counted in\n * *written* matra, so the anacrusis maths stays independent of the লয়. */\n let samMatra: number | undefined;\n let matra = 0;\n\n // লয়: a row's tier holds until another row changes it. `scale` < 1 stretches\n // the written matra over more schedule beats, which is what makes the passage\n // sound slower at one transport tempo.\n const speeds = resolveLaySpeeds(doc.meta.laySpeeds, opts.laySpeeds);\n const withLay = opts.lay !== false;\n const tempo: TempoSegment[] = [{ beat: 0, endBeat: 0, scale: 1, lay: 'drut', taal: doc.meta.taal }];\n let scale = 1;\n // তালফেরতা: likewise a row's taal holds until another row changes it. The\n // two marks are independent, so a segment boundary is wherever either moves.\n let taal: Taal | null | undefined = doc.meta.taal;\n\n doc.parts.forEach((part, p) => {\n part.rows.forEach((row, r) => {\n const lay = withLay ? rowLay(row) : undefined;\n const layChanged = lay !== undefined && speeds[lay] !== scale;\n const nextTaal = row.taal !== undefined ? row.taal : taal;\n if (layChanged || !sameTaal(nextTaal, taal)) {\n if (layChanged) scale = speeds[lay!];\n taal = nextTaal;\n const last = tempo[tempo.length - 1]!;\n const seg: TempoSegment = { beat: cursor, endBeat: cursor, scale, lay: layChanged ? lay! : last.lay, taal };\n // Two marks with nothing between them are one switch, not a 0-beat segment.\n if (last.beat === cursor) tempo[tempo.length - 1] = seg;\n else {\n last.endBeat = cursor;\n tempo.push(seg);\n }\n }\n const glides = meendContinuations(row.notes);\n row.notes.forEach((note, i) => {\n // A সম tick can ride any column (incl. a bar/carrier), so scan before\n // the silent-column early return, recording only the first one.\n if (samMatra === undefined && note.above !== undefined) {\n for (const a of note.above) {\n if (a.raw !== undefined && SAM_TICK.test(a.raw)) {\n samMatra = matra;\n break;\n }\n }\n }\n if (note.bar !== undefined || isSilent(note)) return;\n const path = `${p}.${r}.${i}`;\n const written = note.durationMatra ?? 1;\n const dur = written / scale;\n // Any column that takes time can carry graces, a hold or rest included.\n const kan = graceMidis(note.kan, tonicPc, baseOctave);\n const resh = graceMidis(note.resh, tonicPc, baseOctave);\n\n if (note.svara !== undefined) {\n // svara note or melisma — a real attack, unless a মীড় glides into it.\n events.push({\n beat: cursor,\n durationBeats: dur,\n midi: svaraToMidi(note, tonicPc, baseOctave),\n kan,\n resh,\n path,\n kind: note.sustain === true ? 'sustain' : 'note',\n ...(glides.has(i) && lastPitched >= 0 ? { glide: true as const } : {}),\n });\n lastPitched = events.length - 1;\n } else if (note.sustain === true) {\n // আ-কার hold — extend the ringing note, emit a silent highlight column.\n if (lastPitched >= 0) events[lastPitched]!.durationBeats += dur;\n events.push({ beat: cursor, durationBeats: dur, midi: null, kan, resh, path, kind: 'sustain' });\n } else {\n // rest.\n events.push({ beat: cursor, durationBeats: dur, midi: null, kan, resh, path, kind: 'rest' });\n }\n cursor += dur;\n matra += written;\n });\n });\n });\n\n // Anacrusis: shift the melody so its sam falls on a cyclic sam. If sam is at\n // matra S in an N-beat cycle, S + shift ≡ 0 (mod N) → shift = (N − S mod N) mod N\n // (a partial-to-full cycle count-in; 0 when the song already starts on sam).\n // The opening taal's cycle — the one the count-in is played in.\n const numBeats = tempo[0]!.taal?.numBeats;\n const pickupMatra =\n samMatra !== undefined && numBeats !== undefined && numBeats > 0\n ? (numBeats - (samMatra % numBeats)) % numBeats\n : 0;\n tempo[tempo.length - 1]!.endBeat = cursor;\n\n // The count-in is played at the tempo the song *starts* in.\n return { events, totalBeats: cursor, pickupBeats: pickupMatra / tempo[0]!.scale, tempo };\n}\n","/**\n * Tone.js instrument/asset factories. Tone is **injected** (never imported\n * here) so this module — like the scheduler — stays free of a static `tone`\n * dependency; the player passes the dynamically-imported module in.\n *\n * Melodic instruments are a small registry (INSTRUMENTS) of Tone.Sampler\n * note→file maps + where to fetch them. `piano` (CC-BY Salamander) is the\n * default and CDN-hosted, as are `harmonium`/`violin`/`flute` (CC-BY,\n * tonejs-instruments); `santoor` is local (under the player's `soundsBaseUrl`).\n * Consumers can register more (e.g. a self-hosted sitar) via the player's\n * `customInstruments` option — true sampled sitar/sarod/sarangi/esraj have no\n * free web set, only commercial Kontakt libraries.\n *\n * Asset layout under `soundsBaseUrl`:\n * santoor/<note>.mp3 c4..c7 + sharps (37 files)\n * tanpura/<pc>.mp3 12 pitch classes (c, cs, d, …)\n * tabla/<seg>/<seg><bpm>bpm.mp3 seg = <taal>[-<variation>], bpm 60..300 step 15\n * metronome/metro2.mp3\n */\n// Import TAALS from the taals subpath (not the ../spec/index barrel): the\n// barrel re-exports schema.ts, which statically imports zod — pulling that into\n// the player would break its \"loads bare in a browser\" guarantee. taals.ts has\n// only a type-only schema import, so this keeps the player runtime zod-free.\nimport type { Taal } from '../spec/schema.js';\nimport { TAALS, type TaalId } from '../spec/taals.js';\nimport { sCurve, sampleFileName } from './pitch.js';\n\n/** The dynamically-imported Tone module, typed without importing it. */\nexport type ToneModule = typeof import('tone');\n\n/** A melodic instrument: a Tone.Sampler note→filename map + where to find it. */\nexport interface InstrumentDef {\n /** Human label for UIs. */\n label: string;\n /** Tone.Sampler `urls`: note name (`C#4`) → filename (`Cs4.mp3`). */\n urls: Record<string, string>;\n /** Sample directory: an absolute URL, or — when `local` — a path segment\n * appended to the player's `soundsBaseUrl`. */\n baseUrl: string;\n /** `baseUrl` is relative to `soundsBaseUrl` (needs it set). */\n local?: boolean;\n /** Sampler release time in seconds. */\n release?: number;\n}\n\n/** Built-in melody instrument ids (consumers may add their own via the\n * player's `customInstruments`, so the runtime id type stays open). */\nexport type InstrumentId = string;\n\n/**\n * Default host for the project's samples (santoor / tabla / tanpura /\n * metronome) — CORS-enabled, so the player works with zero config. Override via\n * the player's `soundsBaseUrl` to self-host. (Melody defaults to the CDN-hosted\n * piano, so it needs no host.) Licences: see THIRD-PARTY.md.\n */\nexport const DEFAULT_SOUNDS_BASE = 'https://bitoron.pages.dev/audio/instruments/';\n/** CC-BY Salamander Grand Piano (Tone's de-facto piano), CDN-hosted. */\nexport const SALAMANDER_BASE = 'https://tonejs.github.io/audio/salamander/';\n/** CC-BY tonejs-instruments sample CDN (harmonium/violin/flute). */\nexport const TONEJS_INSTRUMENTS_BASE = 'https://nbrosowsky.github.io/tonejs-instruments/samples/';\n\n/** Build a Tone.Sampler `urls` map from bare note keys (`C#4` → `Cs4.mp3`). */\nfunction keyMap(keys: readonly string[]): Record<string, string> {\n const m: Record<string, string> = {};\n for (const k of keys) m[k] = `${k.replace('#', 's')}.mp3`;\n return m;\n}\n\nconst PIANO_KEYS = [\n 'A0', 'C1', 'D#1', 'F#1', 'A1', 'C2', 'D#2', 'F#2', 'A2', 'C3', 'D#3', 'F#3',\n 'A3', 'C4', 'D#4', 'F#4', 'A4', 'C5', 'D#5', 'F#5', 'A5', 'C6', 'D#6', 'F#6',\n 'A6', 'C7', 'D#7', 'F#7', 'A7', 'C8',\n] as const;\n// harmonium — a core Rabindrasangeet accompaniment (C2..C5).\nconst HARMONIUM_KEYS = [\n 'C2', 'C#2', 'D2', 'D#2', 'E2', 'F2', 'F#2', 'G2', 'G#2', 'A2', 'A#2',\n 'C3', 'C#3', 'D3', 'D#3', 'E3', 'F3', 'F#3', 'G3', 'G#3', 'A3', 'A#3',\n 'C4', 'C#4', 'D4', 'D#4', 'E4', 'F4', 'F#4', 'G4', 'G#4', 'A4', 'A#4',\n 'C5', 'C#5', 'D5',\n] as const;\nconst VIOLIN_KEYS = ['A3', 'C4', 'E4', 'G4', 'A4', 'C5', 'E5', 'G5', 'A5', 'C6', 'E6', 'G6', 'A6', 'C7'] as const;\nconst FLUTE_KEYS = ['C4', 'E4', 'A4', 'C5', 'E5', 'A5', 'C6', 'E6', 'A6', 'C7'] as const;\n\n/** Built-in melody instruments. santoor is local; the rest are CDN-hosted\n * (CC-BY) so they need only network — `pianoBaseUrl`/`customInstruments`\n * override for self-hosting. */\nexport const INSTRUMENTS: Record<string, InstrumentDef> = {\n piano: { label: 'Grand piano', urls: keyMap(PIANO_KEYS), baseUrl: SALAMANDER_BASE, release: 1 },\n santoor: { label: 'Santoor', urls: santoorUrls(), baseUrl: 'santoor/', local: true, release: 1.2 },\n harmonium: { label: 'Harmonium', urls: keyMap(HARMONIUM_KEYS), baseUrl: `${TONEJS_INSTRUMENTS_BASE}harmonium/`, release: 1.4 },\n violin: { label: 'Violin', urls: keyMap(VIOLIN_KEYS), baseUrl: `${TONEJS_INSTRUMENTS_BASE}violin/`, release: 1 },\n flute: { label: 'Flute', urls: keyMap(FLUTE_KEYS), baseUrl: `${TONEJS_INSTRUMENTS_BASE}flute/`, release: 1 },\n};\n\n/** Taals with recorded tabla loops (everything else → metronome fallback). */\nexport const SAMPLED_TAALS: ReadonlySet<TaalId> = new Set(\n Object.keys(TAALS) as TaalId[],\n);\n\n/** Available tabla loop tempos (file suffix `…<bpm>bpm.mp3`). */\nexport const TABLA_BPMS: readonly number[] = Array.from({ length: 17 }, (_, i) => 60 + i * 15);\n\n/**\n * Tabla loop variations per taal. Most taals have one unnamed loop at\n * `tabla/<id>/`; an entry here overrides that:\n * - `variations` lists the variation suffixes, **first = default**.\n * A lone named variation (shashthi → `pro`, i.e. `tabla/shashthi-pro/`) IS\n * the default; later entries are premium upsells the player never auto-selects.\n * An empty-string entry (kehrwa → `''`) means the unnamed `tabla/<id>/` loop\n * is the default and a named entry follows it as an upsell.\n * - `skipBpms` are grid tempos this taal's loops were not recorded at.\n * Consumers read this to build a variation/upsell UI; the player itself only\n * ever plays the default (or whatever a doc's `meta.taal.variation` pins).\n */\nexport const TABLA_VARIATIONS: Record<\n string,\n { variations: string[]; skipBpms?: number[] }\n> = {\n // ষষ্ঠী loops live under `tabla/shashthi-pro/`; full bpm grid recorded except 225.\n shashthi: { variations: ['pro'], skipBpms: [225] },\n // Kehrwa keeps its unnamed default loop; `qawwali` (tabla/kehrwa-qawwali/) is a\n // premium upsell variation recorded 90–300 (no 60/75).\n // ponytail: skipBpms is per-taal, so it can't restrict qawwali's 60/75 without\n // also dropping them from the default (which has them) — a qawwali doc pinned\n // ≤67 bpm 404s harmlessly. Make skipBpms per-variation only if that edge bites.\n kehrwa: { variations: ['', 'qawwali'] },\n};\n\n/** Tabla tempos actually available for a taal: the grid minus any it lacks. */\nexport function tablaBpms(taalId: string): readonly number[] {\n const skip = TABLA_VARIATIONS[taalId]?.skipBpms;\n return skip && skip.length ? TABLA_BPMS.filter((b) => !skip.includes(b)) : TABLA_BPMS;\n}\n\n/** CDN directory/filename segment for a taal's tabla loop: `<taalId>` or\n * `<taalId>-<variation>` (explicit variation, else the taal's default). */\nfunction tablaSeg(taalId: string, variation?: string): string {\n const v = variation ?? TABLA_VARIATIONS[taalId]?.variations[0]; // default = first variation, else unnamed\n return v ? `${taalId}-${v}` : taalId;\n}\n\n/** Ensure exactly one trailing slash. */\nexport function normalizeBase(url: string): string {\n return url.endsWith('/') ? url : `${url}/`;\n}\n\n/** The 37 santoor samples, as a Tone.Sampler `urls` map (`C4` → `c4.mp3`). */\nexport function santoorUrls(): Record<string, string> {\n const urls: Record<string, string> = {};\n const add = (midi: number) => {\n const stem = sampleFileName(midi); // e.g. cs4\n const name = stem.replace('s', '#').replace(/^(.)/, (c) => c.toUpperCase()); // C#4\n urls[name] = `${stem}.mp3`;\n };\n for (let midi = 60; midi <= 95; midi++) add(midi); // C4..B6 (full)\n add(96); // C7\n return urls;\n}\n\nconst PC_BASE: Record<string, number> = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 };\n\n/** Parse a sample note name (`C#4`, `Ds3`, `Bb2`) to a MIDI number. */\nexport function noteToMidi(name: string): number {\n const m = /^([a-g])([#sb]?)(-?\\d+)$/i.exec(name.trim());\n if (!m) return NaN;\n const base = PC_BASE[m[1]!.toLowerCase()]!;\n const acc = m[2] === 'b' ? -1 : m[2] === '' ? 0 : 1; // # / s → sharp\n return (Number(m[3]) + 1) * 12 + base + acc;\n}\n\n/**\n * Restrict a Sampler `urls` map to just the samples needed to cover MIDI\n * `[lo, hi]`: every sample inside the range, plus the nearest one below and\n * above it (so Tone interpolates the edges without extrapolating). Cuts a\n * typical song from ~37 santoor files to a handful — a big win on mobile and\n * never worse on fast links (Tone fetches what's left in parallel). The\n * Sampler pitch-shifts from the nearest sample, so audio is unchanged.\n */\nexport function subsetUrls(\n urls: Record<string, string>,\n lo: number,\n hi: number,\n): Record<string, string> {\n const entries = Object.entries(urls)\n .map(([k, f]) => ({ k, f, midi: noteToMidi(k) }))\n .filter((e) => Number.isFinite(e.midi))\n .sort((a, b) => a.midi - b.midi);\n if (entries.length === 0) return { ...urls };\n\n const keep = new Set<string>();\n for (const e of entries) if (e.midi >= lo && e.midi <= hi) keep.add(e.k);\n // nearest sample at/below lo and at/above hi for clean interpolation\n let floor: string | undefined;\n let ceil: string | undefined;\n for (const e of entries) if (e.midi <= lo) floor = e.k;\n for (const e of entries) if (e.midi >= hi) { ceil = e.k; break; }\n if (floor) keep.add(floor);\n if (ceil) keep.add(ceil);\n if (keep.size === 0) {\n // range entirely outside the sample set — keep the single nearest sample\n const mid = (lo + hi) / 2;\n const nearest = entries.reduce((a, b) => (Math.abs(b.midi - mid) < Math.abs(a.midi - mid) ? b : a));\n keep.add(nearest.k);\n }\n\n const out: Record<string, string> = {};\n for (const e of entries) if (keep.has(e.k)) out[e.k] = e.f;\n return out;\n}\n\n/** Resolve a representative sample URL for an instrument (its first sample),\n * used to probe availability. undefined when a local instrument has no\n * `soundsBaseUrl` or the def has no samples. */\nexport function instrumentSampleUrl(def: InstrumentDef, soundsBaseUrl?: string): string | undefined {\n if (def.local && soundsBaseUrl === undefined) return undefined;\n const base = def.local ? `${normalizeBase(soundsBaseUrl ?? '')}${def.baseUrl}` : def.baseUrl;\n const file = Object.values(def.urls)[0];\n return file === undefined ? undefined : `${normalizeBase(base)}${file}`;\n}\n\n/**\n * Liveness check for a sample URL: a single tiny ranged GET (one byte), the\n * same CORS-enabled fetch Tone uses to load the buffer — so a pass means Tone\n * can actually load it. Resolves false on any error/timeout/non-2xx, and false\n * (no throw) when `fetch` is unavailable (Node/SSR). `Range: bytes=0-0` is a\n * CORS-safelisted header, so no preflight.\n */\nexport async function probeUrl(url: string, timeoutMs = 6000): Promise<boolean> {\n if (typeof fetch === 'undefined') return false;\n const ctrl = typeof AbortController !== 'undefined' ? new AbortController() : undefined;\n const timer = ctrl ? setTimeout(() => ctrl.abort(), timeoutMs) : undefined;\n try {\n const res = await fetch(url, {\n method: 'GET',\n headers: { Range: 'bytes=0-0' },\n cache: 'no-store',\n signal: ctrl?.signal,\n });\n return res.ok; // 200 / 206\n } catch {\n return false;\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n }\n}\n\n/**\n * Snap a tabla loop's end to the nearest whole beat of its measured length so a\n * slightly-mistrimmed file still phase-locks to the melody grid instead of\n * drifting a fraction of a beat each cycle. `spb` = seconds per beat (60/bpm).\n * No-op for correctly trimmed loops (already an integer beat count); never\n * extends past the buffer — a too-short file can't be repaired here.\n *\n * ponytail: belt-and-braces for off-spec audio; the real fix is trimming each\n * loop to exactly `cycles × numBeats × 60/bpm` seconds at production time.\n */\nexport function quantizeLoopEnd(durationSec: number, spb: number): number {\n if (spb <= 0) return durationSec;\n const beats = Math.round(durationSec / spb);\n if (beats < 1) return durationSec; // too short to snap to a beat\n return Math.min(beats * spb, durationSec);\n}\n\n/** Snap an arbitrary bpm to the nearest available tabla tempo (default grid;\n * pass a taal's `tablaBpms()` so it never snaps to a tempo that taal lacks). */\nexport function nearestTablaBpm(bpm: number, bpms: readonly number[] = TABLA_BPMS): number {\n return bpms.reduce((best, b) => (Math.abs(b - bpm) < Math.abs(best - bpm) ? b : best), bpms[0]!);\n}\n\n/** URL of the tabla loop for a taal at a (snapped) bpm, optionally a named\n * variation (defaults to the taal's default variation — see TABLA_VARIATIONS). */\nexport function tablaUrl(base: string, taalId: string, bpm: number, variation?: string): string {\n const seg = tablaSeg(taalId, variation);\n return `${normalizeBase(base)}tabla/${seg}/${seg}${bpm}bpm.mp3`;\n}\n\n/**\n * Pitch class (0 = C) the tabla loops were recorded at: their dayan (the tuned\n * right drum) rings at Sa ≈ C — measured off the CDN loops themselves\n * (spectral peak of the stroke decays, ~529 Hz = C5 +20 cents, consistent\n * across taals and across every bpm variant, i.e. the tempo set was\n * time-stretched, not resampled). Calibration knob: re-measure and change this\n * if the loop set is ever re-recorded.\n */\nexport const TABLA_TONIC_SEMITONE = 0;\n\n/**\n * Semitones to pitch-shift a tabla loop so its dayan matches the song's Sa.\n * Wrapped to the nearer direction (−5…+6): the drum is octave-agnostic, so a\n * B tonic shifts down 1 rather than up 11.\n */\nexport function tablaShiftSemitones(tonicSemitone: number): number {\n const d = (((tonicSemitone - TABLA_TONIC_SEMITONE) % 12) + 12) % 12;\n return d > 6 ? d - 12 : d;\n}\n\n/**\n * Grain settings for a retuned tabla loop (`Tone.GrainPlayer`, which detunes\n * independently of playback rate — so the loop keeps its recorded tempo and\n * stays phase-locked).\n *\n * Why not `Tone.PitchShift` after a plain `Player`: measured offline on the\n * teentaal loop, both shift accurately (±2 and −5 semitones confirmed by\n * spectral cross-correlation), but PitchShift's delay lines push the first\n * stroke 20–35 ms late (audibly behind the melody) and smear the spectrum;\n * GrainPlayer holds the onset at ~4 ms. Grains short enough not to blur the\n * strokes, overlapped half-way so the loop stays seamless.\n */\nexport const TABLA_GRAIN_SEC = 0.06;\nexport const TABLA_GRAIN_OVERLAP_SEC = 0.03;\n\n/** URL of the metronome click. */\nexport function metronomeUrl(base: string): string {\n return `${normalizeBase(base)}metronome/metro2.mp3`;\n}\n\n/** Build the metronome click `Tone.Player`. */\nexport function createMetronome(Tone: ToneModule, base: string) {\n return new Tone.Player(metronomeUrl(base));\n}\n\n/** Tanpura pitch-class sample stems (filename = `<stem>.mp3`). */\nexport const TANPURA_PCS = ['c', 'cs', 'd', 'ds', 'e', 'f', 'fs', 'g', 'gs', 'a', 'as', 'b'] as const;\n\n/* ---- Drum kit (Western-drumset rendering of an Indian taal) ------------\n *\n * A real drumset \"translation\" of tabla bols is an established practice\n * (e.g. Sunny Jain's Drum Atlas: India — kick on sam, snare on the accented\n * tali claps, hi-hat filling/ghosting the khaali wave), not something\n * invented here. DRUM_VOICES are one-shot acoustic-kit samples (CC0-cleared\n * Apache-2.0 Tonejs/audio collection, https://github.com/Tonejs/audio,\n * already the CDN this project uses directly for the Salamander piano) —\n * same trust tier as the other CDN-hosted instruments, no new hosting.\n */\nexport const DRUM_KIT_BASE = 'https://tonejs.github.io/audio/drum-samples/acoustic-kit/';\n\nexport type DrumVoice = 'kick' | 'snare' | 'hihat' | 'tom1' | 'tom2' | 'tom3';\nconst DRUM_FILES: Record<DrumVoice, string> = {\n kick: 'kick.mp3',\n snare: 'snare.mp3',\n hihat: 'hihat.mp3',\n tom1: 'tom1.mp3',\n tom2: 'tom2.mp3',\n tom3: 'tom3.mp3',\n};\n\n/** Build the drum-kit `Tone.Players` (one-shot voices, not looped/pitched). */\nexport function createDrumKit(Tone: ToneModule, base: string = DRUM_KIT_BASE) {\n const urls: Record<string, string> = {};\n for (const [voice, file] of Object.entries(DRUM_FILES)) urls[voice] = file;\n return new Tone.Players({ urls, baseUrl: normalizeBase(base) });\n}\n\n/**\n * Per-beat drumset voice for one তাল cycle, following sam/tali/khaali theory:\n * sam (cycle start) → kick, the first beat of each accented (non-khaali)\n * vibhag → snare (the tali clap), the first beat of a khaali vibhag → closed\n * hihat (the khaali \"wave off\"), every other beat → hihat (fill, keeping the\n * kit's groove going the way a tabla's filler strokes do).\n */\nexport function drumPattern(taal: Pick<Taal, 'numBeats' | 'sam' | 'bhaags' | 'khaali'>): DrumVoice[] {\n const n = taal.numBeats;\n const sam = taal.sam ?? 0;\n const khaali = new Set(taal.khaali ?? []);\n const bhaags = taal.bhaags ?? [n];\n const pattern: DrumVoice[] = new Array(n).fill('hihat');\n\n let pos = 0;\n for (const len of bhaags) {\n pattern[pos] = khaali.has(pos) ? 'hihat' : 'snare';\n pos += len;\n }\n pattern[sam] = 'kick';\n return pattern;\n}\n\n/**\n * Build a melody `Tone.Sampler` for an instrument def. Resolves the sample\n * directory (local → under `soundsBaseUrl`, else the def's absolute CDN URL)\n * and, when a MIDI `range` is given, loads only the samples that cover it.\n */\nexport function createSampler(\n Tone: ToneModule,\n def: InstrumentDef,\n soundsBaseUrl: string | undefined,\n range?: { lo: number; hi: number },\n) {\n const base = def.local ? `${normalizeBase(soundsBaseUrl ?? '')}${def.baseUrl}` : def.baseUrl;\n const urls = range ? subsetUrls(def.urls, range.lo, range.hi) : def.urls;\n return new Tone.Sampler({ urls, baseUrl: normalizeBase(base), release: def.release ?? 1 });\n}\n\n/* ---- মীড় (meend) — the sounded glide -------------------------------------\n *\n * A মীড় is one continuous sound whose pitch travels between svaras; the notes\n * it covers are never re-struck. A `Tone.Sampler` cannot do that — it has no\n * pitch parameter at all (checked against tone 15's own typings). What it does\n * have, one layer down, is `ToneBufferSource.playbackRate`, a real AudioParam:\n * so a glide is the instrument's own sample, started once, with its playback\n * rate automated — a tape slide, which is exactly what a hardware sampler does\n * for portamento.\n *\n * The pitch is automated as an S-curve (see `sCurve`), not a straight line, and\n * it leaves the source svara only after `MEEND_HOLD` of the gap so the written\n * note keeps its identity before rolling into the next — arriving exactly on\n * the target's own matra.\n *\n * ponytail: the glide reads from a second `ToneAudioBuffers` over the same\n * URLs as the Sampler (the browser HTTP cache means no extra download, just a\n * second decode). Share one buffer set if memory ever matters.\n */\n\n/**\n * Fraction of the gap between two মীড় notes spent holding the source pitch\n * before the slide begins. 0 = slide the whole way (an alaap-slow meend);\n * 0.5 = hold half the note, then roll into the target.\n */\nexport const MEEND_HOLD = 0.5;\n\n/** Automation points per glide leg — enough to hear as continuous. */\nconst GLIDE_STEPS = 24;\n\n/** Sampler `urls` key whose pitch is nearest `midi` (the sample to slide). */\nexport function nearestSampleKey(urls: Record<string, string>, midi: number): string {\n let best = '';\n let bestDiff = Infinity;\n for (const key of Object.keys(urls)) {\n const diff = Math.abs(noteToMidi(key) - midi);\n if (diff < bestDiff) {\n bestDiff = diff;\n best = key;\n }\n }\n return best;\n}\n\n/** The same samples `createSampler` loads, as raw buffers for the glide voice. */\nexport function createBuffers(\n Tone: ToneModule,\n def: InstrumentDef,\n soundsBaseUrl: string | undefined,\n range?: { lo: number; hi: number },\n) {\n const base = def.local ? `${normalizeBase(soundsBaseUrl ?? '')}${def.baseUrl}` : def.baseUrl;\n const urls = range ? subsetUrls(def.urls, range.lo, range.hi) : def.urls;\n return { buffers: new Tone.ToneAudioBuffers({ urls, baseUrl: normalizeBase(base) }), urls };\n}\n\n/** One মীড় leg: the pitch to reach and the absolute time to reach it by. */\nexport interface GlideLeg {\n midi: number;\n /** Seconds on the transport; `legs[0]` is the attack itself. */\n at: number;\n}\n\n/**\n * Sound one মীড় phrase: a single attack on `legs[0]` whose pitch then slides\n * through every following leg, ending at `endSec`. Used by both the live\n * player and the offline export.\n */\nexport function triggerGlide(\n Tone: ToneModule,\n buffers: InstanceType<ToneModule['ToneAudioBuffers']>,\n urls: Record<string, string>,\n dest: InstanceType<ToneModule['Gain']>,\n legs: readonly GlideLeg[],\n endSec: number,\n release = 1,\n hold = MEEND_HOLD,\n): void {\n const key = nearestSampleKey(urls, legs[0]!.midi);\n if (key === '' || !buffers.has(key)) return;\n const sampleMidi = noteToMidi(key);\n const rate = (midi: number) => Math.pow(2, (midi - sampleMidi) / 12);\n\n const source = new Tone.ToneBufferSource({\n url: buffers.get(key),\n fadeOut: release,\n curve: 'exponential',\n // Bind to the destination's context explicitly. `Tone.Offline` restores the\n // global context the moment it starts rendering, so a node built inside a\n // transport callback would otherwise land on the *online* context and the\n // offline export would render the glide as silence.\n context: dest.context,\n }).connect(dest);\n // Clean up after the note — but only on a live context. An offline render\n // runs its whole clock *before* `startRendering()`, so an onended-dispose\n // would disconnect the node before a single sample was rendered (the export\n // came out silent). The offline context is discarded wholesale anyway.\n if (!dest.context.isOffline) source.onended = () => source.dispose();\n\n const rates = source.playbackRate;\n rates.setValueAtTime(rate(legs[0]!.midi), legs[0]!.at);\n for (let k = 1; k < legs.length; k++) {\n const from = legs[k - 1]!;\n const to = legs[k]!;\n const start = from.at + (to.at - from.at) * hold;\n const values: number[] = [];\n for (let n = 0; n <= GLIDE_STEPS; n++) {\n values.push(rate(from.midi + (to.midi - from.midi) * sCurve(n / GLIDE_STEPS)));\n }\n rates.setValueCurveAtTime(values, start, Math.max(to.at - start, 0.02));\n }\n source.start(legs[0]!.at, 0, Math.max(endSec - legs[0]!.at, 0.05));\n}\n\n/** Build a tanpura `Tone.Players` loading only the given pitch-class stems\n * (the drone only ever plays Sa + Pa, so we fetch 2 files, not 12). */\nexport function createTanpura(Tone: ToneModule, base: string, stems: readonly string[] = TANPURA_PCS) {\n const urls: Record<string, string> = {};\n for (const s of stems) urls[s] = `${s}.mp3`;\n return new Tone.Players({ urls, baseUrl: `${normalizeBase(base)}tanpura/` });\n}\n"]}