@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.
Files changed (46) hide show
  1. package/dist/{batch-UETOBPHT.js → batch-RUZGM2NR.js} +4 -4
  2. package/dist/{batch-UETOBPHT.js.map → batch-RUZGM2NR.js.map} +1 -1
  3. package/dist/{chunk-C6TRCMPL.js → chunk-GHWURC4U.js} +97 -5
  4. package/dist/chunk-GHWURC4U.js.map +1 -0
  5. package/dist/{chunk-UD2K4UIC.js → chunk-ULK7NVKH.js} +8 -6
  6. package/dist/chunk-ULK7NVKH.js.map +1 -0
  7. package/dist/{chunk-ID44OP2N.js → chunk-ZNVTOY7J.js} +7 -6
  8. package/dist/chunk-ZNVTOY7J.js.map +1 -0
  9. package/dist/cli.cjs +11 -8
  10. package/dist/cli.cjs.map +1 -1
  11. package/dist/cli.js +2 -2
  12. package/dist/convert/index.cjs +10 -7
  13. package/dist/convert/index.cjs.map +1 -1
  14. package/dist/convert/index.d.cts +4 -3
  15. package/dist/convert/index.d.ts +4 -3
  16. package/dist/convert/index.js +2 -2
  17. package/dist/index.cjs +6 -4
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.cts +1 -1
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.js +1 -1
  22. package/dist/player/index.cjs +212 -20
  23. package/dist/player/index.cjs.map +1 -1
  24. package/dist/player/index.d.cts +16 -4
  25. package/dist/player/index.d.ts +16 -4
  26. package/dist/player/index.js +111 -20
  27. package/dist/player/index.js.map +1 -1
  28. package/dist/{schedule-BaYIJf8y.d.ts → schedule-8WFUBJqB.d.ts} +78 -3
  29. package/dist/{schedule-bBvy18MO.d.cts → schedule-CjPcD-Hf.d.cts} +78 -3
  30. package/dist/{schema-D3wyNRqM.d.ts → schema-D_86x4oV.d.cts} +21 -6
  31. package/dist/{schema-D3wyNRqM.d.cts → schema-D_86x4oV.d.ts} +21 -6
  32. package/dist/spec/index.cjs +6 -4
  33. package/dist/spec/index.cjs.map +1 -1
  34. package/dist/spec/index.d.cts +3 -3
  35. package/dist/spec/index.d.ts +3 -3
  36. package/dist/spec/index.js +1 -1
  37. package/dist/viewer/index.cjs +240 -161
  38. package/dist/viewer/index.cjs.map +1 -1
  39. package/dist/viewer/index.d.cts +16 -23
  40. package/dist/viewer/index.d.ts +16 -23
  41. package/dist/viewer/index.js +75 -36
  42. package/dist/viewer/index.js.map +1 -1
  43. package/package.json +1 -1
  44. package/dist/chunk-C6TRCMPL.js.map +0 -1
  45. package/dist/chunk-ID44OP2N.js.map +0 -1
  46. package/dist/chunk-UD2K4UIC.js.map +0 -1
@@ -1,5 +1,5 @@
1
- import { O as Octave, a as SvaraLetter, N as Note, M as Meta, S as Swaralipi, T as Taal } from '../schema-D3wyNRqM.cjs';
2
- import { D as DrumVoice, S as Schedule, P as PlayEvent } from '../schedule-bBvy18MO.cjs';
1
+ import { O as Octave, a as SvaraLetter, N as Note, M as Meta, S as Swaralipi, T as Taal } from '../schema-D_86x4oV.cjs';
2
+ import { D as DrumVoice, S as Schedule, P as PlayEvent } from '../schedule-CjPcD-Hf.cjs';
3
3
  import 'zod';
4
4
  import 'tone';
5
5
  import '../taals-CjzBuZ35.cjs';
