@ugfoundation/swaralipi-js 0.5.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/dist/{batch-UETOBPHT.js → batch-RUZGM2NR.js} +4 -4
- package/dist/{batch-UETOBPHT.js.map → batch-RUZGM2NR.js.map} +1 -1
- package/dist/{chunk-C6TRCMPL.js → chunk-GHWURC4U.js} +97 -5
- package/dist/chunk-GHWURC4U.js.map +1 -0
- package/dist/{chunk-UD2K4UIC.js → chunk-ULK7NVKH.js} +8 -6
- package/dist/chunk-ULK7NVKH.js.map +1 -0
- package/dist/{chunk-ID44OP2N.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 +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 +240 -161
- 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 +75 -36
- 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.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",
|
|
@@ -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;
|
|
@@ -371,15 +530,21 @@ function bracketGlyphs(note, edge) {
|
|
|
371
530
|
function matraGlyph(ctx) {
|
|
372
531
|
return ctx.lang === "bn" ? '<span class="sw-cl">a</span>' : '<span class="sw-iso">\u2013</span>';
|
|
373
532
|
}
|
|
374
|
-
function
|
|
375
|
-
if (
|
|
376
|
-
const [octave, letter] =
|
|
533
|
+
function renderGrace(svara, cls, ctx) {
|
|
534
|
+
if (svara === void 0) return "";
|
|
535
|
+
const [octave, letter] = svara;
|
|
377
536
|
const c = composeCluster(letter, { octave, withMatra: false, lang: ctx.lang });
|
|
378
|
-
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 };
|
|
379
544
|
}
|
|
380
545
|
function renderPitchedInner(note, opts, ctx) {
|
|
381
546
|
let inner = bracketGlyphs(note, "open");
|
|
382
|
-
inner +=
|
|
547
|
+
inner += renderGrace(note.kan, "sw-kan", ctx);
|
|
383
548
|
if (note.sustain === true) inner += `<span class="sw-hy">${ctx.marks.hyphen}</span>`;
|
|
384
549
|
if (note.svara !== void 0) {
|
|
385
550
|
const [octave, letter] = note.svara;
|
|
@@ -387,22 +552,27 @@ function renderPitchedInner(note, opts, ctx) {
|
|
|
387
552
|
komal: note.komal,
|
|
388
553
|
tivra: note.tivra,
|
|
389
554
|
octave,
|
|
390
|
-
|
|
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,
|
|
391
558
|
lang: ctx.lang
|
|
392
559
|
});
|
|
393
560
|
inner += `<span class="${["sw-cl", ...c.classes].join(" ")}">${c.text}</span>`;
|
|
394
561
|
} else if (note.rest === true) {
|
|
395
|
-
inner += matraGlyph(ctx);
|
|
396
|
-
} else if (note.sustain === true
|
|
397
|
-
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);
|
|
398
565
|
}
|
|
399
566
|
if (note.microtone !== void 0) {
|
|
400
567
|
const mark = localizeDigits(note.microtone === "atikomal" ? "2" : "1", ctx.lang);
|
|
401
568
|
inner += `<span class="sw-micro" title="${note.microtone}">${mark}</span>`;
|
|
402
569
|
}
|
|
403
|
-
|
|
404
|
-
if (
|
|
405
|
-
|
|
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);
|
|
406
576
|
inner += bracketGlyphs(note, "close");
|
|
407
577
|
return inner;
|
|
408
578
|
}
|
|
@@ -419,12 +589,12 @@ function renderAnnotation(notes, ctx) {
|
|
|
419
589
|
continue;
|
|
420
590
|
}
|
|
421
591
|
if (cell.kind === "group") {
|
|
422
|
-
const
|
|
592
|
+
const { matras, half } = cellMatra(cell);
|
|
593
|
+
const cuts = half ? 0 : cutMarkCount(cell.notes[0].raw);
|
|
423
594
|
cell.notes.forEach((note2, j) => {
|
|
424
595
|
const last = j === cell.notes.length - 1;
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
);
|
|
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>`);
|
|
428
598
|
});
|
|
429
599
|
continue;
|
|
430
600
|
}
|
|
@@ -462,7 +632,8 @@ function renderRow(row, partIdx, rowIdx, ctx) {
|
|
|
462
632
|
const firstCol = cell.start + 1;
|
|
463
633
|
const lastCol = cell.start + cell.notes.length;
|
|
464
634
|
const cellSpan = lastCol > firstCol ? `grid-column:${firstCol} / ${lastCol + 1}` : `grid-column:${firstCol}`;
|
|
465
|
-
const
|
|
635
|
+
const { matras, half } = cellMatra(cell);
|
|
636
|
+
const cuts = cell.kind === "group" && !half ? cutMarkCount(cell.notes[0].raw) : 0;
|
|
466
637
|
cell.notes.forEach((note, j) => {
|
|
467
638
|
const col = cell.start + j + 1;
|
|
468
639
|
const path = `${partIdx}.${rowIdx}.${cell.start + j}`;
|
|
@@ -473,8 +644,9 @@ function renderRow(row, partIdx, rowIdx, ctx) {
|
|
|
473
644
|
);
|
|
474
645
|
} else if (cell.kind === "group") {
|
|
475
646
|
const last = j === cell.notes.length - 1;
|
|
647
|
+
const pOpts = last ? { matras, half, cuts } : { matras: 0, half: false, cuts: 0 };
|
|
476
648
|
notation.push(
|
|
477
|
-
`<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>`
|
|
478
650
|
);
|
|
479
651
|
} else {
|
|
480
652
|
const raw = rawDisplay(note);
|
|
@@ -565,9 +737,9 @@ ${FONT_FACES}
|
|
|
565
737
|
}
|
|
566
738
|
|
|
567
739
|
/* ---- 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-
|
|
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); }
|
|
571
743
|
|
|
572
744
|
/* ---- header ---- */
|
|
573
745
|
.sw-header {
|
|
@@ -626,18 +798,44 @@ ${FONT_FACES}
|
|
|
626
798
|
.sw-ati-taar::before { top: -0.3em; }
|
|
627
799
|
.sw-ati-mandra::before { bottom: -0.35em; }
|
|
628
800
|
|
|
629
|
-
/*
|
|
630
|
-
.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 {
|
|
631
804
|
position: relative;
|
|
632
805
|
display: inline-block;
|
|
633
806
|
font-size: 0.58em;
|
|
634
807
|
vertical-align: 0.75em;
|
|
635
|
-
margin-right: 0.08em;
|
|
636
808
|
}
|
|
809
|
+
.sw-kan { margin-right: 0.08em; }
|
|
810
|
+
.sw-resh { margin-left: 0.08em; }
|
|
637
811
|
|
|
638
812
|
.sw-hy { padding-right: 0.02em; }
|
|
639
813
|
.sw-iso { display: inline-block; margin-left: -0.18em; }
|
|
640
|
-
|
|
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; }
|
|
641
839
|
.sw-br { opacity: 0.8; padding: 0 0.06em; }
|
|
642
840
|
.sw-micro { font-size: 0.6em; vertical-align: super; opacity: 0.8; }
|
|
643
841
|
|
|
@@ -748,129 +946,6 @@ var SWARALIPI_STAFF_CSS = `
|
|
|
748
946
|
.sw-staff-subtitle { opacity: .8; font-size: .95em; margin-top: .15em; }
|
|
749
947
|
`;
|
|
750
948
|
|
|
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
949
|
// src/viewer/staff.ts
|
|
875
950
|
init_taals();
|
|
876
951
|
|
|
@@ -1036,20 +1111,23 @@ function buildAbc(schedule, lyricOf, opts) {
|
|
|
1036
1111
|
barPos -= beatsPerBar;
|
|
1037
1112
|
}
|
|
1038
1113
|
};
|
|
1039
|
-
|
|
1114
|
+
schedule.events.forEach((ev, i) => {
|
|
1040
1115
|
if (ev.midi === null) {
|
|
1041
1116
|
if (ev.kind === "rest") {
|
|
1042
1117
|
tokens.push(`z${abcDuration(ev.durationBeats)}`);
|
|
1043
1118
|
words.push("*");
|
|
1044
1119
|
advance(ev.durationBeats);
|
|
1045
1120
|
}
|
|
1046
|
-
|
|
1121
|
+
return;
|
|
1047
1122
|
}
|
|
1048
|
-
|
|
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}`);
|
|
1049
1127
|
const lyr = lyricOf(ev);
|
|
1050
1128
|
words.push(lyr ? abcText(lyr).replace(/\s+/g, "~") : "*");
|
|
1051
1129
|
advance(ev.durationBeats);
|
|
1052
|
-
}
|
|
1130
|
+
});
|
|
1053
1131
|
if (tokens.length && tokens[tokens.length - 1] !== "|") tokens.push("|]");
|
|
1054
1132
|
else if (tokens.length) tokens[tokens.length - 1] = "|]";
|
|
1055
1133
|
const meter = ratioMeter(beatsPerBar);
|
|
@@ -1518,6 +1596,7 @@ exports.abcDuration = abcDuration;
|
|
|
1518
1596
|
exports.abcToSvg = abcToSvg;
|
|
1519
1597
|
exports.buildAbc = buildAbc;
|
|
1520
1598
|
exports.buildTaalAbc = buildTaalAbc;
|
|
1599
|
+
exports.cellMatra = cellMatra;
|
|
1521
1600
|
exports.composeCluster = composeCluster;
|
|
1522
1601
|
exports.defineSwaralipiSheet = defineSwaralipiSheet;
|
|
1523
1602
|
exports.docToAbc = docToAbc;
|