@ugfoundation/swaralipi-js 0.2.1 → 0.3.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 (37) hide show
  1. package/dist/{batch-SCSIUDB3.js → batch-QPSHVNDS.js} +4 -4
  2. package/dist/{batch-SCSIUDB3.js.map → batch-QPSHVNDS.js.map} +1 -1
  3. package/dist/{chunk-DINRYGRI.js → chunk-4YTOLN3C.js} +3 -3
  4. package/dist/{chunk-DINRYGRI.js.map → chunk-4YTOLN3C.js.map} +1 -1
  5. package/dist/chunk-V5ZSUJCA.js +354 -0
  6. package/dist/chunk-V5ZSUJCA.js.map +1 -0
  7. package/dist/{chunk-KYNCQ7OF.js → chunk-VCRWS3F3.js} +3 -3
  8. package/dist/{chunk-KYNCQ7OF.js.map → chunk-VCRWS3F3.js.map} +1 -1
  9. package/dist/cli.cjs +1 -1
  10. package/dist/cli.cjs.map +1 -1
  11. package/dist/cli.js +2 -2
  12. package/dist/convert/index.cjs +1 -1
  13. package/dist/convert/index.cjs.map +1 -1
  14. package/dist/convert/index.js +2 -2
  15. package/dist/index.cjs +1 -1
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.js +1 -1
  18. package/dist/player/index.cjs +58 -2
  19. package/dist/player/index.cjs.map +1 -1
  20. package/dist/player/index.d.cts +12 -158
  21. package/dist/player/index.d.ts +12 -158
  22. package/dist/player/index.js +31 -324
  23. package/dist/player/index.js.map +1 -1
  24. package/dist/schedule-DhO_Xzz4.d.cts +167 -0
  25. package/dist/schedule-PTHUONrA.d.ts +167 -0
  26. package/dist/spec/index.cjs +1 -1
  27. package/dist/spec/index.cjs.map +1 -1
  28. package/dist/spec/index.d.cts +1 -1
  29. package/dist/spec/index.d.ts +1 -1
  30. package/dist/spec/index.js +1 -1
  31. package/dist/viewer/index.cjs +584 -54
  32. package/dist/viewer/index.cjs.map +1 -1
  33. package/dist/viewer/index.d.cts +177 -18
  34. package/dist/viewer/index.d.ts +177 -18
  35. package/dist/viewer/index.js +322 -30
  36. package/dist/viewer/index.js.map +1 -1
  37. package/package.json +7 -2
@@ -1,5 +1,8 @@
1
- import { O as Octave, a as SvaraLetter, N as Note, S as Swaralipi } from '../schema-D3wyNRqM.cjs';
1
+ import { O as Octave, a as SvaraLetter, N as Note, M as Meta, S as Swaralipi } from '../schema-D3wyNRqM.cjs';
2
+ import { S as Schedule, P as PlayEvent, c as DrumVoice } from '../schedule-DhO_Xzz4.cjs';
2
3
  import 'zod';
4
+ import 'tone';
5
+ import '../taals-FmEulsjY.cjs';
3
6
 
4
7
  /**
5
8
  * Per-language notation encoding for the viewer.
@@ -28,9 +31,12 @@ import 'zod';
28
31
  * bn ati-octave extra dot (Swarabitan's f/h are single reph/hasanta only).
29
32
  */
30
33
 
31
- type ViewerLang = 'bn' | 'hi' | 'en';
34
+ /** Akarmatrik/Bhatkhande grid languages, rendered synchronously by renderToHtml. */
35
+ type GridLang = 'bn' | 'hi' | 'en';
36
+ /** All viewer languages. `staff` = Western staff notation (async, via abcjs). */
37
+ type ViewerLang = GridLang | 'staff';
32
38
  /** Digits 0-9 in the target script (beat labels, avartan). */
