@real-music-packages/web-core 0.46.0 → 0.48.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.
@@ -0,0 +1,175 @@
1
+ declare function stampNoteIds(xml: string): string;
2
+ /**
3
+ * Insert `<print new-system="yes"/>` as the FIRST child of the `<measure>`
4
+ * at each 0-based position in `breakBeforeBarIndexes`, in EVERY `<part>`
5
+ * (a score's parts share one measure timeline, so a break must be encoded
6
+ * once per part for Verovio to line the systems up across staves). A
7
+ * position that is ≤ 0 or ≥ that part's own measure count is silently
8
+ * ignored FOR THAT PART (0 is a no-op — a part already starts a new system
9
+ * at its own first measure). Idempotent: a measure that already carries a
10
+ * `<print>` element (from a prior call, or from the source data) gets
11
+ * `new-system="yes"` SET on that existing element rather than gaining a
12
+ * second one — calling this twice with the same positions serializes
13
+ * identically both times. Malformed input (fails to parse) is returned
14
+ * unchanged, same defensive style as `stampNoteIds` above. Only ever ADDS
15
+ * `<print>` elements — never touches a `<note>` — so `stampNoteIds`'s
16
+ * position-based id scheme is unaffected by a prior or subsequent call to
17
+ * this function (see this file's own tests for the pinned invariant).
18
+ */
19
+ /**
20
+ * How many `<measure>` elements the FIRST `<part>` has — the `totalBars`
21
+ * that `barsPerLineBreaks`/`sectionAwareBreaks` plan against.
22
+ *
23
+ * The first part specifically, because that is the coordinate system
24
+ * `injectSystemBreaks` positions live in: a score's parts share one measure
25
+ * timeline, so the first part's ordinals index every part. And ORDINALS, not
26
+ * `<measure number>` attributes — scores skip and repeat bar numbers, so a
27
+ * count is the only safe answer. Malformed input, or a document with no
28
+ * part, returns 0 rather than throwing.
29
+ */
30
+ declare function measureCount(xml: string): number;
31
+ declare function injectSystemBreaks(xml: string, breakBeforeBarIndexes: readonly number[]): string;
32
+ /**
33
+ * Evenly-spread system-break positions for `totalMeasures` measures across
34
+ * `systems` systems: `[per, 2·per, …]` (each strictly `< totalMeasures`),
35
+ * with `per = Math.ceil(totalMeasures / systems)` — `Math.ceil` naturally
36
+ * front-loads any remainder into the EARLIER systems (e.g. 7 measures / 2
37
+ * systems -> per=4 -> systems of 4+3, never 3+4), which is what keeps a
38
+ * later system from being the short/orphaned one. `systems <= 1` (nothing to
39
+ * break between) returns `[]`.
40
+ */
41
+ declare function balancedSystemBreaks(totalMeasures: number, systems: number): number[];
42
+ /**
43
+ * Balanced system-break positions for a target BARS-PER-LINE layout — the
44
+ * caller-facing sibling of `balancedSystemBreaks`, which takes a system
45
+ * COUNT. Rather than stepping `every` bars at a time from bar 0 (which
46
+ * strands a short remainder — a 5-bar excerpt at `every: 4` renders 4+1, a
47
+ * widow of our own making), this decides how many LINES the excerpt needs
48
+ * (`ceil(totalBars / every)`) and hands that count to
49
+ * `balancedSystemBreaks`. `(5, 4)` is `[3]` (3+2) instead of `[4]` (4+1);
50
+ * `(9, 4)` is `[3, 6]` (3+3+3) instead of `[4, 8]` (4+4+1).
51
+ *
52
+ * WIDOW BACK-OFF: a small `every` against a small `totalBars` can make the
53
+ * straight `ceil(totalBars / every)` line count land on a trailing line of
54
+ * exactly 1 bar — the same widow this function exists to remove, reappearing
55
+ * at a different total/every combination (`lines = 3` on 7 bars: `per = 3`
56
+ * fills two 3-bar lines and leaves 1 bar for the third). The line count is
57
+ * therefore backed down (never below 1) until the trailing line holds at
58
+ * least 2 bars — 7 bars lands on 2 lines (4+3) instead of 3 (3+3+1), which
59
+ * is the same "fewer, fuller lines" direction `every` already asks for.
60
+ * This never changes the result for any total/every whose straight `ceil`
61
+ * count already avoids a 1-bar trailing line.
62
+ *
63
+ * `every <= 0` (the "auto — no forced breaks" sentinel callers resolve to)
64
+ * or a non-positive/non-finite `totalBars` returns `[]`. Never throws.
65
+ *
66
+ * Ported from stave-web-sightread (`src/lib/bach/xmlTransforms.ts`), whose
67
+ * copy this replaces — see the CONTRACT DUPLICATION NOTICE above. Reached by
68
+ * consumers through `VerovioLayoutOptions.barsPerLine` rather than called
69
+ * directly, so the player owns both the plan and its interaction with the
70
+ * widow pass and the readability floor.
71
+ */
72
+ declare function barsPerLineBreaks(totalBars: number, every: number): number[];
73
+ /**
74
+ * Like `barsPerLineBreaks`, but each SECTION's span is balanced on its own
75
+ * instead of one balance running across the whole excerpt — so a line never
76
+ * ends part-way into a neighbouring section's opening bars, and a section's
77
+ * own bars are never split into a widowed last line either.
78
+ *
79
+ * `sectionStarts` are 0-BASED POSITIONS into the excerpt (the same
80
+ * coordinate `injectSystemBreaks` consumes), NOT MusicXML `<measure
81
+ * number>` attributes — scores skip bar numbers, so a caller holding
82
+ * bar-numbered section labels must convert first. `0` (the excerpt's own
83
+ * start) is never a break and is ignored if present.
84
+ *
85
+ * Every section start > 0 is a HARD break in its own right — a new section
86
+ * always begins its own line, not subject to balancing — unioned with
87
+ * `barsPerLineBreaks`' balanced positions computed WITHIN each section's
88
+ * span (its start up to the next section's start, or `totalBars` for the
89
+ * last) and offset back into excerpt-absolute positions.
90
+ *
91
+ * `every <= 0` suppresses the balanced component, matching
92
+ * `barsPerLineBreaks`, but section-start breaks still apply — that is the
93
+ * "sections only, otherwise let the engraver decide" mode. Out-of-range and
94
+ * non-integer starts are ignored. The result is sorted, de-duplicated, and
95
+ * every entry is a valid `injectSystemBreaks` position (integer, `> 0`,
96
+ * `< totalBars`). Never throws.
97
+ */
98
+ declare function sectionAwareBreaks(totalBars: number, sectionStarts: readonly number[], every: number): number[];
99
+ /** One `<note>`'s MODEL identity — the ground truth `EngravedNote`
100
+ * (notationPlayerSvg.ts) needs that Verovio's rendered SVG cannot supply on
101
+ * its own (a rendered `g.note`/`g.rest` carries geometry, not pitch/tie/
102
+ * duration semantics). Keyed by the note's stamped `id` in
103
+ * `notePositions()`'s return of `verovioEngravedNotes`
104
+ * (notationPlayerVerovio.ts). */
105
+ interface NoteModel {
106
+ /** `12*(octave+1) + stepSemitone + alter` — the standard MusicXML→MIDI
107
+ * conversion (the same formula stave-web-sightread's own
108
+ * `practice/probeInputs.ts:pitchMidi` uses — a universal formula, not app
109
+ * logic, so this is not an owned-layer violation to restate here). `null`
110
+ * for a rest or an `<unpitched>` note (no `<pitch>` child) — mirrors
111
+ * `EngravedNote.midi`'s own "null covers both" contract. */
112
+ midi: number | null;
113
+ /** Has a `<rest/>` child. */
114
+ isRest: boolean;
115
+ /** True for the STOP half of a tie — a direct `<tie type="stop">` child OR
116
+ * `<notations><tied type="stop">` (exporters vary on which they emit;
117
+ * either counts) — matches `EngravedNote.tieContinuation`'s "continuation
118
+ * note of a tie, not the struck start" contract. */
119
+ tieContinuation: boolean;
120
+ /** 0-based, matching `EngravedNote.staffIndex`'s "0 = top staff of the
121
+ * system" contract: every `<part>` is walked in document order, and
122
+ * every DISTINCT staff within it (by `<attributes><staves>` when
123
+ * present, else the highest `<staff>` number any of its notes uses, else
124
+ * 1) is assigned the next index — so a single-part 2-staff piano score
125
+ * numbers 0/1 by `<staff>`, and a 2-part 1-staff-per-part reduction (this
126
+ * app's own chord+bass shape) numbers 0/1 by PART, with neither case
127
+ * needing different code. */
128
+ staffIndex: number;
129
+ /** Whole-note fraction (0.25 = quarter) — `duration / divisions / 4`,
130
+ * divisions tracked per-part from the LAST `<attributes><divisions>`
131
+ * seen at or before this note (MusicXML: divisions persist until
132
+ * overridden, default 1). 0 for a grace note (no `<duration>` child —
133
+ * the true, spec-correct signal; never guessed from `<type>`). */
134
+ durationReal: number;
135
+ /** 0-based position of this note's `<measure>` among its OWN `<part>`'s
136
+ * measure children, in document order. Informational only — the live
137
+ * join (`verovioOnsetColumns`/`verovioEngravedNotes`, both in
138
+ * notationPlayerVerovio.ts) resolves the note's RENDERED measure index
139
+ * from the live SVG DOM independently (Verovio's own render order, which
140
+ * is what geometry/hit-testing must agree with), never from this field. */
141
+ measureIndex: number;
142
+ /** `print-object="no"` on the source `<note>` (stave's `hideDoubledNotes`
143
+ * sets this on editorially-doubled notes/rests before handing MusicXML to
144
+ * this player). Verovio's importer HONORS this for `<note>` elements
145
+ * carrying a `<pitch>` (renders `visibility="hidden"` on its own,
146
+ * empirically confirmed against a real 6.2.0 render) but does NOT honor
147
+ * it for `<rest>` notes (the `<g class="rest">` renders fully visible
148
+ * regardless — same empirical check). `createVerovioNotationPlayer`
149
+ * reads this field to force `visibility="hidden"` after every render for
150
+ * ANY id where it's true — a no-op re-application on notes Verovio
151
+ * already hid, and the actual fix on the rests it doesn't (see that
152
+ * module's `applyPrintObjectHiding`). */
153
+ hidden: boolean;
154
+ }
155
+ /**
156
+ * Pure(ish) — MusicXML-in, `id → NoteModel`-out. Walks every `<part>` in
157
+ * document order, then every `<measure>` in document order, then every
158
+ * DIRECT child in document order — `<attributes>` updates the part's own
159
+ * `divisions` cursor; every other non-`<note>` child (`<backup>`,
160
+ * `<forward>`, `<direction>`, …) is a structural/timeline element this
161
+ * function has NO use for (it reads each note's OWN `<duration>` directly,
162
+ * never a cursor POSITION — see `durationReal`'s doc — so unlike
163
+ * `walkMeasureNotes`-style position walks, `<backup>`/`<forward>` need no
164
+ * special handling here beyond being correctly skipped, which plain
165
+ * tag-name filtering already does) — and every `<note>` becomes one model
166
+ * entry, keyed by its `id` attribute (a note with NO `id` — i.e. input that
167
+ * was never run through `stampNoteIds` — is silently skipped: it has no key
168
+ * to join the render against, so there is nothing useful to record).
169
+ *
170
+ * Never throws: malformed input (fails to parse, or no `<score-partwise>`
171
+ * root) returns an empty Map.
172
+ */
173
+ declare function noteModelFromXml(stampedXml: string): Map<string, NoteModel>;
174
+
175
+ export { type NoteModel, balancedSystemBreaks, barsPerLineBreaks, injectSystemBreaks, measureCount, noteModelFromXml, sectionAwareBreaks, stampNoteIds };
@@ -0,0 +1,19 @@
1
+ import {
2
+ balancedSystemBreaks,
3
+ barsPerLineBreaks,
4
+ injectSystemBreaks,
5
+ measureCount,
6
+ noteModelFromXml,
7
+ sectionAwareBreaks,
8
+ stampNoteIds
9
+ } from "./chunk-JCDNEJBT.js";
10
+ export {
11
+ balancedSystemBreaks,
12
+ barsPerLineBreaks,
13
+ injectSystemBreaks,
14
+ measureCount,
15
+ noteModelFromXml,
16
+ sectionAwareBreaks,
17
+ stampNoteIds
18
+ };
19
+ //# sourceMappingURL=notationXml.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@real-music-packages/web-core",
3
- "version": "0.46.0",
3
+ "version": "0.48.0",
4
4
  "description": "Shared music-theory + audio primitives for the music-suite web apps",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -39,6 +39,10 @@
39
39
  "types": "./dist/ornaments.d.ts",
40
40
  "import": "./dist/ornaments.js"
41
41
  },
42
+ "./notationXml": {
43
+ "types": "./dist/notationXml.d.ts",
44
+ "import": "./dist/notationXml.js"
45
+ },
42
46
  "./notationBeams": {
43
47
  "types": "./dist/notationBeams.d.ts",
44
48
  "import": "./dist/notationBeams.js"