@ugfoundation/swaralipi-js 0.5.0 → 0.6.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/dist/{batch-UETOBPHT.js → batch-E76HM4GM.js} +4 -4
- package/dist/{batch-UETOBPHT.js.map → batch-E76HM4GM.js.map} +1 -1
- package/dist/{chunk-UD2K4UIC.js → chunk-52EPQY47.js} +8 -6
- package/dist/chunk-52EPQY47.js.map +1 -0
- package/dist/{chunk-C6TRCMPL.js → chunk-GHWURC4U.js} +97 -5
- package/dist/chunk-GHWURC4U.js.map +1 -0
- package/dist/{chunk-ID44OP2N.js → chunk-MSVF43WQ.js} +7 -6
- package/dist/chunk-MSVF43WQ.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 +212 -20
- 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 +111 -20
- package/dist/player/index.js.map +1 -1
- package/dist/{schedule-BaYIJf8y.d.ts → schedule-8WFUBJqB.d.ts} +78 -3
- package/dist/{schedule-bBvy18MO.d.cts → schedule-CjPcD-Hf.d.cts} +78 -3
- 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 +264 -166
- 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 +99 -41
- package/dist/viewer/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-C6TRCMPL.js.map +0 -1
- package/dist/chunk-ID44OP2N.js.map +0 -1
- package/dist/chunk-UD2K4UIC.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.1";
|
|
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",
|
|
@@ -253,7 +415,6 @@ function localizeDigits(s, lang) {
|
|
|
253
415
|
}
|
|
254
416
|
|
|
255
417
|
// src/viewer/render.ts
|
|
256
|
-
var NBSP = "\xA0";
|
|
257
418
|
var EPS = 1e-6;
|
|
258
419
|
var isIntegral = (x) => Math.abs(x - Math.round(x)) < EPS;
|
|
259
420
|
function escapeHtml(s) {
|
|
@@ -288,7 +449,7 @@ function groupRowIntoCells(notes) {
|
|
|
288
449
|
return;
|
|
289
450
|
}
|
|
290
451
|
const d = note.durationMatra ?? 1;
|
|
291
|
-
if (isIntegral(d)) {
|
|
452
|
+
if (isIntegral(d) && note.halfMatra !== true) {
|
|
292
453
|
close();
|
|
293
454
|
cells.push({ kind: "group", start: i, notes: [note] });
|
|
294
455
|
return;
|
|
@@ -296,13 +457,11 @@ function groupRowIntoCells(notes) {
|
|
|
296
457
|
cur ??= { kind: "group", start: i, notes: [] };
|
|
297
458
|
cur.notes.push(note);
|
|
298
459
|
acc += d;
|
|
299
|
-
if (acc > EPS && isIntegral(acc)) close();
|
|
460
|
+
if (note.halfMatra === true || acc > EPS && isIntegral(acc)) close();
|
|
300
461
|
});
|
|
301
462
|
close();
|
|
302
463
|
return cells;
|
|
303
464
|
}
|
|
304
|
-
var ARC_RAW_RE = /^[wWxXyYjJeEzZ$=_<>`\\]+$/;
|
|
305
|
-
var isArcRaw = (raw) => raw !== void 0 && ARC_RAW_RE.test(raw);
|
|
306
465
|
function resolveArcs(cells, noteCount) {
|
|
307
466
|
const arcs = [];
|
|
308
467
|
let openStart;
|
|
@@ -351,9 +510,22 @@ function resolveArcs(cells, noteCount) {
|
|
|
351
510
|
}
|
|
352
511
|
return arcs;
|
|
353
512
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
513
|
+
var SVARA_CHAR = /[srgmpqndtkuv]/;
|
|
514
|
+
function rawMarks(raw, count) {
|
|
515
|
+
if (raw === void 0) return void 0;
|
|
516
|
+
const marks = { aakar: false, pre: "", post: new Array(count).fill("") };
|
|
517
|
+
const leads = SVARA_CHAR.test(raw);
|
|
518
|
+
let seen = 0;
|
|
519
|
+
for (const ch of raw) {
|
|
520
|
+
if (SVARA_CHAR.test(ch)) seen = Math.min(seen + 1, count);
|
|
521
|
+
else if (ch === "a") marks.aakar = true;
|
|
522
|
+
else if (ch === "i" || ch === ":") {
|
|
523
|
+
if (seen > 0) marks.post[seen - 1] += ch;
|
|
524
|
+
else if (leads) marks.pre += ch;
|
|
525
|
+
else marks.post[0] += ch;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return marks;
|
|
357
529
|
}
|
|
358
530
|
function parseBeatLabel(raw) {
|
|
359
531
|
const m = raw.match(/^([0-9]{1,2})([fF])?:?$/);
|
|
@@ -371,38 +543,63 @@ function bracketGlyphs(note, edge) {
|
|
|
371
543
|
function matraGlyph(ctx) {
|
|
372
544
|
return ctx.lang === "bn" ? '<span class="sw-cl">a</span>' : '<span class="sw-iso">\u2013</span>';
|
|
373
545
|
}
|
|
374
|
-
function
|
|
375
|
-
if (
|
|
376
|
-
const [octave, letter] =
|
|
546
|
+
function renderGrace(svara, cls, ctx) {
|
|
547
|
+
if (svara === void 0) return "";
|
|
548
|
+
const [octave, letter] = svara;
|
|
377
549
|
const c = composeCluster(letter, { octave, withMatra: false, lang: ctx.lang });
|
|
378
|
-
return `<span class="${[
|
|
550
|
+
return `<span class="${[cls, ...c.classes].join(" ")}">${c.text}</span>`;
|
|
551
|
+
}
|
|
552
|
+
function cutGlyphs(marks, ctx) {
|
|
553
|
+
if (marks === "") return "";
|
|
554
|
+
return ctx.lang === "bn" ? `<span class="sw-cl" title="${ctx.marks.cut}">${marks}</span>` : `<span class="sw-cut" title="${ctx.marks.cut}"></span>`.repeat(marks.length);
|
|
555
|
+
}
|
|
556
|
+
function cellMatra(cell) {
|
|
557
|
+
const total = cell.notes.reduce((a, n) => a + (n.durationMatra ?? 1), 0);
|
|
558
|
+
const half = cell.notes.some((n) => n.halfMatra === true);
|
|
559
|
+
const whole = Math.floor(total + EPS);
|
|
560
|
+
return { matras: half ? whole : Math.max(1, whole), half };
|
|
561
|
+
}
|
|
562
|
+
function cellPitchedOpts(cell) {
|
|
563
|
+
const { matras, half } = cellMatra(cell);
|
|
564
|
+
const marks = rawMarks(cell.notes[0].raw, cell.notes.length);
|
|
565
|
+
const aakars = marks !== void 0 && !marks.aakar ? 0 : matras;
|
|
566
|
+
return cell.notes.map((_, j) => ({
|
|
567
|
+
matras: j === cell.notes.length - 1 ? aakars : 0,
|
|
568
|
+
pre: j === 0 ? marks?.pre ?? "" : "",
|
|
569
|
+
post: marks !== void 0 ? marks.post[j] : half && j === cell.notes.length - 1 ? "i" : ""
|
|
570
|
+
}));
|
|
379
571
|
}
|
|
380
572
|
function renderPitchedInner(note, opts, ctx) {
|
|
381
573
|
let inner = bracketGlyphs(note, "open");
|
|
382
|
-
inner +=
|
|
574
|
+
inner += renderGrace(note.kan, "sw-kan", ctx);
|
|
383
575
|
if (note.sustain === true) inner += `<span class="sw-hy">${ctx.marks.hyphen}</span>`;
|
|
576
|
+
inner += cutGlyphs(opts.pre, ctx);
|
|
384
577
|
if (note.svara !== void 0) {
|
|
385
578
|
const [octave, letter] = note.svara;
|
|
386
579
|
const c = composeCluster(letter, {
|
|
387
580
|
komal: note.komal,
|
|
388
581
|
tivra: note.tivra,
|
|
389
582
|
octave,
|
|
390
|
-
|
|
583
|
+
// The first matra is composed into the cluster itself (bn: the font's
|
|
584
|
+
// trailing `a`); any further ones are separate glyphs below.
|
|
585
|
+
withMatra: opts.matras >= 1,
|
|
391
586
|
lang: ctx.lang
|
|
392
587
|
});
|
|
393
588
|
inner += `<span class="${["sw-cl", ...c.classes].join(" ")}">${c.text}</span>`;
|
|
394
589
|
} else if (note.rest === true) {
|
|
395
|
-
inner += matraGlyph(ctx);
|
|
396
|
-
} else if (note.sustain === true
|
|
397
|
-
inner += matraGlyph(ctx);
|
|
590
|
+
inner += matraGlyph(ctx).repeat(Math.max(1, opts.matras));
|
|
591
|
+
} else if (note.sustain === true) {
|
|
592
|
+
inner += matraGlyph(ctx).repeat(opts.matras);
|
|
398
593
|
}
|
|
399
594
|
if (note.microtone !== void 0) {
|
|
400
595
|
const mark = localizeDigits(note.microtone === "atikomal" ? "2" : "1", ctx.lang);
|
|
401
596
|
inner += `<span class="sw-micro" title="${note.microtone}">${mark}</span>`;
|
|
402
597
|
}
|
|
403
|
-
|
|
404
|
-
if (
|
|
405
|
-
|
|
598
|
+
inner += renderGrace(note.resh, "sw-resh", ctx);
|
|
599
|
+
if (note.svara !== void 0 && opts.matras > 1) {
|
|
600
|
+
inner += matraGlyph(ctx).repeat(opts.matras - 1);
|
|
601
|
+
}
|
|
602
|
+
inner += cutGlyphs(opts.post, ctx);
|
|
406
603
|
inner += bracketGlyphs(note, "close");
|
|
407
604
|
return inner;
|
|
408
605
|
}
|
|
@@ -419,12 +616,9 @@ function renderAnnotation(notes, ctx) {
|
|
|
419
616
|
continue;
|
|
420
617
|
}
|
|
421
618
|
if (cell.kind === "group") {
|
|
422
|
-
const
|
|
619
|
+
const opts = cellPitchedOpts(cell);
|
|
423
620
|
cell.notes.forEach((note2, j) => {
|
|
424
|
-
|
|
425
|
-
parts.push(
|
|
426
|
-
`<span class="sw-n">${renderPitchedInner(note2, { withMatra: last, cuts: last ? cuts : 0 }, ctx)}</span>`
|
|
427
|
-
);
|
|
621
|
+
parts.push(`<span class="sw-n">${renderPitchedInner(note2, opts[j], ctx)}</span>`);
|
|
428
622
|
});
|
|
429
623
|
continue;
|
|
430
624
|
}
|
|
@@ -462,7 +656,7 @@ function renderRow(row, partIdx, rowIdx, ctx) {
|
|
|
462
656
|
const firstCol = cell.start + 1;
|
|
463
657
|
const lastCol = cell.start + cell.notes.length;
|
|
464
658
|
const cellSpan = lastCol > firstCol ? `grid-column:${firstCol} / ${lastCol + 1}` : `grid-column:${firstCol}`;
|
|
465
|
-
const
|
|
659
|
+
const opts = cell.kind === "group" ? cellPitchedOpts(cell) : [];
|
|
466
660
|
cell.notes.forEach((note, j) => {
|
|
467
661
|
const col = cell.start + j + 1;
|
|
468
662
|
const path = `${partIdx}.${rowIdx}.${cell.start + j}`;
|
|
@@ -472,9 +666,8 @@ function renderRow(row, partIdx, rowIdx, ctx) {
|
|
|
472
666
|
`<span class="sw-bar sw-bar--${note.bar}${cellEdges}" data-note="${path}" style="grid-column:${col}">${renderBarInner(note)}</span>`
|
|
473
667
|
);
|
|
474
668
|
} else if (cell.kind === "group") {
|
|
475
|
-
const last = j === cell.notes.length - 1;
|
|
476
669
|
notation.push(
|
|
477
|
-
`<span class="sw-n${cellEdges}" data-note="${path}" style="grid-column:${col}">${renderPitchedInner(note,
|
|
670
|
+
`<span class="sw-n${cellEdges}" data-note="${path}" style="grid-column:${col}">${renderPitchedInner(note, opts[j], ctx)}</span>`
|
|
478
671
|
);
|
|
479
672
|
} else {
|
|
480
673
|
const raw = rawDisplay(note);
|
|
@@ -565,9 +758,9 @@ ${FONT_FACES}
|
|
|
565
758
|
}
|
|
566
759
|
|
|
567
760
|
/* ---- notation font, per language (the note clusters + grace notes) ---- */
|
|
568
|
-
.sw-sheet[lang="bn"] .sw-cl, .sw-
|
|
569
|
-
.sw-sheet[lang="hi"] .sw-cl, .sw-
|
|
570
|
-
.sw-sheet[lang="en"] .sw-cl, .sw-
|
|
761
|
+
.sw-sheet[lang="bn"] :is(.sw-cl, .sw-kan, .sw-resh) { font-family: Swarabitan, var(--sw-font, sans-serif); }
|
|
762
|
+
.sw-sheet[lang="hi"] :is(.sw-cl, .sw-kan, .sw-resh) { font-family: ome_bhatkhande_hindi, var(--sw-font, sans-serif); }
|
|
763
|
+
.sw-sheet[lang="en"] :is(.sw-cl, .sw-kan, .sw-resh) { font-family: ome_bhatkhande_english, var(--sw-font, sans-serif); }
|
|
571
764
|
|
|
572
765
|
/* ---- header ---- */
|
|
573
766
|
.sw-header {
|
|
@@ -626,18 +819,42 @@ ${FONT_FACES}
|
|
|
626
819
|
.sw-ati-taar::before { top: -0.3em; }
|
|
627
820
|
.sw-ati-mandra::before { bottom: -0.35em; }
|
|
628
821
|
|
|
629
|
-
/*
|
|
630
|
-
.sw-kan
|
|
822
|
+
/* grace notes: small raised clusters \u2014 \u0995\u09A3 before the note, \u09B0\u09C7\u09B6 after it */
|
|
823
|
+
.sw-kan,
|
|
824
|
+
.sw-resh {
|
|
631
825
|
position: relative;
|
|
632
826
|
display: inline-block;
|
|
633
827
|
font-size: 0.58em;
|
|
634
828
|
vertical-align: 0.75em;
|
|
635
|
-
margin-right: 0.08em;
|
|
636
829
|
}
|
|
830
|
+
.sw-kan { margin-right: 0.08em; }
|
|
831
|
+
.sw-resh { margin-left: 0.08em; }
|
|
637
832
|
|
|
638
833
|
.sw-hy { padding-right: 0.02em; }
|
|
639
834
|
.sw-iso { display: inline-block; margin-left: -0.18em; }
|
|
640
|
-
|
|
835
|
+
/* The \u0985\u09B0\u09CD\u09A7\u09AE\u09BE\u09A4\u09CD\u09B0\u09BE / \u099B\u09C7\u09A6 mark: two stacked rings (legend p. \u09E7\u09E9\u09EB). bn types
|
|
836
|
+
* Swarabitan's own glyph (the ASCII i key) \u2014 an isolated Unicode \u0983 has no base and
|
|
837
|
+
* every Bengali text font shapes it with a dotted circle (an NBSP carrier
|
|
838
|
+
* does not help), but the notation font has the mark itself. hi/en fall back
|
|
839
|
+
* to these drawn rings: Bhatkhande has no such sign. */
|
|
840
|
+
.sw-cut {
|
|
841
|
+
position: relative;
|
|
842
|
+
display: inline-block;
|
|
843
|
+
width: 0.3em;
|
|
844
|
+
height: 1em;
|
|
845
|
+
vertical-align: baseline;
|
|
846
|
+
}
|
|
847
|
+
.sw-cut::before, .sw-cut::after {
|
|
848
|
+
content: '';
|
|
849
|
+
position: absolute;
|
|
850
|
+
left: 0.08em;
|
|
851
|
+
width: 0.15em;
|
|
852
|
+
height: 0.15em;
|
|
853
|
+
border-radius: 50%;
|
|
854
|
+
background: currentColor;
|
|
855
|
+
}
|
|
856
|
+
.sw-cut::before { top: 0.2em; }
|
|
857
|
+
.sw-cut::after { top: 0.55em; }
|
|
641
858
|
.sw-br { opacity: 0.8; padding: 0 0.06em; }
|
|
642
859
|
.sw-micro { font-size: 0.6em; vertical-align: super; opacity: 0.8; }
|
|
643
860
|
|
|
@@ -748,129 +965,6 @@ var SWARALIPI_STAFF_CSS = `
|
|
|
748
965
|
.sw-staff-subtitle { opacity: .8; font-size: .95em; margin-top: .15em; }
|
|
749
966
|
`;
|
|
750
967
|
|
|
751
|
-
// src/player/pitch.ts
|
|
752
|
-
var OCTAVE_OFFSET = {
|
|
753
|
-
"ati-mandra": -2,
|
|
754
|
-
mandra: -1,
|
|
755
|
-
madhyam: 0,
|
|
756
|
-
taar: 1,
|
|
757
|
-
"ati-taar": 2
|
|
758
|
-
};
|
|
759
|
-
var SHUDDHA = {
|
|
760
|
-
s: 0,
|
|
761
|
-
r: 2,
|
|
762
|
-
g: 4,
|
|
763
|
-
m: 5,
|
|
764
|
-
p: 7,
|
|
765
|
-
d: 9,
|
|
766
|
-
n: 11
|
|
767
|
-
};
|
|
768
|
-
var SHARP_NAMES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
|
|
769
|
-
var PITCH_CLASS = {
|
|
770
|
-
c: 0,
|
|
771
|
-
"c#": 1,
|
|
772
|
-
db: 1,
|
|
773
|
-
d: 2,
|
|
774
|
-
"d#": 3,
|
|
775
|
-
eb: 3,
|
|
776
|
-
e: 4,
|
|
777
|
-
"e#": 5,
|
|
778
|
-
fb: 4,
|
|
779
|
-
f: 5,
|
|
780
|
-
"f#": 6,
|
|
781
|
-
gb: 6,
|
|
782
|
-
g: 7,
|
|
783
|
-
"g#": 8,
|
|
784
|
-
ab: 8,
|
|
785
|
-
a: 9,
|
|
786
|
-
"a#": 10,
|
|
787
|
-
bb: 10,
|
|
788
|
-
b: 11,
|
|
789
|
-
"b#": 0,
|
|
790
|
-
cb: 11
|
|
791
|
-
};
|
|
792
|
-
function parseTonic(tonic) {
|
|
793
|
-
if (!tonic) return 0;
|
|
794
|
-
const key = tonic.trim().toLowerCase().replace(/s/g, "#");
|
|
795
|
-
return PITCH_CLASS[key] ?? 0;
|
|
796
|
-
}
|
|
797
|
-
function svaraSemitone(note) {
|
|
798
|
-
if (note.svara === void 0) return null;
|
|
799
|
-
const [, letter] = note.svara;
|
|
800
|
-
let semi = SHUDDHA[letter];
|
|
801
|
-
if (note.komal === true || note.microtone !== void 0) semi -= 1;
|
|
802
|
-
if (note.tivra === true) semi += 1;
|
|
803
|
-
return semi;
|
|
804
|
-
}
|
|
805
|
-
function svaraToMidi(note, tonicPc, baseOctave = 4) {
|
|
806
|
-
const semi = svaraSemitone(note);
|
|
807
|
-
if (semi === null || note.svara === void 0) return null;
|
|
808
|
-
const [octave] = note.svara;
|
|
809
|
-
return (baseOctave + 1 + OCTAVE_OFFSET[octave]) * 12 + tonicPc + semi;
|
|
810
|
-
}
|
|
811
|
-
function kanToMidi(svara, tonicPc, baseOctave = 4) {
|
|
812
|
-
if (svara === void 0) return null;
|
|
813
|
-
const [octave, letter] = svara;
|
|
814
|
-
return (baseOctave + 1 + OCTAVE_OFFSET[octave]) * 12 + tonicPc + SHUDDHA[letter];
|
|
815
|
-
}
|
|
816
|
-
function midiToSampleName(midi) {
|
|
817
|
-
const pc = (midi % 12 + 12) % 12;
|
|
818
|
-
const octave = Math.floor(midi / 12) - 1;
|
|
819
|
-
return `${SHARP_NAMES[pc]}${octave}`;
|
|
820
|
-
}
|
|
821
|
-
function sampleFileName(midi) {
|
|
822
|
-
return midiToSampleName(midi).toLowerCase().replace("#", "s");
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
// src/player/schedule.ts
|
|
826
|
-
var isSilent = (n) => n.svara === void 0 && n.rest !== true && n.sustain !== true;
|
|
827
|
-
var SAM_TICK = /^\d+[fF]$/;
|
|
828
|
-
function buildSchedule(doc, opts = {}) {
|
|
829
|
-
const tonicPc = parseTonic(opts.tonic ?? doc.meta.tonic);
|
|
830
|
-
const baseOctave = opts.baseOctave ?? 4;
|
|
831
|
-
const events = [];
|
|
832
|
-
let cursor = 0;
|
|
833
|
-
let lastPitched = -1;
|
|
834
|
-
let samBeat;
|
|
835
|
-
doc.parts.forEach((part, p) => {
|
|
836
|
-
part.rows.forEach((row, r) => {
|
|
837
|
-
row.notes.forEach((note, i) => {
|
|
838
|
-
if (samBeat === void 0 && note.above !== void 0) {
|
|
839
|
-
for (const a of note.above) {
|
|
840
|
-
if (a.raw !== void 0 && SAM_TICK.test(a.raw)) {
|
|
841
|
-
samBeat = cursor;
|
|
842
|
-
break;
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
if (note.bar !== void 0 || isSilent(note)) return;
|
|
847
|
-
const path = `${p}.${r}.${i}`;
|
|
848
|
-
const dur = note.durationMatra ?? 1;
|
|
849
|
-
if (note.svara !== void 0) {
|
|
850
|
-
events.push({
|
|
851
|
-
beat: cursor,
|
|
852
|
-
durationBeats: dur,
|
|
853
|
-
midi: svaraToMidi(note, tonicPc, baseOctave),
|
|
854
|
-
kan: kanToMidi(note.kan, tonicPc, baseOctave),
|
|
855
|
-
path,
|
|
856
|
-
kind: note.sustain === true ? "sustain" : "note"
|
|
857
|
-
});
|
|
858
|
-
lastPitched = events.length - 1;
|
|
859
|
-
} else if (note.sustain === true) {
|
|
860
|
-
if (lastPitched >= 0) events[lastPitched].durationBeats += dur;
|
|
861
|
-
events.push({ beat: cursor, durationBeats: dur, midi: null, kan: null, path, kind: "sustain" });
|
|
862
|
-
} else {
|
|
863
|
-
events.push({ beat: cursor, durationBeats: dur, midi: null, kan: null, path, kind: "rest" });
|
|
864
|
-
}
|
|
865
|
-
cursor += dur;
|
|
866
|
-
});
|
|
867
|
-
});
|
|
868
|
-
});
|
|
869
|
-
const numBeats = doc.meta.taal?.numBeats;
|
|
870
|
-
const pickupBeats = samBeat !== void 0 && numBeats !== void 0 && numBeats > 0 ? (numBeats - samBeat % numBeats) % numBeats : 0;
|
|
871
|
-
return { events, totalBeats: cursor, pickupBeats };
|
|
872
|
-
}
|
|
873
|
-
|
|
874
968
|
// src/viewer/staff.ts
|
|
875
969
|
init_taals();
|
|
876
970
|
|
|
@@ -1036,20 +1130,23 @@ function buildAbc(schedule, lyricOf, opts) {
|
|
|
1036
1130
|
barPos -= beatsPerBar;
|
|
1037
1131
|
}
|
|
1038
1132
|
};
|
|
1039
|
-
|
|
1133
|
+
schedule.events.forEach((ev, i) => {
|
|
1040
1134
|
if (ev.midi === null) {
|
|
1041
1135
|
if (ev.kind === "rest") {
|
|
1042
1136
|
tokens.push(`z${abcDuration(ev.durationBeats)}`);
|
|
1043
1137
|
words.push("*");
|
|
1044
1138
|
advance(ev.durationBeats);
|
|
1045
1139
|
}
|
|
1046
|
-
|
|
1140
|
+
return;
|
|
1047
1141
|
}
|
|
1048
|
-
|
|
1142
|
+
const more = glideTargets(schedule.events, i).length > 0;
|
|
1143
|
+
const slurOpen = ev.glide !== true && more ? "(" : "";
|
|
1144
|
+
const slurClose = ev.glide === true && !more ? ")" : "";
|
|
1145
|
+
tokens.push(`${slurOpen}${abcNote(ev)}${slurClose}`);
|
|
1049
1146
|
const lyr = lyricOf(ev);
|
|
1050
1147
|
words.push(lyr ? abcText(lyr).replace(/\s+/g, "~") : "*");
|
|
1051
1148
|
advance(ev.durationBeats);
|
|
1052
|
-
}
|
|
1149
|
+
});
|
|
1053
1150
|
if (tokens.length && tokens[tokens.length - 1] !== "|") tokens.push("|]");
|
|
1054
1151
|
else if (tokens.length) tokens[tokens.length - 1] = "|]";
|
|
1055
1152
|
const meter = ratioMeter(beatsPerBar);
|
|
@@ -1518,6 +1615,7 @@ exports.abcDuration = abcDuration;
|
|
|
1518
1615
|
exports.abcToSvg = abcToSvg;
|
|
1519
1616
|
exports.buildAbc = buildAbc;
|
|
1520
1617
|
exports.buildTaalAbc = buildTaalAbc;
|
|
1618
|
+
exports.cellMatra = cellMatra;
|
|
1521
1619
|
exports.composeCluster = composeCluster;
|
|
1522
1620
|
exports.defineSwaralipiSheet = defineSwaralipiSheet;
|
|
1523
1621
|
exports.docToAbc = docToAbc;
|