@ugfoundation/swaralipi-js 0.14.2 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +3 -1
  2. package/dist/{batch-QEIW6XAA.js → batch-SYLHDJJL.js} +4 -4
  3. package/dist/{batch-QEIW6XAA.js.map → batch-SYLHDJJL.js.map} +1 -1
  4. package/dist/{chunk-CJI7A6AM.js → chunk-J5JYJL5N.js} +3 -3
  5. package/dist/{chunk-CJI7A6AM.js.map → chunk-J5JYJL5N.js.map} +1 -1
  6. package/dist/{chunk-WEXTZRRB.js → chunk-SPZO4573.js} +35 -7
  7. package/dist/chunk-SPZO4573.js.map +1 -0
  8. package/dist/{chunk-6AKH2NLW.js → chunk-WBYJUYI6.js} +3 -3
  9. package/dist/{chunk-6AKH2NLW.js.map → chunk-WBYJUYI6.js.map} +1 -1
  10. package/dist/cli.cjs +1 -1
  11. package/dist/cli.cjs.map +1 -1
  12. package/dist/cli.js +2 -2
  13. package/dist/convert/index.cjs +1 -1
  14. package/dist/convert/index.cjs.map +1 -1
  15. package/dist/convert/index.d.cts +1 -1
  16. package/dist/convert/index.d.ts +1 -1
  17. package/dist/convert/index.js +2 -2
  18. package/dist/index.cjs +1 -1
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +2 -2
  21. package/dist/index.d.ts +2 -2
  22. package/dist/index.js +1 -1
  23. package/dist/{instruments-CfHu4bSQ.d.cts → instruments-C-KtHQiW.d.cts} +12 -9
  24. package/dist/{instruments-C3CbtYdm.d.ts → instruments-SoUQBZNd.d.ts} +12 -9
  25. package/dist/{lay-CFLy_wd_.d.ts → lay-CWlEhImP.d.ts} +1 -1
  26. package/dist/{lay-Bi87MnkT.d.cts → lay-CXhC-HRO.d.cts} +1 -1
  27. package/dist/player/index.cjs +93 -31
  28. package/dist/player/index.cjs.map +1 -1
  29. package/dist/player/index.d.cts +25 -5
  30. package/dist/player/index.d.ts +25 -5
  31. package/dist/player/index.js +62 -28
  32. package/dist/player/index.js.map +1 -1
  33. package/dist/spec/index.cjs +1 -1
  34. package/dist/spec/index.cjs.map +1 -1
  35. package/dist/spec/index.d.cts +12 -12
  36. package/dist/spec/index.d.ts +12 -12
  37. package/dist/spec/index.js +1 -1
  38. package/dist/viewer/index.cjs +43 -10
  39. package/dist/viewer/index.cjs.map +1 -1
  40. package/dist/viewer/index.d.cts +6 -4
  41. package/dist/viewer/index.d.ts +6 -4
  42. package/dist/viewer/index.js +10 -5
  43. package/dist/viewer/index.js.map +1 -1
  44. package/package.json +1 -1
  45. package/dist/chunk-WEXTZRRB.js.map +0 -1
  46. package/dist/{schema-DyL0bMCf.d.ts → schema-DZag9qkF.d.cts} +36 -36
  47. package/dist/{schema-DyL0bMCf.d.cts → schema-DZag9qkF.d.ts} +36 -36
