@ugfoundation/swaralipi-js 0.7.7 → 0.8.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.
Files changed (50) hide show
  1. package/dist/{batch-6MCPHHKF.js → batch-NDL6CW7Q.js} +5 -5
  2. package/dist/{batch-6MCPHHKF.js.map → batch-NDL6CW7Q.js.map} +1 -1
  3. package/dist/{chunk-QM4CXKW2.js → chunk-4HNT6OAT.js} +24 -3
  4. package/dist/chunk-4HNT6OAT.js.map +1 -0
  5. package/dist/{chunk-TKTQCUKD.js → chunk-4IVUPMRK.js} +27 -9
  6. package/dist/chunk-4IVUPMRK.js.map +1 -0
  7. package/dist/{chunk-ALN4SWMS.js → chunk-LPCKL3AM.js} +15 -4
  8. package/dist/chunk-LPCKL3AM.js.map +1 -0
  9. package/dist/{chunk-QQC6WVLJ.js → chunk-Z42M5JAA.js} +4 -4
  10. package/dist/{chunk-QQC6WVLJ.js.map → chunk-Z42M5JAA.js.map} +1 -1
  11. package/dist/cli.cjs +13 -2
  12. package/dist/cli.cjs.map +1 -1
  13. package/dist/cli.js +3 -3
  14. package/dist/convert/index.cjs +12 -1
  15. package/dist/convert/index.cjs.map +1 -1
  16. package/dist/convert/index.d.cts +1 -1
  17. package/dist/convert/index.d.ts +1 -1
  18. package/dist/convert/index.js +3 -3
  19. package/dist/index.cjs +39 -1
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.d.cts +2 -2
  22. package/dist/index.d.ts +2 -2
  23. package/dist/index.js +2 -2
  24. package/dist/{taals-CjzBuZ35.d.ts → lay-Csa4CD46.d.cts} +38 -1
  25. package/dist/{taals-CjzBuZ35.d.cts → lay-Ct9ky_EE.d.ts} +38 -1
  26. package/dist/player/index.cjs +276 -117
  27. package/dist/player/index.cjs.map +1 -1
  28. package/dist/player/index.d.cts +22 -9
  29. package/dist/player/index.d.ts +22 -9
  30. package/dist/player/index.js +230 -114
  31. package/dist/player/index.js.map +1 -1
  32. package/dist/{schedule-BpBYGXPL.d.cts → schedule-B_UaluBi.d.cts} +27 -3
  33. package/dist/{schedule-Tzk01FKj.d.ts → schedule-CdILTNsk.d.ts} +27 -3
  34. package/dist/{schema-DqBXPGkL.d.cts → schema-DGA_bNVZ.d.cts} +97 -1
  35. package/dist/{schema-DqBXPGkL.d.ts → schema-DGA_bNVZ.d.ts} +97 -1
  36. package/dist/spec/index.cjs +39 -1
  37. package/dist/spec/index.cjs.map +1 -1
  38. package/dist/spec/index.d.cts +16 -4
  39. package/dist/spec/index.d.ts +16 -4
  40. package/dist/spec/index.js +2 -2
  41. package/dist/viewer/index.cjs +146 -23
  42. package/dist/viewer/index.cjs.map +1 -1
  43. package/dist/viewer/index.d.cts +10 -5
  44. package/dist/viewer/index.d.ts +10 -5
  45. package/dist/viewer/index.js +75 -18
  46. package/dist/viewer/index.js.map +1 -1
  47. package/package.json +1 -1
  48. package/dist/chunk-ALN4SWMS.js.map +0 -1
  49. package/dist/chunk-QM4CXKW2.js.map +0 -1
  50. package/dist/chunk-TKTQCUKD.js.map +0 -1
@@ -12,6 +12,32 @@ var __export = (target, all) => {
12
12
  __defProp(target, name, { get: all[name], enumerable: true });
13
13
  };
14
14
 
