@ugfoundation/swaralipi-js 0.7.6 → 0.8.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.
Files changed (50) hide show
  1. package/dist/{batch-5NMEPJ6E.js → batch-NJMT3M5Z.js} +5 -5
  2. package/dist/{batch-5NMEPJ6E.js.map → batch-NJMT3M5Z.js.map} +1 -1
  3. package/dist/{chunk-ITOP5O5M.js → chunk-4HMHTXDE.js} +15 -4
  4. package/dist/chunk-4HMHTXDE.js.map +1 -0
  5. package/dist/{chunk-UF5OSJRP.js → chunk-4HNT6OAT.js} +32 -3
  6. package/dist/chunk-4HNT6OAT.js.map +1 -0
  7. package/dist/{chunk-2SAZWTTK.js → chunk-4IVUPMRK.js} +29 -11
  8. package/dist/chunk-4IVUPMRK.js.map +1 -0
  9. package/dist/{chunk-NJGHFQJP.js → chunk-YFVAVICF.js} +4 -4
  10. package/dist/{chunk-NJGHFQJP.js.map → chunk-YFVAVICF.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 +49 -1
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.d.cts +3 -3
  22. package/dist/index.d.ts +3 -3
  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 +286 -119
  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.ts → schema-DGA_bNVZ.d.cts} +97 -1
  35. package/dist/{schema-DqBXPGkL.d.cts → schema-DGA_bNVZ.d.ts} +97 -1
  36. package/dist/spec/index.cjs +49 -1
  37. package/dist/spec/index.cjs.map +1 -1
  38. package/dist/spec/index.d.cts +31 -5
  39. package/dist/spec/index.d.ts +31 -5
  40. package/dist/spec/index.js +2 -2
  41. package/dist/viewer/index.cjs +134 -16
  42. package/dist/viewer/index.cjs.map +1 -1
  43. package/dist/viewer/index.d.cts +10 -32
  44. package/dist/viewer/index.d.ts +10 -32
  45. package/dist/viewer/index.js +44 -9
  46. package/dist/viewer/index.js.map +1 -1
  47. package/package.json +1 -1
  48. package/dist/chunk-2SAZWTTK.js.map +0 -1
  49. package/dist/chunk-ITOP5O5M.js.map +0 -1
  50. package/dist/chunk-UF5OSJRP.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 [];
@@ -22,6 +48,18 @@ var init_grace = __esm({
22
48
  }
23
49
  });
24
50
 
51
+ // src/spec/svara.ts
52
+ function hasKomal(letter) {
53
+ return letter === "r" || letter === "g" || letter === "d" || letter === "n";
54
+ }
55
+ function hasTivra(letter) {
56
+ return letter === "m";
57
+ }
58
+ var init_svara = __esm({
59
+ "src/spec/svara.ts"() {
60
+ }
61
+ });
62
+
25
63
  // src/spec/taals.ts
26
64
  var TAALS;
27
65
  var init_taals = __esm({
@@ -49,7 +87,7 @@ var init_taals = __esm({
49
87
  };
50
88
  }
51
89
  });