package/README.md CHANGED
@@ -242,7 +242,7 @@ player.bindSheet(document.querySelector('swaralipi-sheet'), { autoScroll: true }
242
242
  playButton.onclick = () => player.play(); // must be a user gesture (AudioContext)
243
243
  ```
244
244
 
245
- Controls: `setTonic('C#')`, `setBpm(120)`, `setInstrument(id)`, `setTaal(id|null)`, `setRhythm('tabla'|'metronome'|'none')`, `setTanpura(bool)`, `pause()` / `stop()` / `dispose()`. Each song loads only the samples covering its pitch range.
245
+ Controls: `setTonic('C#')`, `setBpm(120)`, `setInstrument(id)`, `setTaal(id|null)`, `setRhythm('tabla'|'metronome'|'none')`, `setTanpura(bool)`, `setCountIn(bool)`, `pause()` / `stop()` / `dispose()`. Each song loads only the samples covering its pitch range.
246
246
 
247
247
  - **Self-host samples:** override `soundsBaseUrl` (layout: `santoor/<note>.mp3`, `tabla/<taal>/<taal><bpm>bpm.mp3`, `tanpura/<pc>.mp3`).
248
248
  - **Add instruments:** `customInstruments: { sitar: { label, urls, baseUrl } }`.
@@ -252,6 +252,8 @@ The pure, audio-free scheduler is exported too (`buildSchedule`, `svaraToMidi`,
252
252
 
253
253
  **It sings the song's form, not just the page.** `{ }` twice, `( )` only the first time, the `[ ]` notes written above on the repeat. At each kali's closing `II` it goes back to the asthayi's opening `II` and sings up to the `‖` stop mark (`I[ ]I` sings the `[ ]` notes there). The tabla never restarts, so it stays on sam. Existing JSON needs no change. `repeats: false` plays it as written; `exportAudio` / `exportMidi` take the same option. `buildSchedule(doc, { repeats: true })` gives the sung timeline (its default stays as written). What is not handled yet: [REPEATS.md](REPEATS.md).
254
254
 
255
+ **A pickup is sung first; the rhythm comes in on the সম.** Many songs start before the সম (অনাগত). The সম comes from a `1f`/`2f` tick. With no tick, a song that opens on loose notes takes it from its bars: the first line that opens on a bar starts on sam, else the first `I`. The melody starts at once; tabla, drums or metronome come in on the সম; the tanpura sounds from the start. `countIn: true` (or `setCountIn(true)`) puts the rhythm first: it plays the rest of the cycle and the melody comes in on its own beat. `exportAudio` and `exportMidi` take the same option, and the staff view writes the pickup as a short first bar. `buildSchedule(doc)` gives `samBeat` (where the rhythm comes in) and `pickupBeats` (the count-in).
256
+
255
257
  ### Practice: start anywhere, repeat a line, sing it back
256
258
 
257
259
  Learning a song means hearing one line again and again, then singing it back. Once `bindSheet` has run, the sheet is the practice surface — no progress bar to fiddle with:
@@ -1,5 +1,5 @@
1
- import { convertNltrHtml, aggregate, formatSummary } from './chunk-6AKH2NLW.js';
2
- import './chunk-CJI7A6AM.js';
1
+ import { convertNltrHtml, aggregate, formatSummary } from './chunk-WBYJUYI6.js';
2
+ import './chunk-J5JYJL5N.js';
3
3
  import './chunk-BSZ3WQUP.js';
4
4
  import './chunk-K73Y46WU.js';
5
5
  import { stat, readdir, mkdir, readFile, writeFile } from 'fs/promises';
@@ -44,5 +44,5 @@ async function runConvert(input, opts = {}) {
44
44
  }
45
45
 
46
46
  export { runConvert };
47
- //# sourceMappingURL=batch-QEIW6XAA.js.map
48
- //# sourceMappingURL=batch-QEIW6XAA.js.map
47
+ //# sourceMappingURL=batch-SYLHDJJL.js.map
48
+ //# sourceMappingURL=batch-SYLHDJJL.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/convert/batch.ts"],"names":["report"],"mappings":"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","file":"batch-QEIW6XAA.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-SYLHDJJL.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"]}
@@ -135,7 +135,7 @@ var swaralipiSchema = z.object({
135
135
  });
136
136
 
137
137
  // src/spec/index.ts
138
- var SPEC_VERSION = "0.14.2";
138
+ var SPEC_VERSION = "0.15.0";
139
139
  function parseSwaralipi(data) {
140
140
  return swaralipiSchema.parse(data);
141
141
  }
@@ -144,5 +144,5 @@ function safeParseSwaralipi(data) {
144
144
  }
145
145
 
146
146
  export { SPEC_VERSION, barSchema, bracketSchema, graceSchema, laySchema, metaSchema, microtoneSchema, noteSchema, octaveSchema, parseSwaralipi, partSchema, rowSchema, safeParseSwaralipi, svaraLetterSchema, svaraSchema, swaralipiSchema, taalSchema };
147
- //# sourceMappingURL=chunk-CJI7A6AM.js.map
148
- //# sourceMappingURL=chunk-CJI7A6AM.js.map
147
+ //# sourceMappingURL=chunk-J5JYJL5N.js.map
148
+ //# sourceMappingURL=chunk-J5JYJL5N.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/spec/schema.ts","../src/spec/index.ts"],"names":[],"mappings":"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","file":"chunk-CJI7A6AM.js","sourcesContent":["import { z } from 'zod';\nimport type { Lay } from './lay.js';\n\n/**\n * swaralipi-js JSON spec — Zod schema + inferred types.\n\n/** Octave registers */\nexport const octaveSchema = z.enum([\n 'ati-mandra', // very low\n 'mandra', // low (dot below)\n 'madhyam', // middle\n 'taar', // high (ref/dot above)\n 'ati-taar', // very high\n]);\nexport type Octave = z.infer<typeof octaveSchema>;\n\n/**\n * The seven shuddha svara letters:\n * `d` = ধৈবত (dha), `n` = নিষাদ (ni). Komal/tivra are flags, not letters.\n */\nexport const svaraLetterSchema = z.enum(['s', 'r', 'g', 'm', 'p', 'd', 'n']);\nexport type SvaraLetter = z.infer<typeof svaraLetterSchema>;\n\n/** A pitch as `[octave, letter]` */\nexport const svaraSchema = z.tuple([octaveSchema, svaraLetterSchema]);\nexport type Svara = z.infer<typeof svaraSchema>;\n\n/** Microtone variants below komal (legend: অল্পকোমল / অতিকোমল). */\nexport const microtoneSchema = z.enum(['alpakomal', 'atikomal']);\nexport type Microtone = z.infer<typeof microtoneSchema>;\n\n/**\n * Barline weights. Swarabitan distinguishes the light দাঁড়ি (।) that\n * separates vibhags *within* a taal cycle from the দণ্ড (danda, I) strokes:\n * - `dari` — । vibhag separator inside the cycle\n * - `single` — I end of one taal cycle (ফেরা)\n * - `double` — II section (kali) boundary / return-to-asthayi marker\n * - `quad` — IIII end of the song\n */\nexport const barSchema = z.enum(['dari', 'single', 'double', 'quad']);\nexport type Bar = z.infer<typeof barSchema>;\n\n/**\n * A repeat-bracket edge. Brackets span a range of notes; the opening note\n * carries `{ ..., edge: 'open' }` and the closing note `{ ..., edge: 'close' }`.\n * kind: `repeat` = {} গুন্ডবন্ধনী, `skip` = () বক্রবন্ধনী (omit on repeat),\n * `alt` = [] সরল বন্ধনী (changed notes on repeat).\n */\nexport const bracketSchema = z.object({\n kind: z.enum(['repeat', 'skip', 'alt']),\n edge: z.enum(['open', 'close']),\n});\nexport type Bracket = z.infer<typeof bracketSchema>;\n\n/**\n * A কণ / রেশ payload. Historically one `Svara` tuple; real notation puts\n * *several* svaras in a grace, and each may itself be a melisma, so the\n * richer form is a list of notes — reusing `Note` brings komal/tivra/\n * microtone/sustain along for free. The tuple form stays valid, so 0.7.x\n * documents parse unchanged; read either with `graceNotes()`.\n */\nexport type Grace = Svara | Note[];\nexport const graceSchema: z.ZodType<Grace> = z.lazy(() =>\n z.union([svaraSchema, z.array(noteSchema).min(1)]),\n);\n\n/**\n * A single token in a row. A token is one of: a pitched note (`svara`),\n * a rest (`rest`), an আ-কার sustain (`sustain`), a barline (`bar`), or a\n * purely raw/undecoded cell (`raw`) — possibly carrying annotations\n * (`above`/`below`) or a lyric syllable on an otherwise empty column.\n * The `.refine` enforces that a note carries at least one of these so\n * truly empty tokens can't slip through.\n *\n * `above`/`below` make `Note` recursive, so the type is hand-written and\n * the schema is typed `z.ZodType<Note>` via `z.lazy` (all fields are\n * optional, hence input ≡ output and the annotation is sound).\n */\nexport interface Note {\n /** Pitch. Omitted for rests, sustains, and barlines. */\n svara?: Svara;\n /** কোমল (flat). */\n komal?: boolean;\n /** তীব্র / কড়ি (sharp — applies to ma). */\n tivra?: boolean;\n /** Sub-komal microtone (legend: অল্পকোমল / অতিকোমল). */\n microtone?: Microtone;\n /** Duration in মাত্রা (beats): 1 (default), 0.5, 0.25, 2, … */\n durationMatra?: number;\n /**\n * আ-কার: no new syllable attack. Alone (no `svara`) it holds the previous\n * note's pitch and vowel; combined with `svara` it is a melisma — the\n * pitch moves while the previous syllable's vowel continues (Swarabitan\n * writes these with a leading hyphen, e.g. `-sfa`).\n */\n sustain?: boolean;\n /** বিরাম: a rest of `durationMatra` beats. */\n rest?: boolean;\n /** কণ স্বর: one or more grace notes attached before this note. */\n kan?: Grace;\n /**\n * রেশ: an after-touch grace — the legend's mirror of কণ, written small to\n * the *right* of the note (\"আসল স্বরের পরে … অন্য স্বরের ঈষৎ রেশ লাগে;\n * তখন সেই স্বর ক্ষুদ্র অক্ষরে দক্ষিণ পার্শ্বে লিখিত হয়, যথা— রা˰স\").\n * Swarabitan writes it as a trailing uppercase letter (`mpaM`, `raS`).\n */\n resh?: Grace;\n /** মীড় (glide/slur) endpoint; `both` ends the arc coming in and starts the next (ণা‿পা‿ধা). */\n meend?: 'start' | 'end' | 'both';\n /**\n * অর্ধমাত্রা (ঃ): this note closes a cell whose matra unit is a *half* —\n * `সঃ` = ½ matra, `সরঃ` = two ¼s, `রাঃ` = 1½ (legend p. ১৩৫).\n * Explicit rather than derived, because durations alone cannot distinguish\n * two half-matra cells (`সঃ গঃ`) from one matra split in two (`সরা`); this\n * flag is what closes the cell and prints the ঃ.\n */\n halfMatra?: boolean;\n /** Barline. A bar token usually has no `svara`. */\n bar?: Bar;\n /** Repeat-bracket edges opening/closing on this token. */\n brackets?: Bracket[];\n /** Lyric syllable aligned under this note */\n lyric?: string;\n /** Lossless passthrough of the source cell (e.g. raw Swarabitan code). */\n raw?: string;\n /**\n * Curation only: why a converter (e.g. an image reader) doubts this cell —\n * `\"স্বর: পা? ধা?\"`. The creator outlines it until someone edits the cell or\n * accepts it, which deletes the field. Viewer and player ignore it.\n */\n review?: string;\n /**\n * Annotation notes rendered above this column: alt-on-repeat passages\n * (legend: পরিবর্তিত স্বর in [ ] শিরোদেশে), stop marks, beat numbers.\n */\n above?: Note[];\n /** Annotation notes rendered below this column. */\n below?: Note[];\n}\n\nexport const noteSchema: z.ZodType<Note> = z.lazy(() =>\n z\n .object({\n svara: svaraSchema.optional(),\n komal: z.boolean().optional(),\n tivra: z.boolean().optional(),\n microtone: microtoneSchema.optional(),\n durationMatra: z.number().positive().optional(),\n sustain: z.boolean().optional(),\n rest: z.boolean().optional(),\n kan: graceSchema.optional(),\n resh: graceSchema.optional(),\n meend: z.enum(['start', 'end', 'both']).optional(),\n halfMatra: z.boolean().optional(),\n bar: barSchema.optional(),\n brackets: z.array(bracketSchema).optional(),\n lyric: z.string().optional(),\n raw: z.string().optional(),\n review: z.string().optional(),\n above: z.array(noteSchema).optional(),\n below: z.array(noteSchema).optional(),\n })\n .refine(\n (n) =>\n n.svara !== undefined ||\n n.rest === true ||\n n.sustain === true ||\n n.bar !== undefined ||\n n.raw !== undefined ||\n (n.brackets !== undefined && n.brackets.length > 0) ||\n (n.above !== undefined && n.above.length > 0) ||\n (n.below !== undefined && n.below.length > 0) ||\n n.lyric !== undefined,\n {\n message:\n 'note must carry one of: svara, rest, sustain, bar, raw, lyric, brackets, or a non-empty above/below',\n },\n ),\n);\n\n/** লয় tier. `Lay` (and the tempo factors) live in the zod-free ./lay.js,\n * which the viewer/player import directly; `satisfies` keeps the two in step. */\nexport const laySchema = z.enum(['drut', 'madhya', 'vilambit']) satisfies z.ZodType<Lay>;\n\n/**\n * Taal definition. `numBeats` is the cycle length in মাত্রা; `bhaags` (if\n * present) must sum to `numBeats`; `sam`/`khaali` are 0-based beat indices.\n */\nexport const taalSchema = z\n .object({\n /** Player sample key, e.g. `dadra`, `teentaal`. */\n id: z.string(),\n /**\n * Which tabla loop variation to play, e.g. `pro` (CDN dir suffix). Optional\n * and additive — omitted means the taal's default variation, so existing\n * docs are unaffected. The player honors it but never exposes a variation\n * picker; selecting a non-default variation is the consumer's premium policy.\n */\n variation: z.string().optional(),\n numBeats: z.number().int().positive(),\n /** Vibhag groupings; if given, they sum to numBeats. */\n bhaags: z.array(z.number().int().positive()).optional(),\n /** Sam (cycle start) beat index, 0-based. */\n sam: z.number().int().nonnegative().optional(),\n /** Khaali (wave) beat indices, 0-based. */\n khaali: z.array(z.number().int().nonnegative()).optional(),\n })\n .refine((t) => t.bhaags === undefined || t.bhaags.reduce((a, b) => a + b, 0) === t.numBeats, {\n message: 'bhaags must sum to numBeats',\n });\nexport type Taal = z.infer<typeof taalSchema>;\n\n/** A written line of the song: a sequence of note tokens. */\nexport const rowSchema = z.object({\n /** Optional line label (e.g. a tuk/section marker). */\n label: z.string().optional(),\n /**\n * লয়: the tempo tier this row switches to, held until another row changes\n * it (absent on every row = the whole song at `meta.bpm`, i.e. দ্রুত).\n * Read it with `rowLay()`, which also picks up the mark NLTR *prints*\n * (\"বিলম্বিত লয়:\") in already-converted documents.\n */\n lay: laySchema.optional(),\n /**\n * তালফেরতা: the তাল this row switches to, held until another row changes it\n * (absent on every row = the whole song in `meta.taal`). `null` = মুক্তছন্দ\n * from here on: the accompaniment rests until a later row names a taal.\n * Independent of `lay` — either, both or neither may change on one row.\n */\n taal: taalSchema.nullable().optional(),\n /** That taal's name as printed (e.g. `ষষ্ঠী(২/৪)`) — `meta.taalName`'s twin. */\n taalName: z.string().optional(),\n notes: z.array(noteSchema),\n});\nexport type Row = z.infer<typeof rowSchema>;\n\n/** A structural section: আস্থায়ী / অন্তরা / সঞ্চারী / আভোগ, or free text. */\nexport const partSchema = z.object({\n name: z.string().optional(),\n rows: z.array(rowSchema),\n});\nexport type Part = z.infer<typeof partSchema>;\n\n/** Document-level metadata. */\nexport const metaSchema = z.object({\n title: z.string(),\n /** Cross-reference to a gitabitan.io song content id (filename, immutable). */\n songId: z.string().optional(),\n /** Source NLTR node/song id. */\n nltrId: z.number().int().optional(),\n taal: taalSchema.optional(),\n /**\n * The taal name as printed in the source (e.g. `ষষ্ঠী(২/৪)`), except\n * free rhythm which is normalised to `মুক্তছন্দ`. Always set by the NLTR\n * converter; `taal` is set only when the name resolves to a definition.\n */\n taalName: z.string().optional(),\n /** Base pitch for playback, e.g. `C`, `C#`, or the player's `cs` form. */\n tonic: z.string().optional(),\n /** Default playback tempo in matra/min. Player-only — the viewer ignores it.\n * The player adopts it on `load()` (precedence: constructor `bpm` → `setBpm`\n * tweak → this → 90). `null` is the converter's blank placeholder = unset. */\n bpm: z.number().positive().nullable().optional(),\n /** Per-document override of the লয় tempo factors (fractions of `bpm`).\n * Defaults live in `LAY_SPEEDS`; a player/export option overrides both. */\n laySpeeds: z\n .object({ drut: z.number().positive(), madhya: z.number().positive(), vilambit: z.number().positive() })\n .partial()\n .optional(),\n raag: z.string().optional(),\n /** আবর্তন: number of taal cycles, when the source states it. */\n avartan: z.number().int().positive().optional(),\n /** স্বরলিপিকার (notation author). */\n notationBy: z.string().optional(),\n /** Provenance note or URL. */\n source: z.string().optional(),\n});\nexport type Meta = z.infer<typeof metaSchema>;\n\n/** A complete swaralipi document. */\nexport const swaralipiSchema = z.object({\n /** Spec version this document targets (see SPEC_VERSION in ./index). */\n version: z.string(),\n meta: metaSchema,\n parts: z.array(partSchema),\n});\nexport type Swaralipi = z.infer<typeof swaralipiSchema>;\n","/**\n * @ugfoundation/swaralipi-js/spec\n *\n * The swaralipi JSON spec: Zod schemas, inferred TS types, and parse helpers.\n */\nimport { swaralipiSchema, type Swaralipi } from './schema.js';\n\n/** Current spec version. Documents declare the version they target. */\nexport const SPEC_VERSION = '0.14.2';\n\nexport {\n octaveSchema,\n svaraLetterSchema,\n svaraSchema,\n microtoneSchema,\n barSchema,\n bracketSchema,\n graceSchema,\n laySchema,\n noteSchema,\n rowSchema,\n partSchema,\n taalSchema,\n metaSchema,\n swaralipiSchema,\n} from './schema.js';\n\nexport type {\n Octave,\n SvaraLetter,\n Svara,\n Microtone,\n Bar,\n Bracket,\n Grace,\n Note,\n Row,\n Part,\n Taal,\n Meta,\n Swaralipi,\n} from './schema.js';\n\nexport { TAALS, TAAL_FERTA, isTaalFerta, sameTaal, type TaalId } from './taals.js';\nexport { graceNotes } from './grace.js';\nexport {\n LAYS,\n LAY_SPEEDS,\n printedLay,\n resolveLaySpeeds,\n rowLay,\n type Lay,\n type LaySpeeds,\n} from './lay.js';\nexport { hasKomal, hasTivra } from './svara.js';\n\n/** Parse + validate unknown data into a `Swaralipi`. Throws `ZodError` on failure. */\nexport function parseSwaralipi(data: unknown): Swaralipi {\n return swaralipiSchema.parse(data);\n}\n\n/** Non-throwing variant: returns Zod's `{ success, data | error }` result. */\nexport function safeParseSwaralipi(data: unknown) {\n return swaralipiSchema.safeParse(data);\n}\n"]}
1
+ {"version":3,"sources":["../src/spec/schema.ts","../src/spec/index.ts"],"names":[],"mappings":"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","file":"chunk-J5JYJL5N.js","sourcesContent":["import { z } from 'zod';\nimport type { Lay } from './lay.js';\n\n/**\n * swaralipi-js JSON spec — Zod schema + inferred types.\n\n/** Octave registers */\nexport const octaveSchema = z.enum([\n 'ati-mandra', // very low\n 'mandra', // low (dot below)\n 'madhyam', // middle\n 'taar', // high (ref/dot above)\n 'ati-taar', // very high\n]);\nexport type Octave = z.infer<typeof octaveSchema>;\n\n/**\n * The seven shuddha svara letters:\n * `d` = ধৈবত (dha), `n` = নিষাদ (ni). Komal/tivra are flags, not letters.\n */\nexport const svaraLetterSchema = z.enum(['s', 'r', 'g', 'm', 'p', 'd', 'n']);\nexport type SvaraLetter = z.infer<typeof svaraLetterSchema>;\n\n/** A pitch as `[octave, letter]` */\nexport const svaraSchema = z.tuple([octaveSchema, svaraLetterSchema]);\nexport type Svara = z.infer<typeof svaraSchema>;\n\n/** Microtone variants below komal (legend: অল্পকোমল / অতিকোমল). */\nexport const microtoneSchema = z.enum(['alpakomal', 'atikomal']);\nexport type Microtone = z.infer<typeof microtoneSchema>;\n\n/**\n * Barline weights. Swarabitan distinguishes the light দাঁড়ি (।) that\n * separates vibhags *within* a taal cycle from the দণ্ড (danda, I) strokes:\n * - `dari` — । vibhag separator inside the cycle\n * - `single` — I end of one taal cycle (ফেরা)\n * - `double` — II section (kali) boundary / return-to-asthayi marker\n * - `quad` — IIII end of the song\n */\nexport const barSchema = z.enum(['dari', 'single', 'double', 'quad']);\nexport type Bar = z.infer<typeof barSchema>;\n\n/**\n * A repeat-bracket edge. Brackets span a range of notes; the opening note\n * carries `{ ..., edge: 'open' }` and the closing note `{ ..., edge: 'close' }`.\n * kind: `repeat` = {} গুন্ডবন্ধনী, `skip` = () বক্রবন্ধনী (omit on repeat),\n * `alt` = [] সরল বন্ধনী (changed notes on repeat).\n */\nexport const bracketSchema = z.object({\n kind: z.enum(['repeat', 'skip', 'alt']),\n edge: z.enum(['open', 'close']),\n});\nexport type Bracket = z.infer<typeof bracketSchema>;\n\n/**\n * A কণ / রেশ payload. Historically one `Svara` tuple; real notation puts\n * *several* svaras in a grace, and each may itself be a melisma, so the\n * richer form is a list of notes — reusing `Note` brings komal/tivra/\n * microtone/sustain along for free. The tuple form stays valid, so 0.7.x\n * documents parse unchanged; read either with `graceNotes()`.\n */\nexport type Grace = Svara | Note[];\nexport const graceSchema: z.ZodType<Grace> = z.lazy(() =>\n z.union([svaraSchema, z.array(noteSchema).min(1)]),\n);\n\n/**\n * A single token in a row. A token is one of: a pitched note (`svara`),\n * a rest (`rest`), an আ-কার sustain (`sustain`), a barline (`bar`), or a\n * purely raw/undecoded cell (`raw`) — possibly carrying annotations\n * (`above`/`below`) or a lyric syllable on an otherwise empty column.\n * The `.refine` enforces that a note carries at least one of these so\n * truly empty tokens can't slip through.\n *\n * `above`/`below` make `Note` recursive, so the type is hand-written and\n * the schema is typed `z.ZodType<Note>` via `z.lazy` (all fields are\n * optional, hence input ≡ output and the annotation is sound).\n */\nexport interface Note {\n /** Pitch. Omitted for rests, sustains, and barlines. */\n svara?: Svara;\n /** কোমল (flat). */\n komal?: boolean;\n /** তীব্র / কড়ি (sharp — applies to ma). */\n tivra?: boolean;\n /** Sub-komal microtone (legend: অল্পকোমল / অতিকোমল). */\n microtone?: Microtone;\n /** Duration in মাত্রা (beats): 1 (default), 0.5, 0.25, 2, … */\n durationMatra?: number;\n /**\n * আ-কার: no new syllable attack. Alone (no `svara`) it holds the previous\n * note's pitch and vowel; combined with `svara` it is a melisma — the\n * pitch moves while the previous syllable's vowel continues (Swarabitan\n * writes these with a leading hyphen, e.g. `-sfa`).\n */\n sustain?: boolean;\n /** বিরাম: a rest of `durationMatra` beats. */\n rest?: boolean;\n /** কণ স্বর: one or more grace notes attached before this note. */\n kan?: Grace;\n /**\n * রেশ: an after-touch grace — the legend's mirror of কণ, written small to\n * the *right* of the note (\"আসল স্বরের পরে … অন্য স্বরের ঈষৎ রেশ লাগে;\n * তখন সেই স্বর ক্ষুদ্র অক্ষরে দক্ষিণ পার্শ্বে লিখিত হয়, যথা— রা˰স\").\n * Swarabitan writes it as a trailing uppercase letter (`mpaM`, `raS`).\n */\n resh?: Grace;\n /** মীড় (glide/slur) endpoint; `both` ends the arc coming in and starts the next (ণা‿পা‿ধা). */\n meend?: 'start' | 'end' | 'both';\n /**\n * অর্ধমাত্রা (ঃ): this note closes a cell whose matra unit is a *half* —\n * `সঃ` = ½ matra, `সরঃ` = two ¼s, `রাঃ` = 1½ (legend p. ১৩৫).\n * Explicit rather than derived, because durations alone cannot distinguish\n * two half-matra cells (`সঃ গঃ`) from one matra split in two (`সরা`); this\n * flag is what closes the cell and prints the ঃ.\n */\n halfMatra?: boolean;\n /** Barline. A bar token usually has no `svara`. */\n bar?: Bar;\n /** Repeat-bracket edges opening/closing on this token. */\n brackets?: Bracket[];\n /** Lyric syllable aligned under this note */\n lyric?: string;\n /** Lossless passthrough of the source cell (e.g. raw Swarabitan code). */\n raw?: string;\n /**\n * Curation only: why a converter (e.g. an image reader) doubts this cell —\n * `\"স্বর: পা? ধা?\"`. The creator outlines it until someone edits the cell or\n * accepts it, which deletes the field. Viewer and player ignore it.\n */\n review?: string;\n /**\n * Annotation notes rendered above this column: alt-on-repeat passages\n * (legend: পরিবর্তিত স্বর in [ ] শিরোদেশে), stop marks, beat numbers.\n */\n above?: Note[];\n /** Annotation notes rendered below this column. */\n below?: Note[];\n}\n\nexport const noteSchema: z.ZodType<Note> = z.lazy(() =>\n z\n .object({\n svara: svaraSchema.optional(),\n komal: z.boolean().optional(),\n tivra: z.boolean().optional(),\n microtone: microtoneSchema.optional(),\n durationMatra: z.number().positive().optional(),\n sustain: z.boolean().optional(),\n rest: z.boolean().optional(),\n kan: graceSchema.optional(),\n resh: graceSchema.optional(),\n meend: z.enum(['start', 'end', 'both']).optional(),\n halfMatra: z.boolean().optional(),\n bar: barSchema.optional(),\n brackets: z.array(bracketSchema).optional(),\n lyric: z.string().optional(),\n raw: z.string().optional(),\n review: z.string().optional(),\n above: z.array(noteSchema).optional(),\n below: z.array(noteSchema).optional(),\n })\n .refine(\n (n) =>\n n.svara !== undefined ||\n n.rest === true ||\n n.sustain === true ||\n n.bar !== undefined ||\n n.raw !== undefined ||\n (n.brackets !== undefined && n.brackets.length > 0) ||\n (n.above !== undefined && n.above.length > 0) ||\n (n.below !== undefined && n.below.length > 0) ||\n n.lyric !== undefined,\n {\n message:\n 'note must carry one of: svara, rest, sustain, bar, raw, lyric, brackets, or a non-empty above/below',\n },\n ),\n);\n\n/** লয় tier. `Lay` (and the tempo factors) live in the zod-free ./lay.js,\n * which the viewer/player import directly; `satisfies` keeps the two in step. */\nexport const laySchema = z.enum(['drut', 'madhya', 'vilambit']) satisfies z.ZodType<Lay>;\n\n/**\n * Taal definition. `numBeats` is the cycle length in মাত্রা; `bhaags` (if\n * present) must sum to `numBeats`; `sam`/`khaali` are 0-based beat indices.\n */\nexport const taalSchema = z\n .object({\n /** Player sample key, e.g. `dadra`, `teentaal`. */\n id: z.string(),\n /**\n * Which tabla loop variation to play, e.g. `pro` (CDN dir suffix). Optional\n * and additive — omitted means the taal's default variation, so existing\n * docs are unaffected. The player honors it but never exposes a variation\n * picker; selecting a non-default variation is the consumer's premium policy.\n */\n variation: z.string().optional(),\n numBeats: z.number().int().positive(),\n /** Vibhag groupings; if given, they sum to numBeats. */\n bhaags: z.array(z.number().int().positive()).optional(),\n /** Sam (cycle start) beat index, 0-based. */\n sam: z.number().int().nonnegative().optional(),\n /** Khaali (wave) beat indices, 0-based. */\n khaali: z.array(z.number().int().nonnegative()).optional(),\n })\n .refine((t) => t.bhaags === undefined || t.bhaags.reduce((a, b) => a + b, 0) === t.numBeats, {\n message: 'bhaags must sum to numBeats',\n });\nexport type Taal = z.infer<typeof taalSchema>;\n\n/** A written line of the song: a sequence of note tokens. */\nexport const rowSchema = z.object({\n /** Optional line label (e.g. a tuk/section marker). */\n label: z.string().optional(),\n /**\n * লয়: the tempo tier this row switches to, held until another row changes\n * it (absent on every row = the whole song at `meta.bpm`, i.e. দ্রুত).\n * Read it with `rowLay()`, which also picks up the mark NLTR *prints*\n * (\"বিলম্বিত লয়:\") in already-converted documents.\n */\n lay: laySchema.optional(),\n /**\n * তালফেরতা: the তাল this row switches to, held until another row changes it\n * (absent on every row = the whole song in `meta.taal`). `null` = মুক্তছন্দ\n * from here on: the accompaniment rests until a later row names a taal.\n * Independent of `lay` — either, both or neither may change on one row.\n */\n taal: taalSchema.nullable().optional(),\n /** That taal's name as printed (e.g. `ষষ্ঠী(২/৪)`) — `meta.taalName`'s twin. */\n taalName: z.string().optional(),\n notes: z.array(noteSchema),\n});\nexport type Row = z.infer<typeof rowSchema>;\n\n/** A structural section: আস্থায়ী / অন্তরা / সঞ্চারী / আভোগ, or free text. */\nexport const partSchema = z.object({\n name: z.string().optional(),\n rows: z.array(rowSchema),\n});\nexport type Part = z.infer<typeof partSchema>;\n\n/** Document-level metadata. */\nexport const metaSchema = z.object({\n title: z.string(),\n /** Cross-reference to a gitabitan.io song content id (filename, immutable). */\n songId: z.string().optional(),\n /** Source NLTR node/song id. */\n nltrId: z.number().int().optional(),\n taal: taalSchema.optional(),\n /**\n * The taal name as printed in the source (e.g. `ষষ্ঠী(২/৪)`), except\n * free rhythm which is normalised to `মুক্তছন্দ`. Always set by the NLTR\n * converter; `taal` is set only when the name resolves to a definition.\n */\n taalName: z.string().optional(),\n /** Base pitch for playback, e.g. `C`, `C#`, or the player's `cs` form. */\n tonic: z.string().optional(),\n /** Default playback tempo in matra/min. Player-only — the viewer ignores it.\n * The player adopts it on `load()` (precedence: constructor `bpm` → `setBpm`\n * tweak → this → 90). `null` is the converter's blank placeholder = unset. */\n bpm: z.number().positive().nullable().optional(),\n /** Per-document override of the লয় tempo factors (fractions of `bpm`).\n * Defaults live in `LAY_SPEEDS`; a player/export option overrides both. */\n laySpeeds: z\n .object({ drut: z.number().positive(), madhya: z.number().positive(), vilambit: z.number().positive() })\n .partial()\n .optional(),\n raag: z.string().optional(),\n /** আবর্তন: number of taal cycles, when the source states it. */\n avartan: z.number().int().positive().optional(),\n /** স্বরলিপিকার (notation author). */\n notationBy: z.string().optional(),\n /** Provenance note or URL. */\n source: z.string().optional(),\n});\nexport type Meta = z.infer<typeof metaSchema>;\n\n/** A complete swaralipi document. */\nexport const swaralipiSchema = z.object({\n /** Spec version this document targets (see SPEC_VERSION in ./index). */\n version: z.string(),\n meta: metaSchema,\n parts: z.array(partSchema),\n});\nexport type Swaralipi = z.infer<typeof swaralipiSchema>;\n","/**\n * @ugfoundation/swaralipi-js/spec\n *\n * The swaralipi JSON spec: Zod schemas, inferred TS types, and parse helpers.\n */\nimport { swaralipiSchema, type Swaralipi } from './schema.js';\n\n/** Current spec version. Documents declare the version they target. */\nexport const SPEC_VERSION = '0.15.0';\n\nexport {\n octaveSchema,\n svaraLetterSchema,\n svaraSchema,\n microtoneSchema,\n barSchema,\n bracketSchema,\n graceSchema,\n laySchema,\n noteSchema,\n rowSchema,\n partSchema,\n taalSchema,\n metaSchema,\n swaralipiSchema,\n} from './schema.js';\n\nexport type {\n Octave,\n SvaraLetter,\n Svara,\n Microtone,\n Bar,\n Bracket,\n Grace,\n Note,\n Row,\n Part,\n Taal,\n Meta,\n Swaralipi,\n} from './schema.js';\n\nexport { TAALS, TAAL_FERTA, isTaalFerta, sameTaal, type TaalId } from './taals.js';\nexport { graceNotes } from './grace.js';\nexport {\n LAYS,\n LAY_SPEEDS,\n printedLay,\n resolveLaySpeeds,\n rowLay,\n type Lay,\n type LaySpeeds,\n} from './lay.js';\nexport { hasKomal, hasTivra } from './svara.js';\n\n/** Parse + validate unknown data into a `Swaralipi`. Throws `ZodError` on failure. */\nexport function parseSwaralipi(data: unknown): Swaralipi {\n return swaralipiSchema.parse(data);\n}\n\n/** Non-throwing variant: returns Zod's `{ success, data | error }` result. */\nexport function safeParseSwaralipi(data: unknown) {\n return swaralipiSchema.safeParse(data);\n}\n"]}
@@ -466,6 +466,19 @@ function eventAt(events, path, near = -1) {
466
466
  return i;
467
467
  }
