@real-music-packages/web-core 0.45.2 → 0.47.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.
@@ -10,138 +10,14 @@ import {
10
10
  vstackAudioPlayheadLine
11
11
  } from "./chunk-DBFGX6OK.js";
12
12
  import "./chunk-PVZXLC23.js";
13
-
14
- // src/notationXml.ts
15
- function stampNoteIds(xml) {
16
- const doc = new DOMParser().parseFromString(xml, "application/xml");
17
- if (doc.querySelector("parsererror")) return xml;
18
- const parts = Array.from(doc.querySelectorAll("score-partwise > part"));
19
- parts.forEach((part, partIdx) => {
20
- for (const measure of Array.from(part.querySelectorAll("measure"))) {
21
- const number = measure.getAttribute("number") ?? "0";
22
- const notes = Array.from(measure.children).filter((el) => el.tagName === "note");
23
- notes.forEach((note, noteIdx) => {
24
- note.setAttribute("id", `n-${partIdx}-${number}-${noteIdx}`);
25
- });
26
- }
27
- });
28
- return new XMLSerializer().serializeToString(doc);
29
- }
30
- function injectSystemBreaks(xml, breakBeforeBarIndexes) {
31
- const doc = new DOMParser().parseFromString(xml, "application/xml");
32
- if (doc.querySelector("parsererror")) return xml;
33
- const root = doc.documentElement;
34
- if (!root || root.tagName !== "score-partwise") return xml;
35
- for (const part of childrenNamed(root, "part")) {
36
- const measures = childrenNamed(part, "measure");
37
- for (const pos of breakBeforeBarIndexes) {
38
- if (!Number.isInteger(pos) || pos <= 0 || pos >= measures.length) continue;
39
- const measure = measures[pos];
40
- const existingPrint = firstChildNamed(measure, "print");
41
- if (existingPrint) {
42
- existingPrint.setAttribute("new-system", "yes");
43
- } else {
44
- const printEl = doc.createElement("print");
45
- printEl.setAttribute("new-system", "yes");
46
- measure.insertBefore(printEl, measure.firstChild);
47
- }
48
- }
49
- }
50
- return new XMLSerializer().serializeToString(doc);
51
- }
52
- function balancedSystemBreaks(totalMeasures, systems) {
53
- if (systems <= 1 || totalMeasures <= 0) return [];
54
- const per = Math.ceil(totalMeasures / systems);
55
- if (per <= 0) return [];
56
- const breaks = [];
57
- for (let b = per; b < totalMeasures; b += per) breaks.push(b);
58
- return breaks;
59
- }
60
- var STEP_SEMITONE = { C: 0, D: 2, E: 4, F: 5, G: 7, A: 9, B: 11 };
61
- function firstChildNamed(el, tag) {
62
- for (const c of Array.from(el.children)) if (c.tagName === tag) return c;
63
- return null;
64
- }
65
- function childrenNamed(el, tag) {
66
- return Array.from(el.children).filter((c) => c.tagName === tag);
67
- }
68
- function textOf(el) {
69
- return el && el.textContent != null ? el.textContent.trim() : null;
70
- }
71
- function pitchMidiFromElement(pitchEl) {
72
- const step = textOf(firstChildNamed(pitchEl, "step")) ?? "C";
73
- const octave = Number(textOf(firstChildNamed(pitchEl, "octave")) ?? "4");
74
- const alter = Number(textOf(firstChildNamed(pitchEl, "alter")) ?? "0");
75
- return 12 * (octave + 1) + (STEP_SEMITONE[step] ?? 0) + Math.trunc(alter);
76
- }
77
- function partStaffCount(part) {
78
- let maxDeclared = 0;
79
- for (const attrs of childrenNamed(part, "measure").flatMap((m) => childrenNamed(m, "attributes"))) {
80
- const staves = Number(textOf(firstChildNamed(attrs, "staves")) ?? "0");
81
- if (staves > maxDeclared) maxDeclared = staves;
82
- }
83
- if (maxDeclared > 0) return maxDeclared;
84
- let maxStaff = 0;
85
- for (const measure of childrenNamed(part, "measure")) {
86
- for (const note of childrenNamed(measure, "note")) {
87
- const staff = Number(textOf(firstChildNamed(note, "staff")) ?? "0");
88
- if (staff > maxStaff) maxStaff = staff;
89
- }
90
- }
91
- return Math.max(1, maxStaff);
92
- }
93
- function noteModelFromXml(stampedXml) {
94
- const model = /* @__PURE__ */ new Map();
95
- let doc;
96
- try {
97
- doc = new DOMParser().parseFromString(stampedXml, "application/xml");
98
- } catch {
99
- return model;
100
- }
101
- if (doc.querySelector("parsererror")) return model;
102
- const root = doc.documentElement;
103
- if (!root || root.tagName !== "score-partwise") return model;
104
- const parts = childrenNamed(root, "part");
105
- let staffOffset = 0;
106
- for (const part of parts) {
107
- const staffCount = partStaffCount(part);
108
- let divisions = 1;
109
- const measureEls = childrenNamed(part, "measure");
110
- measureEls.forEach((measureEl, measureIndex) => {
111
- for (const child of Array.from(measureEl.children)) {
112
- if (child.tagName === "attributes") {
113
- const divText = textOf(firstChildNamed(child, "divisions"));
114
- if (divText) divisions = Number(divText) || divisions;
115
- continue;
116
- }
117
- if (child.tagName !== "note") continue;
118
- const note = child;
119
- const id = note.getAttribute("id");
120
- if (!id) continue;
121
- const isGrace = !!firstChildNamed(note, "grace");
122
- const isRest = !!firstChildNamed(note, "rest");
123
- const pitchEl = firstChildNamed(note, "pitch");
124
- const staffNumber = Number(textOf(firstChildNamed(note, "staff")) ?? "1") || 1;
125
- const durationText = textOf(firstChildNamed(note, "duration"));
126
- const durationReal = isGrace || !durationText ? 0 : Number(durationText) / divisions / 4;
127
- const tieStopDirect = childrenNamed(note, "tie").some((t) => t.getAttribute("type") === "stop");
128
- const notationsEl = firstChildNamed(note, "notations");
129
- const tieStopNotated = notationsEl ? childrenNamed(notationsEl, "tied").some((t) => t.getAttribute("type") === "stop") : false;
130
- model.set(id, {
131
- midi: pitchEl ? pitchMidiFromElement(pitchEl) : null,
132
- isRest,
133
- tieContinuation: tieStopDirect || tieStopNotated,
134
- staffIndex: staffOffset + Math.max(0, staffNumber - 1),
135
- durationReal,
136
- measureIndex,
137
- hidden: note.getAttribute("print-object") === "no"
138
- });
139
- }
140
- });
141
- staffOffset += staffCount;
142
- }
143
- return model;
144
- }
13
+ import {
14
+ balancedSystemBreaks,
15
+ injectSystemBreaks,
16
+ measureCount,
17
+ noteModelFromXml,
18
+ sectionAwareBreaks,
19
+ stampNoteIds
20
+ } from "./chunk-JCDNEJBT.js";
145
21
 
