@ugfoundation/swaralipi-js 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +347 -0
- package/README.md +257 -0
- package/dist/batch-NF2Q2CFS.js +47 -0
- package/dist/batch-NF2Q2CFS.js.map +1 -0
- package/dist/chunk-C6O7DYU5.js +115 -0
- package/dist/chunk-C6O7DYU5.js.map +1 -0
- package/dist/chunk-GWXI2HJA.js +14 -0
- package/dist/chunk-GWXI2HJA.js.map +1 -0
- package/dist/chunk-N3NNWAOW.js +593 -0
- package/dist/chunk-N3NNWAOW.js.map +1 -0
- package/dist/cli.cjs +913 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +91 -0
- package/dist/cli.js.map +1 -0
- package/dist/convert/index.cjs +720 -0
- package/dist/convert/index.cjs.map +1 -0
- package/dist/convert/index.d.cts +270 -0
- package/dist/convert/index.d.ts +270 -0
- package/dist/convert/index.js +5 -0
- package/dist/convert/index.js.map +1 -0
- package/dist/index.cjs +143 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/player/index.cjs +775 -0
- package/dist/player/index.cjs.map +1 -0
- package/dist/player/index.d.cts +285 -0
- package/dist/player/index.d.ts +285 -0
- package/dist/player/index.js +743 -0
- package/dist/player/index.js.map +1 -0
- package/dist/schema-gxhG45OK.d.cts +467 -0
- package/dist/schema-gxhG45OK.d.ts +467 -0
- package/dist/spec/index.cjs +143 -0
- package/dist/spec/index.cjs.map +1 -0
- package/dist/spec/index.d.cts +70 -0
- package/dist/spec/index.d.ts +70 -0
- package/dist/spec/index.js +4 -0
- package/dist/spec/index.js.map +1 -0
- package/dist/taals-DguYW0wf.d.cts +60 -0
- package/dist/taals-DguYW0wf.d.ts +60 -0
- package/dist/viewer/index.cjs +998 -0
- package/dist/viewer/index.cjs.map +1 -0
- package/dist/viewer/index.d.cts +285 -0
- package/dist/viewer/index.d.ts +285 -0
- package/dist/viewer/index.js +814 -0
- package/dist/viewer/index.js.map +1 -0
- package/package.json +93 -0
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
import { swaralipiSchema, SPEC_VERSION } from './chunk-C6O7DYU5.js';
|
|
2
|
+
import { TAALS } from './chunk-GWXI2HJA.js';
|
|
3
|
+
|
|
4
|
+
// src/convert/codec.ts
|
|
5
|
+
var LETTERS = {
|
|
6
|
+
s: { letter: "s" },
|
|
7
|
+
r: { letter: "r" },
|
|
8
|
+
g: { letter: "g" },
|
|
9
|
+
m: { letter: "m" },
|
|
10
|
+
p: { letter: "p" },
|
|
11
|
+
q: { letter: "d" },
|
|
12
|
+
// ধ — shuddha dha
|
|
13
|
+
n: { letter: "n" },
|
|
14
|
+
d: { letter: "d", komal: true },
|
|
15
|
+
// দ
|
|
16
|
+
t: { letter: "g", komal: true },
|
|
17
|
+
// জ্ঞ
|
|
18
|
+
k: { letter: "m", tivra: true },
|
|
19
|
+
// ক্ষ
|
|
20
|
+
u: { letter: "n", komal: true },
|
|
21
|
+
// ণ
|
|
22
|
+
v: { letter: "r", komal: true }
|
|
23
|
+
// ঋ
|
|
24
|
+
};
|
|
25
|
+
var STRUCTURALS = {
|
|
26
|
+
"%": () => ({ svara: ["madhyam", "s"] }),
|
|
27
|
+
// precomposed সা
|
|
28
|
+
"&": () => ({ svara: ["madhyam", "n"] })
|
|
29
|
+
// precomposed নি
|
|
30
|
+
};
|
|
31
|
+
var BAR_RUN_RE = /^(-)?([lLA[\]]+)$/;
|
|
32
|
+
function decodeBarRun(body) {
|
|
33
|
+
const m = body.match(BAR_RUN_RE);
|
|
34
|
+
if (!m || !/[lLA]/.test(m[2])) return void 0;
|
|
35
|
+
const notes = [];
|
|
36
|
+
if (m[1]) notes.push({ sustain: true });
|
|
37
|
+
const run = m[2];
|
|
38
|
+
let i = 0;
|
|
39
|
+
while (i < run.length) {
|
|
40
|
+
const ch = run[i];
|
|
41
|
+
if (ch === "l" || ch === "L") {
|
|
42
|
+
let j = i;
|
|
43
|
+
while (j < run.length && run[j] === ch) j++;
|
|
44
|
+
const strokes = (j - i) * (ch === "L" ? 2 : 1);
|
|
45
|
+
notes.push({ bar: strokes >= 4 ? "quad" : strokes >= 2 ? "double" : "single" });
|
|
46
|
+
i = j;
|
|
47
|
+
} else if (ch === "A") {
|
|
48
|
+
notes.push({ bar: "dari" });
|
|
49
|
+
i++;
|
|
50
|
+
} else {
|
|
51
|
+
notes.push({ brackets: [{ kind: "alt", edge: ch === "[" ? "open" : "close" }] });
|
|
52
|
+
i++;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return notes;
|
|
56
|
+
}
|
|
57
|
+
var KNOWN_RAW_RE = [
|
|
58
|
+
/^[wxyjEJ$<>=_]+$/,
|
|
59
|
+
// arc & line drawing segments
|
|
60
|
+
/^\|+$/,
|
|
61
|
+
// thin yugal-dari stop mark
|
|
62
|
+
/^[0-9!@#]{1,2}[fF]?:?$/,
|
|
63
|
+
// beat-number labels incl. 10–16 (sam tick = f/F, e.g. 1f / 1F / 2:)
|
|
64
|
+
/^[oOe`+,.;:iI~^*bcBCWXYFHZz'"?]$/
|
|
65
|
+
// dots, ticks, duration marks, legacy forms
|
|
66
|
+
];
|
|
67
|
+
var OPEN_BRACKETS = {
|
|
68
|
+
"{": "repeat",
|
|
69
|
+
"(": "skip",
|
|
70
|
+
"[": "alt"
|
|
71
|
+
};
|
|
72
|
+
var CLOSE_BRACKETS = {
|
|
73
|
+
"}": "repeat",
|
|
74
|
+
")": "skip",
|
|
75
|
+
"]": "alt"
|
|
76
|
+
};
|
|
77
|
+
function octaveOf(mark) {
|
|
78
|
+
if (mark === "f" || mark === "F") return "taar";
|
|
79
|
+
if (mark === "h" || mark === "H") return "mandra";
|
|
80
|
+
return "madhyam";
|
|
81
|
+
}
|
|
82
|
+
var BODY_RE = /^([0-9!@][fF]?)?([zj]*)(-)?([i:])?(?:([SRGMPQNDTKUV])([fFhH])?)?((?:[srgmpqndtkuv][fh]?[i:]?)+)?(a)?([fh])?([i:])?(?:([SRGMPQNDTKUV])([fFhH])?)?([0-9!@][fF]?)?(-)?$/;
|
|
83
|
+
function decodeChunk(chunk) {
|
|
84
|
+
const opens = [];
|
|
85
|
+
const closes = [];
|
|
86
|
+
let body = chunk;
|
|
87
|
+
while (body.length > 0) {
|
|
88
|
+
const kind = OPEN_BRACKETS[body[0]];
|
|
89
|
+
if (kind === void 0) break;
|
|
90
|
+
opens.push(kind);
|
|
91
|
+
body = body.slice(1);
|
|
92
|
+
}
|
|
93
|
+
while (body.length > 0) {
|
|
94
|
+
const kind = CLOSE_BRACKETS[body[body.length - 1]];
|
|
95
|
+
if (kind === void 0) break;
|
|
96
|
+
closes.unshift(kind);
|
|
97
|
+
body = body.slice(0, -1);
|
|
98
|
+
}
|
|
99
|
+
const finish = (notes, kind) => {
|
|
100
|
+
if (notes.length === 0) notes.push({});
|
|
101
|
+
const first = notes[0];
|
|
102
|
+
const last = notes[notes.length - 1];
|
|
103
|
+
if (opens.length > 0) first.brackets = [...opens.map((kind_) => ({ kind: kind_, edge: "open" })), ...first.brackets ?? []];
|
|
104
|
+
if (closes.length > 0) last.brackets = [...last.brackets ?? [], ...closes.map((kind_) => ({ kind: kind_, edge: "close" }))];
|
|
105
|
+
return { notes, kind };
|
|
106
|
+
};
|
|
107
|
+
if (body === "") return finish([{}], "decoded");
|
|
108
|
+
const barRun = decodeBarRun(body);
|
|
109
|
+
if (barRun) return finish(barRun, "decoded");
|
|
110
|
+
const structural = STRUCTURALS[body];
|
|
111
|
+
if (structural) return finish([structural()], "decoded");
|
|
112
|
+
if (KNOWN_RAW_RE.some((re) => re.test(body))) return finish([{}], "known");
|
|
113
|
+
const m = body.match(BODY_RE);
|
|
114
|
+
if (m) {
|
|
115
|
+
const [, , , hyphen, , kanLetter, kanOct, unitsStr, aakar, postOct, , trailKan, trailKanOct] = m;
|
|
116
|
+
const attachKan = (note, letter, oct) => {
|
|
117
|
+
const def = LETTERS[letter.toLowerCase()];
|
|
118
|
+
note.kan = [octaveOf(oct?.toLowerCase()), def.letter];
|
|
119
|
+
};
|
|
120
|
+
if (unitsStr) {
|
|
121
|
+
const units = unitsStr.match(/[srgmpqndtkuv][fh]?/g) ?? [];
|
|
122
|
+
const notes = units.map((unit) => {
|
|
123
|
+
const def = LETTERS[unit[0]];
|
|
124
|
+
const note = { svara: [octaveOf(unit[1]), def.letter] };
|
|
125
|
+
if (def.komal) note.komal = true;
|
|
126
|
+
if (def.tivra) note.tivra = true;
|
|
127
|
+
if (units.length > 1) note.durationMatra = 1 / units.length;
|
|
128
|
+
return note;
|
|
129
|
+
});
|
|
130
|
+
const last = notes[notes.length - 1];
|
|
131
|
+
if (postOct && last.svara[0] === "madhyam") {
|
|
132
|
+
last.svara = [octaveOf(postOct), last.svara[1]];
|
|
133
|
+
}
|
|
134
|
+
if (hyphen) notes[0].sustain = true;
|
|
135
|
+
if (kanLetter) attachKan(notes[0], kanLetter, kanOct);
|
|
136
|
+
if (trailKan && last.kan === void 0) attachKan(last, trailKan, trailKanOct);
|
|
137
|
+
return finish(notes, "decoded");
|
|
138
|
+
}
|
|
139
|
+
if (aakar) {
|
|
140
|
+
const note = hyphen ? { sustain: true } : { rest: true };
|
|
141
|
+
if (trailKan) attachKan(note, trailKan, trailKanOct);
|
|
142
|
+
else if (kanLetter) attachKan(note, kanLetter, kanOct);
|
|
143
|
+
return finish([note], "decoded");
|
|
144
|
+
}
|
|
145
|
+
if (hyphen && !kanLetter && !trailKan) {
|
|
146
|
+
return finish([{ sustain: true }], "decoded");
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return finish([{}], "unknown");
|
|
150
|
+
}
|
|
151
|
+
function decodeCell(text) {
|
|
152
|
+
const chunks = text.split(/\s+/).filter((c) => c.length > 0);
|
|
153
|
+
const out = { notes: [], unknownTokens: [], knownRawTokens: [], decoded: false };
|
|
154
|
+
for (const chunk of chunks) {
|
|
155
|
+
const { notes, kind } = decodeChunk(chunk);
|
|
156
|
+
notes[0].raw = chunk;
|
|
157
|
+
if (kind === "decoded") out.decoded = true;
|
|
158
|
+
else if (kind === "known") out.knownRawTokens.push(chunk);
|
|
159
|
+
else out.unknownTokens.push(chunk);
|
|
160
|
+
out.notes.push(...notes);
|
|
161
|
+
}
|
|
162
|
+
return out;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/convert/grid.ts
|
|
166
|
+
var ROLES = ["above", "notation", "below", "lyric"];
|
|
167
|
+
function buildSystems(page) {
|
|
168
|
+
const warnings = [];
|
|
169
|
+
const byIndex = /* @__PURE__ */ new Map();
|
|
170
|
+
let styleMismatches = 0;
|
|
171
|
+
for (const cell of page.cells) {
|
|
172
|
+
if (cell.text === "") continue;
|
|
173
|
+
const role = ROLES[cell.row % 4];
|
|
174
|
+
const expected = role === "lyric" ? "style2" : "style1";
|
|
175
|
+
if (cell.style !== expected) styleMismatches++;
|
|
176
|
+
const index = Math.floor(cell.row / 4);
|
|
177
|
+
let system = byIndex.get(index);
|
|
178
|
+
if (!system) {
|
|
179
|
+
system = {
|
|
180
|
+
index,
|
|
181
|
+
above: /* @__PURE__ */ new Map(),
|
|
182
|
+
notation: /* @__PURE__ */ new Map(),
|
|
183
|
+
below: /* @__PURE__ */ new Map(),
|
|
184
|
+
lyric: /* @__PURE__ */ new Map(),
|
|
185
|
+
maxCol: 0
|
|
186
|
+
};
|
|
187
|
+
byIndex.set(index, system);
|
|
188
|
+
}
|
|
189
|
+
system[role].set(cell.col, cell.text);
|
|
190
|
+
if (cell.col > system.maxCol) system.maxCol = cell.col;
|
|
191
|
+
}
|
|
192
|
+
if (styleMismatches > 0) {
|
|
193
|
+
warnings.push(`${styleMismatches} cell(s) had an unexpected style class for their row role`);
|
|
194
|
+
}
|
|
195
|
+
const systems = [...byIndex.values()].sort((a, b) => a.index - b.index);
|
|
196
|
+
return { systems, warnings };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/convert/taal-map.ts
|
|
200
|
+
var BN_DIGIT_MAP = {
|
|
201
|
+
"\u09E6": "0",
|
|
202
|
+
"\u09E7": "1",
|
|
203
|
+
"\u09E8": "2",
|
|
204
|
+
"\u09E9": "3",
|
|
205
|
+
"\u09EA": "4",
|
|
206
|
+
"\u09EB": "5",
|
|
207
|
+
"\u09EC": "6",
|
|
208
|
+
"\u09ED": "7",
|
|
209
|
+
"\u09EE": "8",
|
|
210
|
+
"\u09EF": "9"
|
|
211
|
+
};
|
|
212
|
+
function bnInt(s) {
|
|
213
|
+
if (s === void 0) return void 0;
|
|
214
|
+
const ascii = s.trim().replace(/[০-৯]/g, (d) => BN_DIGIT_MAP[d] ?? d);
|
|
215
|
+
return /^\d+$/.test(ascii) ? Number(ascii) : void 0;
|
|
216
|
+
}
|
|
217
|
+
var BN_TAAL_MAP = {
|
|
218
|
+
\u09A6\u09BE\u09A6\u09B0\u09BE: "dadra",
|
|
219
|
+
\u0995\u09BE\u09B9\u09BE\u09B0\u09AC\u09BE: "kehrwa",
|
|
220
|
+
\u098F\u0995\u09A4\u09BE\u09B2\u09BE: "ektaal",
|
|
221
|
+
\u098F\u0995\u09A4\u09BE\u09B2: "ektaal",
|
|
222
|
+
\u09A4\u09BF\u09A8\u09A4\u09BE\u09B2\u09BE: "teentaal",
|
|
223
|
+
\u09A4\u09CD\u09B0\u09BF\u09A4\u09BE\u09B2: "teentaal",
|
|
224
|
+
\u099D\u09BE\u0981\u09AA\u09A4\u09BE\u09B2: "jhaptaal",
|
|
225
|
+
\u099D\u09BE\u0981\u09AA: "jhaptaal",
|
|
226
|
+
\u09B0\u09C1\u09AA\u0995: "rupak",
|
|
227
|
+
\u09B0\u09C2\u09AA\u0995: "rupak",
|
|
228
|
+
\u0986\u09A1\u09BC\u09BE\u099A\u09CC\u09A4\u09BE\u09B2: "adachautaal"
|
|
229
|
+
};
|
|
230
|
+
var BN_TAAL_DEFS = {
|
|
231
|
+
\u09A4\u09C7\u0993\u09A1\u09BC\u09BE: { id: "teora", numBeats: 7, bhaags: [3, 2, 2], sam: 0 },
|
|
232
|
+
\u09A4\u09C7\u0993\u09B0\u09BE: { id: "teora", numBeats: 7, bhaags: [3, 2, 2], sam: 0 },
|
|
233
|
+
\u099D\u09AE\u09CD\u09AA\u0995: { id: "jhampak", numBeats: 5, bhaags: [3, 2], sam: 0 },
|
|
234
|
+
\u09B7\u09B7\u09CD\u09A0\u09C0: { id: "shashthi", numBeats: 6, bhaags: [2, 4], sam: 0 },
|
|
235
|
+
\u09B0\u09C1\u09AA\u0995\u09A1\u09BC\u09BE: { id: "rupakda", numBeats: 8, bhaags: [3, 2, 3], sam: 0 },
|
|
236
|
+
\u09B0\u09C2\u09AA\u0995\u09A1\u09BC\u09BE: { id: "rupakda", numBeats: 8, bhaags: [3, 2, 3], sam: 0 },
|
|
237
|
+
\u09A8\u09AC\u09A4\u09BE\u09B2: { id: "nabataal", numBeats: 9, bhaags: [5, 4], sam: 0 },
|
|
238
|
+
\u098F\u0995\u09BE\u09A6\u09B6\u09C0: { id: "ekadashi", numBeats: 11, bhaags: [3, 4, 4], sam: 0 },
|
|
239
|
+
\u09A8\u09AC\u09AA\u099E\u09CD\u099A: { id: "nabapancha", numBeats: 18, bhaags: [2, 4, 4, 4, 4], sam: 0 },
|
|
240
|
+
\u09A7\u09BE\u09AE\u09BE\u09B0: { id: "dhamar", numBeats: 14, bhaags: [3, 2, 2, 3, 4], sam: 0 },
|
|
241
|
+
\u099A\u09CC\u09A4\u09BE\u09B2: { id: "choutaal", numBeats: 12, bhaags: [2, 2, 2, 2, 2, 2], sam: 0 },
|
|
242
|
+
\u09B8\u09C1\u09B0\u09AB\u09BE\u0981\u0995: { id: "surphank", numBeats: 10, sam: 0 }
|
|
243
|
+
};
|
|
244
|
+
var BN_TAAL_IDS = {
|
|
245
|
+
\u0995\u09BE\u0993\u09AF\u09BC\u09BE\u09B2\u09BF: "kawali",
|
|
246
|
+
\u0996\u09C7\u09AE\u099F\u09BE: "khemta",
|
|
247
|
+
\u0986\u09A1\u09BC\u09BE\u09A0\u09C7\u0995\u09BE: "araatheka",
|
|
248
|
+
\u0995\u09BE\u09B6\u09CD\u09AE\u09C0\u09B0\u09BF: "kashmiri",
|
|
249
|
+
"\u09AF\u09CE": "yat",
|
|
250
|
+
\u09AE\u09A7\u09CD\u09AF\u09AE\u09BE\u09A8: "madhyaman",
|
|
251
|
+
\u0985\u09B0\u09CD\u09A6\u09CD\u09A7\u099D\u09BE\u0981\u09AA: "ardhajhanp",
|
|
252
|
+
\u0985\u09B0\u09CD\u09A7\u099D\u09BE\u0981\u09AA: "ardhajhanp",
|
|
253
|
+
"\u09AC\u09BF\u09B2\u09AE\u09CD\u09AC\u09BF\u09A4 \u09A4\u09CD\u09B0\u09BF\u09A4\u09BE\u09B2": "vilambit-teentaal"
|
|
254
|
+
};
|
|
255
|
+
var TAAL_FREE = "\u09A4\u09BE\u09B2\u09AE\u09C1\u0995\u09CD\u09A4";
|
|
256
|
+
var BAR_TOKENS = /* @__PURE__ */ new Set(["l", "ll", "lll", "llll", "L", "LL", "|"]);
|
|
257
|
+
var DARI_TOKEN = "A";
|
|
258
|
+
function deriveGeometry(systems) {
|
|
259
|
+
let numCols = 0;
|
|
260
|
+
for (const system of systems) {
|
|
261
|
+
if (system.notation.size === 0) continue;
|
|
262
|
+
const cols = [...system.notation.keys()].sort((a, b) => a - b);
|
|
263
|
+
numCols = Math.max(numCols, cols.length);
|
|
264
|
+
const barCols = cols.filter((c) => BAR_TOKENS.has(system.notation.get(c)));
|
|
265
|
+
if (barCols.length < 2) continue;
|
|
266
|
+
const from = barCols[0];
|
|
267
|
+
const to = barCols[1];
|
|
268
|
+
const inner = cols.filter((c) => c > from && c < to);
|
|
269
|
+
if (inner.length === 0) continue;
|
|
270
|
+
const bhaags = [];
|
|
271
|
+
let run = 0;
|
|
272
|
+
for (const c of inner) {
|
|
273
|
+
if (system.notation.get(c) === DARI_TOKEN) {
|
|
274
|
+
if (run > 0) bhaags.push(run);
|
|
275
|
+
run = 0;
|
|
276
|
+
} else {
|
|
277
|
+
run++;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (run > 0) bhaags.push(run);
|
|
281
|
+
const numBeats = bhaags.reduce((a, b) => a + b, 0);
|
|
282
|
+
if (numBeats === 0) continue;
|
|
283
|
+
return { numCols: cols.length, numBeats, bhaags: bhaags.length > 1 ? bhaags : void 0 };
|
|
284
|
+
}
|
|
285
|
+
return numCols > 0 ? { numCols } : void 0;
|
|
286
|
+
}
|
|
287
|
+
function resolveTaal(nameRaw, avartanRaw, geometry) {
|
|
288
|
+
const warnings = [];
|
|
289
|
+
const avartan = bnInt(avartanRaw);
|
|
290
|
+
const taalName = nameRaw?.trim() || void 0;
|
|
291
|
+
if (!taalName || taalName === TAAL_FREE) {
|
|
292
|
+
return { taalName, avartan, matched: "none", warnings };
|
|
293
|
+
}
|
|
294
|
+
const parenMatch = taalName.match(/^(.*?)\s*\(([০-৯\d/\s]+)\)\s*$/);
|
|
295
|
+
const base = (parenMatch ? parenMatch[1] : taalName).trim();
|
|
296
|
+
let hintBhaags;
|
|
297
|
+
if (parenMatch) {
|
|
298
|
+
const parts = parenMatch[2].split("/").map((p) => bnInt(p));
|
|
299
|
+
if (parts.every((p) => p !== void 0) && parts.length > 0) {
|
|
300
|
+
hintBhaags = parts;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const akhandaMatch = base.match(/^অখন্ড\s+([০-৯\d]+)\s*মাত্রা$/);
|
|
304
|
+
let taal;
|
|
305
|
+
let matched = "none";
|
|
306
|
+
const mappedId = BN_TAAL_MAP[base];
|
|
307
|
+
if (mappedId) {
|
|
308
|
+
taal = { ...TAALS[mappedId] };
|
|
309
|
+
matched = "taals";
|
|
310
|
+
} else if (BN_TAAL_DEFS[base]) {
|
|
311
|
+
taal = { ...BN_TAAL_DEFS[base] };
|
|
312
|
+
matched = "table";
|
|
313
|
+
} else if (akhandaMatch) {
|
|
314
|
+
const beats = bnInt(akhandaMatch[1]);
|
|
315
|
+
if (beats) {
|
|
316
|
+
taal = { id: `akhanda-${beats}`, numBeats: beats, sam: 0 };
|
|
317
|
+
matched = "table";
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
const hintBeats = hintBhaags?.reduce((a, b) => a + b, 0);
|
|
321
|
+
if (taal) {
|
|
322
|
+
if (hintBeats !== void 0 && hintBeats !== taal.numBeats) {
|
|
323
|
+
warnings.push(
|
|
324
|
+
`taal ${taal.id}: name declares ${hintBeats} beats (${hintBhaags.join("/")}), table says ${taal.numBeats} \u2014 using the name's`
|
|
325
|
+
);
|
|
326
|
+
taal = { id: taal.id, numBeats: hintBeats, sam: 0 };
|
|
327
|
+
if (hintBhaags.length > 1) taal.bhaags = hintBhaags;
|
|
328
|
+
} else if (hintBhaags && hintBhaags.length > 1) {
|
|
329
|
+
const tableBhaags = taal.bhaags?.join("/");
|
|
330
|
+
if (tableBhaags && tableBhaags !== hintBhaags.join("/")) {
|
|
331
|
+
warnings.push(
|
|
332
|
+
`taal ${taal.id}: name declares bhaags ${hintBhaags.join("/")}, table says ${tableBhaags} \u2014 using the name's`
|
|
333
|
+
);
|
|
334
|
+
const { khaali: _khaali, ...rest } = taal;
|
|
335
|
+
taal = { ...rest, bhaags: hintBhaags };
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
} else if (hintBeats !== void 0 || geometry?.numBeats !== void 0) {
|
|
339
|
+
const evidenceBeats = hintBeats ?? geometry?.numBeats;
|
|
340
|
+
const evidenceBhaags = hintBhaags ?? geometry?.bhaags;
|
|
341
|
+
taal = { id: BN_TAAL_IDS[base] ?? base, numBeats: evidenceBeats, sam: 0 };
|
|
342
|
+
if (evidenceBhaags && evidenceBhaags.length > 1) taal.bhaags = evidenceBhaags;
|
|
343
|
+
matched = "geometry";
|
|
344
|
+
} else {
|
|
345
|
+
warnings.push(`unresolved taal name: ${taalName}`);
|
|
346
|
+
}
|
|
347
|
+
if (taal && geometry?.numBeats !== void 0 && geometry.numBeats !== taal.numBeats) {
|
|
348
|
+
warnings.push(
|
|
349
|
+
`grid geometry says ${geometry.numBeats} beats per cycle but taal ${taal.id} has ${taal.numBeats}`
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
return { taal, taalName, avartan, matched, warnings };
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// src/convert/assemble.ts
|
|
356
|
+
var segmenter = new Intl.Segmenter("bn", { granularity: "grapheme" });
|
|
357
|
+
function splitLyric(text) {
|
|
358
|
+
return [...segmenter.segment(text)].map((s) => s.segment);
|
|
359
|
+
}
|
|
360
|
+
function assemble(page, opts = {}) {
|
|
361
|
+
const { systems, warnings } = buildSystems(page);
|
|
362
|
+
const geometry = deriveGeometry(systems);
|
|
363
|
+
const resolved = resolveTaal(page.header.taalNameRaw, page.header.avartanRaw, geometry);
|
|
364
|
+
warnings.push(...resolved.warnings);
|
|
365
|
+
const report = {
|
|
366
|
+
nltrId: opts.nltrId,
|
|
367
|
+
title: page.header.title ?? "",
|
|
368
|
+
taalName: resolved.taalName,
|
|
369
|
+
taalId: resolved.taal?.id,
|
|
370
|
+
taalMatched: resolved.matched,
|
|
371
|
+
systems: 0,
|
|
372
|
+
notationCells: 0,
|
|
373
|
+
lyricCells: 0,
|
|
374
|
+
annotationCells: 0,
|
|
375
|
+
decodedCells: 0,
|
|
376
|
+
knownRawCells: 0,
|
|
377
|
+
unknownCells: 0,
|
|
378
|
+
unknownTokens: {},
|
|
379
|
+
lyricAlignMismatches: 0,
|
|
380
|
+
warnings
|
|
381
|
+
};
|
|
382
|
+
const countUnknown = (tokens) => {
|
|
383
|
+
for (const t of tokens) report.unknownTokens[t] = (report.unknownTokens[t] ?? 0) + 1;
|
|
384
|
+
};
|
|
385
|
+
const rows = [];
|
|
386
|
+
for (const system of systems) {
|
|
387
|
+
const notes = [];
|
|
388
|
+
for (let col = 0; col <= system.maxCol; col++) {
|
|
389
|
+
const notationText = system.notation.get(col);
|
|
390
|
+
const lyricText = system.lyric.get(col);
|
|
391
|
+
const aboveText = system.above.get(col);
|
|
392
|
+
const belowText = system.below.get(col);
|
|
393
|
+
if (notationText === void 0 && lyricText === void 0 && aboveText === void 0 && belowText === void 0) {
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
let cellNotes;
|
|
397
|
+
if (notationText !== void 0) {
|
|
398
|
+
report.notationCells++;
|
|
399
|
+
const decode = decodeCell(notationText);
|
|
400
|
+
countUnknown(decode.unknownTokens);
|
|
401
|
+
if (decode.unknownTokens.length > 0) report.unknownCells++;
|
|
402
|
+
else if (decode.decoded) report.decodedCells++;
|
|
403
|
+
else report.knownRawCells++;
|
|
404
|
+
cellNotes = decode.notes;
|
|
405
|
+
} else {
|
|
406
|
+
cellNotes = [{}];
|
|
407
|
+
}
|
|
408
|
+
if (lyricText !== void 0) {
|
|
409
|
+
report.lyricCells++;
|
|
410
|
+
attachLyric(cellNotes, lyricText, report);
|
|
411
|
+
}
|
|
412
|
+
if (aboveText !== void 0) {
|
|
413
|
+
report.annotationCells++;
|
|
414
|
+
const decode = decodeCell(aboveText);
|
|
415
|
+
countUnknown(decode.unknownTokens);
|
|
416
|
+
cellNotes[0].above = decode.notes;
|
|
417
|
+
}
|
|
418
|
+
if (belowText !== void 0) {
|
|
419
|
+
report.annotationCells++;
|
|
420
|
+
const decode = decodeCell(belowText);
|
|
421
|
+
countUnknown(decode.unknownTokens);
|
|
422
|
+
cellNotes[0].below = decode.notes;
|
|
423
|
+
}
|
|
424
|
+
notes.push(...cellNotes);
|
|
425
|
+
}
|
|
426
|
+
if (notes.length > 0) {
|
|
427
|
+
rows.push({ notes });
|
|
428
|
+
report.systems++;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
const meta = { title: page.header.title ?? `NLTR ${opts.nltrId ?? "song"}` };
|
|
432
|
+
if (opts.nltrId !== void 0) meta.nltrId = opts.nltrId;
|
|
433
|
+
if (resolved.taal) meta.taal = resolved.taal;
|
|
434
|
+
if (resolved.taalName) meta.taalName = resolved.taalName;
|
|
435
|
+
if (resolved.avartan !== void 0) meta.avartan = resolved.avartan;
|
|
436
|
+
const source = opts.source ?? (opts.nltrId !== void 0 ? `https://rabindra-rachanabali.nltr.org/node/${opts.nltrId}` : page.header.printId !== void 0 ? `https://rabindra-rachanabali.nltr.org/node/${page.header.printId}` : void 0);
|
|
437
|
+
if (source) meta.source = source;
|
|
438
|
+
const doc = swaralipiSchema.parse({ version: SPEC_VERSION, meta, parts: [{ rows }] });
|
|
439
|
+
return { doc, report };
|
|
440
|
+
}
|
|
441
|
+
function attachLyric(cellNotes, lyricText, report) {
|
|
442
|
+
if (cellNotes.length > 1) {
|
|
443
|
+
const clusters = splitLyric(lyricText);
|
|
444
|
+
if (clusters.length === cellNotes.length) {
|
|
445
|
+
clusters.forEach((cluster, i) => {
|
|
446
|
+
cellNotes[i].lyric = cluster;
|
|
447
|
+
});
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
report.lyricAlignMismatches++;
|
|
451
|
+
}
|
|
452
|
+
cellNotes[0].lyric = lyricText;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// src/convert/extract.ts
|
|
456
|
+
var ExtractError = class extends Error {
|
|
457
|
+
constructor(message) {
|
|
458
|
+
super(message);
|
|
459
|
+
this.name = "ExtractError";
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
var NAMED_ENTITIES = {
|
|
463
|
+
nbsp: "\xA0",
|
|
464
|
+
amp: "&",
|
|
465
|
+
lt: "<",
|
|
466
|
+
gt: ">",
|
|
467
|
+
quot: '"',
|
|
468
|
+
apos: "'"
|
|
469
|
+
};
|
|
470
|
+
function decodeEntities(s) {
|
|
471
|
+
return s.replace(/&(?:#x([0-9a-fA-F]+)|#(\d+)|([a-zA-Z]+));/g, (m, hex, dec, name) => {
|
|
472
|
+
if (hex) return String.fromCodePoint(parseInt(hex, 16));
|
|
473
|
+
if (dec) return String.fromCodePoint(parseInt(dec, 10));
|
|
474
|
+
return NAMED_ENTITIES[name] ?? m;
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
function normalizeCellText(s) {
|
|
478
|
+
return decodeEntities(s).replace(/\p{Cf}/gu, "").replace(/[\s ]+/gu, " ").trim().normalize("NFC");
|
|
479
|
+
}
|
|
480
|
+
var CELL_RE = /<td width='auto' id=["']?(\d+)\.(\d+)["']?\s*>\s*<span class='(style\d)'><div align='(?:center|left|right)'>([\s\S]*?)<\/div><\/span><\/td>/g;
|
|
481
|
+
var META_ROW_RE = /<tr><td><div align='left'><span class='style3'>([^<]*)<\/span><\/div><\/td><td><div align='left'><span class='style3'>:<\/span><\/div><\/td><td><div align='left'><span class='style3'>([^<]*)<\/span><\/div><\/td><\/tr>/g;
|
|
482
|
+
function extractPage(html) {
|
|
483
|
+
const header = {};
|
|
484
|
+
for (const m of html.matchAll(META_ROW_RE)) {
|
|
485
|
+
const label = normalizeCellText(m[1]);
|
|
486
|
+
const value = normalizeCellText(m[2]);
|
|
487
|
+
if (label === "\u09A8\u09BE\u09AE") header.title = value;
|
|
488
|
+
else if (label === "\u09A4\u09BE\u09B2") header.taalNameRaw = value;
|
|
489
|
+
else if (label === "\u0986\u09AC\u09B0\u09CD\u09A4\u09A8") header.avartanRaw = value;
|
|
490
|
+
}
|
|
491
|
+
const printMatch = html.match(/\/print\/(\d+)/);
|
|
492
|
+
if (printMatch) header.printId = Number(printMatch[1]);
|
|
493
|
+
const cells = [];
|
|
494
|
+
let maxRow = 0;
|
|
495
|
+
for (const m of html.matchAll(CELL_RE)) {
|
|
496
|
+
const row = Number(m[1]);
|
|
497
|
+
const col = Number(m[2]);
|
|
498
|
+
cells.push({ row, col, style: m[3], text: normalizeCellText(m[4]) });
|
|
499
|
+
if (row > maxRow) maxRow = row;
|
|
500
|
+
}
|
|
501
|
+
const tdCount = (html.match(/<td width='auto' id=/g) ?? []).length;
|
|
502
|
+
if (cells.length !== tdCount) {
|
|
503
|
+
throw new ExtractError(
|
|
504
|
+
`grid cell mismatch: matched ${cells.length} cells but found ${tdCount} '<td width=' openers`
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
if (cells.length === 0) throw new ExtractError("no swaralipi grid cells found");
|
|
508
|
+
if (header.title === void 0) throw new ExtractError("no \u09A8\u09BE\u09AE metadata row found");
|
|
509
|
+
return { header, cells, maxRow };
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// src/convert/report.ts
|
|
513
|
+
function aggregate(reports, failures) {
|
|
514
|
+
const out = {
|
|
515
|
+
files: reports.length + failures.length,
|
|
516
|
+
ok: reports.length,
|
|
517
|
+
failed: failures.length,
|
|
518
|
+
failures,
|
|
519
|
+
systems: 0,
|
|
520
|
+
notationCells: 0,
|
|
521
|
+
lyricCells: 0,
|
|
522
|
+
annotationCells: 0,
|
|
523
|
+
decodedCells: 0,
|
|
524
|
+
knownRawCells: 0,
|
|
525
|
+
unknownCells: 0,
|
|
526
|
+
decodeRate: 0,
|
|
527
|
+
unknownRate: 0,
|
|
528
|
+
unknownTokens: {},
|
|
529
|
+
lyricAlignMismatches: 0,
|
|
530
|
+
taals: {},
|
|
531
|
+
warningCounts: {}
|
|
532
|
+
};
|
|
533
|
+
const unknown = /* @__PURE__ */ new Map();
|
|
534
|
+
for (const r of reports) {
|
|
535
|
+
out.systems += r.systems;
|
|
536
|
+
out.notationCells += r.notationCells;
|
|
537
|
+
out.lyricCells += r.lyricCells;
|
|
538
|
+
out.annotationCells += r.annotationCells;
|
|
539
|
+
out.decodedCells += r.decodedCells;
|
|
540
|
+
out.knownRawCells += r.knownRawCells;
|
|
541
|
+
out.unknownCells += r.unknownCells;
|
|
542
|
+
out.lyricAlignMismatches += r.lyricAlignMismatches;
|
|
543
|
+
for (const [tok, n] of Object.entries(r.unknownTokens)) {
|
|
544
|
+
unknown.set(tok, (unknown.get(tok) ?? 0) + n);
|
|
545
|
+
}
|
|
546
|
+
const taalKey = r.taalName ?? "<none>";
|
|
547
|
+
const taalEntry = out.taals[taalKey] ??= { count: 0, taalId: r.taalId, matched: r.taalMatched };
|
|
548
|
+
taalEntry.count++;
|
|
549
|
+
for (const w of r.warnings) {
|
|
550
|
+
const shape = w.replace(/\d+/g, "N");
|
|
551
|
+
out.warningCounts[shape] = (out.warningCounts[shape] ?? 0) + 1;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
out.unknownTokens = Object.fromEntries([...unknown.entries()].sort((a, b) => b[1] - a[1]));
|
|
555
|
+
if (out.notationCells > 0) {
|
|
556
|
+
out.decodeRate = out.decodedCells / out.notationCells;
|
|
557
|
+
out.unknownRate = out.unknownCells / out.notationCells;
|
|
558
|
+
}
|
|
559
|
+
return out;
|
|
560
|
+
}
|
|
561
|
+
function formatSummary(r) {
|
|
562
|
+
const pct = (x) => `${(x * 100).toFixed(2)}%`;
|
|
563
|
+
const lines = [
|
|
564
|
+
`files: ${r.ok}/${r.files} converted${r.failed ? ` (${r.failed} FAILED)` : ""}`,
|
|
565
|
+
`systems: ${r.systems} notation cells: ${r.notationCells} lyric cells: ${r.lyricCells} annotation cells: ${r.annotationCells}`,
|
|
566
|
+
`decoded: ${r.decodedCells} (${pct(r.decodeRate)}) known-raw: ${r.knownRawCells} unknown: ${r.unknownCells} (${pct(r.unknownRate)})`,
|
|
567
|
+
`lyric alignment fallbacks: ${r.lyricAlignMismatches}`
|
|
568
|
+
];
|
|
569
|
+
const unknowns = Object.entries(r.unknownTokens);
|
|
570
|
+
if (unknowns.length > 0) {
|
|
571
|
+
lines.push(`unknown tokens (${unknowns.length} distinct, top 20):`);
|
|
572
|
+
for (const [tok, n] of unknowns.slice(0, 20)) lines.push(` ${String(n).padStart(6)} ${JSON.stringify(tok)}`);
|
|
573
|
+
}
|
|
574
|
+
const taalLines = Object.entries(r.taals).sort((a, b) => b[1].count - a[1].count);
|
|
575
|
+
lines.push(`taals (${taalLines.length} distinct):`);
|
|
576
|
+
for (const [name, info] of taalLines.slice(0, 15)) {
|
|
577
|
+
lines.push(` ${String(info.count).padStart(5)} ${name} \u2192 ${info.taalId ?? "\u2014"} [${info.matched}]`);
|
|
578
|
+
}
|
|
579
|
+
if (r.failed > 0) {
|
|
580
|
+
lines.push("failures:");
|
|
581
|
+
for (const f of r.failures.slice(0, 10)) lines.push(` ${f.file}: ${f.error}`);
|
|
582
|
+
}
|
|
583
|
+
return lines.join("\n");
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// src/convert/index.ts
|
|
587
|
+
function convertNltrHtml(html, opts = {}) {
|
|
588
|
+
return assemble(extractPage(html), opts);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
export { ExtractError, aggregate, assemble, bnInt, buildSystems, convertNltrHtml, decodeCell, deriveGeometry, extractPage, formatSummary, resolveTaal, splitLyric };
|
|
592
|
+
//# sourceMappingURL=chunk-N3NNWAOW.js.map
|
|
593
|
+
//# sourceMappingURL=chunk-N3NNWAOW.js.map
|