@ugfoundation/swaralipi-js 0.12.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/dist/{batch-ZNVWIGEL.js → batch-XVQRHLTN.js} +5 -4
- package/dist/batch-XVQRHLTN.js.map +1 -0
- package/dist/chunk-BSZ3WQUP.js +164 -0
- package/dist/chunk-BSZ3WQUP.js.map +1 -0
- package/dist/{chunk-A7R3VFIV.js → chunk-GKS4WZZ6.js} +3 -3
- package/dist/{chunk-A7R3VFIV.js.map → chunk-GKS4WZZ6.js.map} +1 -1
- package/dist/{chunk-A6Z7X2LX.js → chunk-L6YZ3GC6.js} +227 -63
- package/dist/chunk-L6YZ3GC6.js.map +1 -0
- package/dist/{chunk-ISNAL632.js → chunk-MORX7G6E.js} +5 -165
- package/dist/chunk-MORX7G6E.js.map +1 -0
- package/dist/cli.cjs +1 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/convert/index.cjs +1 -1
- package/dist/convert/index.cjs.map +1 -1
- package/dist/convert/index.js +3 -2
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/player/index.cjs +470 -122
- package/dist/player/index.cjs.map +1 -1
- package/dist/player/index.d.cts +12 -2
- package/dist/player/index.d.ts +12 -2
- package/dist/player/index.js +85 -60
- package/dist/player/index.js.map +1 -1
- package/dist/{schedule-DN-8X17d.d.ts → schedule-ClyON98C.d.ts} +22 -10
- package/dist/{schedule-CRpjr6JH.d.cts → schedule-V-fgepvP.d.cts} +22 -10
- package/dist/spec/index.cjs +1 -1
- package/dist/spec/index.cjs.map +1 -1
- package/dist/spec/index.d.cts +1 -1
- package/dist/spec/index.d.ts +1 -1
- package/dist/spec/index.js +1 -1
- package/dist/viewer/index.cjs +366 -61
- package/dist/viewer/index.cjs.map +1 -1
- package/dist/viewer/index.d.cts +1 -1
- package/dist/viewer/index.d.ts +1 -1
- package/dist/viewer/index.js +2 -1
- package/dist/viewer/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/batch-ZNVWIGEL.js.map +0 -1
- package/dist/chunk-A6Z7X2LX.js.map +0 -1
- package/dist/chunk-ISNAL632.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
<img src="assets/banner.svg" alt="swaralipi-js" width="100%">
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@ugfoundation/swaralipi-js)
|
|
4
|
+
[](https://www.npmjs.com/package/@ugfoundation/swaralipi-js)
|
|
5
|
+
[](https://x.com/gitabitan_io)
|
|
6
|
+
|
|
3
7
|
`@ugfoundation/swaralipi-js` — a JSON spec, a Unicode-Bengali viewer, a Tone.js player, and an archival-HTML converter for **Rabindrasangeet swaralipi** (Tagore's আকারমাত্রিক / *akarmatrik* notation).
|
|
4
8
|
|
|
5
9
|
The spec is the contract: write (or generate) a swaralipi **JSON document**, then hand the same document to the viewer, the player, or the printer. Built for [gitabitan.io](https://github.com/UGFoundation), published standalone under GPL-2.0-only.
|
|
@@ -244,6 +248,8 @@ Controls: `setTonic('C#')`, `setBpm(120)`, `setInstrument(id)`, `setTaal(id|null
|
|
|
244
248
|
|
|
245
249
|
The pure, audio-free scheduler is exported too (`buildSchedule`, `svaraToMidi`, `parseTonic`, `INSTRUMENTS`, …) for a custom transport.
|
|
246
250
|
|
|
251
|
+
**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).
|
|
252
|
+
|
|
247
253
|
### Practice: start anywhere, repeat a line, sing it back
|
|
248
254
|
|
|
249
255
|
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:
|
|
@@ -259,7 +265,7 @@ player.setEcho(true); // after each pass, one silent pass: tabla + tanp
|
|
|
259
265
|
// the highlight moves hollow — the learner's turn to sing it
|
|
260
266
|
```
|
|
261
267
|
|
|
262
|
-
A repeat follows the playhead (tap or step to another line and it moves there), and a tempo or tonic change keeps both the repeat and the place. Wire buttons to the same calls and mirror them with `onLoop: ({ loop, echo, yourTurn }) => …`. A tap is a cancelable `swaralipi-seek` event on the sheet — `preventDefault()` it in a capturing listener to gate a play (a quota, say). `previewLimitSec` counts listening time, so repeats cannot run past it.
|
|
268
|
+
A line sung more than once (a `{ }`, a return to the asthayi) plays from the pass nearest the playhead, and `↑` `↓` walk the sung order. A repeat follows the playhead (tap or step to another line and it moves there), and a tempo or tonic change keeps both the repeat and the place. Wire buttons to the same calls and mirror them with `onLoop: ({ loop, echo, yourTurn }) => …`. A tap is a cancelable `swaralipi-seek` event on the sheet — `preventDefault()` it in a capturing listener to gate a play (a quota, say). `previewLimitSec` counts listening time, so repeats cannot run past it.
|
|
263
269
|
|
|
264
270
|
### Export audio (WAV / MP3)
|
|
265
271
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { convertNltrHtml, aggregate, formatSummary } from './chunk-
|
|
2
|
-
import './chunk-
|
|
1
|
+
import { convertNltrHtml, aggregate, formatSummary } from './chunk-MORX7G6E.js';
|
|
2
|
+
import './chunk-GKS4WZZ6.js';
|
|
3
|
+
import './chunk-BSZ3WQUP.js';
|
|
3
4
|
import './chunk-KOHSKHXI.js';
|
|
4
5
|
import { stat, readdir, mkdir, readFile, writeFile } from 'fs/promises';
|
|
5
6
|
import { join, basename } from 'path';
|
|
@@ -43,5 +44,5 @@ async function runConvert(input, opts = {}) {
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
export { runConvert };
|
|
46
|
-
//# sourceMappingURL=batch-
|
|
47
|
-
//# sourceMappingURL=batch-
|
|
47
|
+
//# sourceMappingURL=batch-XVQRHLTN.js.map
|
|
48
|
+
//# sourceMappingURL=batch-XVQRHLTN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/convert/batch.ts"],"names":["report"],"mappings":"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","file":"batch-XVQRHLTN.js","sourcesContent":["/**\n * File/dir conversion driver for the CLI (Node-only; not part of the\n * public `/convert` export). Continues past per-file failures and reports\n * them in the aggregate.\n */\nimport { mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises';\nimport { basename, join } from 'node:path';\nimport { convertNltrHtml } from './index.js';\nimport { aggregate, formatSummary, type BatchFailure, type BatchReport, type FileReport } from './report.js';\n\nexport interface BatchOptions {\n /** Output file (single input) or directory (directory input). */\n out?: string;\n /** Write the aggregate report JSON here. */\n reportPath?: string;\n compact?: boolean;\n quiet?: boolean;\n /** Progress/summary sink (default stderr). */\n log?: (line: string) => void;\n}\n\nconst nltrIdOf = (file: string): number | undefined => {\n const m = basename(file).match(/^(\\d+)\\.html$/);\n return m ? Number(m[1]) : undefined;\n};\n\nconst serialize = (doc: unknown, compact: boolean | undefined): string =>\n (compact ? JSON.stringify(doc) : JSON.stringify(doc, null, 2)) + '\\n';\n\nexport async function runConvert(\n input: string,\n opts: BatchOptions = {},\n): Promise<{ exitCode: 0 | 1; report: BatchReport }> {\n const log = opts.log ?? ((line: string) => process.stderr.write(line + '\\n'));\n const stats = await stat(input);\n const files = stats.isDirectory()\n ? (await readdir(input))\n .filter((f) => f.endsWith('.html'))\n .sort((a, b) => (nltrIdOf(a) ?? 0) - (nltrIdOf(b) ?? 0) || a.localeCompare(b))\n .map((f) => join(input, f))\n : [input];\n\n const isDir = stats.isDirectory();\n if (isDir && !opts.out) throw new Error('directory input requires -o <outDir>');\n if (isDir && opts.out) await mkdir(opts.out, { recursive: true });\n\n const reports: FileReport[] = [];\n const failures: BatchFailure[] = [];\n for (const file of files) {\n try {\n const html = await readFile(file, 'utf8');\n const { doc, report } = convertNltrHtml(html, { nltrId: nltrIdOf(file) });\n reports.push(report);\n const json = serialize(doc, opts.compact);\n if (isDir) {\n const name = `${nltrIdOf(file) ?? basename(file, '.html')}.json`;\n await writeFile(join(opts.out!, name), json);\n } else if (opts.out) {\n await writeFile(opts.out, json);\n } else {\n process.stdout.write(json);\n }\n } catch (err) {\n failures.push({ file: basename(file), error: err instanceof Error ? err.message : String(err) });\n }\n }\n\n const report = aggregate(reports, failures);\n if (opts.reportPath) await writeFile(opts.reportPath, serialize(report, false));\n if (!opts.quiet) log(formatSummary(report));\n return { exitCode: failures.length > 0 ? 1 : 0, report };\n}\n"]}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// src/convert/codec.ts
|
|
2
|
+
var LETTERS = {
|
|
3
|
+
s: { letter: "s" },
|
|
4
|
+
r: { letter: "r" },
|
|
5
|
+
g: { letter: "g" },
|
|
6
|
+
m: { letter: "m" },
|
|
7
|
+
p: { letter: "p" },
|
|
8
|
+
q: { letter: "d" },
|
|
9
|
+
// ধ — shuddha dha
|
|
10
|
+
n: { letter: "n" },
|
|
11
|
+
d: { letter: "d", komal: true },
|
|
12
|
+
// দ
|
|
13
|
+
t: { letter: "g", komal: true },
|
|
14
|
+
// জ্ঞ
|
|
15
|
+
k: { letter: "m", tivra: true },
|
|
16
|
+
// ক্ষ
|
|
17
|
+
u: { letter: "n", komal: true },
|
|
18
|
+
// ণ
|
|
19
|
+
v: { letter: "r", komal: true }
|
|
20
|
+
// ঋ
|
|
21
|
+
};
|
|
22
|
+
var STRUCTURALS = {
|
|
23
|
+
"%": () => ({ svara: ["madhyam", "s"] }),
|
|
24
|
+
// precomposed সা
|
|
25
|
+
"&": () => ({ svara: ["madhyam", "n"] })
|
|
26
|
+
// precomposed নি
|
|
27
|
+
};
|
|
28
|
+
var BAR_RUN_RE = /^(-)?([lLA[\]]+)$/;
|
|
29
|
+
function decodeBarRun(body) {
|
|
30
|
+
const m = body.match(BAR_RUN_RE);
|
|
31
|
+
if (!m || !/[lLA]/.test(m[2])) return void 0;
|
|
32
|
+
const notes = [];
|
|
33
|
+
if (m[1]) notes.push({ sustain: true });
|
|
34
|
+
const run = m[2];
|
|
35
|
+
let i = 0;
|
|
36
|
+
while (i < run.length) {
|
|
37
|
+
const ch = run[i];
|
|
38
|
+
if (ch === "l" || ch === "L") {
|
|
39
|
+
let j = i;
|
|
40
|
+
while (j < run.length && run[j] === ch) j++;
|
|
41
|
+
const strokes = (j - i) * (ch === "L" ? 2 : 1);
|
|
42
|
+
notes.push({ bar: strokes >= 4 ? "quad" : strokes >= 2 ? "double" : "single" });
|
|
43
|
+
i = j;
|
|
44
|
+
} else if (ch === "A") {
|
|
45
|
+
notes.push({ bar: "dari" });
|
|
46
|
+
i++;
|
|
47
|
+
} else {
|
|
48
|
+
notes.push({ brackets: [{ kind: "alt", edge: ch === "[" ? "open" : "close" }] });
|
|
49
|
+
i++;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return notes;
|
|
53
|
+
}
|
|
54
|
+
var KNOWN_RAW_RE = [
|
|
55
|
+
/^[wxyjEJ$<>=_]+$/,
|
|
56
|
+
// arc & line drawing segments
|
|
57
|
+
/^\|+$/,
|
|
58
|
+
// thin yugal-dari stop mark
|
|
59
|
+
/^[0-9!@#]{1,2}[fF]?:?$/,
|
|
60
|
+
// beat-number labels incl. 10–16 (sam tick = f/F, e.g. 1f / 1F / 2:)
|
|
61
|
+
/^[oOe`+,.;:iI~^*bcBCWXYFHZz'"?]$/
|
|
62
|
+
// dots, ticks, duration marks, legacy forms
|
|
63
|
+
];
|
|
64
|
+
var OPEN_BRACKETS = {
|
|
65
|
+
"{": "repeat",
|
|
66
|
+
"(": "skip",
|
|
67
|
+
"[": "alt"
|
|
68
|
+
};
|
|
69
|
+
var CLOSE_BRACKETS = {
|
|
70
|
+
"}": "repeat",
|
|
71
|
+
")": "skip",
|
|
72
|
+
"]": "alt"
|
|
73
|
+
};
|
|
74
|
+
function octaveOf(mark) {
|
|
75
|
+
if (mark === "f" || mark === "F") return "taar";
|
|
76
|
+
if (mark === "h" || mark === "H") return "mandra";
|
|
77
|
+
return "madhyam";
|
|
78
|
+
}
|
|
79
|
+
var BODY_RE = /^([0-9!@][fF]?)?([zj]*)(-)?([i:])?(?:([SRGMPQNDTKUV])([fFhH])?)?((?:[srgmpqndtkuv][fh]?[i:]?)+)?(a)?([fh])?([i:])?(?:([SRGMPQNDTKUV])([fFhH])?)?([0-9!@][fF]?)?(-)?$/;
|
|
80
|
+
function decodeChunk(chunk) {
|
|
81
|
+
const opens = [];
|
|
82
|
+
const closes = [];
|
|
83
|
+
let body = chunk;
|
|
84
|
+
while (body.length > 0) {
|
|
85
|
+
const kind = OPEN_BRACKETS[body[0]];
|
|
86
|
+
if (kind === void 0) break;
|
|
87
|
+
opens.push(kind);
|
|
88
|
+
body = body.slice(1);
|
|
89
|
+
}
|
|
90
|
+
while (body.length > 0) {
|
|
91
|
+
const kind = CLOSE_BRACKETS[body[body.length - 1]];
|
|
92
|
+
if (kind === void 0) break;
|
|
93
|
+
closes.unshift(kind);
|
|
94
|
+
body = body.slice(0, -1);
|
|
95
|
+
}
|
|
96
|
+
const finish = (notes, kind) => {
|
|
97
|
+
if (notes.length === 0) notes.push({});
|
|
98
|
+
const first = notes[0];
|
|
99
|
+
const last = notes[notes.length - 1];
|
|
100
|
+
if (opens.length > 0) first.brackets = [...opens.map((kind_) => ({ kind: kind_, edge: "open" })), ...first.brackets ?? []];
|
|
101
|
+
if (closes.length > 0) last.brackets = [...last.brackets ?? [], ...closes.map((kind_) => ({ kind: kind_, edge: "close" }))];
|
|
102
|
+
return { notes, kind };
|
|
103
|
+
};
|
|
104
|
+
if (body === "") return finish([{}], "decoded");
|
|
105
|
+
const barRun = decodeBarRun(body);
|
|
106
|
+
if (barRun) return finish(barRun, "decoded");
|
|
107
|
+
const structural = STRUCTURALS[body];
|
|
108
|
+
if (structural) return finish([structural()], "decoded");
|
|
109
|
+
if (KNOWN_RAW_RE.some((re) => re.test(body))) return finish([{}], "known");
|
|
110
|
+
const m = body.match(BODY_RE);
|
|
111
|
+
if (m) {
|
|
112
|
+
const [, , , hyphen, , kanLetter, kanOct, unitsStr, aakar, postOct, , trailKan, trailKanOct] = m;
|
|
113
|
+
const attachKan = (note, letter, oct) => {
|
|
114
|
+
const def = LETTERS[letter.toLowerCase()];
|
|
115
|
+
note.kan = [octaveOf(oct?.toLowerCase()), def.letter];
|
|
116
|
+
};
|
|
117
|
+
if (unitsStr) {
|
|
118
|
+
const units = unitsStr.match(/[srgmpqndtkuv][fh]?/g) ?? [];
|
|
119
|
+
const notes = units.map((unit) => {
|
|
120
|
+
const def = LETTERS[unit[0]];
|
|
121
|
+
const note = { svara: [octaveOf(unit[1]), def.letter] };
|
|
122
|
+
if (def.komal) note.komal = true;
|
|
123
|
+
if (def.tivra) note.tivra = true;
|
|
124
|
+
if (units.length > 1) note.durationMatra = 1 / units.length;
|
|
125
|
+
return note;
|
|
126
|
+
});
|
|
127
|
+
const last = notes[notes.length - 1];
|
|
128
|
+
if (postOct && last.svara[0] === "madhyam") {
|
|
129
|
+
last.svara = [octaveOf(postOct), last.svara[1]];
|
|
130
|
+
}
|
|
131
|
+
if (hyphen) notes[0].sustain = true;
|
|
132
|
+
if (kanLetter) attachKan(notes[0], kanLetter, kanOct);
|
|
133
|
+
if (trailKan && last.kan === void 0) attachKan(last, trailKan, trailKanOct);
|
|
134
|
+
return finish(notes, "decoded");
|
|
135
|
+
}
|
|
136
|
+
if (aakar) {
|
|
137
|
+
const note = hyphen ? { sustain: true } : { rest: true };
|
|
138
|
+
if (trailKan) attachKan(note, trailKan, trailKanOct);
|
|
139
|
+
else if (kanLetter) attachKan(note, kanLetter, kanOct);
|
|
140
|
+
return finish([note], "decoded");
|
|
141
|
+
}
|
|
142
|
+
if (hyphen && !kanLetter && !trailKan) {
|
|
143
|
+
return finish([{ sustain: true }], "decoded");
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return finish([{}], "unknown");
|
|
147
|
+
}
|
|
148
|
+
function decodeCell(text) {
|
|
149
|
+
const chunks = text.split(/\s+/).filter((c) => c.length > 0);
|
|
150
|
+
const out = { notes: [], unknownTokens: [], knownRawTokens: [], decoded: false };
|
|
151
|
+
for (const chunk of chunks) {
|
|
152
|
+
const { notes, kind } = decodeChunk(chunk);
|
|
153
|
+
notes[0].raw = chunk;
|
|
154
|
+
if (kind === "decoded") out.decoded = true;
|
|
155
|
+
else if (kind === "known") out.knownRawTokens.push(chunk);
|
|
156
|
+
else out.unknownTokens.push(chunk);
|
|
157
|
+
out.notes.push(...notes);
|
|
158
|
+
}
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export { decodeCell };
|
|
163
|
+
//# sourceMappingURL=chunk-BSZ3WQUP.js.map
|
|
164
|
+
//# sourceMappingURL=chunk-BSZ3WQUP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/convert/codec.ts"],"names":[],"mappings":"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","file":"chunk-BSZ3WQUP.js","sourcesContent":["/**\n * The Swarabitan ASCII codec: decode one grid-cell text into spec `Note`s.\n *\n * NLTR renders notation through the legacy \"Swarabitan\" webfont, which maps\n * ASCII to akarmatrik glyphs. The table below was derived by dumping every\n * glyph of `Swarabitan.woff` (tools/font-dump/) and cross-checking against\n * the Swarabitan legend scans, the rendered reference screenshot of song\n * 4270, and corpus-wide token statistics. Evidence per assignment:\n *\n * Letters (full-size glyphs):\n * s=স r=র g=গ m=ম p=প q=ধ n=ন — the seven shuddha letters\n * d=দ (komal dha) t=জ্ঞ (komal ga) — legend: কোমল র—ঋ, কোমল গ—জ্ঞ,\n * k=ক্ষ (tivra ma) u=ণ (komal ni) — কড়ি ম—ক্ষ, কোমল ধ—দ, কোমল ন—ণ\n * v=ঋ (komal re)\n * Modifiers:\n * f = রেফ above → taar octave (zero-width high combining mark)\n * h = হসন্ত below → mandra octave (low combining mark; corpus nh/uh/dh/ph)\n * leading '-' = no new vowel attack (melisma; lyric row shows ৹)\n * trailing 'a' = the আ-কার matra carrier; k letter-units in one cell\n * share it → k notes of 1/k matra each\n * UPPERCASE letter = small raised form = কণ (kan/grace) prefix; takes its\n * own f/h (corpus: Sfua, NHsa, QHpha); width ratios ~0.65\n * confirm uppercase↔lowercase pairing\n * Structurals (own cells):\n * A = thin full-height bar → দাঁড়ি vibhag separator (fixed columns)\n * l = serif danda I → cycle end; ll / single-glyph L = section\n * %=সা &=নি — precomposed one-matra forms (rare)\n * Known-but-kept-raw marks (drawn pieces the spec doesn't model yet):\n * w x = left/right ends of a long under-arc; E J j y $ < > = _ = arc/line\n * fill segments; | = thin yugal-dari stop mark (mostly annotation rows);\n * digits/!/@ (+f/F sam tick) = taal beat labels; b=× o O e ` + , . i : ;\n * = dots/ticks/duration marks; B C * ^ ~ c = rare precomposed/legacy forms.\n *\n * Decoding never throws: any chunk outside the grammar becomes a raw-only\n * note and is reported as unknown — zero data loss by construction.\n */\nimport type { Note, Octave, Svara, SvaraLetter } from '../spec/index.js';\n\nexport interface CellDecode {\n notes: Note[];\n /** Chunks that failed the grammar (reported, kept as raw notes). */\n unknownTokens: string[];\n /** Chunks understood but intentionally left raw (marks, beat labels …). */\n knownRawTokens: string[];\n /** True if at least one chunk decoded to a semantic note. */\n decoded: boolean;\n}\n\ninterface LetterDef {\n letter: SvaraLetter;\n komal?: true;\n tivra?: true;\n}\n\n/** ASCII → svara letter (+ komal/tivra). The five non-shuddha glyph letters\n * are distinct characters in akarmatrik, so the flags live here. */\nconst LETTERS: Record<string, LetterDef> = {\n s: { letter: 's' },\n r: { letter: 'r' },\n g: { letter: 'g' },\n m: { letter: 'm' },\n p: { letter: 'p' },\n q: { letter: 'd' }, // ধ — shuddha dha\n n: { letter: 'n' },\n d: { letter: 'd', komal: true }, // দ\n t: { letter: 'g', komal: true }, // জ্ঞ\n k: { letter: 'm', tivra: true }, // ক্ষ\n u: { letter: 'n', komal: true }, // ণ\n v: { letter: 'r', komal: true }, // ঋ\n};\n\n/** Standalone structural tokens (bars are handled by the bar-run rule). */\nconst STRUCTURALS: Record<string, () => Note> = {\n '%': () => ({ svara: ['madhyam', 's'] }), // precomposed সা\n '&': () => ({ svara: ['madhyam', 'n'] }), // precomposed নি\n};\n\n/**\n * Bar/bracket runs: `l`(I danda) `ll`/`L`(II) `llll`/`LL`(IIII) `A`(দাঁড়ি),\n * plus composites the corpus writes into one cell — `l[]l` / `L[]L` (the\n * legend's \"I [ ] I\" return-to-asthayi-with-changed-notes marker), `AA`,\n * and `-A` (the previous note's continuation dash sharing the dari's cell).\n */\nconst BAR_RUN_RE = /^(-)?([lLA[\\]]+)$/;\n\nfunction decodeBarRun(body: string): Note[] | undefined {\n const m = body.match(BAR_RUN_RE);\n if (!m || !/[lLA]/.test(m[2]!)) return undefined;\n const notes: Note[] = [];\n if (m[1]) notes.push({ sustain: true });\n const run = m[2]!;\n let i = 0;\n while (i < run.length) {\n const ch = run[i]!;\n if (ch === 'l' || ch === 'L') {\n let j = i;\n while (j < run.length && run[j] === ch) j++;\n const strokes = (j - i) * (ch === 'L' ? 2 : 1);\n notes.push({ bar: strokes >= 4 ? 'quad' : strokes >= 2 ? 'double' : 'single' });\n i = j;\n } else if (ch === 'A') {\n notes.push({ bar: 'dari' });\n i++;\n } else {\n notes.push({ brackets: [{ kind: 'alt', edge: ch === '[' ? 'open' : 'close' }] });\n i++;\n }\n }\n return notes;\n}\n\n/** Marks/labels that are understood but deliberately preserved raw-only. */\nconst KNOWN_RAW_RE = [\n /^[wxyjEJ$<>=_]+$/, // arc & line drawing segments\n /^\\|+$/, // thin yugal-dari stop mark\n /^[0-9!@#]{1,2}[fF]?:?$/, // beat-number labels incl. 10–16 (sam tick = f/F, e.g. 1f / 1F / 2:)\n /^[oOe`+,.;:iI~^*bcBCWXYFHZz'\"?]$/, // dots, ticks, duration marks, legacy forms\n];\n\nconst OPEN_BRACKETS: Record<string, 'repeat' | 'skip' | 'alt'> = {\n '{': 'repeat',\n '(': 'skip',\n '[': 'alt',\n};\nconst CLOSE_BRACKETS: Record<string, 'repeat' | 'skip' | 'alt'> = {\n '}': 'repeat',\n ')': 'skip',\n ']': 'alt',\n};\n\nfunction octaveOf(mark: string | undefined): Octave {\n if (mark === 'f' || mark === 'F') return 'taar';\n if (mark === 'h' || mark === 'H') return 'mandra';\n return 'madhyam';\n}\n\n/**\n * Body grammar of one chunk (brackets already peeled):\n *\n * [beat-digit] ['-'] [KAN] unit* ['a'] [fh] [i:] [KAN] [beat-digit]\n *\n * where unit = letter [fh] and KAN = uppercase letter [fFhH]. Corpus\n * realities beyond the canonical `[-]KAN unit+ a` shape:\n * - octave mark typed after the আ-কার: `saf` ≡ `sfa` (taar sa) — the reph\n * applies to the last unit;\n * - trailing `i` (two stacked rings, ঃ-shaped) or `:` (two lozenges — a\n * different glyph) after the matra: a **matra sign worth ½**, the আ-কার's\n * half-brother (legend `রাঃ` = 1½, `সঃ` = ½; corpus rows fit their taal\n * cycle 114:19 in favour of that reading over \"articulation only\"). The\n * durations below still ignore it — they split the cell evenly among its\n * units, so `qinqi` decodes ⅓⅓⅓ where the sheet says ½ ¼ ¼. The viewer\n * reads the marks off `raw` and draws them right (viewer/render.ts\n * `rawMarks`); playback is what the even split still gets wrong;\n * - trailing KAN (`-aR`): an anticipatory grace drawn at the cell's right\n * edge, leading into the next note — attached to this cell's last note;\n * - the cut mark may also lead or sit between units (`-iqi`, `mpimi`);\n * - leading `z` = blank spacer glyph (empty outline in the font) — skipped.\n * A standalone detached `a` (no hyphen) is a 1-matra rest per the legend\n * (\"হাইফেন-বর্জিত … বিরামের মাত্রা\").\n */\nconst BODY_RE =\n /^([0-9!@][fF]?)?([zj]*)(-)?([i:])?(?:([SRGMPQNDTKUV])([fFhH])?)?((?:[srgmpqndtkuv][fh]?[i:]?)+)?(a)?([fh])?([i:])?(?:([SRGMPQNDTKUV])([fFhH])?)?([0-9!@][fF]?)?(-)?$/;\n\nfunction decodeChunk(chunk: string): { notes: Note[]; kind: 'decoded' | 'known' | 'unknown' } {\n // 1. Peel brackets.\n const opens: ('repeat' | 'skip' | 'alt')[] = [];\n const closes: ('repeat' | 'skip' | 'alt')[] = [];\n let body = chunk;\n while (body.length > 0) {\n const kind = OPEN_BRACKETS[body[0]!];\n if (kind === undefined) break;\n opens.push(kind);\n body = body.slice(1);\n }\n while (body.length > 0) {\n const kind = CLOSE_BRACKETS[body[body.length - 1]!];\n if (kind === undefined) break;\n closes.unshift(kind);\n body = body.slice(0, -1);\n }\n\n const finish = (notes: Note[], kind: 'decoded' | 'known' | 'unknown') => {\n if (notes.length === 0) notes.push({});\n const first = notes[0]!;\n const last = notes[notes.length - 1]!;\n if (opens.length > 0) first.brackets = [...opens.map((kind_) => ({ kind: kind_, edge: 'open' as const })), ...(first.brackets ?? [])];\n if (closes.length > 0) last.brackets = [...(last.brackets ?? []), ...closes.map((kind_) => ({ kind: kind_, edge: 'close' as const }))];\n return { notes, kind };\n };\n\n // 2. Bracket-only chunk → raw carrier for the bracket edges.\n if (body === '') return finish([{}], 'decoded');\n\n // 3. Bar/bracket runs, then the remaining standalone structurals.\n const barRun = decodeBarRun(body);\n if (barRun) return finish(barRun, 'decoded');\n const structural = STRUCTURALS[body];\n if (structural) return finish([structural()], 'decoded');\n\n // 4. Known raw marks.\n if (KNOWN_RAW_RE.some((re) => re.test(body))) return finish([{}], 'known');\n\n // 5. Note grammar.\n const m = body.match(BODY_RE);\n if (m) {\n const [, , , hyphen, , kanLetter, kanOct, unitsStr, aakar, postOct, , trailKan, trailKanOct] = m;\n const attachKan = (note: Note, letter: string, oct: string | undefined) => {\n const def = LETTERS[letter.toLowerCase()]!;\n // NOTE: the spec kan is a plain [octave, letter] tuple, so a komal\n // kan letter (e.g. D = small দ) is projected onto its shuddha\n // letter; the exact glyph stays recoverable from `raw`.\n note.kan = [octaveOf(oct?.toLowerCase()), def.letter] as Svara;\n };\n if (unitsStr) {\n const units = unitsStr.match(/[srgmpqndtkuv][fh]?/g) ?? [];\n const notes: Note[] = units.map((unit) => {\n const def = LETTERS[unit[0]!]!;\n const note: Note = { svara: [octaveOf(unit[1]), def.letter] as Svara };\n if (def.komal) note.komal = true;\n if (def.tivra) note.tivra = true;\n if (units.length > 1) note.durationMatra = 1 / units.length;\n return note;\n });\n const last = notes[notes.length - 1]!;\n // Post-আকার octave mark (`saf`): applies to the last unit unless it\n // already carries its own mark.\n if (postOct && last.svara![0] === 'madhyam') {\n last.svara = [octaveOf(postOct), last.svara![1]] as Svara;\n }\n if (hyphen) notes[0]!.sustain = true;\n if (kanLetter) attachKan(notes[0]!, kanLetter, kanOct);\n if (trailKan && last.kan === undefined) attachKan(last, trailKan, trailKanOct);\n return finish(notes, 'decoded');\n }\n if (aakar) {\n // '-a' = sustain (hold previous pitch, no new attack);\n // bare detached 'a' = one-matra rest (legend: hyphen-less matra sign).\n const note: Note = hyphen ? { sustain: true } : { rest: true };\n if (trailKan) attachKan(note, trailKan, trailKanOct);\n else if (kanLetter) attachKan(note, kanLetter, kanOct);\n return finish([note], 'decoded');\n }\n if (hyphen && !kanLetter && !trailKan) {\n // A bare continuation dash (`-`, `-i`) — the আ-কার is implied.\n return finish([{ sustain: true }], 'decoded');\n }\n // Kan without units, or digit-edges with no note between — keep raw.\n }\n\n return finish([{}], 'unknown');\n}\n\n/**\n * Decode one cell's text. Multi-chunk cells (whitespace-separated) emit\n * notes in order; the first note of each chunk carries that chunk's source\n * text in `raw`, so joining a cell's `raw` values with spaces reproduces\n * the normalized cell text (the lossless property tests rely on this).\n */\nexport function decodeCell(text: string): CellDecode {\n const chunks = text.split(/\\s+/).filter((c) => c.length > 0);\n const out: CellDecode = { notes: [], unknownTokens: [], knownRawTokens: [], decoded: false };\n for (const chunk of chunks) {\n const { notes, kind } = decodeChunk(chunk);\n notes[0]!.raw = chunk;\n if (kind === 'decoded') out.decoded = true;\n else if (kind === 'known') out.knownRawTokens.push(chunk);\n else out.unknownTokens.push(chunk);\n out.notes.push(...notes);\n }\n return out;\n}\n"]}
|
|
@@ -135,7 +135,7 @@ var swaralipiSchema = z.object({
|
|
|
135
135
|
});
|
|
136
136
|
|
|
137
137
|
// src/spec/index.ts
|
|
138
|
-
var SPEC_VERSION = "0.
|
|
138
|
+
var SPEC_VERSION = "0.13.1";
|
|
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-
|
|
148
|
-
//# sourceMappingURL=chunk-
|
|
147
|
+
//# sourceMappingURL=chunk-GKS4WZZ6.js.map
|
|
148
|
+
//# sourceMappingURL=chunk-GKS4WZZ6.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/spec/schema.ts","../src/spec/index.ts"],"names":[],"mappings":";;;AAOO,IAAM,YAAA,GAAe,EAAE,IAAA,CAAK;AAAA,EACjC,YAAA;AAAA;AAAA,EACA,QAAA;AAAA;AAAA,EACA,SAAA;AAAA;AAAA,EACA,MAAA;AAAA;AAAA,EACA;AAAA;AACF,CAAC;AAOM,IAAM,iBAAA,GAAoB,CAAA,CAAE,IAAA,CAAK,CAAC,GAAA,EAAK,GAAA,EAAK,GAAA,EAAK,GAAA,EAAK,GAAA,EAAK,GAAA,EAAK,GAAG,CAAC;AAIpE,IAAM,cAAc,CAAA,CAAE,KAAA,CAAM,CAAC,YAAA,EAAc,iBAAiB,CAAC;AAI7D,IAAM,kBAAkB,CAAA,CAAE,IAAA,CAAK,CAAC,WAAA,EAAa,UAAU,CAAC;AAWxD,IAAM,SAAA,GAAY,EAAE,IAAA,CAAK,CAAC,QAAQ,QAAA,EAAU,QAAA,EAAU,MAAM,CAAC;AAS7D,IAAM,aAAA,GAAgB,EAAE,MAAA,CAAO;AAAA,EACpC,MAAM,CAAA,CAAE,IAAA,CAAK,CAAC,QAAA,EAAU,MAAA,EAAQ,KAAK,CAAC,CAAA;AAAA,EACtC,MAAM,CAAA,CAAE,IAAA,CAAK,CAAC,MAAA,EAAQ,OAAO,CAAC;AAChC,CAAC;AAWM,IAAM,cAAgC,CAAA,CAAE,IAAA;AAAA,EAAK,MAClD,CAAA,CAAE,KAAA,CAAM,CAAC,WAAA,EAAa,CAAA,CAAE,KAAA,CAAM,UAAU,CAAA,CAAE,GAAA,CAAI,CAAC,CAAC,CAAC;AACnD;AA4EO,IAAM,aAA8B,CAAA,CAAE,IAAA;AAAA,EAAK,MAChD,EACG,MAAA,CAAO;AAAA,IACN,KAAA,EAAO,YAAY,QAAA,EAAS;AAAA,IAC5B,KAAA,EAAO,CAAA,CAAE,OAAA,EAAQ,CAAE,QAAA,EAAS;AAAA,IAC5B,KAAA,EAAO,CAAA,CAAE,OAAA,EAAQ,CAAE,QAAA,EAAS;AAAA,IAC5B,SAAA,EAAW,gBAAgB,QAAA,EAAS;AAAA,IACpC,eAAe,CAAA,CAAE,MAAA,EAAO,CAAE,QAAA,GAAW,QAAA,EAAS;AAAA,IAC9C,OAAA,EAAS,CAAA,CAAE,OAAA,EAAQ,CAAE,QAAA,EAAS;AAAA,IAC9B,IAAA,EAAM,CAAA,CAAE,OAAA,EAAQ,CAAE,QAAA,EAAS;AAAA,IAC3B,GAAA,EAAK,YAAY,QAAA,EAAS;AAAA,IAC1B,IAAA,EAAM,YAAY,QAAA,EAAS;AAAA,IAC3B,KAAA,EAAO,EAAE,IAAA,CAAK,CAAC,SAAS,KAAA,EAAO,MAAM,CAAC,CAAA,CAAE,QAAA,EAAS;AAAA,IACjD,SAAA,EAAW,CAAA,CAAE,OAAA,EAAQ,CAAE,QAAA,EAAS;AAAA,IAChC,GAAA,EAAK,UAAU,QAAA,EAAS;AAAA,IACxB,QAAA,EAAU,CAAA,CAAE,KAAA,CAAM,aAAa,EAAE,QAAA,EAAS;AAAA,IAC1C,KAAA,EAAO,CAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,IAC3B,GAAA,EAAK,CAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,IACzB,MAAA,EAAQ,CAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,IAC5B,KAAA,EAAO,CAAA,CAAE,KAAA,CAAM,UAAU,EAAE,QAAA,EAAS;AAAA,IACpC,KAAA,EAAO,CAAA,CAAE,KAAA,CAAM,UAAU,EAAE,QAAA;AAAS,GACrC,CAAA,CACA,MAAA;AAAA,IACC,CAAC,CAAA,KACC,CAAA,CAAE,KAAA,KAAU,MAAA,IACZ,EAAE,IAAA,KAAS,IAAA,IACX,CAAA,CAAE,OAAA,KAAY,QACd,CAAA,CAAE,GAAA,KAAQ,UACV,CAAA,CAAE,GAAA,KAAQ,UACT,CAAA,CAAE,QAAA,KAAa,MAAA,IAAa,CAAA,CAAE,SAAS,MAAA,GAAS,CAAA,IAChD,EAAE,KAAA,KAAU,MAAA,IAAa,EAAE,KAAA,CAAM,MAAA,GAAS,CAAA,IAC1C,CAAA,CAAE,UAAU,MAAA,IAAa,CAAA,CAAE,MAAM,MAAA,GAAS,CAAA,IAC3C,EAAE,KAAA,KAAU,MAAA;AAAA,IACd;AAAA,MACE,OAAA,EACE;AAAA;AACJ;AAEN;AAIO,IAAM,YAAY,CAAA,CAAE,IAAA,CAAK,CAAC,MAAA,EAAQ,QAAA,EAAU,UAAU,CAAC;AAMvD,IAAM,UAAA,GAAa,EACvB,MAAA,CAAO;AAAA;AAAA,EAEN,EAAA,EAAI,EAAE,MAAA,EAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOb,SAAA,EAAW,CAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,EAC/B,UAAU,CAAA,CAAE,MAAA,EAAO,CAAE,GAAA,GAAM,QAAA,EAAS;AAAA;AAAA,EAEpC,MAAA,EAAQ,CAAA,CAAE,KAAA,CAAM,CAAA,CAAE,MAAA,EAAO,CAAE,GAAA,EAAI,CAAE,QAAA,EAAU,CAAA,CAAE,QAAA,EAAS;AAAA;AAAA,EAEtD,GAAA,EAAK,EAAE,MAAA,EAAO,CAAE,KAAI,CAAE,WAAA,GAAc,QAAA,EAAS;AAAA;AAAA,EAE7C,MAAA,EAAQ,CAAA,CAAE,KAAA,CAAM,CAAA,CAAE,MAAA,EAAO,CAAE,GAAA,EAAI,CAAE,WAAA,EAAa,CAAA,CAAE,QAAA;AAClD,CAAC,EACA,MAAA,CAAO,CAAC,MAAM,CAAA,CAAE,MAAA,KAAW,UAAa,CAAA,CAAE,MAAA,CAAO,MAAA,CAAO,CAAC,GAAG,CAAA,KAAM,CAAA,GAAI,GAAG,CAAC,CAAA,KAAM,EAAE,QAAA,EAAU;AAAA,EAC3F,OAAA,EAAS;AACX,CAAC;AAII,IAAM,SAAA,GAAY,EAAE,MAAA,CAAO;AAAA;AAAA,EAEhC,KAAA,EAAO,CAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO3B,GAAA,EAAK,UAAU,QAAA,EAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOxB,IAAA,EAAM,UAAA,CAAW,QAAA,EAAS,CAAE,QAAA,EAAS;AAAA;AAAA,EAErC,QAAA,EAAU,CAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,EAC9B,KAAA,EAAO,CAAA,CAAE,KAAA,CAAM,UAAU;AAC3B,CAAC;AAIM,IAAM,UAAA,GAAa,EAAE,MAAA,CAAO;AAAA,EACjC,IAAA,EAAM,CAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,EAC1B,IAAA,EAAM,CAAA,CAAE,KAAA,CAAM,SAAS;AACzB,CAAC;AAIM,IAAM,UAAA,GAAa,EAAE,MAAA,CAAO;AAAA,EACjC,KAAA,EAAO,EAAE,MAAA,EAAO;AAAA;AAAA,EAEhB,MAAA,EAAQ,CAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA;AAAA,EAE5B,QAAQ,CAAA,CAAE,MAAA,EAAO,CAAE,GAAA,GAAM,QAAA,EAAS;AAAA,EAClC,IAAA,EAAM,WAAW,QAAA,EAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM1B,QAAA,EAAU,CAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA;AAAA,EAE9B,KAAA,EAAO,CAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA;AAAA;AAAA;AAAA,EAI3B,GAAA,EAAK,EAAE,MAAA,EAAO,CAAE,UAAS,CAAE,QAAA,GAAW,QAAA,EAAS;AAAA;AAAA;AAAA,EAG/C,SAAA,EAAW,CAAA,CACR,MAAA,CAAO,EAAE,IAAA,EAAM,CAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS,EAAG,MAAA,EAAQ,CAAA,CAAE,MAAA,GAAS,QAAA,EAAS,EAAG,QAAA,EAAU,CAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS,EAAG,CAAA,CACtG,OAAA,EAAQ,CACR,QAAA,EAAS;AAAA,EACZ,IAAA,EAAM,CAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA;AAAA,EAE1B,OAAA,EAAS,EAAE,MAAA,EAAO,CAAE,KAAI,CAAE,QAAA,GAAW,QAAA,EAAS;AAAA;AAAA,EAE9C,UAAA,EAAY,CAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA;AAAA,EAEhC,MAAA,EAAQ,CAAA,CAAE,MAAA,EAAO,CAAE,QAAA;AACrB,CAAC;AAIM,IAAM,eAAA,GAAkB,EAAE,MAAA,CAAO;AAAA;AAAA,EAEtC,OAAA,EAAS,EAAE,MAAA,EAAO;AAAA,EAClB,IAAA,EAAM,UAAA;AAAA,EACN,KAAA,EAAO,CAAA,CAAE,KAAA,CAAM,UAAU;AAC3B,CAAC;;;ACrRM,IAAM,YAAA,GAAe;AAiDrB,SAAS,eAAe,IAAA,EAA0B;AACvD,EAAA,OAAO,eAAA,CAAgB,MAAM,IAAI,CAAA;AACnC;AAGO,SAAS,mBAAmB,IAAA,EAAe;AAChD,EAAA,OAAO,eAAA,CAAgB,UAAU,IAAI,CAAA;AACvC","file":"chunk-A7R3VFIV.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.12.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"]}
|
|
1
|
+
{"version":3,"sources":["../src/spec/schema.ts","../src/spec/index.ts"],"names":[],"mappings":"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","file":"chunk-GKS4WZZ6.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.13.1';\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"]}
|