@ugfoundation/swaralipi-js 0.2.0 → 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-CIAXVKP5.js → batch-QPSHVNDS.js} +4 -4
  2. package/dist/{batch-CIAXVKP5.js.map → batch-QPSHVNDS.js.map} +1 -1
  3. package/dist/{chunk-2NBNF6JP.js → chunk-4YTOLN3C.js} +3 -3
  4. package/dist/{chunk-2NBNF6JP.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-SH2INDZ2.js → chunk-VCRWS3F3.js} +3 -3
  8. package/dist/{chunk-SH2INDZ2.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 +684 -202
  32. package/dist/viewer/index.cjs.map +1 -1
  33. package/dist/viewer/index.d.cts +217 -64
  34. package/dist/viewer/index.d.ts +217 -64
  35. package/dist/viewer/index.js +421 -177
  36. package/dist/viewer/index.js.map +1 -1
  37. package/package.json +7 -2
@@ -1,86 +1,81 @@
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
- * Per-language glyph tables + Unicode cluster composition for the viewer.
8
+ * Per-language notation encoding for the viewer.
6
9
  *
7
- * The akarmatrik system is script-portable: Bengali and Devanagari use the
8
- * same letters-with-distinct-komal-forms convention (legend: কোমল র—ঋ,
9
- * কোমল গ—জ্ঞ, কড়ি ম—ক্ষ, কোমল ধ—দ, কোমল ন—ণ), the same আ-কার matra carrier,
10
- * the same reph-above for taar and understroke for mandra. `en` has no
11
- * native convention, so it uses S R G M P D N with CSS marks (komal
12
- * underline, tivra prime, octave dots) and no matra carrier.
10
+ * Every language renders its notation through a dedicated, purpose-built
11
+ * font whose glyphs ARE the akarmatrik/Bhatkhande marks — so composeCluster
12
+ * emits that font's ASCII encoding and the font does all the hard work
13
+ * (komal/tivra forms, octave reph/dots, matra). This is why the viewer no
14
+ * longer touches Unicode cluster composition at all: the old reph-over-
15
+ * vowel, hasanta-before-matra and dotted-circle hazards simply don't exist
16
+ * when the font supplies precomposed glyphs keyed by plain ASCII.
13
17
  *
14
- * Unicode hazards owned here (see composeCluster):
15
- * - mandra can NEVER be a real hasanta: letter + ্ + া is an invalid
16
- * cluster (virama before vowel sign → dotted circle). It is always a
17
- * CSS understroke (`sw-mandra`).
18
- * - ঋ/ऋ (komal re) is an independent VOWEL letter: both র্+ঋ (reph over a
19
- * vowel) and ঋ+া (vowel sign on a vowel) are invalid clusters. taar-ঋ
20
- * falls back to a CSS reph (`sw-reph-css`); a matra following ঋ must be
21
- * emitted as a separate isolated-matra span (`matraSeparate`).
22
- * - Isolated dependent signs (া, ঃ) need a base: U+00A0 NBSP is the
23
- * Unicode-sanctioned placeholder base and avoids dotted circles.
18
+ * - bn → NLTR's **Swarabitan** (the akarmatrik font this project decodes).
19
+ * Letters s r g m p q n = স র গ ম প ধ ন (q=ধ); komal v t d u = ঋ জ্ঞ দ ণ;
20
+ * tivra ma k = the authentic কড়ি-ম conjunct (ম-based — has NO Unicode
21
+ * form, which is what forced this whole approach); suffix f = taar reph,
22
+ * h = mandra hasanta; trailing a = the আ-কার matra carrier. See codec.ts.
23
+ * - hi/en → **Ome Bhatkhande** Hindi/English (SIL OFL 1.1, © Omenad —
24
+ * github.com/omenad/fonts). Same clean keymap for both: lowercase
25
+ * s r g m p d n = the seven shuddha swaras, uppercase R G M D N = komal
26
+ * Re/Ga, tivra Ma, komal Dha/Ni; octave suffix u/l = taar/mandra dot,
27
+ * U/L = ati (two dots). Bhatkhande has no আ-কার, so no matra is appended.
28
+ *
29
+ * The font is applied by CSS keyed on `.sw-sheet`'s `lang` (see styles.ts);
30
+ * composeCluster only chooses the ASCII. The single residual CSS mark is the
31
+ * bn ati-octave extra dot (Swarabitan's f/h are single reph/hasanta only).
24
32
  */
25
33
 
26
- type ViewerLang = 'bn' | 'hi' | 'en';
27
- interface LetterForms {
28
- shuddha: string;
29
- /** Distinct komal glyph (bn/hi); absent for en (CSS underline instead). */
30
- komal?: string;
31
- /** Distinct tivra glyph (bn/hi ক্ষ/क्ष); absent for en (CSS prime). */
32
- tivra?: string;
33
- }
34
- declare const LETTERS: Record<ViewerLang, Record<SvaraLetter, LetterForms>>;
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';
35
38
  /** Digits 0-9 in the target script (beat labels, avartan). */
36
- declare const DIGITS: Record<ViewerLang, string>;
39
+ declare const DIGITS: Record<GridLang, string>;
37
40
  interface LangMarks {
38
- /** আ-কার matra carrier appended to a group-final cluster ('' for en). */
39
- matra: string;
40
41
  /** Melisma / no-new-attack dash before a sustained cluster. */
41
42
  hyphen: string;
42
43
  /** Lyric continuation mark shown under sustains (the source uses ৹). */
43
44
  melisma: string;
44
45
  /** ছেদ cut/articulation mark (two stacked rings, ঃ-shaped). */
45
46
  cut: string;
46
- /** Real-text reph prefix for taar ('' for en → CSS dot instead). */
47
- reph: string;
48
47
  }
49
- declare const MARKS: Record<ViewerLang, LangMarks>;
48
+ declare const MARKS: Record<GridLang, LangMarks>;
50
49
  /** Header labels (NLTR prints নাম / তাল / আবর্তন …). */
51
- declare const LABELS: Record<ViewerLang, {
50
+ declare const LABELS: Record<GridLang, {
52
51
  title: string;
53
52
  taal: string;
54
53
  raag: string;
55
54
  avartan: string;
56
55
  notationBy: string;
57
56
  }>;
58
- /** A composed notation cluster: text plus the CSS classes for marks the
59
- * text itself cannot carry (mandra understroke, ati dots, en komal/tivra,
60
- * CSS-reph fallback). */
57
+ /** A composed notation cluster: the font's ASCII text plus any residual CSS
58
+ * classes the font itself cannot express (only the bn ati-octave dot). */
61
59
  interface Cluster {
62
60
  text: string;
63
61
  classes: string[];
64
- /** Set when the matra could not join the cluster (ঋ/ऋ, or en where the
65
- * carrier doesn't exist) and must be emitted as an isolated-matra span. */
66
- matraSeparate: boolean;
67
62
  }
68
63
  interface ComposeOptions {
69
64
  komal?: boolean;
70
65
  tivra?: boolean;
71
66
  octave: Octave;
72
- /** Append the আ-কার matra carrier (group-final notes only). */
67
+ /** Append the আ-কার matra carrier (bn group-final notes only). */
73
68
  withMatra: boolean;
74
- lang: ViewerLang;
69
+ lang: GridLang;
75
70
  }
76
71
  /**
77
- * Compose one svara cluster. The single place that knows Unicode
78
- * composition order: reph? + base + matra?, with the ঋ/mandra/en fallbacks
79
- * documented in the file header.
72
+ * Compose one svara into its notation-font ASCII. bn = Swarabitan
73
+ * (letter + f/h octave + a matra); hi/en = Ome Bhatkhande (case-encoded
74
+ * letter + u/l/U/L octave, no matra). The font renders the actual glyph.
80
75
  */
81
76
  declare function composeCluster(letter: SvaraLetter, opts: ComposeOptions): Cluster;
82
77
  /** Map ASCII digits in `s` to the target script's digits. */
83
- declare function localizeDigits(s: string, lang: ViewerLang): string;
78
+ declare function localizeDigits(s: string, lang: GridLang): string;
84
79
 
85
80
  /**
86
81
  * Pure spec-JSON → HTML renderer. No DOM, no side effects: usable for SSR
@@ -97,8 +92,8 @@ declare function localizeDigits(s: string, lang: ViewerLang): string;
97
92
  * Rendering is spec-driven (semantics, not `raw`): grouping derives from
98
93
  * `durationMatra` accumulation so hand-authored documents without `raw`
99
94
  * render identically. `raw` is consulted only for (a) raw-only fallback
100
- * notes (never dropped — dimmed monospace), (b) the ছেদ cut mark and beat
101
- * labels, whose grammar lives in raw (see src/convert/codec.ts).
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).
102
97
  */
103
98
 
104
99
  interface RenderOptions {
@@ -152,6 +147,13 @@ interface Arc {
152
147
  * A row with fill segments but no ends at all renders one straight line.
153
148
  */
154
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;
155
157
  /** Render a complete swaralipi document to an HTML string. Pair with
156
158
  * SWARALIPI_CSS (shadow DOM does this automatically; SSR consumers emit a
157
159
  * `<style>` themselves). All source text is HTML-escaped. */
@@ -170,12 +172,17 @@ declare function renderToHtml(doc: Swaralipi, opts?: RenderOptions): string;
170
172
  * --sw-rule header rule / muted line color
171
173
  *
172
174
  * All ink uses `currentColor`, so the sheet follows the host's text color
173
- * (dark mode included). Octave marks that Unicode cannot express in a valid
174
- * cluster (mandra understroke, ati dots, ঋ-reph, en octave dots) are drawn
175
- * with positioned pseudo-elements; offsets are em-based and tuned against
176
- * the NLTR reference rendering of song 4270.
175
+ * (dark mode included). The only octave mark drawn as a pseudo-element is
176
+ * the bn ati-octave extra dot (Swarabitan's f/h glyphs are a single
177
+ * reph/hasanta); everything else is a real glyph in the notation font.
177
178
  */
178
- declare const SWARALIPI_CSS = "\n.sw-sheet {\n font-family: var(--sw-font, 'Noto Sans Bengali', 'Kohinoor Bangla', 'Bangla Sangam MN', 'Nirmala UI', 'Noto Sans Devanagari', sans-serif);\n font-size: var(--sw-size, 1.125rem);\n line-height: 1.5;\n}\n\n/* ---- header ---- */\n.sw-header {\n margin: 0 0 0.75em;\n padding: 0 0 0.5em;\n border-bottom: 1px solid var(--sw-rule, rgba(128, 128, 128, 0.35));\n font-size: 0.95em;\n}\n.sw-meta-row { display: flex; gap: 0.3em; }\n.sw-meta-label { font-weight: 600; min-width: 4.5em; }\n.sw-meta-label::after { content: ':'; font-weight: 400; margin-left: 0.15em; }\n\n.sw-part-name { font-size: 0.85em; font-weight: 600; margin: 1em 0 0.2em; opacity: 0.85; }\n\n/* ---- row grid: above / notation / below(arcs) / lyric ---- */\n.sw-row {\n display: flex;\n align-items: flex-start;\n overflow-x: auto;\n padding: 0.4em 0;\n}\n.sw-label { flex: none; margin-right: 0.8em; font-weight: 600; }\n.sw-grid {\n display: grid;\n grid-template-rows: auto auto auto auto;\n grid-auto-columns: max-content;\n align-items: end;\n}\n.sw-above { grid-row: 1; font-size: 0.62em; justify-self: start; white-space: nowrap; padding-left: 0.2em; }\n.sw-n { grid-row: 2; white-space: nowrap; }\n.sw-arc,\n.sw-below { grid-row: 3; }\n.sw-lyr { grid-row: 4; white-space: nowrap; padding-top: 0.3em; }\n.sw-below { font-size: 0.62em; justify-self: start; white-space: nowrap; padding-left: 0.2em; }\n\n/* inter-cell gap as padding on each cell's first notation item, so arcs\n * and above-spans crossing cells stay continuous */\n.sw-cs { padding-left: var(--sw-cell-gap, 0.55em); }\n\n/* ---- notation clusters & marks ---- */\n.sw-cl { position: relative; display: inline-block; }\n\n/* mandra: hasanta-shaped understroke (a real \u09CD before \u09BE is invalid Unicode) */\n.sw-mandra::after {\n content: '';\n position: absolute;\n left: 0.1em;\n bottom: -0.08em;\n width: 0.45em;\n height: 0.09em;\n background: currentColor;\n transform: rotate(-32deg);\n}\n/* ati-mandra: an extra dot below */\n.sw-ati-mandra::before {\n content: '';\n position: absolute;\n left: 0.25em;\n bottom: -0.35em;\n width: 0.16em;\n height: 0.16em;\n border-radius: 50%;\n background: currentColor;\n}\n/* ati-taar (reph already in the text): an extra dot above */\n.sw-ati-taar::before {\n content: '';\n position: absolute;\n left: 0.25em;\n top: -0.3em;\n width: 0.16em;\n height: 0.16em;\n border-radius: 50%;\n background: currentColor;\n}\n/* CSS reph fallback for vowel letters \u098B/\u090B that reject a real reph */\n.sw-reph-css::before {\n content: '';\n position: absolute;\n top: -0.12em;\n left: 0.12em;\n width: 0.3em;\n height: 0.3em;\n border: 0.09em solid currentColor;\n border-right: none;\n border-bottom: none;\n border-top-left-radius: 0.3em;\n}\n/* en octave dots (Bhatkhande-style) */\n.sw-dot-above::before,\n.sw-dot-below::before {\n content: '';\n position: absolute;\n left: 50%;\n margin-left: -0.07em;\n width: 0.14em;\n height: 0.14em;\n border-radius: 50%;\n background: currentColor;\n}\n.sw-dot-above::before { top: -0.25em; }\n.sw-dot-below::before { bottom: -0.2em; }\n.sw-ati-taar.sw-dot-above::before,\n.sw-ati-mandra.sw-dot-below::before { box-shadow: 0.24em 0 0 currentColor; margin-left: -0.19em; }\n/* en accidentals (bn/hi use distinct letters instead) */\n.sw-komal { text-decoration: underline; text-underline-offset: 0.18em; }\n.sw-tivra::after { content: '\\2032'; font-size: 0.7em; vertical-align: super; }\n\n/* kan grace: small raised cluster left of the main note */\n.sw-kan {\n position: relative;\n display: inline-block;\n font-size: 0.58em;\n vertical-align: 0.75em;\n margin-right: 0.08em;\n}\n\n.sw-hy { padding-right: 0.02em; }\n.sw-iso { display: inline-block; margin-left: -0.18em; }\n.sw-cut { font-size: 0.8em; display: inline-block; margin-left: -0.15em; }\n.sw-br { opacity: 0.8; padding: 0 0.06em; }\n.sw-micro { font-size: 0.6em; vertical-align: super; opacity: 0.8; }\n\n/* raw fallback: never dropped, visibly non-notation */\n.sw-raw {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.72em;\n opacity: 0.55;\n}\n\n/* ---- bars: dari (thin) and danda strokes, notation-row height ---- */\n/* horizontal gap before a bar comes from its own .sw-cs padding-left \u2014\n * never reset it with a padding shorthand here */\n.sw-bar {\n grid-row: 2;\n align-self: stretch;\n display: inline-flex;\n align-items: stretch;\n gap: 0.16em;\n padding-right: 0.1em;\n}\n.sw-bar i { display: inline-block; width: 0.14em; min-height: 1.25em; background: currentColor; }\n.sw-bar--dari i { width: 0.05em; opacity: 0.9; }\n.sw-bar--quad i:nth-child(3) { margin-left: 0.18em; }\n.sw-above .sw-bar, .sw-below .sw-bar { display: inline-flex; vertical-align: baseline; }\n.sw-above .sw-bar i, .sw-below .sw-bar i { min-height: 1em; }\n\n/* ---- under-arcs (slurs decoded from w/x segments and meend) ---- */\n.sw-arc {\n align-self: start;\n height: 0.4em;\n margin: 0.05em 0.25em 0;\n border: 0.09em solid currentColor;\n border-top: none;\n border-bottom-left-radius: 0.6em;\n border-bottom-right-radius: 0.6em;\n opacity: 0.85;\n}\n.sw-arc--openl { border-left: none; border-bottom-left-radius: 0; }\n.sw-arc--openr { border-right: none; border-bottom-right-radius: 0; }\n.sw-arc--line { border-left: none; border-right: none; border-radius: 0; height: 0.22em; }\n\n/* ---- annotation content ---- */\n.sw-beat { position: relative; display: inline-block; padding: 0 0.1em; }\n.sw-sam::after {\n content: '';\n position: absolute;\n top: -0.45em;\n left: 50%;\n width: 0.1em;\n height: 0.4em;\n background: currentColor;\n transform: rotate(14deg);\n}\n.sw-stop i {\n display: inline-block;\n width: 0.07em;\n height: 1em;\n margin: 0 0.07em;\n background: currentColor;\n vertical-align: text-bottom;\n}\n\n/* ---- player sync (P2.3) ---- */\n.sw-hl { background: var(--sw-hl, rgba(255, 196, 64, 0.45)); border-radius: 0.15em; }\n\n/* ---- element error box ---- */\n.sw-error {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.85em;\n padding: 0.6em 0.8em;\n border: 1px solid rgba(200, 64, 64, 0.6);\n border-radius: 0.3em;\n color: #b33;\n background: rgba(200, 64, 64, 0.08);\n}\n";
179
+ /** Notation fonts, self-hosted on the project CDN (CORS-enabled), keyed by
180
+ * the CSS `font-family` this stylesheet uses. bn = NLTR's Swarabitan; hi/en
181
+ * = Ome Bhatkhande (SIL OFL 1.1, © Omenad — github.com/omenad/fonts).
182
+ * Self-hosters: declare your own \`@font-face\` for any of these families
183
+ * before this stylesheet / before defineSwaralipiSheet() and it wins. */
184
+ declare const NOTATION_FONT_URLS: Record<string, string>;
185
+ declare const SWARALIPI_CSS: string;
179
186
  /**
180
187
  * Print stylesheet — pair with `SWARALIPI_CSS` (renderPrintDocument injects
181
188
  * both). Gated on `@media print`, so it also works if a consumer drops it into
@@ -188,7 +195,130 @@ declare const SWARALIPI_CSS = "\n.sw-sheet {\n font-family: var(--sw-font, 'Not
188
195
  * - rows are un-clipped (no scrollbar on paper) and kept whole across page
189
196
  * breaks; part headings stay with their first row.
190
197
  */
191
- 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>;
192
322
 
193
323
  /**
194
324
  * Print / "Save as PDF" support.
@@ -198,16 +328,24 @@ declare const SWARALIPI_PRINT_CSS = "\n@page { margin: 16mm; }\n@media print {\n
198
328
  * matra) and the CSS-drawn marks (octave dots, slur arcs, danda strokes) that
199
329
  * the viewer renders on screen. Rasterizers (html2canvas) and PDF-primitive
200
330
  * libraries (jsPDF/pdfmake) have no Indic shaping and drop pseudo-element
201
- * 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.
202
333
  *
203
334
  * `renderPrintDocument` builds a self-contained HTML page from the pure
204
335
  * `renderToHtml` + `SWARALIPI_CSS` (so it sidesteps the shadow-DOM "print drops
205
336
  * encapsulated styles" gotcha); `printSwaralipi` prints it via a hidden iframe.
206
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.
207
341
  */
208
342
 
209
- interface PrintOptions {
210
- /** 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`. */
211
349
  lang?: ViewerLang;
212
350
  /** Render the নাম/তাল header block. Default true. */
213
351
  showHeader?: boolean;
@@ -216,20 +354,33 @@ interface PrintOptions {
216
354
  /** Document `<title>` — the browser's default "Save as PDF" filename.
217
355
  * Defaults to the document's `meta.title`. */
218
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;
219
362
  }
220
363
  /**
221
- * Build a complete, standalone HTML document for printing a swaralipi sheet:
222
- * the rendered notation plus `SWARALIPI_CSS` and the print stylesheet, on a
223
- * white page. Usable for a hidden-iframe print, a new browser tab, or
224
- * 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).
225
370
  */
226
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>;
227
378
  /**
228
379
  * Open the browser print dialog for a swaralipi document (the user picks a
229
380
  * printer or "Save as PDF"). Renders into an off-screen iframe so it prints
230
381
  * only the sheet — independent of the host page's chrome and CSS — then cleans
231
- * up. Browser-only; throws in a non-DOM environment (use renderPrintDocument
232
- * for SSR / headless-Chrome instead).
382
+ * up. Browser-only; throws in a non-DOM environment (use renderPrintDocument /
383
+ * renderStaffPrintDocument for SSR / headless-Chrome instead).
233
384
  */
234
385
  declare function printSwaralipi(doc: Swaralipi, opts?: PrintOptions): void;
235
386
 
@@ -258,6 +409,8 @@ interface SwaralipiSheetElement extends HTMLElement {
258
409
  clearHighlights(): void;
259
410
  /** Open the print dialog (→ "Save as PDF") for the current document. */
260
411
  print(options?: PrintOptions): void;
412
+ /** Options for the `staff` language (instrument label, tonic, bpm…). */
413
+ staffOptions: StaffOptions;
261
414
  }
262
415
  /**
263
416
  * Register `<swaralipi-sheet>` (or a custom tag name). Idempotent. Throws
@@ -282,4 +435,4 @@ type Transliterator = (bengaliText: string) => string;
282
435
  */
283
436
  declare function loadLyricTransliterator(lang: ViewerLang): Promise<Transliterator | undefined>;
284
437
 
285
- export { type Arc, type Cell, type Cluster, type ComposeOptions, DIGITS, LABELS, LETTERS, MARKS, 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 };