@sudobility/music_drawing 0.0.1
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/dist/canvas-renderer.d.ts +180 -0
- package/dist/canvas-renderer.d.ts.map +1 -0
- package/dist/canvas-renderer.js +831 -0
- package/dist/canvas-renderer.js.map +1 -0
- package/dist/convert.d.ts +122 -0
- package/dist/convert.d.ts.map +1 -0
- package/dist/convert.js +426 -0
- package/dist/convert.js.map +1 -0
- package/dist/display-timing.d.ts +68 -0
- package/dist/display-timing.d.ts.map +1 -0
- package/dist/display-timing.js +192 -0
- package/dist/display-timing.js.map +1 -0
- package/dist/icon-canvas.d.ts +12 -0
- package/dist/icon-canvas.d.ts.map +1 -0
- package/dist/icon-canvas.js +62 -0
- package/dist/icon-canvas.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/layout.d.ts +159 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +362 -0
- package/dist/layout.js.map +1 -0
- package/dist/measure-content.d.ts +114 -0
- package/dist/measure-content.d.ts.map +1 -0
- package/dist/measure-content.js +553 -0
- package/dist/measure-content.js.map +1 -0
- package/dist/note-color.d.ts +48 -0
- package/dist/note-color.d.ts.map +1 -0
- package/dist/note-color.js +81 -0
- package/dist/note-color.js.map +1 -0
- package/dist/pagination.d.ts +57 -0
- package/dist/pagination.d.ts.map +1 -0
- package/dist/pagination.js +152 -0
- package/dist/pagination.js.map +1 -0
- package/dist/percussion.d.ts +47 -0
- package/dist/percussion.d.ts.map +1 -0
- package/dist/percussion.js +172 -0
- package/dist/percussion.js.map +1 -0
- package/dist/playhead.d.ts +37 -0
- package/dist/playhead.d.ts.map +1 -0
- package/dist/playhead.js +89 -0
- package/dist/playhead.js.map +1 -0
- package/dist/test/canvas-stub.d.ts +21 -0
- package/dist/test/canvas-stub.d.ts.map +1 -0
- package/dist/test/canvas-stub.js +51 -0
- package/dist/test/canvas-stub.js.map +1 -0
- package/dist/test/fixtures.d.ts +47 -0
- package/dist/test/fixtures.d.ts.map +1 -0
- package/dist/test/fixtures.js +433 -0
- package/dist/test/fixtures.js.map +1 -0
- package/dist/types.d.ts +74 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/package.json +62 -0
package/dist/layout.js
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import { snappedOnsetTicks } from './display-timing.js';
|
|
2
|
+
export const BASE_MEASURE_WIDTH = 160;
|
|
3
|
+
/**
|
|
4
|
+
* Width of a measure standing in for a run of silent ones.
|
|
5
|
+
*
|
|
6
|
+
* Fixed rather than proportional to the count: a 60-bar rest should not be
|
|
7
|
+
* thirty times wider than a 2-bar one. It only has to fit the horizontal bar
|
|
8
|
+
* and a numeral.
|
|
9
|
+
*/
|
|
10
|
+
const MULTI_REST_MEASURE_WIDTH = 320;
|
|
11
|
+
/**
|
|
12
|
+
* Width budget per drawn onset.
|
|
13
|
+
*
|
|
14
|
+
* Measured, not guessed: `Formatter.preCalculateMinTotalWidth` asks 446 units
|
|
15
|
+
* for a bar of sixteen sixteenths, which is 27.9 each, and the same 27.9
|
|
16
|
+
* whether or not every one of them carries an accidental. Rounded up to 28, so
|
|
17
|
+
* a dense bar is given at least what VexFlow will ask for and never has to
|
|
18
|
+
* overflow into the next measure's space — which would break the shared
|
|
19
|
+
* barline across tracks.
|
|
20
|
+
*
|
|
21
|
+
* A measure with more onsets than the base width comfortably holds grows
|
|
22
|
+
* proportionally (`max(BASE, onsets * SLOT + PADDING)`), and the count is the
|
|
23
|
+
* union across every track, so all tracks agree on the shared width.
|
|
24
|
+
*/
|
|
25
|
+
export const NOTE_SLOT_WIDTH = 28;
|
|
26
|
+
/** Breathing room added to a density-derived measure width (start padding + last note's stem/flag overhang + barline clearance). */
|
|
27
|
+
export const DENSE_MEASURE_PADDING = 35;
|
|
28
|
+
/** Extra width reserved on a system's first measure for clef + key signature + time signature. */
|
|
29
|
+
export const SYSTEM_HEADER_WIDTH = 90;
|
|
30
|
+
/**
|
|
31
|
+
* The least room the music is ever given, whatever the window does.
|
|
32
|
+
*
|
|
33
|
+
* Only a guard against a zero or negative budget once the gutter and margins
|
|
34
|
+
* are taken out of a very narrow viewport. Music this cramped is unreadable,
|
|
35
|
+
* but it is *there* — better than the right-hand part of every bar being
|
|
36
|
+
* silently clipped away, which is what a larger floor used to cause.
|
|
37
|
+
*/
|
|
38
|
+
const MIN_CONTENT_WIDTH = 80;
|
|
39
|
+
/** The whole vertical row one track occupies in a system. Exported so the renderer can cull staves against the viewport. */
|
|
40
|
+
export const STAVE_HEIGHT = 100;
|
|
41
|
+
/**
|
|
42
|
+
* Vertical distance between adjacent staff positions, in logical units — one
|
|
43
|
+
* line to the space beside it.
|
|
44
|
+
*
|
|
45
|
+
* VexFlow spaces its five lines 10 units apart and the renderer does not
|
|
46
|
+
* override that, so a staff position is half of it. Exported because dragging a
|
|
47
|
+
* note up and down the staff needs to turn pixels into positions, and deriving
|
|
48
|
+
* that from `STAVE_HEIGHT` would be wrong: that is the whole row a track is
|
|
49
|
+
* given, padding included, not the ruled staff.
|
|
50
|
+
*/
|
|
51
|
+
export const STAVE_POSITION_HEIGHT = 5;
|
|
52
|
+
/**
|
|
53
|
+
* Distance from a track box's top edge down to the stave's **top line**.
|
|
54
|
+
*
|
|
55
|
+
* VexFlow reserves headroom above the ruled staff for anything that sits over
|
|
56
|
+
* it — `space_above_staff_ln: 4` line-spaces, at its 10-unit line distance — so
|
|
57
|
+
* the `y` a `Stave` is constructed with is not where the music starts. Exported
|
|
58
|
+
* because the track-info gutter aligns its instrument row to the first line the
|
|
59
|
+
* reader actually sees, and eyeballing that offset would drift the moment the
|
|
60
|
+
* stave's options changed.
|
|
61
|
+
*/
|
|
62
|
+
export const STAVE_TOP_LINE_OFFSET = 8 * STAVE_POSITION_HEIGHT;
|
|
63
|
+
const TRACK_GAP = 20;
|
|
64
|
+
const SYSTEM_GAP = 40;
|
|
65
|
+
const LEFT_MARGIN = 10;
|
|
66
|
+
/**
|
|
67
|
+
* Width reserved at the left of every system for the track-info gutter (track
|
|
68
|
+
* name, instrument, mute/solo), and the width the app's track editing panel
|
|
69
|
+
* matches.
|
|
70
|
+
*
|
|
71
|
+
* Reserving it here is what makes the gutter's alignment with the staves
|
|
72
|
+
* structural: it lives in the same coordinate space, so there is nothing to
|
|
73
|
+
* synchronise. The previous approach positioned a DOM list against reported
|
|
74
|
+
* geometry and had to be stopped from scrolling independently.
|
|
75
|
+
*/
|
|
76
|
+
export const TRACK_INFO_WIDTH = 220;
|
|
77
|
+
const TOP_MARGIN = 10;
|
|
78
|
+
/**
|
|
79
|
+
* Height of the measure-number band above each system's top stave (also the
|
|
80
|
+
* click target for measure selection).
|
|
81
|
+
*
|
|
82
|
+
* Systems after the first take it out of `SYSTEM_GAP`, so nothing reflows.
|
|
83
|
+
* The first system has only `TOP_MARGIN` above it — not enough — so the
|
|
84
|
+
* effective top margin is raised by this much, which is why `totalHeight`
|
|
85
|
+
* grows by twice this value (the margin is counted at both ends).
|
|
86
|
+
*/
|
|
87
|
+
export const MEASURE_HEADER_HEIGHT = 18;
|
|
88
|
+
/** Guards against a zero/negative/non-finite zoom breaking division or `context.scale`. */
|
|
89
|
+
export function resolveZoom(zoom) {
|
|
90
|
+
return zoom > 0 ? zoom : 1;
|
|
91
|
+
}
|
|
92
|
+
/** Tracks to render, in `options.trackIds` order when given (unknown ids are dropped); else score order. */
|
|
93
|
+
function selectTracks(score, options) {
|
|
94
|
+
if (!options.trackIds || options.trackIds.length === 0) {
|
|
95
|
+
return score.tracks;
|
|
96
|
+
}
|
|
97
|
+
const byId = new Map(score.tracks.map(t => [t.id, t]));
|
|
98
|
+
return options.trackIds
|
|
99
|
+
.map(id => byId.get(id))
|
|
100
|
+
.filter((t) => t !== undefined);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Greedily groups measure indices into systems (rows) so each row's total width
|
|
104
|
+
* fits `maxWidth`.
|
|
105
|
+
*
|
|
106
|
+
* `measureWidth` is asked for the width a measure would have *in the position
|
|
107
|
+
* being considered*, because only the first measure of a system carries the
|
|
108
|
+
* clef, key and time signature. Packing used to assume every measure might be
|
|
109
|
+
* that one and add the header to all of them — an upper bound that never
|
|
110
|
+
* overflowed but routinely left a system a bar short of what it could hold.
|
|
111
|
+
*/
|
|
112
|
+
function groupIntoSystems(measureCount, measureWidth, maxWidth) {
|
|
113
|
+
const systems = [];
|
|
114
|
+
let current = [];
|
|
115
|
+
let currentWidth = 0;
|
|
116
|
+
for (let i = 0; i < measureCount; i += 1) {
|
|
117
|
+
const width = measureWidth(i, current.length === 0);
|
|
118
|
+
if (current.length > 0 && currentWidth + width > maxWidth) {
|
|
119
|
+
systems.push(current);
|
|
120
|
+
current = [];
|
|
121
|
+
currentWidth = measureWidth(i, true);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
currentWidth += width;
|
|
125
|
+
}
|
|
126
|
+
current.push(i);
|
|
127
|
+
}
|
|
128
|
+
if (current.length > 0)
|
|
129
|
+
systems.push(current);
|
|
130
|
+
return systems;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Computes per-track, per-measure stave boxes and per-system outer bounds
|
|
134
|
+
* (for brace/connector drawing), all in LOGICAL (unscaled) units — see the
|
|
135
|
+
* module doc. Assumes all selected tracks share the same measure count; a
|
|
136
|
+
* track with fewer measures than the score's max simply has no box for the
|
|
137
|
+
* missing trailing measures (defensive, not expected in practice — spec §4
|
|
138
|
+
* keeps tracks aligned to the same measure grid).
|
|
139
|
+
*/
|
|
140
|
+
export function computeLayout(score, options) {
|
|
141
|
+
const zoom = resolveZoom(options.zoom);
|
|
142
|
+
const tracks = selectTracks(score, options);
|
|
143
|
+
const measureCount = tracks.reduce((max, t) => Math.max(max, t.measures.length), 0);
|
|
144
|
+
const headerWidth = SYSTEM_HEADER_WIDTH;
|
|
145
|
+
const staveHeight = STAVE_HEIGHT;
|
|
146
|
+
const trackGap = TRACK_GAP;
|
|
147
|
+
const systemGap = SYSTEM_GAP;
|
|
148
|
+
// The gutter's reserved column is part of the left margin, so turning the
|
|
149
|
+
// gutter off has to shrink the margin too — otherwise the space stays blank
|
|
150
|
+
// and the music is simply indented for no reason.
|
|
151
|
+
const showTrackInfo = options.showTrackInfo ?? true;
|
|
152
|
+
const leftMargin = LEFT_MARGIN + (showTrackInfo ? TRACK_INFO_WIDTH : 0);
|
|
153
|
+
// Trailing margin is the plain page margin, NOT `leftMargin`: that one
|
|
154
|
+
// carries the track-info gutter's reserved column, and mirroring it on the
|
|
155
|
+
// right padded every page-mode layout with a phantom TRACK_INFO_WIDTH of
|
|
156
|
+
// empty space — enough to push `totalWidth` past the viewport and give page
|
|
157
|
+
// mode a horizontal scrollbar it should never have had.
|
|
158
|
+
const rightMargin = LEFT_MARGIN;
|
|
159
|
+
const topMargin = TOP_MARGIN + MEASURE_HEADER_HEIGHT;
|
|
160
|
+
// Density-aware per-measure widths (see NOTE_SLOT_WIDTH's doc): one shared
|
|
161
|
+
// width per measure index across every track, from the densest voice.
|
|
162
|
+
const contentWidths = Array.from({ length: measureCount }, (_, measureIndex) => {
|
|
163
|
+
// Count the distinct onsets the measure will actually draw, across every
|
|
164
|
+
// track — that is one tick context each, and VexFlow's minimum width is
|
|
165
|
+
// proportional to how many there are.
|
|
166
|
+
//
|
|
167
|
+
// Not `voice.events.length`: recorded events and drawn tickables are not
|
|
168
|
+
// the same thing. A drum bar of 37 hits three ticks apart draws as 16 once
|
|
169
|
+
// they are chorded, and giving it width for 37 left it half empty and
|
|
170
|
+
// pushed every other bar onto its own line.
|
|
171
|
+
const onsets = new Set();
|
|
172
|
+
for (const track of tracks) {
|
|
173
|
+
const measure = track.measures[measureIndex];
|
|
174
|
+
if (!measure)
|
|
175
|
+
continue;
|
|
176
|
+
for (const voice of measure.voices) {
|
|
177
|
+
for (const tick of snappedOnsetTicks(voice.events, measure.startTick, measure.durationTicks, score.ppq)) {
|
|
178
|
+
onsets.add(tick);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const maxEvents = onsets.size;
|
|
183
|
+
// A collapsed measure's width comes from what it draws — one bar and a
|
|
184
|
+
// number — not from the events it replaced, of which there are none.
|
|
185
|
+
const collapsed = tracks.some(track => track.measures[measureIndex]?.multiMeasureRestCount !== undefined);
|
|
186
|
+
if (collapsed)
|
|
187
|
+
return MULTI_REST_MEASURE_WIDTH;
|
|
188
|
+
return Math.max(BASE_MEASURE_WIDTH, maxEvents * NOTE_SLOT_WIDTH + DENSE_MEASURE_PADDING);
|
|
189
|
+
});
|
|
190
|
+
const widthOf = (measureIndex, isFirstInSystem) => (contentWidths[measureIndex] ?? BASE_MEASURE_WIDTH) +
|
|
191
|
+
(isFirstInSystem ? headerWidth : 0);
|
|
192
|
+
// `options.width` is a screen-pixel budget; convert to the equivalent
|
|
193
|
+
// logical-unit budget by dividing out zoom (a more zoomed-in view fits
|
|
194
|
+
// fewer logical pixels in the same screen width) before packing systems.
|
|
195
|
+
//
|
|
196
|
+
//
|
|
197
|
+
// Both margins come out of the budget, so a packed system's right edge lands
|
|
198
|
+
// at or inside the viewport and page mode's `totalWidth` below can be exactly
|
|
199
|
+
// that. What is left is what the music itself may occupy.
|
|
200
|
+
//
|
|
201
|
+
// This used to be floored at what one measure needs *with* its margins, which
|
|
202
|
+
// meant that below about 530 logical units the layout simply stopped
|
|
203
|
+
// shrinking — and since page mode hides horizontal overflow, everything past
|
|
204
|
+
// the viewport was permanently invisible. A narrow window clipped it, and so
|
|
205
|
+
// did zooming in, which divides the same window by more. The floor here is
|
|
206
|
+
// only a guard against a zero or negative budget; the per-system scaling
|
|
207
|
+
// below is what keeps the music inside it.
|
|
208
|
+
const contentBudget = options.layoutMode === 'continuous'
|
|
209
|
+
? Number.POSITIVE_INFINITY
|
|
210
|
+
: Math.max(MIN_CONTENT_WIDTH, options.width / zoom - leftMargin - rightMargin);
|
|
211
|
+
const systemsOfIndices = groupIntoSystems(measureCount, widthOf, contentBudget);
|
|
212
|
+
const rowHeight = (count) => count > 0 ? count * staveHeight + Math.max(0, count - 1) * trackGap : 0;
|
|
213
|
+
const trackRowHeight = rowHeight(tracks.length);
|
|
214
|
+
const trackLayouts = tracks.map(track => ({
|
|
215
|
+
track,
|
|
216
|
+
measures: [],
|
|
217
|
+
}));
|
|
218
|
+
const systems = [];
|
|
219
|
+
let maxSystemRight = 0;
|
|
220
|
+
systemsOfIndices.forEach((measureIndices, systemIndex) => {
|
|
221
|
+
const yTop = topMargin + systemIndex * (trackRowHeight + systemGap);
|
|
222
|
+
const yBottom = yTop + trackRowHeight;
|
|
223
|
+
/**
|
|
224
|
+
* Extra width spread across this system's measures so it reaches the
|
|
225
|
+
* margin — what engravers call justification.
|
|
226
|
+
*
|
|
227
|
+
* Without it a system stops wherever the next measure would not have fit,
|
|
228
|
+
* leaving up to a whole measure's width blank at the right. That reads as
|
|
229
|
+
* a mistake on paper and no better on screen.
|
|
230
|
+
*
|
|
231
|
+
* The last system is deliberately left at its natural width: a final line
|
|
232
|
+
* of two bars stretched across the page looks worse than a short one.
|
|
233
|
+
* Continuous mode is never justified — it has no right margin to reach.
|
|
234
|
+
*/
|
|
235
|
+
const naturalWidth = measureIndices.reduce((sum, index, position) => sum + widthOf(index, position === 0), 0);
|
|
236
|
+
const isLastSystem = systemIndex === systemsOfIndices.length - 1;
|
|
237
|
+
// Where this system's right edge should land.
|
|
238
|
+
//
|
|
239
|
+
// Too wide always shrinks, whatever the mode and whichever system it is:
|
|
240
|
+
// page mode hides horizontal overflow, so a system that does not fit is not
|
|
241
|
+
// merely ugly, it is partly unreadable — which is what happened to a bar
|
|
242
|
+
// dense enough to exceed the budget on its own. Only *stretching* is
|
|
243
|
+
// optional, and stays as it was: page mode, and not the final system.
|
|
244
|
+
const target = naturalWidth > contentBudget ||
|
|
245
|
+
(options.layoutMode === 'page' && !isLastSystem)
|
|
246
|
+
? contentBudget
|
|
247
|
+
: naturalWidth;
|
|
248
|
+
// Shared out in proportion to what each measure already occupies, so a
|
|
249
|
+
// dense bar keeps its extra room rather than a sparse one being padded to
|
|
250
|
+
// match it — and, shrinking, gives up room in proportion too.
|
|
251
|
+
const scale = naturalWidth > 0 ? target / naturalWidth : 1;
|
|
252
|
+
let cursorX = leftMargin;
|
|
253
|
+
measureIndices.forEach((measureIndex, positionInSystem) => {
|
|
254
|
+
const isFirstInSystem = positionInSystem === 0;
|
|
255
|
+
const naturalMeasureWidth = widthOf(measureIndex, isFirstInSystem);
|
|
256
|
+
// Rounded to whole units, then the last measure absorbs whatever the
|
|
257
|
+
// rounding lost — otherwise the system lands a pixel or two short and
|
|
258
|
+
// the final barline never quite meets the margin.
|
|
259
|
+
const isLastInSystem = positionInSystem === measureIndices.length - 1;
|
|
260
|
+
const width = isLastInSystem
|
|
261
|
+
? leftMargin + target - cursorX
|
|
262
|
+
: naturalMeasureWidth * scale;
|
|
263
|
+
tracks.forEach((track, trackIndex) => {
|
|
264
|
+
if (measureIndex >= track.measures.length)
|
|
265
|
+
return;
|
|
266
|
+
const y = yTop + trackIndex * (staveHeight + trackGap);
|
|
267
|
+
trackLayouts[trackIndex].measures.push({
|
|
268
|
+
measureIndex,
|
|
269
|
+
isFirstInSystem,
|
|
270
|
+
box: { x: cursorX, y, width, height: staveHeight },
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
cursorX += width;
|
|
274
|
+
});
|
|
275
|
+
maxSystemRight = Math.max(maxSystemRight, cursorX);
|
|
276
|
+
systems.push({
|
|
277
|
+
measureIndices,
|
|
278
|
+
xLeft: leftMargin,
|
|
279
|
+
xRight: cursorX,
|
|
280
|
+
gutterTop: yTop - MEASURE_HEADER_HEIGHT,
|
|
281
|
+
yTop,
|
|
282
|
+
yBottom,
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
const totalHeight = systems.length > 0
|
|
286
|
+
? systems[systems.length - 1].yBottom + topMargin
|
|
287
|
+
: topMargin * 2;
|
|
288
|
+
// Both terms are logical units here: `maxSystemRight` is logical by
|
|
289
|
+
// construction, and `options.width` (a screen-pixel budget) is divided by
|
|
290
|
+
// zoom to match, same as `logicalAvailableWidth` above.
|
|
291
|
+
const totalWidth = Math.max(maxSystemRight + rightMargin, options.layoutMode === 'page' ? options.width / zoom : 0);
|
|
292
|
+
return { tracks, trackLayouts, systems, totalWidth, totalHeight };
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* The stave box for `measureIndex` on `plan.trackLayouts[trackIndex]`
|
|
296
|
+
* (logical units), or `null` if that track/measure index isn't present in
|
|
297
|
+
* the plan. Lets a caller locate a measure's position (e.g. to scroll to
|
|
298
|
+
* it during playback) directly from layout, independent of whether that
|
|
299
|
+
* measure fell inside the drawn window of a windowed canvas render pass
|
|
300
|
+
* (system boxes never shift with what a given frame chooses to draw).
|
|
301
|
+
*/
|
|
302
|
+
export function boxForMeasureIndex(plan, trackIndex, measureIndex) {
|
|
303
|
+
const trackLayout = plan.trackLayouts[trackIndex];
|
|
304
|
+
if (!trackLayout)
|
|
305
|
+
return null;
|
|
306
|
+
return (trackLayout.measures.find(m => m.measureIndex === measureIndex)?.box ?? null);
|
|
307
|
+
}
|
|
308
|
+
/** Binary search over the y-sorted `plan.systems` for the system containing logical `y`; `null` in inter-system gaps or outside the score. O(log n). */
|
|
309
|
+
export function systemAtY(plan, y) {
|
|
310
|
+
const systems = plan.systems;
|
|
311
|
+
let lo = 0;
|
|
312
|
+
let hi = systems.length - 1;
|
|
313
|
+
while (lo <= hi) {
|
|
314
|
+
const mid = (lo + hi) >> 1;
|
|
315
|
+
const s = systems[mid];
|
|
316
|
+
if (y < s.yTop)
|
|
317
|
+
hi = mid - 1;
|
|
318
|
+
else if (y > s.yBottom)
|
|
319
|
+
lo = mid + 1;
|
|
320
|
+
else
|
|
321
|
+
return s;
|
|
322
|
+
}
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Binary search over `system`'s x-sorted measures (first track's layouts)
|
|
327
|
+
* for the measure containing logical `x`, clamping x into the system's
|
|
328
|
+
* measure span (clicks left of the clef resolve to the first measure,
|
|
329
|
+
* right of the last barline to the last). `null` only when the system
|
|
330
|
+
* resolves to no measure layouts. O(log n).
|
|
331
|
+
*
|
|
332
|
+
* Indexing note: `computeLayout` pushes one `MeasureLayout` per measure in
|
|
333
|
+
* ascending order for every track that has the measure, so for track 0
|
|
334
|
+
* `trackLayouts[0].measures[i].measureIndex === i` — direct indexing by
|
|
335
|
+
* `system.measureIndices` values is safe (and bounds-guarded here anyway).
|
|
336
|
+
*/
|
|
337
|
+
export function measureAtXInSystem(plan, system, x) {
|
|
338
|
+
const measures = plan.trackLayouts[0]?.measures;
|
|
339
|
+
if (!measures || system.measureIndices.length === 0)
|
|
340
|
+
return null;
|
|
341
|
+
const first = measures[system.measureIndices[0]];
|
|
342
|
+
const last = measures[system.measureIndices[system.measureIndices.length - 1]];
|
|
343
|
+
if (!first || !last)
|
|
344
|
+
return null;
|
|
345
|
+
const clamped = Math.min(Math.max(x, first.box.x), last.box.x + last.box.width - 1e-9);
|
|
346
|
+
let lo = 0;
|
|
347
|
+
let hi = system.measureIndices.length - 1;
|
|
348
|
+
while (lo <= hi) {
|
|
349
|
+
const mid = (lo + hi) >> 1;
|
|
350
|
+
const m = measures[system.measureIndices[mid]];
|
|
351
|
+
if (!m)
|
|
352
|
+
return null;
|
|
353
|
+
if (clamped < m.box.x)
|
|
354
|
+
hi = mid - 1;
|
|
355
|
+
else if (clamped >= m.box.x + m.box.width)
|
|
356
|
+
lo = mid + 1;
|
|
357
|
+
else
|
|
358
|
+
return m;
|
|
359
|
+
}
|
|
360
|
+
return last;
|
|
361
|
+
}
|
|
362
|
+
//# sourceMappingURL=layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout.js","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared VexFlow measure-content builders (spec §7, §26): turn one domain
|
|
3
|
+
* measure into its `Stave`/`Voice`s/`Beam`s, and a track's accumulated
|
|
4
|
+
* note channels into `StaveTie`s. Consumed by `canvas-renderer.ts` per
|
|
5
|
+
* drawn system; kept renderer-agnostic (nothing here draws or touches a
|
|
6
|
+
* context/backend).
|
|
7
|
+
*
|
|
8
|
+
* Pure adapter: no store/React imports (spec §3, §37).
|
|
9
|
+
*/
|
|
10
|
+
import { Beam, MultiMeasureRest, Stave, Curve, StaveHairpin, StaveLine, TextBracket, StaveTie, Tuplet, Voice } from 'vexflow';
|
|
11
|
+
import type { StaveNote } from 'vexflow';
|
|
12
|
+
import type { Measure, Track } from '@sudobility/music_types';
|
|
13
|
+
import type { NoteMeta } from './convert.js';
|
|
14
|
+
import type { MeasureLayout } from './layout.js';
|
|
15
|
+
/**
|
|
16
|
+
* Glyph scale for cue notes. VexFlow's default is 38; grace notes use a
|
|
17
|
+
* comparable reduction. Small enough to read as "not yours" at a glance,
|
|
18
|
+
* large enough to read at all.
|
|
19
|
+
*/
|
|
20
|
+
export declare const CUE_GLYPH_SCALE = 26;
|
|
21
|
+
/** Cue labels are small italic by convention, and must not crowd the bar number. */
|
|
22
|
+
export declare const CUE_LABEL_FONT_SIZE = 9;
|
|
23
|
+
/** A single note/rest "channel" (spec §25 voice-ordinal convention, mirrored from `domain/score/ties.ts`) accumulated across a track's measures, for cross-measure/cross-decomposition tie detection. */
|
|
24
|
+
export type Channel = Array<{
|
|
25
|
+
note: StaveNote;
|
|
26
|
+
meta: NoteMeta;
|
|
27
|
+
}>;
|
|
28
|
+
/** Builds one measure's `Stave`, its VexFlow `Voice`s, and its beams; records notes into `channels` for tie building. */
|
|
29
|
+
export declare function buildMeasureContent(measure: Measure, track: Track,
|
|
30
|
+
/**
|
|
31
|
+
* This measure's position in `track.measures`.
|
|
32
|
+
*
|
|
33
|
+
* Passed in rather than read off `measure.index`: a print-only part collapses
|
|
34
|
+
* runs of rests, which shortens the array while every surviving measure keeps
|
|
35
|
+
* its original `index`, so the two stop agreeing. The caller iterates the
|
|
36
|
+
* array and knows the truth.
|
|
37
|
+
*/
|
|
38
|
+
measureIndex: number, placement: MeasureLayout, prevMeasure: Measure | undefined,
|
|
39
|
+
/** The bar after this one, for deriving where a volta bracket ends. */
|
|
40
|
+
nextMeasure: Measure | undefined, ppq: number, channels: Map<number, Channel>, allMetas: NoteMeta[]): {
|
|
41
|
+
stave: Stave;
|
|
42
|
+
voices: Voice[];
|
|
43
|
+
beams: Beam[];
|
|
44
|
+
/** Tuplet brackets and their numbers, drawn after the voices like beams. */
|
|
45
|
+
tuplets: Tuplet[];
|
|
46
|
+
multiMeasureRest?: MultiMeasureRest;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Ties every adjacent pair of notes in a voice-ordinal channel whose keys
|
|
50
|
+
* actually match by pitch — covers both cross-barline ties and same-measure
|
|
51
|
+
* duration-decomposition ties with one mechanism. Matches by pitch, never
|
|
52
|
+
* by array index/adjacency (a chord where only one of several members ties
|
|
53
|
+
* forward must produce exactly one `StaveTie`, on the correct key indices
|
|
54
|
+
* on each side — see `KeyTie`'s doc in convert.ts and
|
|
55
|
+
* `domain/score/ties.ts`'s `findForwardPartner`/`findBackwardPartner`,
|
|
56
|
+
* which requires the same tick/pitch/tie-flag matching for the same
|
|
57
|
+
* reason: a same-tick note sharing a channel by coincidence must not be
|
|
58
|
+
* spliced in).
|
|
59
|
+
*/
|
|
60
|
+
export declare function buildTies(channel: Channel,
|
|
61
|
+
/**
|
|
62
|
+
* Whether a note was actually drawn this frame.
|
|
63
|
+
*
|
|
64
|
+
* A tie is positioned from its two notes' Y values, which only exist once
|
|
65
|
+
* the note has been drawn — so a tie to a note on a culled (off-screen)
|
|
66
|
+
* stave throws `NoYValues` inside VexFlow. Filtering here rather than
|
|
67
|
+
* catching there is what keeps one such pair from taking the rest of the
|
|
68
|
+
* channel's ties with it.
|
|
69
|
+
*/
|
|
70
|
+
isDrawn?: (note: StaveNote) => boolean): StaveTie[];
|
|
71
|
+
/**
|
|
72
|
+
* Draws each phrase mark in a channel as one curve, start note to stop note.
|
|
73
|
+
*
|
|
74
|
+
* Unlike `buildTies` this does not work on adjacent pairs: a slur spans a run
|
|
75
|
+
* of notes, and the pair to join is "the note that opened it" and "the next
|
|
76
|
+
* one that closes it". Matching is by order rather than by pitch for the same
|
|
77
|
+
* reason — a slur groups different pitches, which is what distinguishes it
|
|
78
|
+
* from a tie.
|
|
79
|
+
*
|
|
80
|
+
* `isDrawn` filters the same hazard ties have: a curve is positioned from its
|
|
81
|
+
* two notes' Y values, which only exist once they have been drawn, so one
|
|
82
|
+
* reaching a culled off-screen stave would throw inside VexFlow.
|
|
83
|
+
*/
|
|
84
|
+
/**
|
|
85
|
+
* Draws each hairpin in a channel as one wedge, opening note to closing note.
|
|
86
|
+
*
|
|
87
|
+
* `buildSlurs`' structure exactly, because a hairpin is the same kind of span:
|
|
88
|
+
* opened by one note, closed by a later one, matched in order rather than by
|
|
89
|
+
* pitch. It carries the same culling hazard too — a wedge is positioned from
|
|
90
|
+
* its two notes' Y values, which only exist once they have been drawn, so one
|
|
91
|
+
* reaching a culled off-screen stave throws inside VexFlow.
|
|
92
|
+
*
|
|
93
|
+
* Below the stave, where the dynamics already sit, so a level and the change
|
|
94
|
+
* between two levels read as one system rather than two.
|
|
95
|
+
*/
|
|
96
|
+
export declare function buildHairpins(channel: Channel, isDrawn?: (note: StaveNote) => boolean): StaveHairpin[];
|
|
97
|
+
/**
|
|
98
|
+
* The octave brackets in a channel, one `TextBracket` per span.
|
|
99
|
+
*
|
|
100
|
+
* Above the stave for `8va`/`15ma` and below for the `b` forms, which is where
|
|
101
|
+
* the displacement points. Culled like the curves: a bracket is positioned
|
|
102
|
+
* from its two notes, so one reaching an off-screen stave throws.
|
|
103
|
+
*/
|
|
104
|
+
export declare function buildOttavas(channel: Channel, isDrawn?: (note: StaveNote) => boolean): TextBracket[];
|
|
105
|
+
/**
|
|
106
|
+
* The slides in a channel, drawn as a straight line between the two notes.
|
|
107
|
+
*
|
|
108
|
+
* VexFlow has no `Glissando`; a `StaveLine` between the notes is what its own
|
|
109
|
+
* examples use, and it is what a glissando is on the page — a line from one
|
|
110
|
+
* notehead to the next.
|
|
111
|
+
*/
|
|
112
|
+
export declare function buildGlissandos(channel: Channel, isDrawn?: (note: StaveNote) => boolean): StaveLine[];
|
|
113
|
+
export declare function buildSlurs(channel: Channel, isDrawn?: (note: StaveNote) => boolean): Curve[];
|
|
114
|
+
//# sourceMappingURL=measure-content.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"measure-content.d.ts","sourceRoot":"","sources":["../src/measure-content.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAEL,IAAI,EACJ,gBAAgB,EAChB,KAAK,EAGL,KAAK,EAGL,YAAY,EACZ,SAAS,EACT,WAAW,EACX,QAAQ,EACR,MAAM,EAIN,KAAK,EACN,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAE,SAAS,EAAiB,MAAM,SAAS,CAAC;AAExD,OAAO,KAAK,EAEV,OAAO,EAGP,KAAK,EACN,MAAM,yBAAyB,CAAC;AAWjC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD;;;;GAIG;AACH,eAAO,MAAM,eAAe,KAAK,CAAC;AAElC,oFAAoF;AACpF,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAUrC,yMAAyM;AACzM,MAAM,MAAM,OAAO,GAAG,KAAK,CAAC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC,CAAC;AAiEjE,yHAAyH;AACzH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,KAAK;AACZ;;;;;;;GAOG;AACH,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,aAAa,EACxB,WAAW,EAAE,OAAO,GAAG,SAAS;AAChC,uEAAuE;AACvE,WAAW,EAAE,OAAO,GAAG,SAAS,EAChC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,QAAQ,EAAE,QAAQ,EAAE,GACnB;IACD,KAAK,EAAE,KAAK,CAAC;IACb,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,4EAA4E;IAC5E,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC,CAoTA;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CACvB,OAAO,EAAE,OAAO;AAChB;;;;;;;;GAQG;AACH,OAAO,GAAE,CAAC,IAAI,EAAE,SAAS,KAAK,OAAoB,GACjD,QAAQ,EAAE,CAsCZ;AAED;;;;;;;;;;;;GAYG;AACH;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,CAAC,IAAI,EAAE,SAAS,KAAK,OAAoB,GACjD,YAAY,EAAE,CA8BhB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,CAAC,IAAI,EAAE,SAAS,KAAK,OAAoB,GACjD,WAAW,EAAE,CAgCf;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,CAAC,IAAI,EAAE,SAAS,KAAK,OAAoB,GACjD,SAAS,EAAE,CAyBb;AAED,wBAAgB,UAAU,CACxB,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,CAAC,IAAI,EAAE,SAAS,KAAK,OAAoB,GACjD,KAAK,EAAE,CAoBT"}
|