15
+ // src/spec/lay.ts
16
+ function resolveLaySpeeds(doc, opt) {
17
+ return { ...LAY_SPEEDS, ...doc, ...opt };
18
+ }
19
+ function printedLay(notes) {
20
+ const first = notes[0];
21
+ if (first === void 0 || first.lyric === void 0) return void 0;
22
+ for (const k of Object.keys(first)) if (k !== "lyric") return void 0;
23
+ return PRINTED[first.lyric.normalize("NFC")];
24
+ }
25
+ function rowLay(row) {
26
+ return row.lay ?? printedLay(row.notes);
27
+ }
28
+ var LAYS, LAY_SPEEDS, PRINTED;
29
+ var init_lay = __esm({
30
+ "src/spec/lay.ts"() {
31
+ LAYS = ["drut", "madhya", "vilambit"];
32
+ LAY_SPEEDS = {
33
+ drut: 1,
34
+ madhya: 0.75,
35
+ vilambit: 0.5
36
+ };
37
+ PRINTED = { \u09A6\u09CD\u09B0\u09C1\u09A4: "drut", \u09AE\u09A7\u09CD\u09AF: "madhya", \u09AC\u09BF\u09B2\u09AE\u09CD\u09AC\u09BF\u09A4: "vilambit" };
38
+ }
39
+ });
40
+
15
41
  // src/spec/grace.ts
16
42
  function graceNotes(grace) {
17
43
  if (grace === void 0) return [];
@@ -61,7 +87,7 @@ var init_taals = __esm({
61
87
  };
62
88
  }
63
89
  });