146
22
  // src/notationPlayerVerovio.ts
147
23
  var MARKED_NOTE_CLASS = "rmp-note-marked";
@@ -396,7 +272,13 @@ var VEROVIO_LAYOUT_DEFAULTS = {
396
272
  };
397
273
  function verovioRenderOptions(hostWidthPx, zoom, layout) {
398
274
  const { scale, pageWidth } = verovioZoomOptions(hostWidthPx, zoom);
399
- const { avoidWidows: _avoidWidows, minFitFactor: _minFitFactor, ...verovioLayout } = layout ?? {};
275
+ const {
276
+ avoidWidows: _avoidWidows,
277
+ minFitFactor: _minFitFactor,
278
+ barsPerLine: _barsPerLine,
279
+ sectionStarts: _sectionStarts,
280
+ ...verovioLayout
281
+ } = layout ?? {};
400
282
  return {
401
283
  ...VEROVIO_LAYOUT_DEFAULTS,
402
284
  ...verovioLayout,
@@ -470,6 +352,14 @@ function createVerovioNotationPlayer(opts) {
470
352
  }
471
353
  const stampedXml = opts.rendered ? musicXml : stampNoteIds(musicXml);
472
354
  const noteModel = opts.rendered ? /* @__PURE__ */ new Map() : noteModelFromXml(stampedXml);
355
+ const breakPlan = (() => {
356
+ const layout = opts.verovioOptions;
357
+ const starts = layout?.sectionStarts ?? [];
358
+ const every = layout?.barsPerLine ?? 0;
359
+ if (!starts.length && !(every > 0)) return [];
360
+ return sectionAwareBreaks(measureCount(stampedXml), starts, every);
361
+ })();
362
+ const plannedXml = breakPlan.length ? injectSystemBreaks(stampedXml, breakPlan) : stampedXml;
473
363
  const root = document.createElement("div");
474
364
  root.style.position = "relative";
475
365
  root.style.width = "100%";
@@ -581,11 +471,12 @@ function createVerovioNotationPlayer(opts) {
581
471
  }
582
472
  function engraveOnce(toolkit, widthPx, zoom) {
583
473
  const layoutOpts = opts.verovioOptions;
584
- const effectiveBreaks = layoutOpts?.breaks ?? VEROVIO_LAYOUT_DEFAULTS.breaks;
474
+ const planLayoutOpts = breakPlan.length ? { ...layoutOpts, breaks: "line" } : layoutOpts;
475
+ const effectiveBreaks = planLayoutOpts?.breaks ?? VEROVIO_LAYOUT_DEFAULTS.breaks;
585
476
  const minFitFactor = layoutOpts?.minFitFactor ?? DEFAULT_MIN_FIT_FACTOR;
586
477
  const avoidWidows = layoutOpts?.avoidWidows !== false;
587
- let xmlToRender = stampedXml;
588
- let renderLayoutOpts = layoutOpts;
478
+ let xmlToRender = plannedXml;
479
+ let renderLayoutOpts = planLayoutOpts;
589
480
  toolkit.setOptions(verovioRenderOptions(widthPx, zoom, renderLayoutOpts));
590
481
  const ok = !!toolkit.loadData(xmlToRender);
591
482
  if (ok) renderAllPages(toolkit);