@barmark/render 0.7.2 → 0.8.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.
- package/CHANGELOG.md +29 -1
- package/dist/html.js +45 -7
- package/dist/model.js +4 -1
- package/dist/speech.js +9 -1
- package/dist/styles.js +6 -0
- package/dist/types.d.ts +8 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,32 @@ package uses [semantic versioning](https://semver.org/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.8.0] - 2026-11-03
|
|
10
|
+
|
|
11
|
+
Requires `barmark` 0.11.0.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- **Depends on `barmark` `^0.11.0`** (was `^0.10.0`).
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **Endings:** an ending row has a bracket over its bars and a `1.`, `2.`
|
|
20
|
+
or `1.–3.` label; screen readers hear "ending for pass 1".
|
|
21
|
+
- **Directions:** Segno and Coda as their signs, To Coda with the coda
|
|
22
|
+
sign, Fine and the jumps as words. Each is spoken ("dal segno al
|
|
23
|
+
coda"). Bar numbers skip them, since they are not bars.
|
|
24
|
+
|
|
25
|
+
## [0.7.3] - 2026-11-03
|
|
26
|
+
|
|
27
|
+
Requires `barmark` 0.10.0. No change to the renderer's code: a page that
|
|
28
|
+
installs `barmark` 0.10.0 alongside it gets one copy instead of two.
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- **Depends on `barmark` `^0.10.0`** (was `^0.9.0`, which in 0.x stops
|
|
33
|
+
short of 0.10.0).
|
|
34
|
+
|
|
9
35
|
## [0.7.2] - 2026-11-02
|
|
10
36
|
|
|
11
37
|
Requires `barmark` 0.9.0, and shows its swing amount.
|
|
@@ -213,7 +239,9 @@ that needs no script or as the `<barmark-chart>` web component. Requires
|
|
|
213
239
|
zoom without sideways scrolling, the element renders and re-renders, and
|
|
214
240
|
a screen reader hears "bar 9: D". Run them with `bun run test:browser`.
|
|
215
241
|
|
|
216
|
-
[Unreleased]: https://github.com/kevinpeckham/barmark/compare/render-v0.
|
|
242
|
+
[Unreleased]: https://github.com/kevinpeckham/barmark/compare/render-v0.8.0...HEAD
|
|
243
|
+
[0.8.0]: https://github.com/kevinpeckham/barmark/compare/render-v0.7.3...render-v0.8.0
|
|
244
|
+
[0.7.3]: https://github.com/kevinpeckham/barmark/compare/render-v0.7.2...render-v0.7.3
|
|
217
245
|
[0.7.2]: https://github.com/kevinpeckham/barmark/compare/render-v0.7.1...render-v0.7.2
|
|
218
246
|
[0.7.1]: https://github.com/kevinpeckham/barmark/compare/render-v0.7.0...render-v0.7.1
|
|
219
247
|
[0.7.0]: https://github.com/kevinpeckham/barmark/compare/render-v0.6.0...render-v0.7.0
|
package/dist/html.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The chart model as HTML: a <barmark-chart> holding a declarative shadow
|
|
3
|
+
* root, or the bare markup for pages that bring the stylesheet themselves.
|
|
4
|
+
* Every string from the chart is escaped; every bar carries its spoken name.
|
|
5
|
+
*/
|
|
6
|
+
import { printDirection, printPasses, } from "barmark";
|
|
1
7
|
import { glyphs, NUMERALS, splitPitch, typesetChord, typesetNumeral, } from "./chord.js";
|
|
2
8
|
import { escapeHtml } from "./escape.js";
|
|
3
9
|
import { fontFaceCss } from "./font.js";
|
|
@@ -91,9 +97,11 @@ function fitWidth(block) {
|
|
|
91
97
|
return FIT_STEPS.find((step) => step >= total) ?? FIT_STEPS.at(-1) ?? 0;
|
|
92
98
|
}
|
|
93
99
|
/** Em per bar: padding, plus each slot's text and the gap after it. */
|
|
94
|
-
const cellWidth = (cell) => cell.kind === "
|
|
95
|
-
?
|
|
96
|
-
:
|
|
100
|
+
const cellWidth = (cell) => cell.kind === "direction"
|
|
101
|
+
? 1 + 0.55 * printDirection(cell.direction).length
|
|
102
|
+
: cell.kind === "fermata"
|
|
103
|
+
? MIN_BAR
|
|
104
|
+
: Math.max(MIN_BAR, 1.5 + (cell.meter ? 0.8 : 0) + cell.slots.reduce((w, s) => w + slotWidth(s), 0));
|
|
97
105
|
/** Roughly: 0.65em a baseline character, 0.4em a superscript one, 0.6em between slots. */
|
|
98
106
|
function slotWidth(slot) {
|
|
99
107
|
if (slot.kind === "group")
|
|
@@ -145,15 +153,28 @@ function rowHtml(row, columns, settings) {
|
|
|
145
153
|
? `<span class="bm-bar-number" part="bar-number" aria-hidden="true">${row.barNumber ?? ""}</span>`
|
|
146
154
|
: "";
|
|
147
155
|
let n = row.barNumber;
|
|
156
|
+
const label = row.ending ? endingLabel(row.ending) : null;
|
|
148
157
|
const cells = row.cells.map((cell, i) => {
|
|
149
|
-
const
|
|
158
|
+
const ending = i === 0 && label ? ` data-ending="${label.shown}"` : "";
|
|
159
|
+
const html = cellHtml(cell, n, repeatMarks(row, i) + ending, settings.numerals);
|
|
150
160
|
if (n !== null && cell.kind === "slots")
|
|
151
161
|
n++;
|
|
152
162
|
return html;
|
|
153
163
|
});
|
|
154
164
|
const empty = `<span class="bm-empty" aria-hidden="true"></span>`.repeat(Math.max(0, columns - row.cells.length));
|
|
155
165
|
const opens = row.opens || typeof row.repeat === "number" || row.repeat === "open";
|
|
156
|
-
|
|
166
|
+
const ending = label ? `<span class="bm-sr">${label.spoken}</span>` : "";
|
|
167
|
+
return `<div class="bm-row${label ? " bm-ending" : ""}">${opens ? `<span class="bm-sr">repeat start</span>` : ""}${ending}${number}${cells.join("")}${empty}<span class="bm-repeat">${rowRepeatHtml(row)}</span></div>`;
|
|
168
|
+
}
|
|
169
|
+
/** An ending's passes as printed, "1.", "1.–3.", "1., 3.", and spoken, "ending for passes 1 to 3". */
|
|
170
|
+
function endingLabel(passes) {
|
|
171
|
+
const parts = printPasses(passes)
|
|
172
|
+
.split(",")
|
|
173
|
+
.map((part) => part.split("-"));
|
|
174
|
+
const shown = parts.map((p) => p.map((n) => `${n}.`).join("–")).join(", ");
|
|
175
|
+
const said = parts.map((p) => p.join(" to "));
|
|
176
|
+
const list = said.length > 1 ? `${said.slice(0, -1).join(", ")} and ${said.at(-1)}` : said[0];
|
|
177
|
+
return { shown, spoken: `ending for pass${passes.length > 1 ? "es" : ""} ${list}` };
|
|
157
178
|
}
|
|
158
179
|
/** Repeat bar lines: ‖: on a row's first bar, :‖ on its last. */
|
|
159
180
|
function repeatMarks(row, i) {
|
|
@@ -170,8 +191,8 @@ function rowRepeatHtml(row) {
|
|
|
170
191
|
function cellHtml(cell, barNumber, marks, numerals) {
|
|
171
192
|
const view = numerals && cell.kind === "slots" && cell.key ? { style: numerals, key: cell.key } : null;
|
|
172
193
|
const spoken = `<span class="bm-sr">${escapeHtml(speakBar(barNumber, cell, view !== null))}</span>`;
|
|
173
|
-
if (cell.kind
|
|
174
|
-
return
|
|
194
|
+
if (cell.kind !== "slots")
|
|
195
|
+
return betweenBarsHtml(cell, spoken, marks);
|
|
175
196
|
const meter = cell.meter
|
|
176
197
|
? `<span class="bm-meter"><span>${cell.meter.n}</span><span>${cell.meter.d}</span></span>`
|
|
177
198
|
: "";
|
|
@@ -179,6 +200,23 @@ function cellHtml(cell, barNumber, marks, numerals) {
|
|
|
179
200
|
const change = cell.tempoChange ? ` data-tempo-change="${cell.tempoChange}"` : "";
|
|
180
201
|
return `<div class="bm-bar" part="bar"${cell.cut ? " data-cut" : ""}${change}${marks}>${spoken}<span class="bm-bar-body" aria-hidden="true">${meter}${cell.slots.map((s) => slotHtml(s, view)).join("")}${cut}</span></div>`;
|
|
181
202
|
}
|
|
203
|
+
/** A fermata or a direction: a place between bars, drawn as its sign. */
|
|
204
|
+
function betweenBarsHtml(cell, spoken, marks) {
|
|
205
|
+
if (cell.kind === "fermata")
|
|
206
|
+
return `<div class="bm-bar bm-fermata" part="bar"${marks}>${spoken}<span aria-hidden="true">${FERMATA}</span></div>`;
|
|
207
|
+
return `<div class="bm-bar bm-direction" part="direction" data-direction="${cell.direction.kind}"${marks}>${spoken}<span aria-hidden="true">${directionGlyph(cell.direction)}</span></div>`;
|
|
208
|
+
}
|
|
209
|
+
/** Segno and coda as the score's signs; To Coda and the jumps as their words. */
|
|
210
|
+
function directionGlyph(d) {
|
|
211
|
+
if (d.kind === "segno")
|
|
212
|
+
return SEGNO;
|
|
213
|
+
if (d.kind === "coda")
|
|
214
|
+
return CODA;
|
|
215
|
+
const words = `<span class="bm-direction-words">${escapeHtml(printDirection(d))}</span>`;
|
|
216
|
+
return d.kind === "to coda" ? `${words} ${CODA}` : words;
|
|
217
|
+
}
|
|
218
|
+
const SEGNO = `<svg class="bm-sign" viewBox="0 0 20 28" width="0.9em" height="1.25em"><path d="M14.5 6.5c-1-3-6.5-3.5-7.5-.5s3 4.5 4.5 5.5 5 3.5 3 7-7.5 2-8-1" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/><path d="M4 24 16 3" stroke="currentColor" stroke-width="1.6"/><circle cx="4.5" cy="13" r="1.6" fill="currentColor"/><circle cx="15.5" cy="15" r="1.6" fill="currentColor"/></svg>`;
|
|
219
|
+
const CODA = `<svg class="bm-sign" viewBox="0 0 24 24" width="1em" height="1em"><circle cx="12" cy="12" r="6.5" fill="none" stroke="currentColor" stroke-width="2"/><path d="M12 1v22M1 12h22" stroke="currentColor" stroke-width="1.6"/></svg>`;
|
|
182
220
|
const FERMATA = `<svg class="bm-fermata-glyph" viewBox="0 0 24 14" width="1.4em" height="0.8em"><path d="M2 13 A10 10 0 0 1 22 13" fill="none" stroke="currentColor" stroke-width="1.8"/><circle cx="12" cy="11" r="1.8" fill="currentColor"/></svg>`;
|
|
183
221
|
function slotHtml(slot, view) {
|
|
184
222
|
switch (slot.kind) {
|
package/dist/model.js
CHANGED
|
@@ -220,7 +220,7 @@ function layoutRow(l, played, previous, ctx) {
|
|
|
220
220
|
let prev = previous;
|
|
221
221
|
for (const cell of l.cells) {
|
|
222
222
|
const model = layoutCell(cell, played[bar] ?? written[bar], prev);
|
|
223
|
-
if (cell.kind !== "fermata")
|
|
223
|
+
if (cell.kind !== "fermata" && cell.kind !== "direction")
|
|
224
224
|
bar++;
|
|
225
225
|
if (model.kind === "slots")
|
|
226
226
|
prev = model;
|
|
@@ -234,6 +234,7 @@ function layoutRow(l, played, previous, ctx) {
|
|
|
234
234
|
opens: l.opens,
|
|
235
235
|
closes: l.closes,
|
|
236
236
|
repeat: l.repeat,
|
|
237
|
+
ending: l.ending,
|
|
237
238
|
cells,
|
|
238
239
|
};
|
|
239
240
|
}
|
|
@@ -241,6 +242,8 @@ function layoutRow(l, played, previous, ctx) {
|
|
|
241
242
|
function layoutCell(cell, played, previous) {
|
|
242
243
|
if (cell.kind === "fermata")
|
|
243
244
|
return { kind: "fermata" };
|
|
245
|
+
if (cell.kind === "direction")
|
|
246
|
+
return cell;
|
|
244
247
|
const from = cell.kind === "slots" ? cell : previous;
|
|
245
248
|
return withPlayed({
|
|
246
249
|
kind: "slots",
|
package/dist/speech.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Spoken names: how a screen reader reads a chart as music. Built from the
|
|
3
3
|
* parsed structure, never from the symbol's text.
|
|
4
4
|
*/
|
|
5
|
-
import { degreeOf } from "barmark";
|
|
5
|
+
import { degreeOf, } from "barmark";
|
|
6
6
|
const NUMBERS = {
|
|
7
7
|
"1": "one",
|
|
8
8
|
"2": "two",
|
|
@@ -118,10 +118,18 @@ export function speakSlot(slot, key) {
|
|
|
118
118
|
return `${GROUPS[slot.members.length] ?? `group of ${slot.members.length}`}: ${slot.members.map((m) => speakSlot(m, key)).join(", ")}`;
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
|
+
/** "segno", "to coda", "dal segno al coda", "da capo al fine". */
|
|
122
|
+
function speakDirection(d) {
|
|
123
|
+
if (d.kind !== "jump")
|
|
124
|
+
return d.kind;
|
|
125
|
+
return `${d.to === "start" ? "da capo" : "dal segno"}${d.al ? ` al ${d.al}` : ""}`;
|
|
126
|
+
}
|
|
121
127
|
/** "bar 9: G, hold, D, hold"; a fermata is held until cued. With `numerals`, each chord's degree comes first. */
|
|
122
128
|
export function speakBar(barNumber, cell, numerals = false) {
|
|
123
129
|
if (cell.kind === "fermata")
|
|
124
130
|
return "fermata: hold until cued";
|
|
131
|
+
if (cell.kind === "direction")
|
|
132
|
+
return speakDirection(cell.direction);
|
|
125
133
|
const meter = cell.meter ? `, in ${cell.meter.n}/${cell.meter.d}` : "";
|
|
126
134
|
const cut = cell.cut ? ", cut on this pass" : "";
|
|
127
135
|
return `bar${barNumber === null ? "" : ` ${barNumber}`}${meter}: ${cell.slots.map((s) => speakSlot(s, numerals ? cell.key : undefined)).join(", ")}${cut}`;
|
package/dist/styles.js
CHANGED
|
@@ -116,6 +116,12 @@ const css = `
|
|
|
116
116
|
.bm-tuplet::after{border-left:none;margin-left:.3em}
|
|
117
117
|
.bm-meter{display:inline-flex;flex-direction:column;align-items:center;align-self:center;font:700 ${em(SIZES.meter)}/.95 ${SANS};color:var(--bm-muted)}
|
|
118
118
|
.bm-fermata-glyph{display:block;color:var(--bm-ink)}
|
|
119
|
+
.bm-row>div.bm-direction{min-width:0;padding-inline:.35em;border-right:none;align-items:flex-start;color:var(--bm-ink)}
|
|
120
|
+
.bm-sign{display:inline-block;vertical-align:middle}
|
|
121
|
+
.bm-direction-words{font:italic ${em(SIZES.repeat)}/1.2 Georgia,serif;white-space:nowrap}
|
|
122
|
+
.bm-bar[data-ending]::after{content:attr(data-ending);position:absolute;left:.45em;top:.2em;font:italic .75em/1 Georgia,serif;color:var(--bm-muted)}
|
|
123
|
+
.bm-ending>.bm-bar{border-top:.06em solid var(--bm-muted)}
|
|
124
|
+
.bm-ending>.bm-bar[data-ending]{box-shadow:inset .06em 0 var(--bm-muted)}
|
|
119
125
|
.bm-bar[data-tempo-change]::before{content:"";position:absolute;inset:.15em 0 auto;border-top:.08em dashed var(--barmark-directive,var(--bm-muted))}
|
|
120
126
|
.bm-bar[data-cut]{outline:.08em dashed var(--bm-muted);outline-offset:-.25em}
|
|
121
127
|
.bm-cut{position:absolute;top:.3em;right:.45em;font:${em(SIZES.cut)}/1 ${SANS};color:var(--bm-muted)}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ChartDiagnostic, Directives, Key, Meter, Repeat, Slot } from "barmark";
|
|
1
|
+
import type { ChartDiagnostic, Direction, Directives, Key, Meter, Repeat, Slot } from "barmark";
|
|
2
2
|
export type LayoutOptions = {
|
|
3
3
|
rehearsalLetters?: boolean;
|
|
4
4
|
};
|
|
@@ -30,6 +30,8 @@ export type BlockItem = {
|
|
|
30
30
|
opens: boolean;
|
|
31
31
|
closes: boolean;
|
|
32
32
|
repeat: Repeat;
|
|
33
|
+
/** The passes an ending plays on; null for any other row. */
|
|
34
|
+
ending: number[] | null;
|
|
33
35
|
cells: CellModel[];
|
|
34
36
|
}
|
|
35
37
|
/** `note` marks a `###` line, whose text is given without the marks. */
|
|
@@ -57,4 +59,9 @@ export type CellModel = {
|
|
|
57
59
|
key?: Key;
|
|
58
60
|
} | {
|
|
59
61
|
kind: "fermata";
|
|
62
|
+
}
|
|
63
|
+
/** A segno, coda or jump between bars (barmark's grammar 5.2). */
|
|
64
|
+
| {
|
|
65
|
+
kind: "direction";
|
|
66
|
+
direction: Direction;
|
|
60
67
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barmark/render",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Render barmark chord charts as a clean, accessible bar grid: HTML with Declarative Shadow DOM, or the <barmark-chart> web component.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"url": "https://github.com/kevinpeckham/barmark/issues"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"barmark": "^0.
|
|
50
|
+
"barmark": "^0.11.0"
|
|
51
51
|
},
|
|
52
52
|
"scripts": {
|
|
53
53
|
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|