64
- var octaveSchema, svaraLetterSchema, svaraSchema, microtoneSchema, barSchema, bracketSchema, graceSchema, noteSchema, rowSchema, partSchema, taalSchema, metaSchema, swaralipiSchema;
90
+ var octaveSchema, svaraLetterSchema, svaraSchema, microtoneSchema, barSchema, bracketSchema, graceSchema, noteSchema, laySchema, rowSchema, partSchema, taalSchema, metaSchema, swaralipiSchema;
65
91
  var init_schema = __esm({
66
92
  "src/spec/schema.ts"() {
67
93
  octaveSchema = zod.z.enum([
@@ -113,9 +139,17 @@ var init_schema = __esm({
113
139
  }
114
140
  )
115
141
  );
142
+ laySchema = zod.z.enum(["drut", "madhya", "vilambit"]);
116
143
  rowSchema = zod.z.object({
117
144
  /** Optional line label (e.g. a tuk/section marker). */
118
145
  label: zod.z.string().optional(),
146
+ /**
147
+ * লয়: the tempo tier this row switches to, held until another row changes
148
+ * it (absent on every row = the whole song at `meta.bpm`, i.e. দ্রুত).
149
+ * Read it with `rowLay()`, which also picks up the mark NLTR *prints*
150
+ * ("বিলম্বিত লয়:") in already-converted documents.
151
+ */
152
+ lay: laySchema.optional(),
119
153
  notes: zod.z.array(noteSchema)
120
154
  });
121
155
  partSchema = zod.z.object({
@@ -161,6 +195,9 @@ var init_schema = __esm({
161
195
  * The player adopts it on `load()` (precedence: constructor `bpm` → `setBpm`
162
196
  * tweak → this → 90). `null` is the converter's blank placeholder = unset. */
163
197
  bpm: zod.z.number().positive().nullable().optional(),
198
+ /** Per-document override of the লয় tempo factors (fractions of `bpm`).
199
+ * Defaults live in `LAY_SPEEDS`; a player/export option overrides both. */
200
+ laySpeeds: zod.z.object({ drut: zod.z.number().positive(), madhya: zod.z.number().positive(), vilambit: zod.z.number().positive() }).partial().optional(),
164
201
  raag: zod.z.string().optional(),
165
202
  /** আবর্তন: number of taal cycles, when the source states it. */
166
203
  avartan: zod.z.number().int().positive().optional(),
@@ -181,6 +218,8 @@ var init_schema = __esm({
181
218
  // src/spec/index.ts
182
219
  var spec_exports = {};
183
220
  __export(spec_exports, {
221
+ LAYS: () => LAYS,
222
+ LAY_SPEEDS: () => LAY_SPEEDS,
184
223
  SPEC_VERSION: () => SPEC_VERSION,
185
224
  TAALS: () => TAALS,
186
225
  barSchema: () => barSchema,
@@ -189,12 +228,16 @@ __export(spec_exports, {
189
228
  graceSchema: () => graceSchema,
190
229
  hasKomal: () => hasKomal,
191
230
  hasTivra: () => hasTivra,
231
+ laySchema: () => laySchema,
192
232
  metaSchema: () => metaSchema,
193
233
  microtoneSchema: () => microtoneSchema,
194
234
  noteSchema: () => noteSchema,
195
235
  octaveSchema: () => octaveSchema,
196
236
  parseSwaralipi: () => parseSwaralipi,
197
237
  partSchema: () => partSchema,
238
+ printedLay: () => printedLay,
239
+ resolveLaySpeeds: () => resolveLaySpeeds,
240
+ rowLay: () => rowLay,
198
241
  rowSchema: () => rowSchema,
199
242
  safeParseSwaralipi: () => safeParseSwaralipi,
200
243
  svaraLetterSchema: () => svaraLetterSchema,
@@ -215,11 +258,15 @@ var init_spec = __esm({
215
258
  init_schema();
216
259
  init_taals();
217
260
  init_grace();
261
+ init_lay();
218
262
  init_svara();
219
- SPEC_VERSION = "0.7.7";
263
+ SPEC_VERSION = "0.8.1";
220
264
  }
221
265
  });
222
266
 
267
+ // src/player/schedule.ts
268
+ init_lay();
269
+
223
270
  // src/player/pitch.ts
224
271
  init_grace();
225
272
  init_svara();
@@ -338,22 +385,38 @@ function buildSchedule(doc, opts = {}) {
338
385
  const events = [];
339
386
  let cursor = 0;
340
387
  let lastPitched = -1;
341
- let samBeat;
388
+ let samMatra;
389
+ let matra = 0;
390
+ const speeds = resolveLaySpeeds(doc.meta.laySpeeds, opts.laySpeeds);
391
+ const withLay = opts.lay !== false;
392
+ const tempo = [{ beat: 0, endBeat: 0, scale: 1, lay: "drut" }];
393
+ let scale = 1;
342
394
  doc.parts.forEach((part, p) => {
343
395
  part.rows.forEach((row, r) => {
396
+ const lay = withLay ? rowLay(row) : void 0;
397
+ if (lay !== void 0 && speeds[lay] !== scale) {
398
+ scale = speeds[lay];
399
+ const last = tempo[tempo.length - 1];
400
+ if (last.beat === cursor) tempo[tempo.length - 1] = { beat: cursor, endBeat: cursor, scale, lay };
401
+ else {
402
+ last.endBeat = cursor;
403
+ tempo.push({ beat: cursor, endBeat: cursor, scale, lay });
404
+ }
405
+ }
344
406
  const glides = meendContinuations(row.notes);
345
407
  row.notes.forEach((note, i) => {
346
- if (samBeat === void 0 && note.above !== void 0) {
408
+ if (samMatra === void 0 && note.above !== void 0) {
347
409
  for (const a of note.above) {
348
410
  if (a.raw !== void 0 && SAM_TICK.test(a.raw)) {
349
- samBeat = cursor;
411
+ samMatra = matra;
350
412
  break;
351
413
  }
352
414
  }
353
415
  }
354
416
  if (note.bar !== void 0 || isSilent(note)) return;
355
417
  const path = `${p}.${r}.${i}`;
356
- const dur = note.durationMatra ?? 1;
418
+ const written = note.durationMatra ?? 1;
419
+ const dur = written / scale;
357
420
  if (note.svara !== void 0) {
358
421
  events.push({
359
422
  beat: cursor,
@@ -372,12 +435,14 @@ function buildSchedule(doc, opts = {}) {
372
435
  events.push({ beat: cursor, durationBeats: dur, midi: null, kan: [], path, kind: "rest" });
373
436
  }
374
437
  cursor += dur;
438
+ matra += written;
375
439
  });
376
440
  });
377
441
  });
378
442
  const numBeats = doc.meta.taal?.numBeats;
379
- const pickupBeats = samBeat !== void 0 && numBeats !== void 0 && numBeats > 0 ? (numBeats - samBeat % numBeats) % numBeats : 0;
380
- return { events, totalBeats: cursor, pickupBeats };
443
+ const pickupMatra = samMatra !== void 0 && numBeats !== void 0 && numBeats > 0 ? (numBeats - samMatra % numBeats) % numBeats : 0;
444
+ tempo[tempo.length - 1].endBeat = cursor;
445
+ return { events, totalBeats: cursor, pickupBeats: pickupMatra / tempo[0].scale, tempo };
381
446
  }
382
447
 
383
448
  // src/viewer/render.ts
@@ -427,6 +492,11 @@ var LABELS = {
427
492
  // one taal cycle is exactly what the number counts.
428
493
  en: { title: "Song", taal: "Taal", raag: "Raag", avartan: "Cycles", notationBy: "Notator" }
429
494
  };
495
+ var LAY_LABELS = {
496
+ bn: { drut: "\u09A6\u09CD\u09B0\u09C1\u09A4 \u09B2\u09AF\u09BC", madhya: "\u09AE\u09A7\u09CD\u09AF \u09B2\u09AF\u09BC", vilambit: "\u09AC\u09BF\u09B2\u09AE\u09CD\u09AC\u09BF\u09A4 \u09B2\u09AF\u09BC" },
497
+ hi: { drut: "\u0926\u094D\u0930\u0941\u0924 \u0932\u092F", madhya: "\u092E\u0927\u094D\u092F \u0932\u092F", vilambit: "\u0935\u093F\u0932\u0902\u092C\u093F\u0924 \u0932\u092F" },
498
+ en: { drut: "Fast (drut)", madhya: "Medium (madhya)", vilambit: "Slow (vilambit)" }
499
+ };
430
500
  function composeCluster(letter, opts) {
431
501
  const classes = [];
432
502
  if (opts.lang === "bn") {
@@ -792,16 +862,20 @@ function renderNote(note, kind, opts, ctx) {
792
862
  }
793
863
  function renderArc(arc, offset) {
794
864
  const open = arc.line === true;
795
- const cls = [
796
- "sw-arc",
797
- arc.openLeft || open ? "sw-arc--openl" : "",
798
- arc.openRight || open ? "sw-arc--openr" : ""
799
- ].filter(Boolean).join(" ");
800
- const left = open || arc.openLeft ? "" : '<i class="sw-arc-l">w</i>';
801
- const right = open || arc.openRight ? "" : '<i class="sw-arc-r">x</i>';
802
- const from = offset + arc.start + COL_BASE;
803
- const to = offset + arc.end + COL_BASE + 1;
804
- return `<span class="${cls}" style="grid-column:${from} / ${to}">${left}<i class="sw-arc-m"></i>${right}</span>`;
865
+ const openL = arc.openLeft || open;
866
+ const openR = arc.openRight || open;
867
+ const fill = '<i class="sw-arc-m"></i>';
868
+ const hookL = openL ? "" : '<i class="sw-arc-l">w</i>';
869
+ const hookR = openR ? "" : '<i class="sw-arc-r">x</i>';
870
+ const piece = (col, cls, inner) => `<span class="sw-arc${cls}" style="grid-column:${col}">${inner}</span>`;
871
+ const f = offset + arc.start + COL_BASE;
872
+ const t = offset + arc.end + COL_BASE;
873
+ const openCls = (openL ? " sw-arc--openl" : "") + (openR ? " sw-arc--openr" : "");
874
+ if (t === f) return piece(`${f}`, openCls, hookL + fill + hookR);
875
+ const head = piece(`${f}`, openL ? " sw-arc--openl" : " sw-arc--head", hookL + fill);
876
+ const tail = piece(`${t}`, openR ? " sw-arc--openr" : " sw-arc--tail", fill + hookR);
877
+ const mid = t > f + 1 ? piece(`${f + 1} / ${t}`, " sw-arc--mid", fill) : "";
878
+ return head + mid + tail;
805
879
  }
806
880
  function renderRow(row, partIdx, rowIdx, offset, ctx) {
807
881
  const cells = groupRowIntoCells(row.notes);
@@ -861,11 +935,18 @@ function renderToHtml(doc, opts = {}) {
861
935
  if (opts.showHeader !== false) out.push(renderHeader(doc.meta, ctx));
862
936
  out.push(`<div class="sw-scroll"><div class="sw-block" style="${tracks}">`);
863
937
  let n = 0;
938
+ let lay;
864
939
  doc.parts.forEach((part, p) => {
865
940
  out.push('<section class="sw-part">');
866
941
  if (part.name !== void 0)
867
942
  out.push(`<h3 class="sw-part-name">${escapeHtml(ctx.tr(part.name))}</h3>`);
868
- part.rows.forEach((row, r) => out.push(renderRow(row, p, r, layout.offsets[n++], ctx)));
943
+ part.rows.forEach((row, r) => {
944
+ if (row.lay !== void 0 && row.lay !== lay) {
945
+ lay = row.lay;
946
+ out.push(`<div class="sw-lay" data-lay="${lay}">${escapeHtml(LAY_LABELS[ctx.lang][lay])}</div>`);
947
+ }
948
+ out.push(renderRow(row, p, r, layout.offsets[n++], ctx));
949
+ });
869
950
  out.push("</section>");
870
951
  });
871
952
  out.push("</div></div></div>");
@@ -957,6 +1038,30 @@ ${FONT_FACES}
957
1038
 
958
1039
  .sw-part-name { font-size: 0.85em; font-weight: 600; margin: 1em 0 0.2em; opacity: 0.85; }
959
1040
 
1041
+ /* \u09B2\u09AF\u09BC marker: the line where the tempo tier turns over. A full-width rule
1042
+ rather than a row label \u2014 the label track is what the notation columns are
1043
+ measured against, and a six-em word in it would shove the whole sheet right. */
1044
+ .sw-lay {
1045
+ grid-column: 1 / -1;
1046
+ display: flex;
1047
+ align-items: center;
1048
+ gap: 0.6em;
1049
+ margin: 0.8em 0 0;
1050
+ /* Floored like the header: fitSwaralipi shrinks the sheet to fit the
1051
+ notation's columns, and this label has none to align. */
1052
+ font-size: max(0.7em, var(--sw-lay-min, 0.72rem));
1053
+ font-weight: 600;
1054
+ letter-spacing: 0.09em;
1055
+ opacity: 0.75;
1056
+ break-after: avoid;
1057
+ }
1058
+ .sw-lay::after {
1059
+ content: '';
1060
+ flex: 1;
1061
+ height: 1px;
1062
+ background: var(--sw-rule, rgba(128, 128, 128, 0.35));
1063
+ }
1064
+
960
1065
  /* ---- block grid: ONE column set for the whole sheet ----
961
1066
  * NLTR emits a song as a single html table, which is why its lines align
962
1067
  * down the page. Same here: .sw-block owns the tracks, every .sw-row
@@ -1081,9 +1186,26 @@ ${FONT_FACES}
1081
1186
  align-items: baseline;
1082
1187
  line-height: 1;
1083
1188
  font-family: Swarabitan, var(--sw-font, sans-serif);
1084
- font-size: 0.9em;
1189
+ /* The 0.9em sits on the children, not here: the end pieces below size
1190
+ themselves off the block's own column-gap, which is in the block's em. */
1085
1191
  }
1192
+ .sw-arc > i, .sw-arc::before { font-size: 0.9em; }
1086
1193
  .sw-arc-l, .sw-arc-r { flex: none; font-style: normal; }
1194
+ /* Every piece needs the same baseline \u2014 the fill rides it, so a piece holding
1195
+ only fill would otherwise drop to the bottom of the row. A zero-width copy
1196
+ of the hook glyph is the strut (same font, same metrics). */
1197
+ .sw-arc::before { content: 'w'; flex: none; width: 0; overflow: hidden; }
1198
+ /* The ends hang from the two notes' CENTRES, as the print sets them, so the
1199
+ arc is three grid items: each end piece takes half of its own column plus
1200
+ the column-gap it has to bridge, and the negative margin on the same side
1201
+ lets that bridge overhang the grid area instead of shifting the piece. */
1202
+ .sw-arc--head, .sw-arc--tail {
1203
+ --sw-arc-gap: calc(var(--sw-cell-gap, 0.4em) * var(--sw-tight, 1));
1204
+ width: calc(50% + var(--sw-arc-gap));
1205
+ }
1206
+ .sw-arc--head { justify-self: end; margin-right: calc(var(--sw-arc-gap) * -1); }
1207
+ .sw-arc--tail { justify-self: start; margin-left: calc(var(--sw-arc-gap) * -1);
1208
+ justify-content: flex-end; }
1087
1209
  /* The fill's geometry is measured off the font's own 'y' glyph: ink 0.11em
1088
1210
  tall whose bottom sits 0.01em above the baseline. An empty inline-block
1089
1211
  takes its bottom margin edge as its baseline, so -0.01em lifts the bar to
@@ -1141,7 +1263,7 @@ var SWARALIPI_PRINT_CSS = `
1141
1263
  .sw-sheet, .sw-staff { -webkit-print-color-adjust: exact; print-color-adjust: exact; color: #000; }
1142
1264
  .sw-scroll { overflow: visible !important; }
1143
1265
  .sw-row { break-inside: avoid; page-break-inside: avoid; }
1144
- .sw-part-name, .sw-header { break-after: avoid-page; page-break-after: avoid; }
1266
+ .sw-part-name, .sw-header, .sw-lay { break-after: avoid-page; page-break-after: avoid; }
1145
1267
  .sw-hl { background: none !important; }
1146
1268
  /* Keep each engraved staff system whole across a page break (the abcjs line
1147
1269
  is a <div> wrapping the <svg>, so the break rule must sit on the div). */
@@ -1413,7 +1535,7 @@ function resolveDrumTaal(doc) {
1413
1535
  return id !== void 0 && id in TAALS ? TAALS[id] : { numBeats: 4, sam: 0 };
1414
1536
  }
1415
1537
  function docToAbc(doc, opts = {}) {
1416
- const schedule = buildSchedule(doc, { tonic: opts.tonic, baseOctave: opts.baseOctave });
1538
+ const schedule = buildSchedule(doc, { tonic: opts.tonic, baseOctave: opts.baseOctave, lay: false });
1417
1539
  const bpm = opts.bpm ?? doc.meta.bpm ?? 90;
1418
1540
  const beatsPerBar = opts.beatsPerBar ?? doc.meta.taal?.numBeats ?? 4;
1419
1541
  const drums = opts.drums ? drumPattern(resolveDrumTaal(doc)) : void 0;
@@ -1477,7 +1599,7 @@ async function abcToSvg(abc, opts = {}, notePaths) {
1477
1599
  async function renderStaffHtml(doc, opts = {}) {
1478
1600
  const bpm = opts.bpm ?? doc.meta.bpm ?? 90;
1479
1601
  const abc = docToAbc(doc, { ...opts, bpm });
1480
- const schedule = buildSchedule(doc, { tonic: opts.tonic, baseOctave: opts.baseOctave });
1602
+ const schedule = buildSchedule(doc, { tonic: opts.tonic, baseOctave: opts.baseOctave, lay: false });
1481
1603
  const engraved = await abcToSvg(abc, opts, eventPaths(schedule));
1482
1604
  const lines = engraved.includes("sw-staff-missing") ? engraved : `<div class="sw-staff-lines">${engraved}</div>`;
1483
1605
  const header = opts.showHeader === false ? "" : renderMetaHeader(doc.meta, opts.headerLang ?? "en", opts.transliterate);
@@ -1888,6 +2010,7 @@ exports.DEFAULT_NOTATOR = DEFAULT_NOTATOR;
1888
2010
  exports.DEFAULT_WATERMARK = DEFAULT_WATERMARK;
1889
2011
  exports.DIGITS = DIGITS;
1890
2012
  exports.LABELS = LABELS;
2013
+ exports.LAY_LABELS = LAY_LABELS;
1891
2014
  exports.MARKS = MARKS;
1892
2015
  exports.MAX_ZOOM = MAX_ZOOM;
1893
2016
  exports.MIN_ZOOM = MIN_ZOOM;