52
- 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;
53
91
  var init_schema = __esm({
54
92
  "src/spec/schema.ts"() {
55
93
  octaveSchema = zod.z.enum([
@@ -101,9 +139,17 @@ var init_schema = __esm({
101
139
  }
102
140
  )
103
141
  );
142
+ laySchema = zod.z.enum(["drut", "madhya", "vilambit"]);
104
143
  rowSchema = zod.z.object({
105
144
  /** Optional line label (e.g. a tuk/section marker). */
106
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(),
107
153
  notes: zod.z.array(noteSchema)
108
154
  });
109
155
  partSchema = zod.z.object({
@@ -149,6 +195,9 @@ var init_schema = __esm({
149
195
  * The player adopts it on `load()` (precedence: constructor `bpm` → `setBpm`
150
196
  * tweak → this → 90). `null` is the converter's blank placeholder = unset. */
151
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(),
152
201
  raag: zod.z.string().optional(),
153
202
  /** আবর্তন: number of taal cycles, when the source states it. */
154
203
  avartan: zod.z.number().int().positive().optional(),
@@ -169,18 +218,26 @@ var init_schema = __esm({
169
218
  // src/spec/index.ts
170
219
  var spec_exports = {};
171
220
  __export(spec_exports, {
221
+ LAYS: () => LAYS,
222
+ LAY_SPEEDS: () => LAY_SPEEDS,
172
223
  SPEC_VERSION: () => SPEC_VERSION,
173
224
  TAALS: () => TAALS,
174
225
  barSchema: () => barSchema,
175
226
  bracketSchema: () => bracketSchema,
176
227
  graceNotes: () => graceNotes,
177
228
  graceSchema: () => graceSchema,
229
+ hasKomal: () => hasKomal,
230
+ hasTivra: () => hasTivra,
231
+ laySchema: () => laySchema,
178
232
  metaSchema: () => metaSchema,
179
233
  microtoneSchema: () => microtoneSchema,
180
234
  noteSchema: () => noteSchema,
181
235
  octaveSchema: () => octaveSchema,
182
236
  parseSwaralipi: () => parseSwaralipi,
183
237
  partSchema: () => partSchema,
238
+ printedLay: () => printedLay,
239
+ resolveLaySpeeds: () => resolveLaySpeeds,
240
+ rowLay: () => rowLay,
184
241
  rowSchema: () => rowSchema,
185
242
  safeParseSwaralipi: () => safeParseSwaralipi,
186
243
  svaraLetterSchema: () => svaraLetterSchema,
@@ -201,12 +258,18 @@ var init_spec = __esm({
201
258
  init_schema();
202
259
  init_taals();
203
260
  init_grace();
204
- SPEC_VERSION = "0.7.6";
261
+ init_lay();
262
+ init_svara();
263
+ SPEC_VERSION = "0.8.0";
205
264
  }
206
265
  });
207
266
 
267
+ // src/player/schedule.ts
268
+ init_lay();
269
+
208
270
  // src/player/pitch.ts
209
271
  init_grace();
272
+ init_svara();
210
273
  var OCTAVE_OFFSET = {
211
274
  "ati-mandra": -2,
212
275
  mandra: -1,
@@ -256,8 +319,8 @@ function svaraSemitone(note) {
256
319
  if (note.svara === void 0) return null;
257
320
  const [, letter] = note.svara;
258
321
  let semi = SHUDDHA[letter];
259
- if (note.komal === true || note.microtone !== void 0) semi -= 1;
260
- if (note.tivra === true) semi += 1;
322
+ if ((note.komal === true || note.microtone !== void 0) && hasKomal(letter)) semi -= 1;
323
+ if (note.tivra === true && hasTivra(letter)) semi += 1;
261
324
  return semi;
262
325
  }
263
326
  function svaraToMidi(note, tonicPc, baseOctave = 4) {
@@ -322,22 +385,38 @@ function buildSchedule(doc, opts = {}) {
322
385
  const events = [];
323
386
  let cursor = 0;
324
387
  let lastPitched = -1;
325
- 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;
326
394
  doc.parts.forEach((part, p) => {
327
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
+ }
328
406
  const glides = meendContinuations(row.notes);
329
407
  row.notes.forEach((note, i) => {
330
- if (samBeat === void 0 && note.above !== void 0) {
408
+ if (samMatra === void 0 && note.above !== void 0) {
331
409
  for (const a of note.above) {
332
410
  if (a.raw !== void 0 && SAM_TICK.test(a.raw)) {
333
- samBeat = cursor;
411
+ samMatra = matra;
334
412
  break;
335
413
  }
336
414
  }
337
415
  }
338
416
  if (note.bar !== void 0 || isSilent(note)) return;
339
417
  const path = `${p}.${r}.${i}`;
340
- const dur = note.durationMatra ?? 1;
418
+ const written = note.durationMatra ?? 1;
419
+ const dur = written / scale;
341
420
  if (note.svara !== void 0) {
342
421
  events.push({
343
422
  beat: cursor,
@@ -356,18 +435,21 @@ function buildSchedule(doc, opts = {}) {
356
435
  events.push({ beat: cursor, durationBeats: dur, midi: null, kan: [], path, kind: "rest" });
357
436
  }
358
437
  cursor += dur;
438
+ matra += written;
359
439
  });
360
440
  });
361
441
  });
362
442
  const numBeats = doc.meta.taal?.numBeats;
363
- const pickupBeats = samBeat !== void 0 && numBeats !== void 0 && numBeats > 0 ? (numBeats - samBeat % numBeats) % numBeats : 0;
364
- 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 };
365
446
  }
366
447
 
367
448
  // src/viewer/render.ts
368
449
  init_grace();
369
450
 
370
451
  // src/viewer/glyphs.ts
452
+ init_svara();
371
453
  var SWARABITAN_SHUDDHA = {
372
454
  s: "s",
373
455
  r: "r",
@@ -393,7 +475,6 @@ var BHATKHANDE_OCTAVE = {
393
475
  "ati-taar": "U",
394
476
  "ati-mandra": "L"
395
477
  };
396
- var BHATKHANDE_HAS_KOMAL = /* @__PURE__ */ new Set(["r", "g", "d", "n"]);
397
478
  var DIGITS = {
398
479
  bn: "\u09E6\u09E7\u09E8\u09E9\u09EA\u09EB\u09EC\u09ED\u09EE\u09EF",
399
480
  hi: "\u0966\u0967\u0968\u0969\u096A\u096B\u096C\u096D\u096E\u096F",
@@ -411,6 +492,11 @@ var LABELS = {
411
492
  // one taal cycle is exactly what the number counts.
412
493
  en: { title: "Song", taal: "Taal", raag: "Raag", avartan: "Cycles", notationBy: "Notator" }
413
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
+ };
414
500
  function composeCluster(letter, opts) {
415
501
  const classes = [];
416
502
  if (opts.lang === "bn") {
@@ -424,7 +510,7 @@ function composeCluster(letter, opts) {
424
510
  }
425
511
  let base = letter;
426
512
  if (opts.tivra && letter === "m") base = "M";
427
- else if (opts.komal && BHATKHANDE_HAS_KOMAL.has(letter)) base = base.toUpperCase();
513
+ else if (opts.komal && hasKomal(letter)) base = base.toUpperCase();
428
514
  return { text: base + BHATKHANDE_OCTAVE[opts.octave], classes };
429
515
  }
430
516
  function localizeDigits(s, lang) {
@@ -845,11 +931,18 @@ function renderToHtml(doc, opts = {}) {
845
931
  if (opts.showHeader !== false) out.push(renderHeader(doc.meta, ctx));
846
932
  out.push(`<div class="sw-scroll"><div class="sw-block" style="${tracks}">`);
847
933
  let n = 0;
934
+ let lay;
848
935
  doc.parts.forEach((part, p) => {
849
936
  out.push('<section class="sw-part">');
850
937
  if (part.name !== void 0)
851
938
  out.push(`<h3 class="sw-part-name">${escapeHtml(ctx.tr(part.name))}</h3>`);
852
- part.rows.forEach((row, r) => out.push(renderRow(row, p, r, layout.offsets[n++], ctx)));
939
+ part.rows.forEach((row, r) => {
940
+ if (row.lay !== void 0 && row.lay !== lay) {
941
+ lay = row.lay;
942
+ out.push(`<div class="sw-lay" data-lay="${lay}">${escapeHtml(LAY_LABELS[ctx.lang][lay])}</div>`);
943
+ }
944
+ out.push(renderRow(row, p, r, layout.offsets[n++], ctx));
945
+ });
853
946
  out.push("</section>");
854
947
  });
855
948
  out.push("</div></div></div>");
@@ -941,6 +1034,30 @@ ${FONT_FACES}
941
1034
 
942
1035
  .sw-part-name { font-size: 0.85em; font-weight: 600; margin: 1em 0 0.2em; opacity: 0.85; }
943
1036
 
1037
+ /* \u09B2\u09AF\u09BC marker: the line where the tempo tier turns over. A full-width rule
1038
+ rather than a row label \u2014 the label track is what the notation columns are
1039
+ measured against, and a six-em word in it would shove the whole sheet right. */
1040
+ .sw-lay {
1041
+ grid-column: 1 / -1;
1042
+ display: flex;
1043
+ align-items: center;
1044
+ gap: 0.6em;
1045
+ margin: 0.8em 0 0;
1046
+ /* Floored like the header: fitSwaralipi shrinks the sheet to fit the
1047
+ notation's columns, and this label has none to align. */
1048
+ font-size: max(0.7em, var(--sw-lay-min, 0.72rem));
1049
+ font-weight: 600;
1050
+ letter-spacing: 0.09em;
1051
+ opacity: 0.75;
1052
+ break-after: avoid;
1053
+ }
1054
+ .sw-lay::after {
1055
+ content: '';
1056
+ flex: 1;
1057
+ height: 1px;
1058
+ background: var(--sw-rule, rgba(128, 128, 128, 0.35));
1059
+ }
1060
+
944
1061
  /* ---- block grid: ONE column set for the whole sheet ----
945
1062
  * NLTR emits a song as a single html table, which is why its lines align
946
1063
  * down the page. Same here: .sw-block owns the tracks, every .sw-row
@@ -1125,7 +1242,7 @@ var SWARALIPI_PRINT_CSS = `
1125
1242
  .sw-sheet, .sw-staff { -webkit-print-color-adjust: exact; print-color-adjust: exact; color: #000; }
1126
1243
  .sw-scroll { overflow: visible !important; }
1127
1244
  .sw-row { break-inside: avoid; page-break-inside: avoid; }
1128
- .sw-part-name, .sw-header { break-after: avoid-page; page-break-after: avoid; }
1245
+ .sw-part-name, .sw-header, .sw-lay { break-after: avoid-page; page-break-after: avoid; }
1129
1246
  .sw-hl { background: none !important; }
1130
1247
  /* Keep each engraved staff system whole across a page break (the abcjs line
1131
1248
  is a <div> wrapping the <svg>, so the break rule must sit on the div). */
@@ -1397,7 +1514,7 @@ function resolveDrumTaal(doc) {
1397
1514
  return id !== void 0 && id in TAALS ? TAALS[id] : { numBeats: 4, sam: 0 };
1398
1515
  }
1399
1516
  function docToAbc(doc, opts = {}) {
1400
- const schedule = buildSchedule(doc, { tonic: opts.tonic, baseOctave: opts.baseOctave });
1517
+ const schedule = buildSchedule(doc, { tonic: opts.tonic, baseOctave: opts.baseOctave, lay: false });
1401
1518
  const bpm = opts.bpm ?? doc.meta.bpm ?? 90;
1402
1519
  const beatsPerBar = opts.beatsPerBar ?? doc.meta.taal?.numBeats ?? 4;
1403
1520
  const drums = opts.drums ? drumPattern(resolveDrumTaal(doc)) : void 0;
@@ -1461,7 +1578,7 @@ async function abcToSvg(abc, opts = {}, notePaths) {
1461
1578
  async function renderStaffHtml(doc, opts = {}) {
1462
1579
  const bpm = opts.bpm ?? doc.meta.bpm ?? 90;
1463
1580
  const abc = docToAbc(doc, { ...opts, bpm });
1464
- const schedule = buildSchedule(doc, { tonic: opts.tonic, baseOctave: opts.baseOctave });
1581
+ const schedule = buildSchedule(doc, { tonic: opts.tonic, baseOctave: opts.baseOctave, lay: false });
1465
1582
  const engraved = await abcToSvg(abc, opts, eventPaths(schedule));
1466
1583
  const lines = engraved.includes("sw-staff-missing") ? engraved : `<div class="sw-staff-lines">${engraved}</div>`;
1467
1584
  const header = opts.showHeader === false ? "" : renderMetaHeader(doc.meta, opts.headerLang ?? "en", opts.transliterate);
@@ -1872,6 +1989,7 @@ exports.DEFAULT_NOTATOR = DEFAULT_NOTATOR;
1872
1989
  exports.DEFAULT_WATERMARK = DEFAULT_WATERMARK;
1873
1990
  exports.DIGITS = DIGITS;
1874
1991
  exports.LABELS = LABELS;
1992
+ exports.LAY_LABELS = LAY_LABELS;
1875
1993
  exports.MARKS = MARKS;
1876
1994
  exports.MAX_ZOOM = MAX_ZOOM;
1877
1995
  exports.MIN_ZOOM = MIN_ZOOM;