33
- declare const DIGITS: Record<ViewerLang, string>;
39
+ declare const DIGITS: Record<GridLang, string>;
34
40
  interface LangMarks {
35
41
  /** Melisma / no-new-attack dash before a sustained cluster. */
36
42
  hyphen: string;
@@ -39,9 +45,9 @@ interface LangMarks {
39
45
  /** ছেদ cut/articulation mark (two stacked rings, ঃ-shaped). */
40
46
  cut: string;
41
47
  }
42
- declare const MARKS: Record<ViewerLang, LangMarks>;
48
+ declare const MARKS: Record<GridLang, LangMarks>;
43
49
  /** Header labels (NLTR prints নাম / তাল / আবর্তন …). */
44
- declare const LABELS: Record<ViewerLang, {
50
+ declare const LABELS: Record<GridLang, {
45
51
  title: string;
46
52
  taal: string;
47
53
  raag: string;
@@ -60,7 +66,7 @@ interface ComposeOptions {
60
66
  octave: Octave;
61
67
  /** Append the আ-কার matra carrier (bn group-final notes only). */
62
68
  withMatra: boolean;
63
- lang: ViewerLang;
69
+ lang: GridLang;
64
70
  }
65
71
  /**
66
72
  * Compose one svara into its notation-font ASCII. bn = Swarabitan
@@ -69,7 +75,7 @@ interface ComposeOptions {
69
75
  */
70
76
  declare function composeCluster(letter: SvaraLetter, opts: ComposeOptions): Cluster;
71
77
  /** Map ASCII digits in `s` to the target script's digits. */
72
- declare function localizeDigits(s: string, lang: ViewerLang): string;
78
+ declare function localizeDigits(s: string, lang: GridLang): string;
73
79
 
74
80
  /**
75
81
  * Pure spec-JSON → HTML renderer. No DOM, no side effects: usable for SSR
@@ -141,6 +147,13 @@ interface Arc {
141
147
  * A row with fill segments but no ends at all renders one straight line.
142
148
  */
143
149
  declare function resolveArcs(cells: Cell[], noteCount: number): Arc[];
150
+ /**
151
+ * The নাম / তাল / রাগ / আবর্তন / স্বরলিপিকার header block, shared by the
152
+ * akarmatrik grid and the staff view so both show the same metadata (localized
153
+ * labels; values injected pre-localized by the consumer). Exported so the
154
+ * staff renderer can reuse the exact markup + styling.
155
+ */
156
+ declare function renderMetaHeader(meta: Meta, lang: GridLang, tr?: (s: string) => string): string;
144
157
  /** Render a complete swaralipi document to an HTML string. Pair with
145
158
  * SWARALIPI_CSS (shadow DOM does this automatically; SSR consumers emit a
146
159
  * `<style>` themselves). All source text is HTML-escaped. */
@@ -182,7 +195,130 @@ declare const SWARALIPI_CSS: string;
182
195
  * - rows are un-clipped (no scrollbar on paper) and kept whole across page
183
196
  * breaks; part headings stay with their first row.
184
197
  */
185
- declare const SWARALIPI_PRINT_CSS = "\n@page { margin: 16mm; }\n@media print {\n .sw-sheet { -webkit-print-color-adjust: exact; print-color-adjust: exact; color: #000; }\n .sw-row { overflow: visible !important; break-inside: avoid; page-break-inside: avoid; }\n .sw-part { break-inside: auto; }\n .sw-part-name, .sw-header { break-after: avoid-page; page-break-after: avoid; }\n .sw-hl { background: none !important; }\n}\n";
198
+ declare const SWARALIPI_PRINT_CSS = "\n@page { margin: 16mm; }\n@media print {\n .sw-sheet, .sw-staff { -webkit-print-color-adjust: exact; print-color-adjust: exact; color: #000; }\n .sw-row { overflow: visible !important; break-inside: avoid; page-break-inside: avoid; }\n .sw-part { break-inside: auto; }\n .sw-part-name, .sw-header { break-after: avoid-page; page-break-after: avoid; }\n .sw-hl { background: none !important; }\n /* Keep each engraved staff system whole across a page break (the abcjs line\n is a <div> wrapping the <svg>, so the break rule must sit on the div). */\n .sw-staff-lines > div { break-inside: avoid; page-break-inside: avoid; }\n .sw-staff-meta { break-after: avoid-page; page-break-after: avoid; }\n}\n";
199
+ /** Staff-notation (abcjs) wrapper chrome: instrument/tempo header + a fluid
200
+ * SVG. The engraving itself is drawn by abcjs; this only frames it. */
201
+ declare const SWARALIPI_STAFF_CSS = "\n.sw-staff { color: currentColor; }\n.sw-staff-meta { display: flex; gap: 1.25em; align-items: baseline; margin: 0 0 .5em; font-size: .95em; opacity: .85; }\n.sw-staff-instrument { font-weight: 600; }\n.sw-staff-drums { opacity: .75; }\n.sw-staff-tempo { font-variant-numeric: tabular-nums; }\n/* abcjs (oneSvgPerLine) wraps the music into one <div><svg> per system; stack\n them as short, swaralipi-style lines. abcjs sets a fixed pixel height +\n overflow:hidden on each wrapper div \u2014 neutralise it so the viewBox'd SVG can\n scale to the container (and to the print page) instead of being clipped. */\n.sw-staff-lines > div { margin: 0 0 .35em; height: auto !important; overflow: visible !important; }\n/* Fill up to the engraved (staffwidth) size \u2014 shrink to fit a narrow container,\n but never stretch past natural width (abcToSvg pins per-SVG max-width in px),\n else a short last system balloons. */\n.sw-staff-lines svg { display: block; width: 100%; height: auto; }\n.sw-staff-missing { padding: 1em; border: 1px dashed currentColor; border-radius: 6px; opacity: .8; font-size: .9em; }\n.sw-staff-title { font-size: 1.15em; font-weight: 700; }\n.sw-staff-subtitle { opacity: .8; font-size: .95em; margin-top: .15em; }\n";
202
+
203
+ /**
204
+ * Staff (Western) notation — a 4th "language" for the viewer.
205
+ *
206
+ * Staff notation IS the melody voice drawn as a Western score, so it reuses
207
+ * the player's pure `buildSchedule` (the same (midi, beat, durationBeats, kan)
208
+ * the MIDI export writes) and turns it into an **ABC notation** string, which
209
+ * `abcjs` (MIT, zero-dep) engraves to SVG. abcjs is loaded through a *dynamic*
210
+ * `import('abcjs')` — an optional peer dep, exactly like tone/lamejs — so the
211
+ * viewer bundle keeps its bare-load guarantee and only consumers who want staff
212
+ * notation install it. Without it, `renderStaffHtml` returns a graceful notice.
213
+ *
214
+ * Scope (matches the player/MIDI): a single treble-clef melody line. The chosen
215
+ * instrument is a header label, not a clef change (the line is monophonic and
216
+ * treble covers piano + violin range with ledger lines). Microtones render as
217
+ * their komal approximation — the same documented limitation as playback, since
218
+ * abcjs has no sub-komal glyph.
219
+ */
220
+
221
+ interface StaffOptions {
222
+ /** Tonic (Sa). Accepts `C`, `C#`, `cs`… Default: `meta.tonic`, else `C`. */
223
+ tonic?: string;
224
+ /** Octave of madhyam Sa (C4 = MIDI 60). Default 4. */
225
+ baseOctave?: number;
226
+ /** Tempo written into the score (Q:) and shown as a header. Default `meta.bpm`, else 90. */
227
+ bpm?: number;
228
+ /** Instrument name shown above the staff (label only — clef stays treble). */
229
+ instrument?: string;
230
+ /** Show the নাম/তাল/আবর্তন/স্বরলিপিকার header block. Default true. */
231
+ showHeader?: boolean;
232
+ /** Language for the header **labels** (values are injected pre-localized by
233
+ * the consumer). Default `en` — staff is the Western view. */
234
+ headerLang?: GridLang;
235
+ /** Transliterate header values (title/taal/notator) — same fn as the grid. */
236
+ transliterate?: (bengaliText: string) => string;
237
+ /** Include the `w:` lyric line under each staff. Default true. */
238
+ lyrics?: boolean;
239
+ /** Matra per bar (one তাল cycle). Default `meta.taal.numBeats`, else 4. */
240
+ beatsPerBar?: number;
241
+ /** Target line width in px passed to abcjs (music wraps within it). Default 680. */
242
+ staffwidth?: number;
243
+ /** Preferred bars per wrapped line (swaralipi-short systems). Default 4. */
244
+ measuresPerLine?: number;
245
+ /** abcjs `scale`. Default 1. */
246
+ scale?: number;
247
+ /** Add a second percussion staff below the melody, sequencing a Western
248
+ * drumset (kick/snare/hi-hat) to the taal's sam/tali/khaali structure —
249
+ * the same pattern `SwaralipiPlayer`'s `rhythm: 'drums'` plays, engraved via
250
+ * abcjs `%%percmap`. Default false (the score stays a single staff). Taal:
251
+ * `meta.taal`, else a plain 4-beat cycle (matches `beatsPerBar`'s fallback). */
252
+ drums?: boolean;
253
+ }
254
+ /**
255
+ * MIDI number → ABC pitch token. ABC's octave anchor is fixed by the standard:
256
+ * middle C (MIDI 60, C4) is uppercase `C`; lowercase `c` = C5; a comma drops an
257
+ * octave (`C,` = C3), an apostrophe raises one (`c'` = C6). Sharps carry a `^`.
258
+ */
259
+ declare function midiToAbc(midi: number): string;
260
+ /**
261
+ * ABC duration suffix for a beat length, where the unit note (`L:`) is one
262
+ * matra = a quarter note. Whole numbers append the multiplier (`2`, `3`);
263
+ * simple fractions use `/n` (`/2` = eighth); anything else falls back to the
264
+ * nearest 1/4-matra count of `/4` units so odd durations still sum correctly.
265
+ */
266
+ declare function abcDuration(beats: number): string;
267
+ /**
268
+ * Build a complete ABC tune string from a schedule. Pure and dependency-free
269
+ * (the testable seam). Rests advance time as `z`; melody notes carry duration;
270
+ * lyrics become a `w:` line aligned to the notes.
271
+ *
272
+ * Bar lines are inserted every `beatsPerBar` matra (= one তাল cycle, so an ABC
273
+ * measure is one আবর্তন). This is what lets abcjs **wrap into short systems** —
274
+ * it only breaks lines at bar lines. `M:` is set to match so the notes-per-bar
275
+ * check-out. The tempo (`Q:`) rides the header.
276
+ *
277
+ * `drums`, if given, is the taal's per-beat `drumPattern()` — a second
278
+ * `V:2 clef=perc` percussion staff is added below the melody, cycling that
279
+ * pattern one quarter-note per matra with `%%percmap` GM drum sounds (see
280
+ * `buildDrumVoice`). Both voices share the same bar grid, so they wrap and
281
+ * align together.
282
+ */
283
+ declare function buildAbc(schedule: Schedule, lyricOf: (ev: PlayEvent) => string | undefined, opts: {
284
+ bpm: number;
285
+ lyrics: boolean;
286
+ beatsPerBar: number;
287
+ drums?: DrumVoice[];
288
+ }): string;
289
+ /** Build the ABC tune string for a whole document (schedule + lyrics + tempo).
290
+ * The measure length is one তাল cycle (`meta.taal.numBeats`), falling back to 4.
291
+ * `opts.drums` adds the percussion staff (see `buildAbc`). */
292
+ declare function docToAbc(doc: Swaralipi, opts?: StaffOptions): string;
293
+ /**
294
+ * Render an ABC tune string to engraved HTML using abcjs. Requires a DOM
295
+ * (`document`) — abcjs measures glyphs and draws into an element; call it in a
296
+ * browser, in a print iframe, or under jsdom (tests stub `getBBox`). abcjs is
297
+ * dynamically imported so it is never bundled or loaded unless staff notation
298
+ * is actually used.
299
+ *
300
+ * The music is **wrapped into short systems** (a few bars per line, each its
301
+ * own SVG-in-DIV) — matching how আকারমাত্রিক swaralipi is laid out in stacked
302
+ * rows rather than one long staff. Returns the full multi-line markup (all
303
+ * line DIVs), or a notice string if abcjs is not installed.
304
+ *
305
+ * `notePaths`, if given, is the `part.row.note` highlight path for each ABC
306
+ * melody note/rest token in schedule order (see `eventPaths`). The melody is
307
+ * voice 0; abcjs emits its `.abcjs-v0.abcjs-note`/`.abcjs-rest` elements in
308
+ * document order = that same order, so painting `notePaths[i]` onto the i-th
309
+ * melody element gives every engraved note the same `data-note` attribute the
310
+ * grid languages use — playback highlighting works identically for staff. (We
311
+ * key off voice + document order, NOT `data-index`: with a drum voice abcjs
312
+ * numbers each line/voice block separately, so data-index isn't global order.)
313
+ */
314
+ declare function abcToSvg(abc: string, opts?: StaffOptions, notePaths?: string[]): Promise<string>;
315
+ /**
316
+ * Full doc → staff-notation HTML fragment: the shared নাম/তাল/আবর্তন/স্বরলিপিকার
317
+ * header (same block as the akarmatrik grid, so the staff shows the identical
318
+ * metadata) + an instrument label + the wrapped Western systems. Styled by
319
+ * SWARALIPI_CSS + SWARALIPI_STAFF_CSS. Async because abcjs is dynamic-imported.
320
+ */
321
+ declare function renderStaffHtml(doc: Swaralipi, opts?: StaffOptions): Promise<string>;
186
322
 
187
323
  /**
188
324
  * Print / "Save as PDF" support.
@@ -192,16 +328,24 @@ declare const SWARALIPI_PRINT_CSS = "\n@page { margin: 16mm; }\n@media print {\n
192
328
  * matra) and the CSS-drawn marks (octave dots, slur arcs, danda strokes) that
193
329
  * the viewer renders on screen. Rasterizers (html2canvas) and PDF-primitive
194
330
  * libraries (jsPDF/pdfmake) have no Indic shaping and drop pseudo-element
195
- * marks, so we deliberately avoid them.
331
+ * marks, so we deliberately avoid them. Staff notation (lang `staff`) is drawn
332
+ * by abcjs to SVG, which the same print engine engraves faithfully.
196
333
  *
197
334
  * `renderPrintDocument` builds a self-contained HTML page from the pure
198
335
  * `renderToHtml` + `SWARALIPI_CSS` (so it sidesteps the shadow-DOM "print drops
199
336
  * encapsulated styles" gotcha); `printSwaralipi` prints it via a hidden iframe.
200
337
  * Both reuse the same renderer as the viewer, so the PDF matches the screen.
338
+ *
339
+ * Every printed page also carries a light diagonal watermark and a notator
340
+ * credit footer — both defaulted for gitabitan.io but overridable by consumers.
201
341
  */
202
342
 
203
- interface PrintOptions {
204
- /** Notation + UI language. Default `bn`. */
343
+ /** Default diagonal page watermark (empty string disables it). */
344
+ declare const DEFAULT_WATERMARK = "www.gitabitan.io";
345
+ /** Default notator credit printed in the footer. */
346
+ declare const DEFAULT_NOTATOR = "gitabitan.io Team";
347
+ interface PrintOptions extends StaffOptions {
348
+ /** Notation + UI language. `staff` = Western staff notation. Default `bn`. */
205
349
  lang?: ViewerLang;
206
350
  /** Render the নাম/তাল header block. Default true. */
207
351
  showHeader?: boolean;
@@ -210,20 +354,33 @@ interface PrintOptions {
210
354
  /** Document `<title>` — the browser's default "Save as PDF" filename.
211
355
  * Defaults to the document's `meta.title`. */
212
356
  title?: string;
357
+ /** Diagonal full-page watermark. Default `www.gitabitan.io`; '' to disable. */
358
+ watermark?: string;
359
+ /** Notator credited in the footer. Default `gitabitan.io Team`; '' to disable.
360
+ * A document's `meta.notationBy` takes precedence when present. */
361
+ notator?: string;
213
362
  }
214
363
  /**
215
- * Build a complete, standalone HTML document for printing a swaralipi sheet:
216
- * the rendered notation plus `SWARALIPI_CSS` and the print stylesheet, on a
217
- * white page. Usable for a hidden-iframe print, a new browser tab, or
218
- * server-side headless-Chrome PDF generation.
364
+ * Build a complete, standalone HTML document for printing an akarmatrik
365
+ * (bn/hi/en) swaralipi sheet, with watermark + notator footer. Usable for a
366
+ * hidden-iframe print, a new browser tab, or server-side headless-Chrome PDF.
367
+ *
368
+ * For `lang: 'staff'` this renders the grid instead (staff engraving is async
369
+ * — use `renderStaffPrintDocument` or `printSwaralipi`, which handle it).
219
370
  */
220
371
  declare function renderPrintDocument(doc: Swaralipi, opts?: PrintOptions): string;
372
+ /**
373
+ * Async variant that engraves staff notation (abcjs) into the print page. Falls
374
+ * back to the grid document for non-staff langs. Returns a self-contained HTML
375
+ * string (for headless-Chrome PDF or a print iframe).
376
+ */
377
+ declare function renderStaffPrintDocument(doc: Swaralipi, opts?: PrintOptions): Promise<string>;
221
378
  /**
222
379
  * Open the browser print dialog for a swaralipi document (the user picks a
223
380
  * printer or "Save as PDF"). Renders into an off-screen iframe so it prints
224
381
  * only the sheet — independent of the host page's chrome and CSS — then cleans
225
- * up. Browser-only; throws in a non-DOM environment (use renderPrintDocument
226
- * for SSR / headless-Chrome instead).
382
+ * up. Browser-only; throws in a non-DOM environment (use renderPrintDocument /
383
+ * renderStaffPrintDocument for SSR / headless-Chrome instead).
227
384
  */
228
385
  declare function printSwaralipi(doc: Swaralipi, opts?: PrintOptions): void;
229
386
 
@@ -252,6 +409,8 @@ interface SwaralipiSheetElement extends HTMLElement {
252
409
  clearHighlights(): void;
253
410
  /** Open the print dialog (→ "Save as PDF") for the current document. */
254
411
  print(options?: PrintOptions): void;
412
+ /** Options for the `staff` language (instrument label, tonic, bpm…). */
413
+ staffOptions: StaffOptions;
255
414
  }
256
415
  /**
257
416
  * Register `<swaralipi-sheet>` (or a custom tag name). Idempotent. Throws
@@ -276,4 +435,4 @@ type Transliterator = (bengaliText: string) => string;
276
435
  */
277
436
  declare function loadLyricTransliterator(lang: ViewerLang): Promise<Transliterator | undefined>;
278
437
 
279
- export { type Arc, type Cell, type Cluster, type ComposeOptions, DIGITS, LABELS, MARKS, NOTATION_FONT_URLS, type PrintOptions, type RenderOptions, SWARALIPI_CSS, SWARALIPI_PRINT_CSS, type SwaralipiSheetElement, type Transliterator, type ViewerLang, composeCluster, defineSwaralipiSheet, groupRowIntoCells, loadLyricTransliterator, localizeDigits, printSwaralipi, renderPrintDocument, renderToHtml, resolveArcs };
438
+ 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 Transliterator, type ViewerLang, abcDuration, abcToSvg, buildAbc, composeCluster, defineSwaralipiSheet, docToAbc, groupRowIntoCells, loadLyricTransliterator, localizeDigits, midiToAbc, printSwaralipi, renderMetaHeader, renderPrintDocument, renderStaffHtml, renderStaffPrintDocument, renderToHtml, resolveArcs };
@@ -1,5 +1,8 @@
1
- import { O as Octave, a as SvaraLetter, N as Note, S as Swaralipi } from '../schema-D3wyNRqM.js';
1
+ import { O as Octave, a as SvaraLetter, N as Note, M as Meta, S as Swaralipi } from '../schema-D3wyNRqM.js';
2
+ import { S as Schedule, P as PlayEvent, c as DrumVoice } from '../schedule-PTHUONrA.js';
2
3
  import 'zod';
4
+ import 'tone';
5
+ import '../taals-FmEulsjY.js';
3
6
 
4
7
  /**
5
8
  * Per-language notation encoding for the viewer.
@@ -28,9 +31,12 @@ import 'zod';
28
31
  * bn ati-octave extra dot (Swarabitan's f/h are single reph/hasanta only).
29
32
  */
30
33
 
31
- type ViewerLang = 'bn' | 'hi' | 'en';
34
+ /** Akarmatrik/Bhatkhande grid languages, rendered synchronously by renderToHtml. */
35
+ type GridLang = 'bn' | 'hi' | 'en';
36
+ /** All viewer languages. `staff` = Western staff notation (async, via abcjs). */
37
+ type ViewerLang = GridLang | 'staff';
32
38
  /** Digits 0-9 in the target script (beat labels, avartan). */
33
- declare const DIGITS: Record<ViewerLang, string>;
39
+ declare const DIGITS: Record<GridLang, string>;
34
40
  interface LangMarks {
35
41
  /** Melisma / no-new-attack dash before a sustained cluster. */
36
42
  hyphen: string;
@@ -39,9 +45,9 @@ interface LangMarks {
39
45
  /** ছেদ cut/articulation mark (two stacked rings, ঃ-shaped). */
40
46
  cut: string;
41
47
  }
42
- declare const MARKS: Record<ViewerLang, LangMarks>;
48
+ declare const MARKS: Record<GridLang, LangMarks>;
43
49
  /** Header labels (NLTR prints নাম / তাল / আবর্তন …). */
44
- declare const LABELS: Record<ViewerLang, {
50
+ declare const LABELS: Record<GridLang, {
45
51
  title: string;
46
52
  taal: string;
47
53
  raag: string;
@@ -60,7 +66,7 @@ interface ComposeOptions {
60
66
  octave: Octave;
61
67
  /** Append the আ-কার matra carrier (bn group-final notes only). */
62
68
  withMatra: boolean;
63
- lang: ViewerLang;
69
+ lang: GridLang;
64
70
  }
65
71
  /**
66
72
  * Compose one svara into its notation-font ASCII. bn = Swarabitan
@@ -69,7 +75,7 @@ interface ComposeOptions {
69
75
  */
70
76
  declare function composeCluster(letter: SvaraLetter, opts: ComposeOptions): Cluster;
71
77
  /** Map ASCII digits in `s` to the target script's digits. */
72
- declare function localizeDigits(s: string, lang: ViewerLang): string;
78
+ declare function localizeDigits(s: string, lang: GridLang): string;
73
79
 
74
80
  /**
75
81
  * Pure spec-JSON → HTML renderer. No DOM, no side effects: usable for SSR
@@ -141,6 +147,13 @@ interface Arc {
141
147
  * A row with fill segments but no ends at all renders one straight line.
142
148
  */
143
149
  declare function resolveArcs(cells: Cell[], noteCount: number): Arc[];
150
+ /**
151
+ * The নাম / তাল / রাগ / আবর্তন / স্বরলিপিকার header block, shared by the
152
+ * akarmatrik grid and the staff view so both show the same metadata (localized
153
+ * labels; values injected pre-localized by the consumer). Exported so the
154
+ * staff renderer can reuse the exact markup + styling.
155
+ */
156
+ declare function renderMetaHeader(meta: Meta, lang: GridLang, tr?: (s: string) => string): string;
144
157
  /** Render a complete swaralipi document to an HTML string. Pair with
145
158
  * SWARALIPI_CSS (shadow DOM does this automatically; SSR consumers emit a
146
159
  * `<style>` themselves). All source text is HTML-escaped. */
@@ -182,7 +195,130 @@ declare const SWARALIPI_CSS: string;
182
195
  * - rows are un-clipped (no scrollbar on paper) and kept whole across page
183
196
  * breaks; part headings stay with their first row.
184
197
  */
185
- declare const SWARALIPI_PRINT_CSS = "\n@page { margin: 16mm; }\n@media print {\n .sw-sheet { -webkit-print-color-adjust: exact; print-color-adjust: exact; color: #000; }\n .sw-row { overflow: visible !important; break-inside: avoid; page-break-inside: avoid; }\n .sw-part { break-inside: auto; }\n .sw-part-name, .sw-header { break-after: avoid-page; page-break-after: avoid; }\n .sw-hl { background: none !important; }\n}\n";
198
+ declare const SWARALIPI_PRINT_CSS = "\n@page { margin: 16mm; }\n@media print {\n .sw-sheet, .sw-staff { -webkit-print-color-adjust: exact; print-color-adjust: exact; color: #000; }\n .sw-row { overflow: visible !important; break-inside: avoid; page-break-inside: avoid; }\n .sw-part { break-inside: auto; }\n .sw-part-name, .sw-header { break-after: avoid-page; page-break-after: avoid; }\n .sw-hl { background: none !important; }\n /* Keep each engraved staff system whole across a page break (the abcjs line\n is a <div> wrapping the <svg>, so the break rule must sit on the div). */\n .sw-staff-lines > div { break-inside: avoid; page-break-inside: avoid; }\n .sw-staff-meta { break-after: avoid-page; page-break-after: avoid; }\n}\n";
199
+ /** Staff-notation (abcjs) wrapper chrome: instrument/tempo header + a fluid
200
+ * SVG. The engraving itself is drawn by abcjs; this only frames it. */
201
+ declare const SWARALIPI_STAFF_CSS = "\n.sw-staff { color: currentColor; }\n.sw-staff-meta { display: flex; gap: 1.25em; align-items: baseline; margin: 0 0 .5em; font-size: .95em; opacity: .85; }\n.sw-staff-instrument { font-weight: 600; }\n.sw-staff-drums { opacity: .75; }\n.sw-staff-tempo { font-variant-numeric: tabular-nums; }\n/* abcjs (oneSvgPerLine) wraps the music into one <div><svg> per system; stack\n them as short, swaralipi-style lines. abcjs sets a fixed pixel height +\n overflow:hidden on each wrapper div \u2014 neutralise it so the viewBox'd SVG can\n scale to the container (and to the print page) instead of being clipped. */\n.sw-staff-lines > div { margin: 0 0 .35em; height: auto !important; overflow: visible !important; }\n/* Fill up to the engraved (staffwidth) size \u2014 shrink to fit a narrow container,\n but never stretch past natural width (abcToSvg pins per-SVG max-width in px),\n else a short last system balloons. */\n.sw-staff-lines svg { display: block; width: 100%; height: auto; }\n.sw-staff-missing { padding: 1em; border: 1px dashed currentColor; border-radius: 6px; opacity: .8; font-size: .9em; }\n.sw-staff-title { font-size: 1.15em; font-weight: 700; }\n.sw-staff-subtitle { opacity: .8; font-size: .95em; margin-top: .15em; }\n";
202
+
203
+ /**
204
+ * Staff (Western) notation — a 4th "language" for the viewer.
205
+ *
206
+ * Staff notation IS the melody voice drawn as a Western score, so it reuses
207
+ * the player's pure `buildSchedule` (the same (midi, beat, durationBeats, kan)
208
+ * the MIDI export writes) and turns it into an **ABC notation** string, which
209
+ * `abcjs` (MIT, zero-dep) engraves to SVG. abcjs is loaded through a *dynamic*
210
+ * `import('abcjs')` — an optional peer dep, exactly like tone/lamejs — so the
211
+ * viewer bundle keeps its bare-load guarantee and only consumers who want staff
212
+ * notation install it. Without it, `renderStaffHtml` returns a graceful notice.
213
+ *
214
+ * Scope (matches the player/MIDI): a single treble-clef melody line. The chosen
215
+ * instrument is a header label, not a clef change (the line is monophonic and
216
+ * treble covers piano + violin range with ledger lines). Microtones render as
217
+ * their komal approximation — the same documented limitation as playback, since
218
+ * abcjs has no sub-komal glyph.
219
+ */
220
+
221
+ interface StaffOptions {
222
+ /** Tonic (Sa). Accepts `C`, `C#`, `cs`… Default: `meta.tonic`, else `C`. */
223
+ tonic?: string;
224
+ /** Octave of madhyam Sa (C4 = MIDI 60). Default 4. */
225
+ baseOctave?: number;
226
+ /** Tempo written into the score (Q:) and shown as a header. Default `meta.bpm`, else 90. */
227
+ bpm?: number;
228
+ /** Instrument name shown above the staff (label only — clef stays treble). */
229
+ instrument?: string;
230
+ /** Show the নাম/তাল/আবর্তন/স্বরলিপিকার header block. Default true. */
231
+ showHeader?: boolean;
232
+ /** Language for the header **labels** (values are injected pre-localized by
233
+ * the consumer). Default `en` — staff is the Western view. */
234
+ headerLang?: GridLang;
235
+ /** Transliterate header values (title/taal/notator) — same fn as the grid. */
236
+ transliterate?: (bengaliText: string) => string;
237
+ /** Include the `w:` lyric line under each staff. Default true. */
238
+ lyrics?: boolean;
239
+ /** Matra per bar (one তাল cycle). Default `meta.taal.numBeats`, else 4. */
240
+ beatsPerBar?: number;
241
+ /** Target line width in px passed to abcjs (music wraps within it). Default 680. */
242
+ staffwidth?: number;
243
+ /** Preferred bars per wrapped line (swaralipi-short systems). Default 4. */
244
+ measuresPerLine?: number;
245
+ /** abcjs `scale`. Default 1. */
246
+ scale?: number;
247
+ /** Add a second percussion staff below the melody, sequencing a Western
248
+ * drumset (kick/snare/hi-hat) to the taal's sam/tali/khaali structure —
249
+ * the same pattern `SwaralipiPlayer`'s `rhythm: 'drums'` plays, engraved via
250
+ * abcjs `%%percmap`. Default false (the score stays a single staff). Taal:
251
+ * `meta.taal`, else a plain 4-beat cycle (matches `beatsPerBar`'s fallback). */
252
+ drums?: boolean;
253
+ }
254
+ /**
255
+ * MIDI number → ABC pitch token. ABC's octave anchor is fixed by the standard:
256
+ * middle C (MIDI 60, C4) is uppercase `C`; lowercase `c` = C5; a comma drops an
257
+ * octave (`C,` = C3), an apostrophe raises one (`c'` = C6). Sharps carry a `^`.
258
+ */
259
+ declare function midiToAbc(midi: number): string;
260
+ /**
261
+ * ABC duration suffix for a beat length, where the unit note (`L:`) is one
262
+ * matra = a quarter note. Whole numbers append the multiplier (`2`, `3`);
263
+ * simple fractions use `/n` (`/2` = eighth); anything else falls back to the
264
+ * nearest 1/4-matra count of `/4` units so odd durations still sum correctly.
265
+ */
266
+ declare function abcDuration(beats: number): string;
267
+ /**
268
+ * Build a complete ABC tune string from a schedule. Pure and dependency-free
269
+ * (the testable seam). Rests advance time as `z`; melody notes carry duration;
270
+ * lyrics become a `w:` line aligned to the notes.
271
+ *
272
+ * Bar lines are inserted every `beatsPerBar` matra (= one তাল cycle, so an ABC
273
+ * measure is one আবর্তন). This is what lets abcjs **wrap into short systems** —
274
+ * it only breaks lines at bar lines. `M:` is set to match so the notes-per-bar
275
+ * check-out. The tempo (`Q:`) rides the header.
276
+ *
277
+ * `drums`, if given, is the taal's per-beat `drumPattern()` — a second
278
+ * `V:2 clef=perc` percussion staff is added below the melody, cycling that
279
+ * pattern one quarter-note per matra with `%%percmap` GM drum sounds (see
280
+ * `buildDrumVoice`). Both voices share the same bar grid, so they wrap and
281
+ * align together.
282
+ */
283
+ declare function buildAbc(schedule: Schedule, lyricOf: (ev: PlayEvent) => string | undefined, opts: {
284
+ bpm: number;
285
+ lyrics: boolean;
286
+ beatsPerBar: number;
287
+ drums?: DrumVoice[];
288
+ }): string;
289
+ /** Build the ABC tune string for a whole document (schedule + lyrics + tempo).
290
+ * The measure length is one তাল cycle (`meta.taal.numBeats`), falling back to 4.
291
+ * `opts.drums` adds the percussion staff (see `buildAbc`). */
292
+ declare function docToAbc(doc: Swaralipi, opts?: StaffOptions): string;
293
+ /**
294
+ * Render an ABC tune string to engraved HTML using abcjs. Requires a DOM
295
+ * (`document`) — abcjs measures glyphs and draws into an element; call it in a
296
+ * browser, in a print iframe, or under jsdom (tests stub `getBBox`). abcjs is
297
+ * dynamically imported so it is never bundled or loaded unless staff notation
298
+ * is actually used.
299
+ *
300
+ * The music is **wrapped into short systems** (a few bars per line, each its
301
+ * own SVG-in-DIV) — matching how আকারমাত্রিক swaralipi is laid out in stacked
302
+ * rows rather than one long staff. Returns the full multi-line markup (all
303
+ * line DIVs), or a notice string if abcjs is not installed.
304
+ *
305
+ * `notePaths`, if given, is the `part.row.note` highlight path for each ABC
306
+ * melody note/rest token in schedule order (see `eventPaths`). The melody is
307
+ * voice 0; abcjs emits its `.abcjs-v0.abcjs-note`/`.abcjs-rest` elements in
308
+ * document order = that same order, so painting `notePaths[i]` onto the i-th
309
+ * melody element gives every engraved note the same `data-note` attribute the
310
+ * grid languages use — playback highlighting works identically for staff. (We
311
+ * key off voice + document order, NOT `data-index`: with a drum voice abcjs
312
+ * numbers each line/voice block separately, so data-index isn't global order.)
313
+ */
314
+ declare function abcToSvg(abc: string, opts?: StaffOptions, notePaths?: string[]): Promise<string>;
315
+ /**
316
+ * Full doc → staff-notation HTML fragment: the shared নাম/তাল/আবর্তন/স্বরলিপিকার
317
+ * header (same block as the akarmatrik grid, so the staff shows the identical
318
+ * metadata) + an instrument label + the wrapped Western systems. Styled by
319
+ * SWARALIPI_CSS + SWARALIPI_STAFF_CSS. Async because abcjs is dynamic-imported.
320
+ */
321
+ declare function renderStaffHtml(doc: Swaralipi, opts?: StaffOptions): Promise<string>;
186
322
 
187
323
  /**
188
324
  * Print / "Save as PDF" support.
@@ -192,16 +328,24 @@ declare const SWARALIPI_PRINT_CSS = "\n@page { margin: 16mm; }\n@media print {\n
192
328
  * matra) and the CSS-drawn marks (octave dots, slur arcs, danda strokes) that
193
329
  * the viewer renders on screen. Rasterizers (html2canvas) and PDF-primitive
194
330
  * libraries (jsPDF/pdfmake) have no Indic shaping and drop pseudo-element
195
- * marks, so we deliberately avoid them.
331
+ * marks, so we deliberately avoid them. Staff notation (lang `staff`) is drawn
332
+ * by abcjs to SVG, which the same print engine engraves faithfully.
196
333
  *
197
334
  * `renderPrintDocument` builds a self-contained HTML page from the pure
198
335
  * `renderToHtml` + `SWARALIPI_CSS` (so it sidesteps the shadow-DOM "print drops
199
336
  * encapsulated styles" gotcha); `printSwaralipi` prints it via a hidden iframe.
200
337
  * Both reuse the same renderer as the viewer, so the PDF matches the screen.
338
+ *
339
+ * Every printed page also carries a light diagonal watermark and a notator
340
+ * credit footer — both defaulted for gitabitan.io but overridable by consumers.
201
341
  */
202
342
 
203
- interface PrintOptions {
204
- /** Notation + UI language. Default `bn`. */
343
+ /** Default diagonal page watermark (empty string disables it). */
344
+ declare const DEFAULT_WATERMARK = "www.gitabitan.io";
345
+ /** Default notator credit printed in the footer. */
346
+ declare const DEFAULT_NOTATOR = "gitabitan.io Team";
347
+ interface PrintOptions extends StaffOptions {
348
+ /** Notation + UI language. `staff` = Western staff notation. Default `bn`. */
205
349
  lang?: ViewerLang;
206
350
  /** Render the নাম/তাল header block. Default true. */
207
351
  showHeader?: boolean;
@@ -210,20 +354,33 @@ interface PrintOptions {
210
354
  /** Document `<title>` — the browser's default "Save as PDF" filename.
211
355
  * Defaults to the document's `meta.title`. */
212
356
  title?: string;
357
+ /** Diagonal full-page watermark. Default `www.gitabitan.io`; '' to disable. */
358
+ watermark?: string;
359
+ /** Notator credited in the footer. Default `gitabitan.io Team`; '' to disable.
360
+ * A document's `meta.notationBy` takes precedence when present. */
361
+ notator?: string;
213
362
  }
214
363
  /**
215
- * Build a complete, standalone HTML document for printing a swaralipi sheet:
216
- * the rendered notation plus `SWARALIPI_CSS` and the print stylesheet, on a
217
- * white page. Usable for a hidden-iframe print, a new browser tab, or
218
- * server-side headless-Chrome PDF generation.
364
+ * Build a complete, standalone HTML document for printing an akarmatrik
365
+ * (bn/hi/en) swaralipi sheet, with watermark + notator footer. Usable for a
366
+ * hidden-iframe print, a new browser tab, or server-side headless-Chrome PDF.
367
+ *
368
+ * For `lang: 'staff'` this renders the grid instead (staff engraving is async
369
+ * — use `renderStaffPrintDocument` or `printSwaralipi`, which handle it).
219
370
  */
220
371
  declare function renderPrintDocument(doc: Swaralipi, opts?: PrintOptions): string;
372
+ /**
373
+ * Async variant that engraves staff notation (abcjs) into the print page. Falls
374
+ * back to the grid document for non-staff langs. Returns a self-contained HTML
375
+ * string (for headless-Chrome PDF or a print iframe).
376
+ */
377
+ declare function renderStaffPrintDocument(doc: Swaralipi, opts?: PrintOptions): Promise<string>;
221
378
  /**
222
379
  * Open the browser print dialog for a swaralipi document (the user picks a
223
380
  * printer or "Save as PDF"). Renders into an off-screen iframe so it prints
224
381
  * only the sheet — independent of the host page's chrome and CSS — then cleans
225
- * up. Browser-only; throws in a non-DOM environment (use renderPrintDocument
226
- * for SSR / headless-Chrome instead).
382
+ * up. Browser-only; throws in a non-DOM environment (use renderPrintDocument /
383
+ * renderStaffPrintDocument for SSR / headless-Chrome instead).
227
384
  */
228
385
  declare function printSwaralipi(doc: Swaralipi, opts?: PrintOptions): void;
229
386
 
@@ -252,6 +409,8 @@ interface SwaralipiSheetElement extends HTMLElement {
252
409
  clearHighlights(): void;
253
410
  /** Open the print dialog (→ "Save as PDF") for the current document. */
254
411
  print(options?: PrintOptions): void;
412
+ /** Options for the `staff` language (instrument label, tonic, bpm…). */
413
+ staffOptions: StaffOptions;
255
414
  }
256
415
  /**
257
416
  * Register `<swaralipi-sheet>` (or a custom tag name). Idempotent. Throws
@@ -276,4 +435,4 @@ type Transliterator = (bengaliText: string) => string;
276
435
  */
277
436
  declare function loadLyricTransliterator(lang: ViewerLang): Promise<Transliterator | undefined>;
278
437
 
279
- export { type Arc, type Cell, type Cluster, type ComposeOptions, DIGITS, LABELS, MARKS, NOTATION_FONT_URLS, type PrintOptions, type RenderOptions, SWARALIPI_CSS, SWARALIPI_PRINT_CSS, type SwaralipiSheetElement, type Transliterator, type ViewerLang, composeCluster, defineSwaralipiSheet, groupRowIntoCells, loadLyricTransliterator, localizeDigits, printSwaralipi, renderPrintDocument, renderToHtml, resolveArcs };
438
+ 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 Transliterator, type ViewerLang, abcDuration, abcToSvg, buildAbc, composeCluster, defineSwaralipiSheet, docToAbc, groupRowIntoCells, loadLyricTransliterator, localizeDigits, midiToAbc, printSwaralipi, renderMetaHeader, renderPrintDocument, renderStaffHtml, renderStaffPrintDocument, renderToHtml, resolveArcs };