@@ -77,25 +77,6 @@ declare function composeCluster(letter: SvaraLetter, opts: ComposeOptions): Clus
77
77
  /** Map ASCII digits in `s` to the target script's digits. */
78
78
  declare function localizeDigits(s: string, lang: GridLang): string;
79
79
 
80
- /**
81
- * Pure spec-JSON → HTML renderer. No DOM, no side effects: usable for SSR
82
- * (Astro/static sites emit `renderToHtml(doc)` next to a
83
- * `<style>${SWARALIPI_CSS}</style>`) and unit-testable in plain Node.
84
- *
85
- * Layout model (mirrors NLTR's 4-row systems): each spec `Row` becomes one
86
- * CSS grid with **one column per top-level note** and four grid rows —
87
- * above-annotations / notation / below (slur arcs, marks) / lyric. Source
88
- * cells (groups of sub-notes sharing one matra) become column *spans*, so
89
- * per-sub-note lyric alignment and P2.3's per-note highlight targets fall
90
- * out of the column model for free.
91
- *
92
- * Rendering is spec-driven (semantics, not `raw`): grouping derives from
93
- * `durationMatra` accumulation so hand-authored documents without `raw`
94
- * render identically. `raw` is consulted only for (a) raw-only fallback
95
- * notes (never dropped — dimmed, in Swarabitan when loaded), (b) the ছেদ
96
- * cut mark and beat labels, whose grammar lives in raw (see codec.ts).
97
- */
98
-
99
80
  interface RenderOptions {
100
81
  /** Notation + UI language. Default `bn`. */
101
82
  lang?: ViewerLang;
@@ -119,7 +100,9 @@ interface Cell {
119
100
  * - bar / raw-only / carrier notes force-close and form singleton cells;
120
101
  * - full-matra notes are singleton groups;
121
102
  * - fractional notes accumulate until the sum reaches an integer matra
122
- * boundary (epsilon for 1/3, 0.2 chains), then the group closes.
103
+ * boundary (epsilon for 1/3, 0.2 chains), then the group closes;
104
+ * - `halfMatra` (the অর্ধমাত্রা ঃ) closes the cell on a half boundary — the
105
+ * only case durations alone cannot resolve (`সঃ গঃ` vs `সরা`).
123
106
  * The converter emits k notes × 1/k per source cell, so groups coincide
124
107
  * with source cells; an incomplete trailing group still closes at row end
125
108
  * (the matra carrier lands on its last note).
@@ -147,6 +130,16 @@ interface Arc {
147
130
  * A row with fill segments but no ends at all renders one straight line.
148
131
  */
149
132
  declare function resolveArcs(cells: Cell[], noteCount: number): Arc[];
133
+ /**
134
+ * The matra marks a cell's final note carries: its total duration as whole
135
+ * আ-কার matras plus an optional অর্ধমাত্রা ঃ (legend `রাঃ` = 1½, `সঃ` = ½).
136
+ * Only a `halfMatra` cell may show zero matras — every other cell keeps at
137
+ * least one, so an incomplete trailing group still gets its carrier.
138
+ */
139
+ declare function cellMatra(cell: Cell): {
140
+ matras: number;
141
+ half: boolean;
142
+ };
150
143
  /**
151
144
  * The নাম / তাল / রাগ / আবর্তন / স্বরলিপিকার header block, shared by the
152
145
  * akarmatrik grid and the staff view so both show the same metadata (localized
@@ -467,4 +460,4 @@ type Transliterator = (bengaliText: string) => string;
467
460
  */
468
461
  declare function loadLyricTransliterator(lang: ViewerLang): Promise<Transliterator | undefined>;
469
462
 
470
- export { type Arc, type Cell, type Cluster, type ComposeOptions, DEFAULT_NOTATOR, DEFAULT_WATERMARK, DIGITS, type GridLang, LABELS, MARKS, NOTATION_FONT_URLS, type PrintOptions, type RenderOptions, SWARALIPI_CSS, SWARALIPI_PRINT_CSS, SWARALIPI_STAFF_CSS, type StaffOptions, type SwaralipiSheetElement, type TaalStaffOptions, type Transliterator, type ViewerLang, abcDuration, abcToSvg, buildAbc, buildTaalAbc, composeCluster, defineSwaralipiSheet, docToAbc, groupRowIntoCells, loadLyricTransliterator, localizeDigits, midiToAbc, printSwaralipi, renderMetaHeader, renderPrintDocument, renderStaffHtml, renderStaffPrintDocument, renderTaalStaffHtml, renderToHtml, resolveArcs };
463
+ export { type Arc, type Cell, type Cluster, type ComposeOptions, DEFAULT_NOTATOR, DEFAULT_WATERMARK, DIGITS, type GridLang, LABELS, MARKS, NOTATION_FONT_URLS, type PrintOptions, type RenderOptions, SWARALIPI_CSS, SWARALIPI_PRINT_CSS, SWARALIPI_STAFF_CSS, type StaffOptions, type SwaralipiSheetElement, type TaalStaffOptions, type Transliterator, type ViewerLang, abcDuration, abcToSvg, buildAbc, buildTaalAbc, cellMatra, composeCluster, defineSwaralipiSheet, docToAbc, groupRowIntoCells, loadLyricTransliterator, localizeDigits, midiToAbc, printSwaralipi, renderMetaHeader, renderPrintDocument, renderStaffHtml, renderStaffPrintDocument, renderTaalStaffHtml, renderToHtml, resolveArcs };
@@ -1,5 +1,5 @@
1
- import { O as Octave, a as SvaraLetter, N as Note, M as Meta, S as Swaralipi, T as Taal } from '../schema-D3wyNRqM.js';
2
- import { D as DrumVoice, S as Schedule, P as PlayEvent } from '../schedule-BaYIJf8y.js';
1
+ import { O as Octave, a as SvaraLetter, N as Note, M as Meta, S as Swaralipi, T as Taal } from '../schema-D_86x4oV.js';
2
+ import { D as DrumVoice, S as Schedule, P as PlayEvent } from '../schedule-8WFUBJqB.js';
3
3
  import 'zod';
4
4
  import 'tone';
5
5
  import '../taals-CjzBuZ35.js';
@@ -77,25 +77,6 @@ declare function composeCluster(letter: SvaraLetter, opts: ComposeOptions): Clus
77
77
  /** Map ASCII digits in `s` to the target script's digits. */
78
78
  declare function localizeDigits(s: string, lang: GridLang): string;
79
79
 
80
- /**
81
- * Pure spec-JSON → HTML renderer. No DOM, no side effects: usable for SSR
82
- * (Astro/static sites emit `renderToHtml(doc)` next to a
83
- * `<style>${SWARALIPI_CSS}</style>`) and unit-testable in plain Node.
84
- *
85
- * Layout model (mirrors NLTR's 4-row systems): each spec `Row` becomes one
86
- * CSS grid with **one column per top-level note** and four grid rows —
87
- * above-annotations / notation / below (slur arcs, marks) / lyric. Source
88
- * cells (groups of sub-notes sharing one matra) become column *spans*, so
89
- * per-sub-note lyric alignment and P2.3's per-note highlight targets fall
90
- * out of the column model for free.
91
- *
92
- * Rendering is spec-driven (semantics, not `raw`): grouping derives from
93
- * `durationMatra` accumulation so hand-authored documents without `raw`
94
- * render identically. `raw` is consulted only for (a) raw-only fallback
95
- * notes (never dropped — dimmed, in Swarabitan when loaded), (b) the ছেদ
96
- * cut mark and beat labels, whose grammar lives in raw (see codec.ts).
97
- */
98
-
99
80
  interface RenderOptions {
100
81
  /** Notation + UI language. Default `bn`. */
101
82
  lang?: ViewerLang;
@@ -119,7 +100,9 @@ interface Cell {
119
100
  * - bar / raw-only / carrier notes force-close and form singleton cells;
120
101
  * - full-matra notes are singleton groups;
121
102
  * - fractional notes accumulate until the sum reaches an integer matra
122
- * boundary (epsilon for 1/3, 0.2 chains), then the group closes.
103
+ * boundary (epsilon for 1/3, 0.2 chains), then the group closes;
104
+ * - `halfMatra` (the অর্ধমাত্রা ঃ) closes the cell on a half boundary — the
105
+ * only case durations alone cannot resolve (`সঃ গঃ` vs `সরা`).
123
106
  * The converter emits k notes × 1/k per source cell, so groups coincide
124
107
  * with source cells; an incomplete trailing group still closes at row end
125
108
  * (the matra carrier lands on its last note).
@@ -147,6 +130,16 @@ interface Arc {
147
130
  * A row with fill segments but no ends at all renders one straight line.
148
131
  */
149
132
  declare function resolveArcs(cells: Cell[], noteCount: number): Arc[];
133
+ /**
134
+ * The matra marks a cell's final note carries: its total duration as whole
135
+ * আ-কার matras plus an optional অর্ধমাত্রা ঃ (legend `রাঃ` = 1½, `সঃ` = ½).
136
+ * Only a `halfMatra` cell may show zero matras — every other cell keeps at
137
+ * least one, so an incomplete trailing group still gets its carrier.
138
+ */
139
+ declare function cellMatra(cell: Cell): {
140
+ matras: number;
141
+ half: boolean;
142
+ };
150
143
  /**
151
144
  * The নাম / তাল / রাগ / আবর্তন / স্বরলিপিকার header block, shared by the
152
145
  * akarmatrik grid and the staff view so both show the same metadata (localized
@@ -467,4 +460,4 @@ type Transliterator = (bengaliText: string) => string;
467
460
  */
468
461
  declare function loadLyricTransliterator(lang: ViewerLang): Promise<Transliterator | undefined>;
469
462
 
470
- export { type Arc, type Cell, type Cluster, type ComposeOptions, DEFAULT_NOTATOR, DEFAULT_WATERMARK, DIGITS, type GridLang, LABELS, MARKS, NOTATION_FONT_URLS, type PrintOptions, type RenderOptions, SWARALIPI_CSS, SWARALIPI_PRINT_CSS, SWARALIPI_STAFF_CSS, type StaffOptions, type SwaralipiSheetElement, type TaalStaffOptions, type Transliterator, type ViewerLang, abcDuration, abcToSvg, buildAbc, buildTaalAbc, composeCluster, defineSwaralipiSheet, docToAbc, groupRowIntoCells, loadLyricTransliterator, localizeDigits, midiToAbc, printSwaralipi, renderMetaHeader, renderPrintDocument, renderStaffHtml, renderStaffPrintDocument, renderTaalStaffHtml, renderToHtml, resolveArcs };
463
+ export { type Arc, type Cell, type Cluster, type ComposeOptions, DEFAULT_NOTATOR, DEFAULT_WATERMARK, DIGITS, type GridLang, LABELS, MARKS, NOTATION_FONT_URLS, type PrintOptions, type RenderOptions, SWARALIPI_CSS, SWARALIPI_PRINT_CSS, SWARALIPI_STAFF_CSS, type StaffOptions, type SwaralipiSheetElement, type TaalStaffOptions, type Transliterator, type ViewerLang, abcDuration, abcToSvg, buildAbc, buildTaalAbc, cellMatra, composeCluster, defineSwaralipiSheet, docToAbc, groupRowIntoCells, loadLyricTransliterator, localizeDigits, midiToAbc, printSwaralipi, renderMetaHeader, renderPrintDocument, renderStaffHtml, renderStaffPrintDocument, renderTaalStaffHtml, renderToHtml, resolveArcs };
@@ -1,4 +1,4 @@
1
- import { buildSchedule, drumPattern } from '../chunk-C6TRCMPL.js';
1
+ import { isArcRaw, glideTargets, buildSchedule, drumPattern } from '../chunk-GHWURC4U.js';
2
2
  import { TAALS } from '../chunk-6ETPFHZ6.js';
3
3
 
4
4
  // src/viewer/glyphs.ts
@@ -67,7 +67,6 @@ function localizeDigits(s, lang) {
67
67
  }
68
68
 
69
69
  // src/viewer/render.ts
70
- var NBSP = "\xA0";
71
70
  var EPS = 1e-6;
72
71
  var isIntegral = (x) => Math.abs(x - Math.round(x)) < EPS;
73
72
  function escapeHtml(s) {
@@ -102,7 +101,7 @@ function groupRowIntoCells(notes) {
102
101
  return;
103
102
  }
104
103
  const d = note.durationMatra ?? 1;
105
- if (isIntegral(d)) {
104
+ if (isIntegral(d) && note.halfMatra !== true) {
106
105
  close();
107
106
  cells.push({ kind: "group", start: i, notes: [note] });
108
107
  return;
@@ -110,13 +109,11 @@ function groupRowIntoCells(notes) {
110
109
  cur ??= { kind: "group", start: i, notes: [] };
111
110
  cur.notes.push(note);
112
111
  acc += d;
113
- if (acc > EPS && isIntegral(acc)) close();
112
+ if (note.halfMatra === true || acc > EPS && isIntegral(acc)) close();
114
113
  });
115
114
  close();
116
115
  return cells;
117
116
  }
118
- var ARC_RAW_RE = /^[wWxXyYjJeEzZ$=_<>`\\]+$/;
119
- var isArcRaw = (raw) => raw !== void 0 && ARC_RAW_RE.test(raw);
120
117
  function resolveArcs(cells, noteCount) {
121
118
  const arcs = [];
122
119
  let openStart;
@@ -185,15 +182,21 @@ function bracketGlyphs(note, edge) {
185
182
  function matraGlyph(ctx) {
186
183
  return ctx.lang === "bn" ? '<span class="sw-cl">a</span>' : '<span class="sw-iso">\u2013</span>';
187
184
  }
188
- function renderKan(note, ctx) {
189
- if (!note.kan) return "";
190
- const [octave, letter] = note.kan;
185
+ function renderGrace(svara, cls, ctx) {
186
+ if (svara === void 0) return "";
187
+ const [octave, letter] = svara;
191
188
  const c = composeCluster(letter, { octave, withMatra: false, lang: ctx.lang });
192
- return `<span class="${["sw-kan", ...c.classes].join(" ")}">${c.text}</span>`;
189
+ return `<span class="${[cls, ...c.classes].join(" ")}">${c.text}</span>`;
190
+ }
191
+ function cellMatra(cell) {
192
+ const total = cell.notes.reduce((a, n) => a + (n.durationMatra ?? 1), 0);
193
+ const half = cell.notes.some((n) => n.halfMatra === true);
194
+ const whole = Math.floor(total + EPS);
195
+ return { matras: half ? whole : Math.max(1, whole), half };
193
196
  }
194
197
  function renderPitchedInner(note, opts, ctx) {
195
198
  let inner = bracketGlyphs(note, "open");
196
- inner += renderKan(note, ctx);
199
+ inner += renderGrace(note.kan, "sw-kan", ctx);
197
200
  if (note.sustain === true) inner += `<span class="sw-hy">${ctx.marks.hyphen}</span>`;
198
201
  if (note.svara !== void 0) {
199
202
  const [octave, letter] = note.svara;
@@ -201,22 +204,27 @@ function renderPitchedInner(note, opts, ctx) {
201
204
  komal: note.komal,
202
205
  tivra: note.tivra,
203
206
  octave,
204
- withMatra: opts.withMatra,
207
+ // The first matra is composed into the cluster itself (bn: the font's
208
+ // trailing `a`); any further ones are separate glyphs below.
209
+ withMatra: opts.matras >= 1,
205
210
  lang: ctx.lang
206
211
  });
207
212
  inner += `<span class="${["sw-cl", ...c.classes].join(" ")}">${c.text}</span>`;
208
213
  } else if (note.rest === true) {
209
- inner += matraGlyph(ctx);
210
- } else if (note.sustain === true && opts.withMatra) {
211
- inner += matraGlyph(ctx);
214
+ inner += matraGlyph(ctx).repeat(Math.max(1, opts.matras));
215
+ } else if (note.sustain === true) {
216
+ inner += matraGlyph(ctx).repeat(opts.matras);
212
217
  }
213
218
  if (note.microtone !== void 0) {
214
219
  const mark = localizeDigits(note.microtone === "atikomal" ? "2" : "1", ctx.lang);
215
220
  inner += `<span class="sw-micro" title="${note.microtone}">${mark}</span>`;
216
221
  }
217
- const d = note.durationMatra ?? 1;
218
- if (isIntegral(d) && d > 1 + EPS) inner += matraGlyph(ctx).repeat(Math.round(d) - 1);
219
- if (opts.cuts > 0) inner += `<span class="sw-cut">${NBSP}${ctx.marks.cut}</span>`.repeat(opts.cuts);
222
+ inner += renderGrace(note.resh, "sw-resh", ctx);
223
+ if (note.svara !== void 0 && opts.matras > 1) {
224
+ inner += matraGlyph(ctx).repeat(opts.matras - 1);
225
+ }
226
+ if (opts.half) inner += `<span class="sw-half" title="${ctx.marks.cut}"></span>`;
227
+ if (opts.cuts > 0) inner += `<span class="sw-cut" title="${ctx.marks.cut}"></span>`.repeat(opts.cuts);
220
228
  inner += bracketGlyphs(note, "close");
221
229
  return inner;
222
230
  }
@@ -233,12 +241,12 @@ function renderAnnotation(notes, ctx) {
233
241
  continue;
234
242
  }
235
243
  if (cell.kind === "group") {
236
- const cuts = cutMarkCount(cell.notes[0].raw);
244
+ const { matras, half } = cellMatra(cell);
245
+ const cuts = half ? 0 : cutMarkCount(cell.notes[0].raw);
237
246
  cell.notes.forEach((note2, j) => {
238
247
  const last = j === cell.notes.length - 1;
239
- parts.push(
240
- `<span class="sw-n">${renderPitchedInner(note2, { withMatra: last, cuts: last ? cuts : 0 }, ctx)}</span>`
241
- );
248
+ const opts = last ? { matras, half, cuts } : { matras: 0, half: false, cuts: 0 };
249
+ parts.push(`<span class="sw-n">${renderPitchedInner(note2, opts, ctx)}</span>`);
242
250
  });
243
251
  continue;
244
252
  }
@@ -276,7 +284,8 @@ function renderRow(row, partIdx, rowIdx, ctx) {
276
284
  const firstCol = cell.start + 1;
277
285
  const lastCol = cell.start + cell.notes.length;
278
286
  const cellSpan = lastCol > firstCol ? `grid-column:${firstCol} / ${lastCol + 1}` : `grid-column:${firstCol}`;
279
- const cuts = cell.kind === "group" ? cutMarkCount(cell.notes[0].raw) : 0;
287
+ const { matras, half } = cellMatra(cell);
288
+ const cuts = cell.kind === "group" && !half ? cutMarkCount(cell.notes[0].raw) : 0;
280
289
  cell.notes.forEach((note, j) => {
281
290
  const col = cell.start + j + 1;
282
291
  const path = `${partIdx}.${rowIdx}.${cell.start + j}`;
@@ -287,8 +296,9 @@ function renderRow(row, partIdx, rowIdx, ctx) {
287
296
  );
288
297
  } else if (cell.kind === "group") {
289
298
  const last = j === cell.notes.length - 1;
299
+ const pOpts = last ? { matras, half, cuts } : { matras: 0, half: false, cuts: 0 };
290
300
  notation.push(
291
- `<span class="sw-n${cellEdges}" data-note="${path}" style="grid-column:${col}">${renderPitchedInner(note, { withMatra: last, cuts: last ? cuts : 0 }, ctx)}</span>`
301
+ `<span class="sw-n${cellEdges}" data-note="${path}" style="grid-column:${col}">${renderPitchedInner(note, pOpts, ctx)}</span>`
292
302
  );
293
303
  } else {
294
304
  const raw = rawDisplay(note);
@@ -379,9 +389,9 @@ ${FONT_FACES}
379
389
  }
380
390
 
381
391
  /* ---- notation font, per language (the note clusters + grace notes) ---- */
382
- .sw-sheet[lang="bn"] .sw-cl, .sw-sheet[lang="bn"] .sw-kan { font-family: Swarabitan, var(--sw-font, sans-serif); }
383
- .sw-sheet[lang="hi"] .sw-cl, .sw-sheet[lang="hi"] .sw-kan { font-family: ome_bhatkhande_hindi, var(--sw-font, sans-serif); }
384
- .sw-sheet[lang="en"] .sw-cl, .sw-sheet[lang="en"] .sw-kan { font-family: ome_bhatkhande_english, var(--sw-font, sans-serif); }
392
+ .sw-sheet[lang="bn"] :is(.sw-cl, .sw-kan, .sw-resh) { font-family: Swarabitan, var(--sw-font, sans-serif); }
393
+ .sw-sheet[lang="hi"] :is(.sw-cl, .sw-kan, .sw-resh) { font-family: ome_bhatkhande_hindi, var(--sw-font, sans-serif); }
394
+ .sw-sheet[lang="en"] :is(.sw-cl, .sw-kan, .sw-resh) { font-family: ome_bhatkhande_english, var(--sw-font, sans-serif); }
385
395
 
386
396
  /* ---- header ---- */
387
397
  .sw-header {
@@ -440,18 +450,44 @@ ${FONT_FACES}
440
450
  .sw-ati-taar::before { top: -0.3em; }
441
451
  .sw-ati-mandra::before { bottom: -0.35em; }
442
452
 
443
- /* kan grace: small raised cluster left of the main note */
444
- .sw-kan {
453
+ /* grace notes: small raised clusters \u2014 \u0995\u09A3 before the note, \u09B0\u09C7\u09B6 after it */
454
+ .sw-kan,
455
+ .sw-resh {
445
456
  position: relative;
446
457
  display: inline-block;
447
458
  font-size: 0.58em;
448
459
  vertical-align: 0.75em;
449
- margin-right: 0.08em;
450
460
  }
461
+ .sw-kan { margin-right: 0.08em; }
462
+ .sw-resh { margin-left: 0.08em; }
451
463
 
452
464
  .sw-hy { padding-right: 0.02em; }
453
465
  .sw-iso { display: inline-block; margin-left: -0.18em; }
454
- .sw-cut { font-size: 0.8em; display: inline-block; margin-left: -0.15em; }
466
+ /* 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
467
+ * than typed \u2014 an isolated \u0983 has no base, so every Bengali font shapes it with
468
+ * a dotted circle (an NBSP carrier does not help). sw-half = semantic
469
+ * (halfMatra), sw-cut = decoded from a source cell's raw: one look, two
470
+ * provenances. */
471
+ .sw-cut,
472
+ .sw-half {
473
+ position: relative;
474
+ display: inline-block;
475
+ width: 0.3em;
476
+ height: 1em;
477
+ vertical-align: baseline;
478
+ }
479
+ .sw-cut::before, .sw-cut::after,
480
+ .sw-half::before, .sw-half::after {
481
+ content: '';
482
+ position: absolute;
483
+ left: 0.08em;
484
+ width: 0.15em;
485
+ height: 0.15em;
486
+ border-radius: 50%;
487
+ background: currentColor;
488
+ }
489
+ .sw-cut::before, .sw-half::before { top: 0.2em; }
490
+ .sw-cut::after, .sw-half::after { top: 0.55em; }
455
491
  .sw-br { opacity: 0.8; padding: 0 0.06em; }
456
492
  .sw-micro { font-size: 0.6em; vertical-align: super; opacity: 0.8; }
457
493
 
@@ -608,20 +644,23 @@ function buildAbc(schedule, lyricOf, opts) {
608
644
  barPos -= beatsPerBar;
609
645
  }
610
646
  };
611
- for (const ev of schedule.events) {
647
+ schedule.events.forEach((ev, i) => {
612
648
  if (ev.midi === null) {
613
649
  if (ev.kind === "rest") {
614
650
  tokens.push(`z${abcDuration(ev.durationBeats)}`);
615
651
  words.push("*");
616
652
  advance(ev.durationBeats);
617
653
  }
618
- continue;
654
+ return;
619
655
  }
620
- tokens.push(abcNote(ev));
656
+ const more = glideTargets(schedule.events, i).length > 0;
657
+ const slurOpen = ev.glide !== true && more ? "(" : "";
658
+ const slurClose = ev.glide === true && !more ? ")" : "";
659
+ tokens.push(`${slurOpen}${abcNote(ev)}${slurClose}`);
621
660
  const lyr = lyricOf(ev);
622
661
  words.push(lyr ? abcText(lyr).replace(/\s+/g, "~") : "*");
623
662
  advance(ev.durationBeats);
624
- }
663
+ });
625
664
  if (tokens.length && tokens[tokens.length - 1] !== "|") tokens.push("|]");
626
665
  else if (tokens.length) tokens[tokens.length - 1] = "|]";
627
666
  const meter = ratioMeter(beatsPerBar);
@@ -1077,6 +1116,6 @@ function defineSwaralipiSheet(tagName = "swaralipi-sheet") {
1077
1116
  return ctor;
1078
1117
  }
1079
1118
 
1080
- export { DEFAULT_NOTATOR, DEFAULT_WATERMARK, DIGITS, LABELS, MARKS, NOTATION_FONT_URLS, SWARALIPI_CSS, SWARALIPI_PRINT_CSS, SWARALIPI_STAFF_CSS, abcDuration, abcToSvg, buildAbc, buildTaalAbc, composeCluster, defineSwaralipiSheet, docToAbc, groupRowIntoCells, loadLyricTransliterator, localizeDigits, midiToAbc, printSwaralipi, renderMetaHeader, renderPrintDocument, renderStaffHtml, renderStaffPrintDocument, renderTaalStaffHtml, renderToHtml, resolveArcs };
1119
+ export { DEFAULT_NOTATOR, DEFAULT_WATERMARK, DIGITS, LABELS, MARKS, NOTATION_FONT_URLS, SWARALIPI_CSS, SWARALIPI_PRINT_CSS, SWARALIPI_STAFF_CSS, abcDuration, abcToSvg, buildAbc, buildTaalAbc, cellMatra, composeCluster, defineSwaralipiSheet, docToAbc, groupRowIntoCells, loadLyricTransliterator, localizeDigits, midiToAbc, printSwaralipi, renderMetaHeader, renderPrintDocument, renderStaffHtml, renderStaffPrintDocument, renderTaalStaffHtml, renderToHtml, resolveArcs };
1081
1120
  //# sourceMappingURL=index.js.map
1082
1121
  //# sourceMappingURL=index.js.map