@ugfoundation/swaralipi-js 0.4.0 → 0.6.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/README.md +8 -8
- package/dist/{batch-V4X3GN4N.js → batch-RUZGM2NR.js} +4 -4
- package/dist/{batch-V4X3GN4N.js.map → batch-RUZGM2NR.js.map} +1 -1
- package/dist/{chunk-6QW7MSJO.js → chunk-GHWURC4U.js} +98 -6
- package/dist/chunk-GHWURC4U.js.map +1 -0
- package/dist/{chunk-RTKWFJFI.js → chunk-ULK7NVKH.js} +8 -6
- package/dist/chunk-ULK7NVKH.js.map +1 -0
- package/dist/{chunk-ZS43BDP7.js → chunk-ZNVTOY7J.js} +7 -6
- package/dist/chunk-ZNVTOY7J.js.map +1 -0
- package/dist/cli.cjs +11 -8
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/convert/index.cjs +10 -7
- package/dist/convert/index.cjs.map +1 -1
- package/dist/convert/index.d.cts +4 -3
- package/dist/convert/index.d.ts +4 -3
- package/dist/convert/index.js +2 -2
- package/dist/index.cjs +6 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/player/index.cjs +222 -23
- package/dist/player/index.cjs.map +1 -1
- package/dist/player/index.d.cts +16 -4
- package/dist/player/index.d.ts +16 -4
- package/dist/player/index.js +120 -22
- package/dist/player/index.js.map +1 -1
- package/dist/{schedule-CAns78TD.d.ts → schedule-8WFUBJqB.d.ts} +82 -7
- package/dist/{schedule-D3JyEhuO.d.cts → schedule-CjPcD-Hf.d.cts} +82 -7
- package/dist/{schema-D3wyNRqM.d.ts → schema-D_86x4oV.d.cts} +21 -6
- package/dist/{schema-D3wyNRqM.d.cts → schema-D_86x4oV.d.ts} +21 -6
- package/dist/spec/index.cjs +6 -4
- package/dist/spec/index.cjs.map +1 -1
- package/dist/spec/index.d.cts +3 -3
- package/dist/spec/index.d.ts +3 -3
- package/dist/spec/index.js +1 -1
- package/dist/viewer/index.cjs +253 -172
- package/dist/viewer/index.cjs.map +1 -1
- package/dist/viewer/index.d.cts +16 -23
- package/dist/viewer/index.d.ts +16 -23
- package/dist/viewer/index.js +88 -47
- package/dist/viewer/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-6QW7MSJO.js.map +0 -1
- package/dist/chunk-RTKWFJFI.js.map +0 -1
- package/dist/chunk-ZS43BDP7.js.map +0 -1
package/dist/viewer/index.cjs
CHANGED
|
@@ -72,7 +72,9 @@ var init_schema = __esm({
|
|
|
72
72
|
sustain: zod.z.boolean().optional(),
|
|
73
73
|
rest: zod.z.boolean().optional(),
|
|
74
74
|
kan: svaraSchema.optional(),
|
|
75
|
+
resh: svaraSchema.optional(),
|
|
75
76
|
meend: zod.z.enum(["start", "end"]).optional(),
|
|
77
|
+
halfMatra: zod.z.boolean().optional(),
|
|
76
78
|
bar: barSchema.optional(),
|
|
77
79
|
brackets: zod.z.array(bracketSchema).optional(),
|
|
78
80
|
lyric: zod.z.string().optional(),
|
|
@@ -123,9 +125,9 @@ var init_schema = __esm({
|
|
|
123
125
|
nltrId: zod.z.number().int().optional(),
|
|
124
126
|
taal: taalSchema.optional(),
|
|
125
127
|
/**
|
|
126
|
-
* The taal name
|
|
127
|
-
*
|
|
128
|
-
* when the name resolves to a
|
|
128
|
+
* The taal name as printed in the source (e.g. `ষষ্ঠী(২/৪)`), except
|
|
129
|
+
* free rhythm which is normalised to `মুক্তছন্দ`. Always set by the NLTR
|
|
130
|
+
* converter; `taal` is set only when the name resolves to a definition.
|
|
129
131
|
*/
|
|
130
132
|
taalName: zod.z.string().optional(),
|
|
131
133
|
/** Base pitch for playback, e.g. `C`, `C#`, or the player's `cs` form. */
|
|
@@ -183,10 +185,170 @@ var init_spec = __esm({
|
|
|
183
185
|
init_schema();
|
|
184
186
|
init_schema();
|
|
185
187
|
init_taals();
|
|
186
|
-
SPEC_VERSION = "0.
|
|
188
|
+
SPEC_VERSION = "0.6.0";
|
|
187
189
|
}
|
|
188
190
|
});
|
|
189
191
|
|
|
192
|
+
// src/player/pitch.ts
|
|
193
|
+
var OCTAVE_OFFSET = {
|
|
194
|
+
"ati-mandra": -2,
|
|
195
|
+
mandra: -1,
|
|
196
|
+
madhyam: 0,
|
|
197
|
+
taar: 1,
|
|
198
|
+
"ati-taar": 2
|
|
199
|
+
};
|
|
200
|
+
var SHUDDHA = {
|
|
201
|
+
s: 0,
|
|
202
|
+
r: 2,
|
|
203
|
+
g: 4,
|
|
204
|
+
m: 5,
|
|
205
|
+
p: 7,
|
|
206
|
+
d: 9,
|
|
207
|
+
n: 11
|
|
208
|
+
};
|
|
209
|
+
var SHARP_NAMES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
|
|
210
|
+
var PITCH_CLASS = {
|
|
211
|
+
c: 0,
|
|
212
|
+
"c#": 1,
|
|
213
|
+
db: 1,
|
|
214
|
+
d: 2,
|
|
215
|
+
"d#": 3,
|
|
216
|
+
eb: 3,
|
|
217
|
+
e: 4,
|
|
218
|
+
"e#": 5,
|
|
219
|
+
fb: 4,
|
|
220
|
+
f: 5,
|
|
221
|
+
"f#": 6,
|
|
222
|
+
gb: 6,
|
|
223
|
+
g: 7,
|
|
224
|
+
"g#": 8,
|
|
225
|
+
ab: 8,
|
|
226
|
+
a: 9,
|
|
227
|
+
"a#": 10,
|
|
228
|
+
bb: 10,
|
|
229
|
+
b: 11,
|
|
230
|
+
"b#": 0,
|
|
231
|
+
cb: 11
|
|
232
|
+
};
|
|
233
|
+
function parseTonic(tonic) {
|
|
234
|
+
if (!tonic) return 0;
|
|
235
|
+
const key = tonic.trim().toLowerCase().replace(/s/g, "#");
|
|
236
|
+
return PITCH_CLASS[key] ?? 0;
|
|
237
|
+
}
|
|
238
|
+
function svaraSemitone(note) {
|
|
239
|
+
if (note.svara === void 0) return null;
|
|
240
|
+
const [, letter] = note.svara;
|
|
241
|
+
let semi = SHUDDHA[letter];
|
|
242
|
+
if (note.komal === true || note.microtone !== void 0) semi -= 1;
|
|
243
|
+
if (note.tivra === true) semi += 1;
|
|
244
|
+
return semi;
|
|
245
|
+
}
|
|
246
|
+
function svaraToMidi(note, tonicPc, baseOctave = 4) {
|
|
247
|
+
const semi = svaraSemitone(note);
|
|
248
|
+
if (semi === null || note.svara === void 0) return null;
|
|
249
|
+
const [octave] = note.svara;
|
|
250
|
+
return (baseOctave + 1 + OCTAVE_OFFSET[octave]) * 12 + tonicPc + semi;
|
|
251
|
+
}
|
|
252
|
+
function kanToMidi(svara, tonicPc, baseOctave = 4) {
|
|
253
|
+
if (svara === void 0) return null;
|
|
254
|
+
const [octave, letter] = svara;
|
|
255
|
+
return (baseOctave + 1 + OCTAVE_OFFSET[octave]) * 12 + tonicPc + SHUDDHA[letter];
|
|
256
|
+
}
|
|
257
|
+
function midiToSampleName(midi) {
|
|
258
|
+
const pc = (midi % 12 + 12) % 12;
|
|
259
|
+
const octave = Math.floor(midi / 12) - 1;
|
|
260
|
+
return `${SHARP_NAMES[pc]}${octave}`;
|
|
261
|
+
}
|
|
262
|
+
function sampleFileName(midi) {
|
|
263
|
+
return midiToSampleName(midi).toLowerCase().replace("#", "s");
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// src/player/schedule.ts
|
|
267
|
+
var UNDECODED_CELL = /[srgmpqnvtduka]/i;
|
|
268
|
+
var isSilent = (n) => n.svara === void 0 && n.rest !== true && n.sustain !== true && !(n.raw !== void 0 && UNDECODED_CELL.test(n.raw));
|
|
269
|
+
var ARC_RAW_RE = /^[wWxXyYjJeEzZ$=_<>`\\]+$/;
|
|
270
|
+
var isArcRaw = (raw) => raw !== void 0 && ARC_RAW_RE.test(raw);
|
|
271
|
+
function meendContinuations(notes) {
|
|
272
|
+
const out = /* @__PURE__ */ new Set();
|
|
273
|
+
let pending;
|
|
274
|
+
notes.forEach((note, i) => {
|
|
275
|
+
let marks = note.meend === "start" ? "w" : note.meend === "end" ? "x" : "";
|
|
276
|
+
for (const b of note.below ?? []) if (isArcRaw(b.raw)) marks += b.raw;
|
|
277
|
+
let marked = false;
|
|
278
|
+
for (const ch of marks) {
|
|
279
|
+
if (ch === "x" || ch === "X") {
|
|
280
|
+
if (pending !== void 0) for (const k of [...pending, i]) out.add(k);
|
|
281
|
+
pending = void 0;
|
|
282
|
+
marked = true;
|
|
283
|
+
} else if (ch === "w" || ch === "W") {
|
|
284
|
+
pending = [];
|
|
285
|
+
marked = true;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (!marked && pending !== void 0) pending.push(i);
|
|
289
|
+
});
|
|
290
|
+
return out;
|
|
291
|
+
}
|
|
292
|
+
function glideTargets(events, i) {
|
|
293
|
+
const out = [];
|
|
294
|
+
for (let j = i + 1; j < events.length; j++) {
|
|
295
|
+
const ev = events[j];
|
|
296
|
+
if (ev.kind === "rest") break;
|
|
297
|
+
if (ev.midi === null) continue;
|
|
298
|
+
if (ev.glide !== true) break;
|
|
299
|
+
out.push(ev);
|
|
300
|
+
}
|
|
301
|
+
return out;
|
|
302
|
+
}
|
|
303
|
+
var SAM_TICK = /^\d+[fF]$/;
|
|
304
|
+
function buildSchedule(doc, opts = {}) {
|
|
305
|
+
const tonicPc = parseTonic(opts.tonic ?? doc.meta.tonic);
|
|
306
|
+
const baseOctave = opts.baseOctave ?? 4;
|
|
307
|
+
const events = [];
|
|
308
|
+
let cursor = 0;
|
|
309
|
+
let lastPitched = -1;
|
|
310
|
+
let samBeat;
|
|
311
|
+
doc.parts.forEach((part, p) => {
|
|
312
|
+
part.rows.forEach((row, r) => {
|
|
313
|
+
const glides = meendContinuations(row.notes);
|
|
314
|
+
row.notes.forEach((note, i) => {
|
|
315
|
+
if (samBeat === void 0 && note.above !== void 0) {
|
|
316
|
+
for (const a of note.above) {
|
|
317
|
+
if (a.raw !== void 0 && SAM_TICK.test(a.raw)) {
|
|
318
|
+
samBeat = cursor;
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (note.bar !== void 0 || isSilent(note)) return;
|
|
324
|
+
const path = `${p}.${r}.${i}`;
|
|
325
|
+
const dur = note.durationMatra ?? 1;
|
|
326
|
+
if (note.svara !== void 0) {
|
|
327
|
+
events.push({
|
|
328
|
+
beat: cursor,
|
|
329
|
+
durationBeats: dur,
|
|
330
|
+
midi: svaraToMidi(note, tonicPc, baseOctave),
|
|
331
|
+
kan: kanToMidi(note.kan, tonicPc, baseOctave),
|
|
332
|
+
path,
|
|
333
|
+
kind: note.sustain === true ? "sustain" : "note",
|
|
334
|
+
...glides.has(i) && lastPitched >= 0 ? { glide: true } : {}
|
|
335
|
+
});
|
|
336
|
+
lastPitched = events.length - 1;
|
|
337
|
+
} else if (note.sustain === true) {
|
|
338
|
+
if (lastPitched >= 0) events[lastPitched].durationBeats += dur;
|
|
339
|
+
events.push({ beat: cursor, durationBeats: dur, midi: null, kan: null, path, kind: "sustain" });
|
|
340
|
+
} else {
|
|
341
|
+
events.push({ beat: cursor, durationBeats: dur, midi: null, kan: null, path, kind: "rest" });
|
|
342
|
+
}
|
|
343
|
+
cursor += dur;
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
const numBeats = doc.meta.taal?.numBeats;
|
|
348
|
+
const pickupBeats = samBeat !== void 0 && numBeats !== void 0 && numBeats > 0 ? (numBeats - samBeat % numBeats) % numBeats : 0;
|
|
349
|
+
return { events, totalBeats: cursor, pickupBeats };
|
|
350
|
+
}
|
|
351
|
+
|
|
190
352
|
// src/viewer/glyphs.ts
|
|
191
353
|
var SWARABITAN_SHUDDHA = {
|
|
192
354
|
s: "s",
|
|
@@ -227,7 +389,9 @@ var MARKS = {
|
|
|
227
389
|
var LABELS = {
|
|
228
390
|
bn: { title: "\u09A8\u09BE\u09AE", taal: "\u09A4\u09BE\u09B2", raag: "\u09B0\u09BE\u0997", avartan: "\u0986\u09AC\u09B0\u09CD\u09A4\u09A8", notationBy: "\u09B8\u09CD\u09AC\u09B0\u09B2\u09BF\u09AA\u09BF\u0995\u09BE\u09B0" },
|
|
229
391
|
hi: { title: "\u0928\u093E\u092E", taal: "\u0924\u093E\u0932", raag: "\u0930\u093E\u0917", avartan: "\u0906\u0935\u0930\u094D\u0924\u0928", notationBy: "\u0938\u094D\u0935\u0930\u0932\u093F\u092A\u093F\u0915\u093E\u0930" },
|
|
230
|
-
|
|
392
|
+
// "Cycle", not "Avartan": the English sheet is read by people who do not know the word, and
|
|
393
|
+
// one taal cycle is exactly what the number counts.
|
|
394
|
+
en: { title: "Song", taal: "Taal", raag: "Raag", avartan: "Cycles", notationBy: "Notator" }
|
|
231
395
|
};
|
|
232
396
|
function composeCluster(letter, opts) {
|
|
233
397
|
const classes = [];
|
|
@@ -251,7 +415,6 @@ function localizeDigits(s, lang) {
|
|
|
251
415
|
}
|
|
252
416
|
|
|
253
417
|
// src/viewer/render.ts
|
|
254
|
-
var NBSP = "\xA0";
|
|
255
418
|
var EPS = 1e-6;
|
|
256
419
|
var isIntegral = (x) => Math.abs(x - Math.round(x)) < EPS;
|
|
257
420
|
function escapeHtml(s) {
|
|
@@ -286,7 +449,7 @@ function groupRowIntoCells(notes) {
|
|
|
286
449
|
return;
|
|
287
450
|
}
|
|
288
451
|
const d = note.durationMatra ?? 1;
|
|
289
|
-
if (isIntegral(d)) {
|
|
452
|
+
if (isIntegral(d) && note.halfMatra !== true) {
|
|
290
453
|
close();
|
|
291
454
|
cells.push({ kind: "group", start: i, notes: [note] });
|
|
292
455
|
return;
|
|
@@ -294,13 +457,11 @@ function groupRowIntoCells(notes) {
|
|
|
294
457
|
cur ??= { kind: "group", start: i, notes: [] };
|
|
295
458
|
cur.notes.push(note);
|
|
296
459
|
acc += d;
|
|
297
|
-
if (acc > EPS && isIntegral(acc)) close();
|
|
460
|
+
if (note.halfMatra === true || acc > EPS && isIntegral(acc)) close();
|
|
298
461
|
});
|
|
299
462
|
close();
|
|
300
463
|
return cells;
|
|
301
464
|
}
|
|
302
|
-
var ARC_RAW_RE = /^[wWxXyYjJeEzZ$=_<>`\\]+$/;
|
|
303
|
-
var isArcRaw = (raw) => raw !== void 0 && ARC_RAW_RE.test(raw);
|
|
304
465
|
function resolveArcs(cells, noteCount) {
|
|
305
466
|
const arcs = [];
|
|
306
467
|
let openStart;
|
|
@@ -369,15 +530,21 @@ function bracketGlyphs(note, edge) {
|
|
|
369
530
|
function matraGlyph(ctx) {
|
|
370
531
|
return ctx.lang === "bn" ? '<span class="sw-cl">a</span>' : '<span class="sw-iso">\u2013</span>';
|
|
371
532
|
}
|
|
372
|
-
function
|
|
373
|
-
if (
|
|
374
|
-
const [octave, letter] =
|
|
533
|
+
function renderGrace(svara, cls, ctx) {
|
|
534
|
+
if (svara === void 0) return "";
|
|
535
|
+
const [octave, letter] = svara;
|
|
375
536
|
const c = composeCluster(letter, { octave, withMatra: false, lang: ctx.lang });
|
|
376
|
-
return `<span class="${[
|
|
537
|
+
return `<span class="${[cls, ...c.classes].join(" ")}">${c.text}</span>`;
|
|
538
|
+
}
|
|
539
|
+
function cellMatra(cell) {
|
|
540
|
+
const total = cell.notes.reduce((a, n) => a + (n.durationMatra ?? 1), 0);
|
|
541
|
+
const half = cell.notes.some((n) => n.halfMatra === true);
|
|
542
|
+
const whole = Math.floor(total + EPS);
|
|
543
|
+
return { matras: half ? whole : Math.max(1, whole), half };
|
|
377
544
|
}
|
|
378
545
|
function renderPitchedInner(note, opts, ctx) {
|
|
379
546
|
let inner = bracketGlyphs(note, "open");
|
|
380
|
-
inner +=
|
|
547
|
+
inner += renderGrace(note.kan, "sw-kan", ctx);
|
|
381
548
|
if (note.sustain === true) inner += `<span class="sw-hy">${ctx.marks.hyphen}</span>`;
|
|
382
549
|
if (note.svara !== void 0) {
|
|
383
550
|
const [octave, letter] = note.svara;
|
|
@@ -385,22 +552,27 @@ function renderPitchedInner(note, opts, ctx) {
|
|
|
385
552
|
komal: note.komal,
|
|
386
553
|
tivra: note.tivra,
|
|
387
554
|
octave,
|
|
388
|
-
|
|
555
|
+
// The first matra is composed into the cluster itself (bn: the font's
|
|
556
|
+
// trailing `a`); any further ones are separate glyphs below.
|
|
557
|
+
withMatra: opts.matras >= 1,
|
|
389
558
|
lang: ctx.lang
|
|
390
559
|
});
|
|
391
560
|
inner += `<span class="${["sw-cl", ...c.classes].join(" ")}">${c.text}</span>`;
|
|
392
561
|
} else if (note.rest === true) {
|
|
393
|
-
inner += matraGlyph(ctx);
|
|
394
|
-
} else if (note.sustain === true
|
|
395
|
-
inner += matraGlyph(ctx);
|
|
562
|
+
inner += matraGlyph(ctx).repeat(Math.max(1, opts.matras));
|
|
563
|
+
} else if (note.sustain === true) {
|
|
564
|
+
inner += matraGlyph(ctx).repeat(opts.matras);
|
|
396
565
|
}
|
|
397
566
|
if (note.microtone !== void 0) {
|
|
398
567
|
const mark = localizeDigits(note.microtone === "atikomal" ? "2" : "1", ctx.lang);
|
|
399
568
|
inner += `<span class="sw-micro" title="${note.microtone}">${mark}</span>`;
|
|
400
569
|
}
|
|
401
|
-
|
|
402
|
-
if (
|
|
403
|
-
|
|
570
|
+
inner += renderGrace(note.resh, "sw-resh", ctx);
|
|
571
|
+
if (note.svara !== void 0 && opts.matras > 1) {
|
|
572
|
+
inner += matraGlyph(ctx).repeat(opts.matras - 1);
|
|
573
|
+
}
|
|
574
|
+
if (opts.half) inner += `<span class="sw-half" title="${ctx.marks.cut}"></span>`;
|
|
575
|
+
if (opts.cuts > 0) inner += `<span class="sw-cut" title="${ctx.marks.cut}"></span>`.repeat(opts.cuts);
|
|
404
576
|
inner += bracketGlyphs(note, "close");
|
|
405
577
|
return inner;
|
|
406
578
|
}
|
|
@@ -417,12 +589,12 @@ function renderAnnotation(notes, ctx) {
|
|
|
417
589
|
continue;
|
|
418
590
|
}
|
|
419
591
|
if (cell.kind === "group") {
|
|
420
|
-
const
|
|
592
|
+
const { matras, half } = cellMatra(cell);
|
|
593
|
+
const cuts = half ? 0 : cutMarkCount(cell.notes[0].raw);
|
|
421
594
|
cell.notes.forEach((note2, j) => {
|
|
422
595
|
const last = j === cell.notes.length - 1;
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
);
|
|
596
|
+
const opts = last ? { matras, half, cuts } : { matras: 0, half: false, cuts: 0 };
|
|
597
|
+
parts.push(`<span class="sw-n">${renderPitchedInner(note2, opts, ctx)}</span>`);
|
|
426
598
|
});
|
|
427
599
|
continue;
|
|
428
600
|
}
|
|
@@ -460,7 +632,8 @@ function renderRow(row, partIdx, rowIdx, ctx) {
|
|
|
460
632
|
const firstCol = cell.start + 1;
|
|
461
633
|
const lastCol = cell.start + cell.notes.length;
|
|
462
634
|
const cellSpan = lastCol > firstCol ? `grid-column:${firstCol} / ${lastCol + 1}` : `grid-column:${firstCol}`;
|
|
463
|
-
const
|
|
635
|
+
const { matras, half } = cellMatra(cell);
|
|
636
|
+
const cuts = cell.kind === "group" && !half ? cutMarkCount(cell.notes[0].raw) : 0;
|
|
464
637
|
cell.notes.forEach((note, j) => {
|
|
465
638
|
const col = cell.start + j + 1;
|
|
466
639
|
const path = `${partIdx}.${rowIdx}.${cell.start + j}`;
|
|
@@ -471,8 +644,9 @@ function renderRow(row, partIdx, rowIdx, ctx) {
|
|
|
471
644
|
);
|
|
472
645
|
} else if (cell.kind === "group") {
|
|
473
646
|
const last = j === cell.notes.length - 1;
|
|
647
|
+
const pOpts = last ? { matras, half, cuts } : { matras: 0, half: false, cuts: 0 };
|
|
474
648
|
notation.push(
|
|
475
|
-
`<span class="sw-n${cellEdges}" data-note="${path}" style="grid-column:${col}">${renderPitchedInner(note,
|
|
649
|
+
`<span class="sw-n${cellEdges}" data-note="${path}" style="grid-column:${col}">${renderPitchedInner(note, pOpts, ctx)}</span>`
|
|
476
650
|
);
|
|
477
651
|
} else {
|
|
478
652
|
const raw = rawDisplay(note);
|
|
@@ -545,9 +719,9 @@ function renderToHtml(doc, opts = {}) {
|
|
|
545
719
|
|
|
546
720
|
// src/viewer/styles.ts
|
|
547
721
|
var NOTATION_FONT_URLS = {
|
|
548
|
-
Swarabitan: "https://
|
|
549
|
-
ome_bhatkhande_hindi: "https://
|
|
550
|
-
ome_bhatkhande_english: "https://
|
|
722
|
+
Swarabitan: "https://lipighor.pages.dev/swarabitan/Swarabitan.ttf",
|
|
723
|
+
ome_bhatkhande_hindi: "https://lipighor.pages.dev/ome-bhatkhande/OmeBhatkhandeHindi.ttf",
|
|
724
|
+
ome_bhatkhande_english: "https://lipighor.pages.dev/ome-bhatkhande/OmeBhatkhandeEnglish.ttf"
|
|
551
725
|
};
|
|
552
726
|
NOTATION_FONT_URLS.Swarabitan;
|
|
553
727
|
var FONT_FACES = Object.entries(NOTATION_FONT_URLS).map(
|
|
@@ -563,9 +737,9 @@ ${FONT_FACES}
|
|
|
563
737
|
}
|
|
564
738
|
|
|
565
739
|
/* ---- notation font, per language (the note clusters + grace notes) ---- */
|
|
566
|
-
.sw-sheet[lang="bn"] .sw-cl, .sw-
|
|
567
|
-
.sw-sheet[lang="hi"] .sw-cl, .sw-
|
|
568
|
-
.sw-sheet[lang="en"] .sw-cl, .sw-
|
|
740
|
+
.sw-sheet[lang="bn"] :is(.sw-cl, .sw-kan, .sw-resh) { font-family: Swarabitan, var(--sw-font, sans-serif); }
|
|
741
|
+
.sw-sheet[lang="hi"] :is(.sw-cl, .sw-kan, .sw-resh) { font-family: ome_bhatkhande_hindi, var(--sw-font, sans-serif); }
|
|
742
|
+
.sw-sheet[lang="en"] :is(.sw-cl, .sw-kan, .sw-resh) { font-family: ome_bhatkhande_english, var(--sw-font, sans-serif); }
|
|
569
743
|
|
|
570
744
|
/* ---- header ---- */
|
|
571
745
|
.sw-header {
|
|
@@ -624,18 +798,44 @@ ${FONT_FACES}
|
|
|
624
798
|
.sw-ati-taar::before { top: -0.3em; }
|
|
625
799
|
.sw-ati-mandra::before { bottom: -0.35em; }
|
|
626
800
|
|
|
627
|
-
/*
|
|
628
|
-
.sw-kan
|
|
801
|
+
/* grace notes: small raised clusters \u2014 \u0995\u09A3 before the note, \u09B0\u09C7\u09B6 after it */
|
|
802
|
+
.sw-kan,
|
|
803
|
+
.sw-resh {
|
|
629
804
|
position: relative;
|
|
630
805
|
display: inline-block;
|
|
631
806
|
font-size: 0.58em;
|
|
632
807
|
vertical-align: 0.75em;
|
|
633
|
-
margin-right: 0.08em;
|
|
634
808
|
}
|
|
809
|
+
.sw-kan { margin-right: 0.08em; }
|
|
810
|
+
.sw-resh { margin-left: 0.08em; }
|
|
635
811
|
|
|
636
812
|
.sw-hy { padding-right: 0.02em; }
|
|
637
813
|
.sw-iso { display: inline-block; margin-left: -0.18em; }
|
|
638
|
-
|
|
814
|
+
/* The \u0985\u09B0\u09CD\u09A7\u09AE\u09BE\u09A4\u09CD\u09B0\u09BE / \u099B\u09C7\u09A6 mark: two stacked rings (legend p. \u09E7\u09E9\u09EB), drawn rather
|
|
815
|
+
* than typed \u2014 an isolated \u0983 has no base, so every Bengali font shapes it with
|
|
816
|
+
* a dotted circle (an NBSP carrier does not help). sw-half = semantic
|
|
817
|
+
* (halfMatra), sw-cut = decoded from a source cell's raw: one look, two
|
|
818
|
+
* provenances. */
|
|
819
|
+
.sw-cut,
|
|
820
|
+
.sw-half {
|
|
821
|
+
position: relative;
|
|
822
|
+
display: inline-block;
|
|
823
|
+
width: 0.3em;
|
|
824
|
+
height: 1em;
|
|
825
|
+
vertical-align: baseline;
|
|
826
|
+
}
|
|
827
|
+
.sw-cut::before, .sw-cut::after,
|
|
828
|
+
.sw-half::before, .sw-half::after {
|
|
829
|
+
content: '';
|
|
830
|
+
position: absolute;
|
|
831
|
+
left: 0.08em;
|
|
832
|
+
width: 0.15em;
|
|
833
|
+
height: 0.15em;
|
|
834
|
+
border-radius: 50%;
|
|
835
|
+
background: currentColor;
|
|
836
|
+
}
|
|
837
|
+
.sw-cut::before, .sw-half::before { top: 0.2em; }
|
|
838
|
+
.sw-cut::after, .sw-half::after { top: 0.55em; }
|
|
639
839
|
.sw-br { opacity: 0.8; padding: 0 0.06em; }
|
|
640
840
|
.sw-micro { font-size: 0.6em; vertical-align: super; opacity: 0.8; }
|
|
641
841
|
|
|
@@ -746,129 +946,6 @@ var SWARALIPI_STAFF_CSS = `
|
|
|
746
946
|
.sw-staff-subtitle { opacity: .8; font-size: .95em; margin-top: .15em; }
|
|
747
947
|
`;
|
|
748
948
|
|
|
749
|
-
// src/player/pitch.ts
|
|
750
|
-
var OCTAVE_OFFSET = {
|
|
751
|
-
"ati-mandra": -2,
|
|
752
|
-
mandra: -1,
|
|
753
|
-
madhyam: 0,
|
|
754
|
-
taar: 1,
|
|
755
|
-
"ati-taar": 2
|
|
756
|
-
};
|
|
757
|
-
var SHUDDHA = {
|
|
758
|
-
s: 0,
|
|
759
|
-
r: 2,
|
|
760
|
-
g: 4,
|
|
761
|
-
m: 5,
|
|
762
|
-
p: 7,
|
|
763
|
-
d: 9,
|
|
764
|
-
n: 11
|
|
765
|
-
};
|
|
766
|
-
var SHARP_NAMES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
|
|
767
|
-
var PITCH_CLASS = {
|
|
768
|
-
c: 0,
|
|
769
|
-
"c#": 1,
|
|
770
|
-
db: 1,
|
|
771
|
-
d: 2,
|
|
772
|
-
"d#": 3,
|
|
773
|
-
eb: 3,
|
|
774
|
-
e: 4,
|
|
775
|
-
"e#": 5,
|
|
776
|
-
fb: 4,
|
|
777
|
-
f: 5,
|
|
778
|
-
"f#": 6,
|
|
779
|
-
gb: 6,
|
|
780
|
-
g: 7,
|
|
781
|
-
"g#": 8,
|
|
782
|
-
ab: 8,
|
|
783
|
-
a: 9,
|
|
784
|
-
"a#": 10,
|
|
785
|
-
bb: 10,
|
|
786
|
-
b: 11,
|
|
787
|
-
"b#": 0,
|
|
788
|
-
cb: 11
|
|
789
|
-
};
|
|
790
|
-
function parseTonic(tonic) {
|
|
791
|
-
if (!tonic) return 0;
|
|
792
|
-
const key = tonic.trim().toLowerCase().replace(/s/g, "#");
|
|
793
|
-
return PITCH_CLASS[key] ?? 0;
|
|
794
|
-
}
|
|
795
|
-
function svaraSemitone(note) {
|
|
796
|
-
if (note.svara === void 0) return null;
|
|
797
|
-
const [, letter] = note.svara;
|
|
798
|
-
let semi = SHUDDHA[letter];
|
|
799
|
-
if (note.komal === true || note.microtone !== void 0) semi -= 1;
|
|
800
|
-
if (note.tivra === true) semi += 1;
|
|
801
|
-
return semi;
|
|
802
|
-
}
|
|
803
|
-
function svaraToMidi(note, tonicPc, baseOctave = 4) {
|
|
804
|
-
const semi = svaraSemitone(note);
|
|
805
|
-
if (semi === null || note.svara === void 0) return null;
|
|
806
|
-
const [octave] = note.svara;
|
|
807
|
-
return (baseOctave + 1 + OCTAVE_OFFSET[octave]) * 12 + tonicPc + semi;
|
|
808
|
-
}
|
|
809
|
-
function kanToMidi(svara, tonicPc, baseOctave = 4) {
|
|
810
|
-
if (svara === void 0) return null;
|
|
811
|
-
const [octave, letter] = svara;
|
|
812
|
-
return (baseOctave + 1 + OCTAVE_OFFSET[octave]) * 12 + tonicPc + SHUDDHA[letter];
|
|
813
|
-
}
|
|
814
|
-
function midiToSampleName(midi) {
|
|
815
|
-
const pc = (midi % 12 + 12) % 12;
|
|
816
|
-
const octave = Math.floor(midi / 12) - 1;
|
|
817
|
-
return `${SHARP_NAMES[pc]}${octave}`;
|
|
818
|
-
}
|
|
819
|
-
function sampleFileName(midi) {
|
|
820
|
-
return midiToSampleName(midi).toLowerCase().replace("#", "s");
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
// src/player/schedule.ts
|
|
824
|
-
var isSilent = (n) => n.svara === void 0 && n.rest !== true && n.sustain !== true;
|
|
825
|
-
var SAM_TICK = /^\d+[fF]$/;
|
|
826
|
-
function buildSchedule(doc, opts = {}) {
|
|
827
|
-
const tonicPc = parseTonic(opts.tonic ?? doc.meta.tonic);
|
|
828
|
-
const baseOctave = opts.baseOctave ?? 4;
|
|
829
|
-
const events = [];
|
|
830
|
-
let cursor = 0;
|
|
831
|
-
let lastPitched = -1;
|
|
832
|
-
let samBeat;
|
|
833
|
-
doc.parts.forEach((part, p) => {
|
|
834
|
-
part.rows.forEach((row, r) => {
|
|
835
|
-
row.notes.forEach((note, i) => {
|
|
836
|
-
if (samBeat === void 0 && note.above !== void 0) {
|
|
837
|
-
for (const a of note.above) {
|
|
838
|
-
if (a.raw !== void 0 && SAM_TICK.test(a.raw)) {
|
|
839
|
-
samBeat = cursor;
|
|
840
|
-
break;
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
if (note.bar !== void 0 || isSilent(note)) return;
|
|
845
|
-
const path = `${p}.${r}.${i}`;
|
|
846
|
-
const dur = note.durationMatra ?? 1;
|
|
847
|
-
if (note.svara !== void 0) {
|
|
848
|
-
events.push({
|
|
849
|
-
beat: cursor,
|
|
850
|
-
durationBeats: dur,
|
|
851
|
-
midi: svaraToMidi(note, tonicPc, baseOctave),
|
|
852
|
-
kan: kanToMidi(note.kan, tonicPc, baseOctave),
|
|
853
|
-
path,
|
|
854
|
-
kind: note.sustain === true ? "sustain" : "note"
|
|
855
|
-
});
|
|
856
|
-
lastPitched = events.length - 1;
|
|
857
|
-
} else if (note.sustain === true) {
|
|
858
|
-
if (lastPitched >= 0) events[lastPitched].durationBeats += dur;
|
|
859
|
-
events.push({ beat: cursor, durationBeats: dur, midi: null, kan: null, path, kind: "sustain" });
|
|
860
|
-
} else {
|
|
861
|
-
events.push({ beat: cursor, durationBeats: dur, midi: null, kan: null, path, kind: "rest" });
|
|
862
|
-
}
|
|
863
|
-
cursor += dur;
|
|
864
|
-
});
|
|
865
|
-
});
|
|
866
|
-
});
|
|
867
|
-
const numBeats = doc.meta.taal?.numBeats;
|
|
868
|
-
const pickupBeats = samBeat !== void 0 && numBeats !== void 0 && numBeats > 0 ? (numBeats - samBeat % numBeats) % numBeats : 0;
|
|
869
|
-
return { events, totalBeats: cursor, pickupBeats };
|
|
870
|
-
}
|
|
871
|
-
|
|
872
949
|
// src/viewer/staff.ts
|
|
873
950
|
init_taals();
|
|
874
951
|
|
|
@@ -1034,20 +1111,23 @@ function buildAbc(schedule, lyricOf, opts) {
|
|
|
1034
1111
|
barPos -= beatsPerBar;
|
|
1035
1112
|
}
|
|
1036
1113
|
};
|
|
1037
|
-
|
|
1114
|
+
schedule.events.forEach((ev, i) => {
|
|
1038
1115
|
if (ev.midi === null) {
|
|
1039
1116
|
if (ev.kind === "rest") {
|
|
1040
1117
|
tokens.push(`z${abcDuration(ev.durationBeats)}`);
|
|
1041
1118
|
words.push("*");
|
|
1042
1119
|
advance(ev.durationBeats);
|
|
1043
1120
|
}
|
|
1044
|
-
|
|
1121
|
+
return;
|
|
1045
1122
|
}
|
|
1046
|
-
|
|
1123
|
+
const more = glideTargets(schedule.events, i).length > 0;
|
|
1124
|
+
const slurOpen = ev.glide !== true && more ? "(" : "";
|
|
1125
|
+
const slurClose = ev.glide === true && !more ? ")" : "";
|
|
1126
|
+
tokens.push(`${slurOpen}${abcNote(ev)}${slurClose}`);
|
|
1047
1127
|
const lyr = lyricOf(ev);
|
|
1048
1128
|
words.push(lyr ? abcText(lyr).replace(/\s+/g, "~") : "*");
|
|
1049
1129
|
advance(ev.durationBeats);
|
|
1050
|
-
}
|
|
1130
|
+
});
|
|
1051
1131
|
if (tokens.length && tokens[tokens.length - 1] !== "|") tokens.push("|]");
|
|
1052
1132
|
else if (tokens.length) tokens[tokens.length - 1] = "|]";
|
|
1053
1133
|
const meter = ratioMeter(beatsPerBar);
|
|
@@ -1218,26 +1298,26 @@ function escapeHtml2(s) {
|
|
|
1218
1298
|
}
|
|
1219
1299
|
function watermarkCss(text) {
|
|
1220
1300
|
if (!text) return "";
|
|
1301
|
+
const span = 92;
|
|
1302
|
+
const size = Math.min(18, span / (0.62 * text.length));
|
|
1303
|
+
const len = Math.min(span, 0.62 * text.length * size);
|
|
1304
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet"><text x="50" y="50" dy=".35em" text-anchor="middle" font-family="sans-serif" font-weight="700" fill="rgba(0,0,0,0.07)" font-size="${size.toFixed(2)}" textLength="${len.toFixed(2)}" lengthAdjust="spacingAndGlyphs" transform="rotate(-35 50 50)">${escapeHtml2(text)}</text></svg>`;
|
|
1221
1305
|
return `
|
|
1222
1306
|
.sw-watermark {
|
|
1223
1307
|
position: fixed; inset: 0; z-index: 0; pointer-events: none;
|
|
1224
|
-
|
|
1225
|
-
overflow: hidden;
|
|
1308
|
+
background: url("data:image/svg+xml,${encodeURIComponent(svg)}") center / contain no-repeat;
|
|
1226
1309
|
-webkit-print-color-adjust: exact; print-color-adjust: exact;
|
|
1227
1310
|
}
|
|
1228
|
-
.sw-watermark span {
|
|
1229
|
-
font-size: 11vw; font-weight: 700; letter-spacing: .08em;
|
|
1230
|
-
color: rgba(0,0,0,0.07); transform: rotate(-35deg); white-space: nowrap;
|
|
1231
|
-
}
|
|
1232
1311
|
body > .sw-sheet, body > .sw-footer { position: relative; z-index: 1; }
|
|
1233
1312
|
`;
|
|
1234
1313
|
}
|
|
1235
1314
|
function chrome(text, notator) {
|
|
1236
|
-
const top = text ?
|
|
1315
|
+
const top = text ? '<div class="sw-watermark" aria-hidden="true"></div>' : "";
|
|
1237
1316
|
const bottom = notator ? `<footer class="sw-footer">${escapeHtml2(notator)}</footer>` : "";
|
|
1238
1317
|
return { top, bottom };
|
|
1239
1318
|
}
|
|
1240
1319
|
function resolveNotator(doc, opts) {
|
|
1320
|
+
if (opts.notator === "") return "";
|
|
1241
1321
|
return doc.meta.notationBy ?? opts.notator ?? DEFAULT_NOTATOR;
|
|
1242
1322
|
}
|
|
1243
1323
|
function pageHtml(lang, title, bodyHtml, opts, doc) {
|
|
@@ -1516,6 +1596,7 @@ exports.abcDuration = abcDuration;
|
|
|
1516
1596
|
exports.abcToSvg = abcToSvg;
|
|
1517
1597
|
exports.buildAbc = buildAbc;
|
|
1518
1598
|
exports.buildTaalAbc = buildTaalAbc;
|
|
1599
|
+
exports.cellMatra = cellMatra;
|
|
1519
1600
|
exports.composeCluster = composeCluster;
|
|
1520
1601
|
exports.defineSwaralipiSheet = defineSwaralipiSheet;
|
|
1521
1602
|
exports.docToAbc = docToAbc;
|