468
468
  var SAM_TICK = /^\d+[fF]$/;
469
+ function loosePickupSam(doc) {
470
+ const rows = doc.parts.flatMap((part, p) => part.rows.map((row, r) => ({ p, r, notes: row.notes })));
471
+ const opener = (notes) => notes.find((n) => n.bar !== void 0 || !isSilent(n));
472
+ const first = rows.findIndex((row) => opener(row.notes) !== void 0);
473
+ if (first < 0 || opener(rows[first].notes).bar !== void 0) return void 0;
474
+ const line = rows.slice(first + 1).find((row) => opener(row.notes)?.bar !== void 0);
475
+ if (line !== void 0) return [line.p, line.r, 0];
476
+ for (const { p, r, notes } of rows) {
477
+ const i = notes.findIndex((n) => n.bar === "single");
478
+ if (i >= 0) return [p, r, i];
479
+ }
480
+ return void 0;
481
+ }
469
482
  function buildSchedule(doc, opts = {}) {
470
483
  const tonicPc = parseTonic(opts.tonic ?? doc.meta.tonic);
471
484
  const baseOctave = opts.baseOctave ?? 4;
@@ -492,12 +505,16 @@ function buildSchedule(doc, opts = {}) {
492
505
  );
493
506
  const order = opts.repeats === true ? playOrder(doc) : doc.parts.flatMap((part, p) => part.rows.flatMap((row, r) => row.notes.map((note, i) => ({ p, r, i, note }))));
494
507
  const at = [0];
495
- let samMatra;
496
- for (const { note } of order) {
508
+ const loose = loosePickupSam(doc);
509
+ let tick;
510
+ let inferred;
511
+ for (const { p, r, i, note } of order) {
497
512
  const m = at[at.length - 1];
498
- if (samMatra === void 0 && note.above?.some((a) => a.raw !== void 0 && SAM_TICK.test(a.raw))) samMatra = m;
513
+ if (tick === void 0 && note.above?.some((a) => a.raw !== void 0 && SAM_TICK.test(a.raw))) tick = m;
514
+ if (inferred === void 0 && loose !== void 0 && p === loose[0] && r === loose[1] && i >= loose[2]) inferred = m;
499
515
  at.push(note.bar === void 0 && !isSilent(note) ? m + (note.durationMatra ?? 1) : m);
500
516
  }
517
+ const samMatra = tick ?? inferred;
501
518
  const { visits, matra: matra2 } = opts.excerpt ? excerptVisits(order, at, opts.excerpt, (v) => rows.get(`${v.p}.${v.r}`).taal?.numBeats) : { visits: order, matra: 0 };
502
519
  let rowNow = "";
503
520
  for (const { p, r, i, note } of visits) {
@@ -541,9 +558,20 @@ function buildSchedule(doc, opts = {}) {
541
558
  cursor += dur;
542
559
  }
543
560
  const numBeats = tempo[0].taal?.numBeats;
544
- const pickupMatra = numBeats !== void 0 && numBeats > 0 ? ((matra2 - (samMatra ?? 0)) % numBeats + numBeats) % numBeats : 0;
561
+ let pickupMatra = 0;
562
+ if (numBeats !== void 0 && numBeats > 0) {
563
+ pickupMatra = ((matra2 - (samMatra ?? 0)) % numBeats + numBeats) % numBeats;
564
+ if (pickupMatra < 1e-6 || pickupMatra > numBeats - 1e-6) pickupMatra = 0;
565
+ }
545
566
  tempo[tempo.length - 1].endBeat = cursor;
546
- return { events, totalBeats: cursor, pickupBeats: pickupMatra / tempo[0].scale, tempo };
567
+ const opening = tempo[0].scale;
568
+ return {
569
+ events,
570
+ totalBeats: cursor,
571
+ pickupBeats: pickupMatra / opening,
572
+ samBeat: pickupMatra > 0 ? (numBeats - pickupMatra) / opening : 0,
573
+ tempo
574
+ };
547
575
  }
548
576
 
549
577
  // src/player/instruments.ts
@@ -840,5 +868,5 @@ function createTanpura(Tone, base, stems = TANPURA_PCS) {
840
868
  }
841
869
 
842
870
  export { ARC_RAW_RE, DEFAULT_SOUNDS_BASE, DRUM_KIT_BASE, GRACE_STEP, INSTRUMENTS, MEEND_HOLD, OCTAVE_OFFSET, SALAMANDER_BASE, SAMPLED_TAALS, SHUDDHA, TABLA_BPMS, TABLA_GRAIN_OVERLAP_SEC, TABLA_GRAIN_SEC, TABLA_VARIATIONS, TANPURA_PCS, TONEJS_INSTRUMENTS_BASE, alignLyrics, buildSchedule, byRow, createBuffers, createDrumKit, createMetronome, createSampler, createTanpura, drumPattern, eventAt, glideTargets, graceTouches, instrumentSampleUrl, isArcRaw, lineAt, lineFor, lyricCues, meendContinuations, midiToSampleName, nearestSampleKey, nearestTablaBpm, normalizeBase, parseTonic, probeUrl, quantizeLoopEnd, sCurve, sampleFileName, santoorUrls, scheduleLines, subsetUrls, svaraSemitone, svaraToMidi, tablaBpms, tablaShiftSemitones, tablaUrl, triggerGlide };
843
- //# sourceMappingURL=chunk-WEXTZRRB.js.map
844
- //# sourceMappingURL=chunk-WEXTZRRB.js.map
871
+ //# sourceMappingURL=chunk-SPZO4573.js.map
872
+ //# sourceMappingURL=chunk-SPZO4573.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/player/pitch.ts","../src/player/lyrics.ts","../src/player/repeats.ts","../src/player/schedule.ts","../src/player/instruments.ts"],"names":["j","EPS","last","matra"],"mappings":"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","file":"chunk-SPZO4573.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 * Where in the song's text each note sings, and which notes sing a stretch of it.\n *\n * A swaralipi's lyric row is phonetic and split into syllables (আ|বা|র্, চি|ৎ|ত,\n * ৹ for a held syllable), and it often writes a refrain out again. So the\n * notation's letters are aligned to the text by global alignment\n * (Needleman–Wunsch) that may also jump to any word start, the way JumpDTW lets\n * a score re-enter its own repeat blocks (Fremerey, Müller & Clausen, ISMIR\n * 2010). Over gitabitan's 1827 song/notation pairs it aligns 97.9 % of the\n * notation's letters; the rest are songs whose text is only a fragment.\n */\nimport type { Swaralipi } from '../spec/schema.js';\nimport type { Visit } from './repeats.js';\nimport type { PlayEvent } from './schedule.js';\n\n/** `part.row.note` → the `[start, end)` UTF-16 offsets of the text it sings. */\nexport type LyricMap = Map<string, [number, number]>;\n\n/** The text's lines as the events sing them, each word timed by its first note — an LRC's\n * cues. A line sung again (a repeat) is a new cue. Words are the text's whitespace runs. */\nexport function lyricCues(\n events: PlayEvent[],\n map: LyricMap,\n text: string,\n): { beat: number; words: { beat: number; text: string }[] }[] {\n const tokens = [...text.matchAll(/\\S+/g)].map((m) => ({\n text: m[0],\n start: m.index,\n end: m.index + m[0].length,\n line: text.slice(0, m.index).split('\\n').length - 1,\n }));\n const cues: { beat: number; line: number; last: number; words: { beat: number; text: string }[] }[] = [];\n for (const ev of events) {\n const span = map.get(ev.path);\n const index = span ? tokens.findIndex((t) => t.start <= span[0] && span[0] < t.end) : -1;\n if (index < 0) continue;\n const token = tokens[index]!;\n let cue = cues.at(-1);\n if (!cue || cue.line !== token.line || index < cue.last) {\n cue = { beat: ev.beat, line: token.line, last: -1, words: [] };\n cues.push(cue);\n }\n if (index !== cue.last) {\n cue.words.push({ beat: ev.beat, text: token.text });\n cue.last = index;\n }\n }\n return cues.map(({ beat, words }) => ({ beat, words }));\n}\n\n/** Play only the notes singing these `[start, end)` stretches of the text, in order. */\nexport interface Excerpt {\n lyrics: LyricMap;\n ranges: [number, number][];\n}\n\nconst MATCH = 2;\nconst GAP = 1;\nconst JUMP = 4;\nconst RUN = 3;\n// 98 % of the corpus's misspelt syllables run under 8 unplaced letters.\nconst STRAY = 8;\nconst EPS = 1e-6;\nconst [DIAG, UP, LEFT, JUMPED] = [1, 2, 3, 4];\n\n/** The letters two spellings share: virama, nukta and ৹ dropped, খণ্ড ত read as ত. */\nconst letters = (s: string): string =>\n s\n .normalize('NFD')\n .replace(/ৎ/g, 'ত')\n .replace(/[^\\p{L}\\p{M}]|[়্]/gu, '')\n .toLowerCase();\n\n/** Each note of `doc` that sings a letter of `text`, with the stretch of `text` it sings. */\nexport function alignLyrics(doc: Swaralipi, text: string): LyricMap {\n const sung: { letter: string; path: string }[] = [];\n doc.parts.forEach((part, p) =>\n part.rows.forEach((row, r) =>\n row.notes.forEach((note, i) => {\n for (const letter of letters(note.lyric ?? '')) sung.push({ letter, path: `${p}.${r}.${i}` });\n }),\n ),\n );\n const said: { letter: string; from: number; to: number }[] = [];\n const starts: number[] = [];\n let at = 0;\n let gap = true;\n for (const ch of text) {\n const ls = letters(ch);\n if (ls === '') {\n gap ||= /[\\s\\p{P}]/u.test(ch);\n // A virama / nukta / joiner belongs to the letter before it, so a span never splits a conjunct's marks off.\n if (/[\\p{M}\\p{Cf}]/u.test(ch) && said.at(-1)?.to === at) said.at(-1)!.to += ch.length;\n } else {\n if (gap) starts.push(said.length);\n gap = false;\n }\n for (const letter of ls) said.push({ letter, from: at, to: at + ch.length });\n at += ch.length;\n }\n\n const n = sung.length;\n const m = said.length;\n const w = m + 1;\n const from = new Uint8Array((n + 1) * w);\n const best = new Int32Array(n + 1);\n // Before any letter is sung the text starts at its top; anywhere else costs a jump.\n let prev = new Int32Array(w).fill(-(2 ** 30));\n prev[0] = 0;\n let cur = new Int32Array(w);\n /** Jump from the row's best cell to every word start, then let the row skip text letters. */\n const settle = (i: number, row: Int32Array): void => {\n for (let j = 1; j < w; j++) if (row[j]! > row[best[i]!]!) best[i] = j;\n for (const s of starts) {\n if (row[best[i]!]! - JUMP > row[s]!) {\n row[s] = row[best[i]!]! - JUMP;\n from[i * w + s] = JUMPED;\n }\n }\n for (let j = 1; j < w; j++) {\n if (row[j - 1]! - GAP > row[j]!) {\n row[j] = row[j - 1]! - GAP;\n from[i * w + j] = LEFT;\n }\n }\n };\n settle(0, prev);\n for (let i = 1; i <= n; i++) {\n cur[0] = prev[0]! - GAP;\n from[i * w] = UP;\n for (let j = 1; j < w; j++) {\n const diag = prev[j - 1]! + (sung[i - 1]!.letter === said[j - 1]!.letter ? MATCH : -GAP);\n const up = prev[j]! - GAP;\n cur[j] = Math.max(diag, up);\n from[i * w + j] = diag >= up ? DIAG : UP;\n }\n best[i] = 0;\n settle(i, cur);\n [prev, cur] = [cur, prev];\n }\n\n // Trace back from the best end, collecting the letters that agree.\n const pairs: [number, number][] = [];\n let j = 0;\n for (let k = 1; k < w; k++) if (prev[k]! > prev[j]!) j = k;\n for (let i = n; i > 0; ) {\n const step = from[i * w + j];\n if (step === JUMPED) j = best[i]!;\n else if (step === LEFT) j--;\n else if (step === UP) i--;\n else {\n if (sung[i - 1]!.letter === said[j - 1]!.letter) pairs.push([i - 1, j - 1]);\n i--;\n j--;\n }\n }\n pairs.reverse();\n // A lone agreeing letter (a vowel sign past the text's end) is chance, not\n // the same word: only RUN sung letters agreeing in a row place a note. A row\n // carries on into the text's next letter, or across a jump to a word start,\n // so a short word sung over and over (আয়্ আয়্) holds.\n const wordStart = new Set(starts);\n const follows = (k: number): boolean =>\n pairs[k]![0] === pairs[k - 1]![0] + 1 && (pairs[k]![1] === pairs[k - 1]![1] + 1 || wordStart.has(pairs[k]![1]));\n const out: LyricMap = new Map();\n /** The text letter each note's span ends on: a syllable grows only by the letter after it. */\n const last = new Map<string, number>();\n for (let a = 0, b = 1; a < pairs.length; a = b++) {\n while (b < pairs.length && follows(b)) b++;\n if (b - a < RUN) continue;\n for (const [s, t] of pairs.slice(a, b)) {\n const { path } = sung[s]!;\n const { from: lo, to: hi } = said[t]!;\n const span = out.get(path);\n if (span === undefined) out.set(path, [lo, hi]);\n else if (t === last.get(path)! + 1) span[1] = hi;\n else continue;\n last.set(path, t);\n }\n }\n return out;\n}\n\nconst mod = (a: number, n: number): number => ((a % n) + n) % n;\nconst sings = (v: Visit): boolean => v.note.svara !== undefined || v.note.sustain === true;\n\n/**\n * The visits of `order` that sing `excerpt`, and the matra (in `at`, each\n * visit's written matra in the whole song) the first one starts on.\n *\n * Each range plays the song's form while its words stay inside the range — a\n * `{ }` repeat, a return to the asthayi — from its first sung note to the last\n * one before the words leave it. Rests and held syllables go with it; a range\n * the notation does not sing is left out. Between two ranges a rest keeps the\n * next one on the beat it has in the whole song, so the taal never breaks.\n */\nexport function excerptVisits(\n order: Visit[],\n at: number[],\n { lyrics, ranges }: Excerpt,\n cycle: (v: Visit) => number | undefined,\n): { visits: Visit[]; matra: number } {\n // Where each visit sings in the text; -1 for words the text does not have\n // (another stanza, another song): a stretch of STRAY or more letters the\n // alignment left unplaced. A shorter one is a syllable spelt otherwise.\n const pos = order.map((v) => lyrics.get(`${v.p}.${v.r}.${v.i}`)?.[0]);\n for (let a = 0; a < order.length; a++) {\n if (pos[a] !== undefined || letters(order[a]!.note.lyric ?? '') === '') continue;\n let b = a;\n let stray = 0;\n while (b < order.length && pos[b] === undefined) stray += letters(order[b++]!.note.lyric ?? '').length;\n if (stray >= STRAY) pos.fill(-1, a, b);\n a = b;\n }\n\n const visits: Visit[] = [];\n let first: number | undefined;\n let end = 0;\n for (const [lo, hi] of ranges) {\n const inside = (k: number): boolean | undefined => (pos[k] === undefined ? undefined : pos[k]! >= lo && pos[k]! < hi);\n const a = order.findIndex((_, k) => inside(k) === true);\n if (a < 0) continue;\n let b = a + 1;\n while (b < order.length && inside(b) !== false) b++;\n while (b - 1 > a && !sings(order[b - 1]!)) b--;\n const n = cycle(order[a]!);\n if (first === undefined) first = a;\n else if (n !== undefined) {\n // ponytail: phase is the whole song's matra mod this taal's cycle, so a jump across a তালফেরতা change is approximate.\n const gap = mod(at[a]! - at[end]!, n);\n if (gap > EPS) visits.push({ ...order[end - 1]!, note: { rest: true, durationMatra: gap } });\n }\n visits.push(...order.slice(a, b));\n end = b;\n }\n return { visits, matra: first === undefined ? 0 : at[first]! };\n}\n","/**\n * The order a swaralipi is **sung** in, as opposed to the order it is written.\n * Swarabitan's legend (স্বরবিতান pp. ১৩৫–১৩৬, gitabitan.io/bn/swaralipi/how-to-read/):\n *\n * - `{ }` is sung twice; `( )` inside it is left out the second time, and the\n * `[ ]` notes written above stand in for the notes under them.\n * - II at a kali's end sends the singer back to the asthayi's opening II, on\n * to the ‖ stop mark, then into the next kali: D.S. al Fine, with ‖ as the\n * Fine. I[ ]I / II[ ]II make that return sing the `[ ]` notes.\n * - IIII written as one stroke group ends the song: no return.\n *\n * The notation is written for this order, so every jump lands where the grid\n * expects it: the \"৹ ৹ ৹\" after a kali-end II is the rest of the cycle the ‖\n * broke off, which is what keeps a looping tabla on sam. Whatever the marks\n * leave unsaid (no ‖, a stray bracket) is sung as written.\n */\nimport { decodeCell } from '../convert/codec.js';\nimport type { Bracket, Note, Swaralipi } from '../spec/schema.js';\n\n/** One note as sung: where it is written (for highlights) and what sounds. */\nexport interface Visit {\n p: number;\n r: number;\n i: number;\n /** The written note, or a `[ ]` note standing in for it on a repeat. */\n note: Note;\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. */\nexport const 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/** ‖ written above a note: অবসানের চিহ্ন, where a return to the asthayi stops. */\nconst STOP_MARK = /^\\|+$/;\nconst EPS = 1e-6;\n\ninterface Token extends Visit {\n timed: boolean;\n}\n\nconst edges = (n: Note, kind: Bracket['kind'], edge: Bracket['edge']): number =>\n n.brackets?.filter((b) => b.kind === kind && b.edge === edge).length ?? 0;\nconst sounds = (n: Note): boolean => n.svara !== undefined || n.rest === true || n.sustain === true;\nconst matra = (notes: Note[]): number => notes.reduce((sum, n) => sum + (n.durationMatra ?? 1), 0);\n\n/** Every note of `doc` in the order it is sung. */\nexport function playOrder(doc: Swaralipi): Visit[] {\n const tokens: Token[] = [];\n doc.parts.forEach((part, p) =>\n part.rows.forEach((row, r) =>\n row.notes.forEach((note, i) => tokens.push({ p, r, i, note, timed: note.bar === undefined && !isSilent(note) })),\n ),\n );\n const { asthayi, stop, returns } = form(tokens);\n const changed = changedNotes(tokens);\n const out: Visit[] = [];\n // The token of the last note sung.\n let last = -1;\n\n /** Sing tokens lo..hi-1: each `{ }` twice, its `( )` only the first time. */\n const sing = (lo: number, hi: number, useChanged: boolean): void => {\n const repeats: { open: number; again: boolean }[] = [];\n // Matra of written notes a longer `[ ]` note already sang over.\n let owed = 0;\n for (let k = lo; k < hi; ) {\n const t = tokens[k]!;\n if (edges(t.note, 'repeat', 'open') > 0 && repeats.at(-1)?.open !== k) repeats.push({ open: k, again: false });\n const again = repeats.at(-1)?.again === true;\n const skipTo = again && edges(t.note, 'skip', 'open') > 0 ? skipEnd(tokens, k, hi) : -1;\n if (skipTo >= 0) {\n if (edges(tokens[skipTo]!.note, 'repeat', 'close') > 0) repeats.pop();\n k = skipTo + 1;\n owed = 0;\n continue;\n }\n if (owed > EPS && t.timed) owed -= matra([t.note]);\n else {\n const sub = useChanged || again ? changed.get(k) : undefined;\n // A `[ ]` note shorter than the one it replaces would pull the melody off sam.\n const fits = sub !== undefined && matra(sub) >= matra([t.note]) - EPS;\n for (const note of fits ? sub : [t.note]) out.push({ p: t.p, r: t.r, i: t.i, note });\n if (t.timed) last = k;\n owed = fits ? matra(sub) - matra([t.note]) : 0;\n }\n const loop = edges(t.note, 'repeat', 'close') > 0 ? repeats.at(-1) : undefined;\n if (loop !== undefined && !loop.again) {\n loop.again = true;\n k = loop.open;\n owed = 0;\n continue;\n }\n if (loop !== undefined) repeats.pop();\n k++;\n }\n };\n\n let from = 0;\n for (const ret of returns) {\n sing(from, ret.resume, false);\n // Singing that already stopped at ‖ \"takes up another kali\" there: a ‖ at\n // the asthayi's own close, or a `{ }` whose second pass skips from ‖ to `}`.\n if (last !== stop) sing(asthayi, stop + 1, ret.changed);\n from = ret.resume;\n }\n sing(from, tokens.length, false);\n return out;\n}\n\n/**\n * The song's form: where the asthayi starts (its opening II, else the top),\n * where a return stops (the first ‖ in the asthayi), and the token after each\n * kali-end II or I[ ]I. No ‖ → no returns: the notation then does not say how\n * far back to sing.\n */\nfunction form(tokens: Token[]): { asthayi: number; stop: number; returns: { resume: number; changed: boolean }[] } {\n // A II opens a kali when its row's singing comes after it, and closes one\n // when the singing comes before; mid-row, it alternates with the last II.\n const first = new Map<string, number>();\n const last = new Map<string, number>();\n tokens.forEach((t, k) => {\n if (!t.timed) return;\n const row = `${t.p}.${t.r}`;\n if (!first.has(row)) first.set(row, k);\n last.set(row, k);\n });\n const opens: number[] = [];\n let opening = false;\n tokens.forEach((t, k) => {\n if (t.note.bar !== 'double') return;\n const row = `${t.p}.${t.r}`;\n const before = (first.get(row) ?? Infinity) < k;\n const after = (last.get(row) ?? -1) > k;\n opening = before !== after ? after : !opening;\n if (opening) opens.push(k);\n });\n\n // A kali ends on a closing II, or on I[ ]I / II[ ]II (an empty `[ ]` among\n // the bars); a run of bars holding IIII ends the song instead.\n const returns: { resume: number; changed: boolean }[] = [];\n for (let k = 0; k < tokens.length; k++) {\n let at = -1;\n let open = false;\n let changed = false;\n let end = false;\n for (; k < tokens.length && !tokens[k]!.timed; k++) {\n const n = tokens[k]!.note;\n if (n.bar === 'quad') end = true;\n if (n.bar === 'double' && !opens.includes(k)) at = k;\n if (edges(n, 'alt', 'open') > 0) open = true;\n if (open && edges(n, 'alt', 'close') > 0) {\n at = k;\n changed = true;\n }\n }\n if (at >= 0 && !end) returns.push({ resume: at + 1, changed });\n }\n\n const top = opens[0];\n const asthayi = top !== undefined && top < (returns[0]?.resume ?? Infinity) ? top : 0;\n // The asthayi ends at its own closing II, or where the next kali opens. A ‖\n // with nothing sung after it is the song's own end (\"এইখানে একেবারে থামিবে\"),\n // not where a return stops.\n const asthayiEnd = Math.min(returns[0]?.resume ?? Infinity, opens.find((k) => k > asthayi) ?? Infinity, tokens.length);\n const lastSung = tokens.reduce((last, t, k) => (t.timed ? k : last), -1);\n const stop = tokens.findIndex(\n (t, k) =>\n k >= asthayi && k < asthayiEnd && k < lastSung && t.note.above?.some((a) => STOP_MARK.test(a.raw ?? '')) === true,\n );\n return { asthayi, stop, returns: stop < 0 ? [] : returns };\n}\n\n/**\n * The `[ ]` notes written above each column, by token: what is sung there on\n * a repeat. A span runs from the above-row `[` to its `]`; a column under it\n * with nothing above keeps its own note.\n */\nfunction changedNotes(tokens: Token[]): Map<number, Note[]> {\n const out = new Map<number, Note[]>();\n let row = '';\n let inside = false;\n tokens.forEach((t, k) => {\n // ponytail: a span is closed at the row's end — no corpus span crosses a row.\n if (row !== `${t.p}.${t.r}`) {\n row = `${t.p}.${t.r}`;\n inside = false;\n }\n const notes: Note[] = [];\n for (const above of t.note.above ?? []) {\n // An older converter left a few cells like `[sfa]` undecoded: read them as it now would.\n const read = above.raw !== undefined && !sounds(above) && above.brackets === undefined;\n for (const a of read ? decodeCell(above.raw!).notes : [above]) {\n if (edges(a, 'alt', 'open') > 0) inside = true;\n if (inside && sounds(a)) notes.push(a);\n if (edges(a, 'alt', 'close') > 0) inside = false;\n }\n }\n if (notes.length > 0) out.set(k, notes);\n });\n return out;\n}\n\n/** The token closing the `( )` that opens at `k`, or -1 if it never closes. */\nfunction skipEnd(tokens: Token[], k: number, hi: number): number {\n for (let depth = 0, j = k; j < hi; j++) {\n depth += edges(tokens[j]!.note, 'skip', 'open') - edges(tokens[j]!.note, 'skip', 'close');\n if (depth <= 0) return j;\n }\n return -1;\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';\nimport { excerptVisits, type Excerpt } from './lyrics.js';\nimport { isSilent, playOrder } from './repeats.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: how far into its তাল cycle the song's first note falls, in\n * schedule beats — the count-in a rhythm plays when it leads (the player's\n * `countIn`), with the melody pushed this much later. Many songs begin\n * part-way through the cycle and sing a pickup before the সম. Found from the\n * notation's সম tick (`1f`/`2f`…), else, for a song that opens on loose notes,\n * from its bars (see loosePickupSam), against the opening taal's numBeats; 0\n * when the song starts on sam.\n */\n pickupBeats: number;\n /** Schedule beat of the song's first সম — where a rhythm that waits for the\n * singer comes in (the default). 0 when the song starts on sam. */\n samBeat: 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 /** Sing the song's form (see playOrder): each `{ }` twice, `( )` only the\n * first time, the `[ ]` notes on the repeat, and back to the asthayi at each\n * kali's end. Default false — the written order, which the staff view and\n * anything grouping events by row rely on; the player and the audio/MIDI\n * exports turn it on. */\n repeats?: boolean;\n /** Play only the notes singing these stretches of the song's text (see\n * alignLyrics / excerptVisits). Taken from the order above. */\n excerpt?: Excerpt;\n}\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/** The spec's `meend` as the corpus's own arc marks; `both` = close, then re-open. */\nconst MEEND_MARKS: Record<NonNullable<Note['meend']>, string> = { start: 'w', end: 'x', both: 'xw' };\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 ? MEEND_MARKS[note.meend] : '';\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/**\n * One line of the sheet as the practice controls move by it: a `part.row`\n * with at least one timed column. It runs from its first event to the next\n * line's (the last one to `totalBeats`), so a line's trailing bars and holds\n * belong to it. A row sung twice (a repeat, a return to the asthayi) is two\n * lines with one `row`.\n */\nexport interface Line {\n /** `part.row` — the viewer's `data-row`. */\n row: string;\n /** Index of the line's first event. */\n first: number;\n beat: number;\n endBeat: number;\n}\n\nconst rowOf = (path: string): string => path.split('.').slice(0, 2).join('.');\nconst noteOf = (path: string): number => Number(path.split('.')[2] ?? 0);\n/** Written order of two `part.row` keys. */\nexport const byRow = (a: string, b: string): number => {\n const [ap = 0, ar = 0] = a.split('.').map(Number);\n const [bp = 0, br = 0] = b.split('.').map(Number);\n return ap - bp || ar - br;\n};\n\n/** The schedule's events grouped into sheet lines, in play order. A row of\n * bars and annotations only has no events, so it is not a line; a repeat that\n * jumps back within a row starts a new one. */\nexport function scheduleLines(schedule: Pick<Schedule, 'events' | 'totalBeats'>): Line[] {\n const lines: Line[] = [];\n let note = Infinity;\n schedule.events.forEach((ev, i) => {\n const n = noteOf(ev.path);\n if (lines.at(-1)?.row !== rowOf(ev.path) || n < note) lines.push({ row: rowOf(ev.path), first: i, beat: ev.beat, endBeat: 0 });\n note = n;\n });\n lines.forEach((line, k) => (line.endBeat = lines[k + 1]?.beat ?? schedule.totalBeats));\n return lines;\n}\n\n/** Index of the line holding event `i`. */\nexport function lineAt(lines: Line[], i: number): number {\n let k = 0;\n while (k + 1 < lines.length && lines[k + 1]!.first <= i) k++;\n return k;\n}\n\n/**\n * The line `path` (`part.row[.note]`) names — its own row, else the next row\n * that sounds — sung at the pass nearest line `near` (-1: the first pass).\n * -1 when no line is at or after it.\n */\nexport function lineFor(lines: Line[], path: string, near: number): number {\n let row: string | undefined;\n for (const line of lines) if (byRow(line.row, rowOf(path)) >= 0 && (row === undefined || byRow(line.row, row) < 0)) row = line.row;\n let best = -1;\n lines.forEach((line, k) => {\n if (line.row === row && (best < 0 || Math.abs(k - near) < Math.abs(best - near))) best = k;\n });\n return best;\n}\n\n/**\n * The event a tap or a seek to `path` (`part.row.note`, or `part.row` for the\n * line's start) should start from: the first event at or after that note in\n * its own line, else the line's last one — a closing bar belongs to its line,\n * not the next — else the next line's first. A line sung more than once\n * resolves to the pass nearest event `near` (default: the first pass). A note\n * reached by a মীড় glide has no attack of its own, so the glide's source is\n * returned instead. -1 when nothing sounds from there on.\n */\nexport function eventAt(events: PlayEvent[], path: string, near = -1): number {\n const lines = scheduleLines({ events, totalBeats: 0 });\n const k = lineFor(lines, path, near < 0 ? -1 : lineAt(lines, near));\n if (k < 0) return -1;\n const end = lines[k + 1]?.first ?? events.length;\n let i = lines[k]!.first;\n if (lines[k]!.row === rowOf(path)) while (i < end - 1 && noteOf(events[i]!.path) < noteOf(path)) i++;\n while (i > 0 && events[i]!.glide === true) i--;\n return i;\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 * Where the first সম falls in a song that marks none but opens on loose notes\n * (a pickup): `[part, row, note]`. Swarabitan closes each cycle with `I`\n * (\"তালের এক ফেরা হইয়া গেলে … I\"), opens a section with `II`, and sings what\n * stands before the asthayi's `II` once; NLTR sets each line as whole cycles,\n * with the pickup right-aligned before them. So the first line that opens on\n * a bar starts on sam — else (a creator line never opens on one) the first\n * `I` closes the pickup. Over the 116 loose-note songs that do mark sam, this\n * finds it in 112; the rest are source slips. Undefined for a song that opens\n * on a bar: one that starts mid-cycle there is 7 in 948, all matra slips.\n */\nfunction loosePickupSam(doc: Swaralipi): [number, number, number] | undefined {\n const rows = doc.parts.flatMap((part, p) => part.rows.map((row, r) => ({ p, r, notes: row.notes })));\n const opener = (notes: Note[]) => notes.find((n) => n.bar !== undefined || !isSilent(n));\n const first = rows.findIndex((row) => opener(row.notes) !== undefined);\n if (first < 0 || opener(rows[first]!.notes)!.bar !== undefined) return undefined;\n const line = rows.slice(first + 1).find((row) => opener(row.notes)?.bar !== undefined);\n if (line !== undefined) return [line.p, line.r, 0];\n for (const { p, r, notes } of rows) {\n const i = notes.findIndex((n) => n.bar === 'single');\n if (i >= 0) return [p, r, i];\n }\n return undefined;\n}\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 isSilent) instead plays as a\n * rest, so it keeps its matra and the তাল stays aligned.\n *\n * Written order by default; with `repeats` the walk follows the sung order\n * (playOrder), so a column sung twice is two events with one path.\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\n // লয় and তাল: a row's mark holds until another row changes it, in written\n // order — so a repeat or a return to the asthayi sings each row at its own\n // tempo and taal, not at whatever the jump came from. `scale` < 1 stretches\n // the written matra over more schedule beats, which is what makes the\n // passage 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 const rows = new Map<string, { scale: number; lay: Lay; taal: Taal | null | undefined; glides: Set<number> }>();\n let scale = 1;\n let layNow: Lay = 'drut';\n let taal: Taal | null | undefined = doc.meta.taal;\n doc.parts.forEach((part, p) =>\n part.rows.forEach((row, r) => {\n const lay = withLay ? rowLay(row) : undefined;\n if (lay !== undefined && speeds[lay] !== scale) {\n scale = speeds[lay];\n layNow = lay;\n }\n if (row.taal !== undefined) taal = row.taal;\n rows.set(`${p}.${r}`, { scale, lay: layNow, taal, glides: meendContinuations(row.notes) });\n }),\n );\n\n const order =\n opts.repeats === true\n ? playOrder(doc)\n : doc.parts.flatMap((part, p) => part.rows.flatMap((row, r) => row.notes.map((note, i) => ({ p, r, i, note }))));\n // Each visit's matra from the song's start, and the first সম's (a SAM_TICK,\n // else loosePickupSam's first visit) — *written* matra, so the anacrusis\n // maths stays independent of the লয়, and an excerpt keeps every note on the\n // beat it has in the song.\n const at = [0];\n const loose = loosePickupSam(doc);\n let tick: number | undefined;\n let inferred: number | undefined;\n for (const { p, r, i, note } of order) {\n const m = at[at.length - 1]!;\n if (tick === undefined && note.above?.some((a) => a.raw !== undefined && SAM_TICK.test(a.raw))) tick = m;\n if (inferred === undefined && loose !== undefined && p === loose[0] && r === loose[1] && i >= loose[2]) inferred = m;\n at.push(note.bar === undefined && !isSilent(note) ? m + (note.durationMatra ?? 1) : m);\n }\n const samMatra = tick ?? inferred;\n const { visits, matra } = opts.excerpt\n ? excerptVisits(order, at, opts.excerpt, (v) => rows.get(`${v.p}.${v.r}`)!.taal?.numBeats)\n : { visits: order, matra: 0 };\n let rowNow = '';\n for (const { p, r, i, note } of visits) {\n const row = rows.get(`${p}.${r}`)!;\n if (rowNow !== `${p}.${r}`) {\n rowNow = `${p}.${r}`;\n // The two marks are independent, so a segment starts wherever either moves.\n const last = tempo[tempo.length - 1]!;\n if (row.scale !== last.scale || !sameTaal(row.taal, last.taal)) {\n const seg: TempoSegment = { beat: cursor, endBeat: cursor, scale: row.scale, lay: row.lay, taal: row.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 }\n if (note.bar !== undefined || isSilent(note)) continue;\n const path = `${p}.${r}.${i}`;\n const written = note.durationMatra ?? 1;\n const dur = written / row.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 ...(row.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 }\n\n // Anacrusis: if the first note is at matra M and sam at S in an N-beat\n // cycle, the first note falls (M − S) mod N into the cycle — the count-in a\n // leading rhythm plays — and the next sam is N less that after it (0 when the\n // song starts on sam). No সম at all: the first note is sam. The opening\n // taal's cycle, since that is the one the pickup is sung in. Snapped, since\n // 1/3 and 1/5 matra never sum to an exact integer.\n const numBeats = tempo[0]!.taal?.numBeats;\n let pickupMatra = 0;\n if (numBeats !== undefined && numBeats > 0) {\n pickupMatra = (((matra - (samMatra ?? 0)) % numBeats) + numBeats) % numBeats;\n if (pickupMatra < 1e-6 || pickupMatra > numBeats - 1e-6) pickupMatra = 0;\n }\n tempo[tempo.length - 1]!.endBeat = cursor;\n\n // Both are played at the tempo the song *starts* in.\n const opening = tempo[0]!.scale;\n return {\n events,\n totalBeats: cursor,\n pickupBeats: pickupMatra / opening,\n samBeat: pickupMatra > 0 ? (numBeats! - pickupMatra) / opening : 0,\n tempo,\n };\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). The CDN set has no F#4 (404).\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', '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"]}
@@ -1,4 +1,4 @@
1
- import { swaralipiSchema, SPEC_VERSION } from './chunk-CJI7A6AM.js';
1
+ import { swaralipiSchema, SPEC_VERSION } from './chunk-J5JYJL5N.js';
2
2
  import { decodeCell } from './chunk-BSZ3WQUP.js';
3
3
  import { isTaalFerta, TAAL_FERTA, TAALS } from './chunk-K73Y46WU.js';
4
4
 
@@ -431,5 +431,5 @@ function convertNltrHtml(html, opts = {}) {
431
431
  }
432
432
 
433
433
  export { ExtractError, aggregate, assemble, bnInt, buildSystems, convertNltrHtml, deriveGeometry, extractPage, formatSummary, resolveTaal, splitLyric };
434
- //# sourceMappingURL=chunk-6AKH2NLW.js.map
435
- //# sourceMappingURL=chunk-6AKH2NLW.js.map
434
+ //# sourceMappingURL=chunk-WBYJUYI6.js.map
435
+ //# sourceMappingURL=chunk-WBYJUYI6.js.map