@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
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Highest precedence first. `playing` wins so playback stays followable even
|
|
3
|
+
* over a selection; `regenerated` beats `selected` so a just-regenerated
|
|
4
|
+
* passage stays visible while it is still selected.
|
|
5
|
+
*
|
|
6
|
+
* Pressing play clears the selection, so `playing` and `selected` do not
|
|
7
|
+
* normally coexist — this ordering only resolves the case where the user
|
|
8
|
+
* selects notes while playback is already running.
|
|
9
|
+
*/
|
|
10
|
+
const PRECEDENCE = [
|
|
11
|
+
'playing',
|
|
12
|
+
'regenerated',
|
|
13
|
+
'selected',
|
|
14
|
+
'normal',
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* The role to draw a VexFlow note with, given every domain event id it
|
|
18
|
+
* represents (>1 for a chord, or for one segment of a duration-decomposed
|
|
19
|
+
* long note). Ids absent from `noteColors` count as `normal`.
|
|
20
|
+
*/
|
|
21
|
+
export function resolveNoteColorRole(eventIds, noteColors) {
|
|
22
|
+
if (!noteColors || noteColors.size === 0)
|
|
23
|
+
return 'normal';
|
|
24
|
+
let best = PRECEDENCE.length - 1;
|
|
25
|
+
for (const eventId of eventIds) {
|
|
26
|
+
const role = noteColors.get(eventId);
|
|
27
|
+
if (!role)
|
|
28
|
+
continue;
|
|
29
|
+
const rank = PRECEDENCE.indexOf(role);
|
|
30
|
+
if (rank !== -1 && rank < best)
|
|
31
|
+
best = rank;
|
|
32
|
+
}
|
|
33
|
+
return PRECEDENCE[best];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The non-color half of a note's state cue (spec §27: "do not rely on color
|
|
37
|
+
* alone"). The old highlight overlay carried this with solid/dashed/dotted
|
|
38
|
+
* stroke patterns; those went away with the rectangles, so the weight of the
|
|
39
|
+
* glyph carries it now — a thicker `lineWidth` on stems, flags and beams.
|
|
40
|
+
*
|
|
41
|
+
* Deliberately NOT a shadow. An earlier version added `shadowBlur` so the cue
|
|
42
|
+
* would reach a notehead too (a filled glyph, which no stroke width can
|
|
43
|
+
* thicken). Canvas shadows force the rasterizer down a separate blur pass per
|
|
44
|
+
* draw, and this is applied to precisely the notes that change most often —
|
|
45
|
+
* every note-on and note-off during playback, on the thread Tone.js schedules
|
|
46
|
+
* from. Playback hesitated. The cue is worth less than smooth audio.
|
|
47
|
+
*
|
|
48
|
+
* The cost of that trade: a *stemless* whole note now has color as its only
|
|
49
|
+
* cue. Every other note keeps the stroke-weight channel, and the selection
|
|
50
|
+
* summary in the status bar and screen-reader text still names the state.
|
|
51
|
+
*
|
|
52
|
+
* One emphasis level for every non-normal state rather than three: the
|
|
53
|
+
* perceptually important distinction is "is this note affected" versus "is it
|
|
54
|
+
* not". `selected` and `regenerated` are mutually exclusive anyway
|
|
55
|
+
* (`regenerated` is a property of the whole selection), and `playing` is
|
|
56
|
+
* transient and accompanied by the moving caret.
|
|
57
|
+
*/
|
|
58
|
+
export function noteEmphasisFor(role) {
|
|
59
|
+
return { lineWidth: role === 'normal' ? 1 : 2.5 };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The theme color a role draws in.
|
|
63
|
+
*
|
|
64
|
+
* Only `normal` varies by track. A selected, regenerated or playing note keeps
|
|
65
|
+
* its state color wherever it is: those states are the reason to look at the
|
|
66
|
+
* note, and cmd-shift-click selects across every track, so dimming a selection
|
|
67
|
+
* because it is not on the active track would hide what the user just did.
|
|
68
|
+
*/
|
|
69
|
+
export function noteColorFor(role, theme, onActiveTrack = true) {
|
|
70
|
+
switch (role) {
|
|
71
|
+
case 'playing':
|
|
72
|
+
return theme.notePlaying;
|
|
73
|
+
case 'regenerated':
|
|
74
|
+
return theme.noteRegenerated;
|
|
75
|
+
case 'selected':
|
|
76
|
+
return theme.noteSelected;
|
|
77
|
+
case 'normal':
|
|
78
|
+
return onActiveTrack ? theme.noteNormal : theme.noteInactive;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=note-color.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"note-color.js","sourceRoot":"","sources":["../src/note-color.ts"],"names":[],"mappings":"AAOA;;;;;;;;GAQG;AACH,MAAM,UAAU,GAA6B;IAC3C,SAAS;IACT,aAAa;IACb,UAAU;IACV,QAAQ;CACT,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAClC,QAA2B,EAC3B,UAA0D;IAE1D,IAAI,CAAC,UAAU,IAAI,UAAU,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,QAAQ,CAAC;IAC1D,IAAI,IAAI,GAAG,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC;IACjC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACrC,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,IAAI,KAAK,CAAC,CAAC,IAAI,IAAI,GAAG,IAAI;YAAE,IAAI,GAAG,IAAI,CAAC;IAC9C,CAAC;IACD,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,eAAe,CAAC,IAAmB;IACjD,OAAO,EAAE,SAAS,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;AACpD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAC1B,IAAmB,EACnB,KAAkB,EAClB,aAAa,GAAG,IAAI;IAEpB,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,SAAS;YACZ,OAAO,KAAK,CAAC,WAAW,CAAC;QAC3B,KAAK,aAAa;YAChB,OAAO,KAAK,CAAC,eAAe,CAAC;QAC/B,KAAK,UAAU;YACb,OAAO,KAAK,CAAC,YAAY,CAAC;QAC5B,KAAK,QAAQ;YACX,OAAO,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC;IACjE,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Track } from '@sudobility/music_types';
|
|
2
|
+
import type { LayoutPlan } from './layout.js';
|
|
3
|
+
export type PaperSize = 'a4' | 'letter' | 'legal';
|
|
4
|
+
export type PaperOrientation = 'portrait' | 'landscape';
|
|
5
|
+
/** Portrait dimensions in millimetres; landscape swaps them. */
|
|
6
|
+
export declare const PAPER_DIMENSIONS_MM: Record<PaperSize, {
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
}>;
|
|
10
|
+
/**
|
|
11
|
+
* The margin the printed page reserves on every side.
|
|
12
|
+
*
|
|
13
|
+
* The single source: the `@page` rule is emitted from this, and the height
|
|
14
|
+
* below is computed from it. Two numbers here means pagination that silently
|
|
15
|
+
* disagrees with what the printer does.
|
|
16
|
+
*/
|
|
17
|
+
export declare const PAGE_MARGIN_MM = 12;
|
|
18
|
+
/**
|
|
19
|
+
* How much height `logicalWidth` units of layout buy on this paper.
|
|
20
|
+
*
|
|
21
|
+
* Paper only ever enters as a ratio: the layout is computed at a fixed logical
|
|
22
|
+
* width and each page is displayed at the paper's printable width, so the
|
|
23
|
+
* scale is uniform and millimetres cancel.
|
|
24
|
+
*/
|
|
25
|
+
export declare function usablePageHeight(paper: PaperSize, orientation: PaperOrientation, logicalWidth: number, marginMm?: number): number;
|
|
26
|
+
/** The systems printed on one page, by index into `LayoutPlan.systems`. */
|
|
27
|
+
export type PrintPage = {
|
|
28
|
+
systemIndices: number[];
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* How many systems earlier than the greedy break a page may end.
|
|
32
|
+
*
|
|
33
|
+
* Two. Every system pulled back makes the part longer, and a turn bought three
|
|
34
|
+
* systems early costs more paper than it saves the player.
|
|
35
|
+
*/
|
|
36
|
+
export declare const MAX_PULL_BACK = 2;
|
|
37
|
+
/**
|
|
38
|
+
* Bars `track`'s player has free across a turn taken after system
|
|
39
|
+
* `lastSystemIndex`.
|
|
40
|
+
*
|
|
41
|
+
* They may begin turning once their last note on the page has finished and
|
|
42
|
+
* must be reading again by their first on the next, so both sides count.
|
|
43
|
+
* Zero after the last system: there is no turn there.
|
|
44
|
+
*/
|
|
45
|
+
export declare function turnFreeBars(plan: LayoutPlan, track: Track, lastSystemIndex: number): number;
|
|
46
|
+
/**
|
|
47
|
+
* Which systems go on which page.
|
|
48
|
+
*
|
|
49
|
+
* Greedy: take systems in order while they fit. A system taller than the page
|
|
50
|
+
* still gets its own page — it overflows, which is better than dropping it and
|
|
51
|
+
* better than never advancing.
|
|
52
|
+
*
|
|
53
|
+
* With `turnTrack`, each break is then pulled back up to `MAX_PULL_BACK`
|
|
54
|
+
* systems if that buys its player a better page turn.
|
|
55
|
+
*/
|
|
56
|
+
export declare function paginate(plan: LayoutPlan, pageHeight: number, turnTrack?: Track): PrintPage[];
|
|
57
|
+
//# sourceMappingURL=pagination.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../src/pagination.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAW,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAE,UAAU,EAAgB,MAAM,aAAa,CAAC;AAE5D,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,QAAQ,GAAG,OAAO,CAAC;AAClD,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,WAAW,CAAC;AAExD,gEAAgE;AAChE,eAAO,MAAM,mBAAmB,EAAE,MAAM,CACtC,SAAS,EACT;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAKlC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,SAAS,EAChB,WAAW,EAAE,gBAAgB,EAC7B,YAAY,EAAE,MAAM,EACpB,QAAQ,GAAE,MAAuB,GAChC,MAAM,CAOR;AAED,2EAA2E;AAC3E,MAAM,MAAM,SAAS,GAAG;IAAE,aAAa,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAOpD;;;;;GAKG;AACH,eAAO,MAAM,aAAa,IAAI,CAAC;AA6B/B;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,KAAK,EACZ,eAAe,EAAE,MAAM,GACtB,MAAM,CAQR;AA8BD;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CACtB,IAAI,EAAE,UAAU,EAChB,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,KAAK,GAChB,SAAS,EAAE,CA+Bb"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which systems go on which page, and where a page turn should fall.
|
|
3
|
+
*
|
|
4
|
+
* Pure over a `LayoutPlan` — no DOM, no canvas, no React — so the decisions
|
|
5
|
+
* about paper and page turns are testable without printing anything.
|
|
6
|
+
*
|
|
7
|
+
* Feature 1 left pagination to the browser: every system was a block with
|
|
8
|
+
* `break-inside: avoid`, and the browser fitted as many as the paper allowed.
|
|
9
|
+
* That has no opinion about *where* the break falls, and a turn in the middle
|
|
10
|
+
* of a phrase costs a real player a hand.
|
|
11
|
+
*/
|
|
12
|
+
import { isSilentMeasure } from '@sudobility/music_types';
|
|
13
|
+
/** Portrait dimensions in millimetres; landscape swaps them. */
|
|
14
|
+
export const PAPER_DIMENSIONS_MM = {
|
|
15
|
+
a4: { width: 210, height: 297 },
|
|
16
|
+
letter: { width: 215.9, height: 279.4 }, // 8.5in x 11in
|
|
17
|
+
legal: { width: 215.9, height: 355.6 }, // 8.5in x 14in
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* The margin the printed page reserves on every side.
|
|
21
|
+
*
|
|
22
|
+
* The single source: the `@page` rule is emitted from this, and the height
|
|
23
|
+
* below is computed from it. Two numbers here means pagination that silently
|
|
24
|
+
* disagrees with what the printer does.
|
|
25
|
+
*/
|
|
26
|
+
export const PAGE_MARGIN_MM = 12;
|
|
27
|
+
/**
|
|
28
|
+
* How much height `logicalWidth` units of layout buy on this paper.
|
|
29
|
+
*
|
|
30
|
+
* Paper only ever enters as a ratio: the layout is computed at a fixed logical
|
|
31
|
+
* width and each page is displayed at the paper's printable width, so the
|
|
32
|
+
* scale is uniform and millimetres cancel.
|
|
33
|
+
*/
|
|
34
|
+
export function usablePageHeight(paper, orientation, logicalWidth, marginMm = PAGE_MARGIN_MM) {
|
|
35
|
+
const { width, height } = PAPER_DIMENSIONS_MM[paper];
|
|
36
|
+
const shortSide = orientation === 'portrait' ? width : height;
|
|
37
|
+
const longSide = orientation === 'portrait' ? height : width;
|
|
38
|
+
return ((logicalWidth * (longSide - 2 * marginMm)) / (shortSide - 2 * marginMm));
|
|
39
|
+
}
|
|
40
|
+
/** A system's full printed height, measure-number band included. */
|
|
41
|
+
function systemHeight(system) {
|
|
42
|
+
return system.yBottom - system.gutterTop;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* How many systems earlier than the greedy break a page may end.
|
|
46
|
+
*
|
|
47
|
+
* Two. Every system pulled back makes the part longer, and a turn bought three
|
|
48
|
+
* systems early costs more paper than it saves the player.
|
|
49
|
+
*/
|
|
50
|
+
export const MAX_PULL_BACK = 2;
|
|
51
|
+
/** How many bars `measure` stands for — a multi-measure rest stands for its count. */
|
|
52
|
+
function barSpan(measure) {
|
|
53
|
+
return measure.multiMeasureRestCount ?? 1;
|
|
54
|
+
}
|
|
55
|
+
/** Silent bars at the end of `indices`. */
|
|
56
|
+
function trailingFreeBars(track, indices) {
|
|
57
|
+
let bars = 0;
|
|
58
|
+
for (let i = indices.length - 1; i >= 0; i -= 1) {
|
|
59
|
+
const measure = track.measures[indices[i]];
|
|
60
|
+
if (!measure || !isSilentMeasure(measure))
|
|
61
|
+
break;
|
|
62
|
+
bars += barSpan(measure);
|
|
63
|
+
}
|
|
64
|
+
return bars;
|
|
65
|
+
}
|
|
66
|
+
/** Silent bars at the start of `indices`. */
|
|
67
|
+
function leadingFreeBars(track, indices) {
|
|
68
|
+
let bars = 0;
|
|
69
|
+
for (const index of indices) {
|
|
70
|
+
const measure = track.measures[index];
|
|
71
|
+
if (!measure || !isSilentMeasure(measure))
|
|
72
|
+
break;
|
|
73
|
+
bars += barSpan(measure);
|
|
74
|
+
}
|
|
75
|
+
return bars;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Bars `track`'s player has free across a turn taken after system
|
|
79
|
+
* `lastSystemIndex`.
|
|
80
|
+
*
|
|
81
|
+
* They may begin turning once their last note on the page has finished and
|
|
82
|
+
* must be reading again by their first on the next, so both sides count.
|
|
83
|
+
* Zero after the last system: there is no turn there.
|
|
84
|
+
*/
|
|
85
|
+
export function turnFreeBars(plan, track, lastSystemIndex) {
|
|
86
|
+
const last = plan.systems[lastSystemIndex];
|
|
87
|
+
const next = plan.systems[lastSystemIndex + 1];
|
|
88
|
+
if (!last || !next)
|
|
89
|
+
return 0;
|
|
90
|
+
return (trailingFreeBars(track, last.measureIndices) +
|
|
91
|
+
leadingFreeBars(track, next.measureIndices));
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The end index (exclusive) that buys the best turn, at most `MAX_PULL_BACK`
|
|
95
|
+
* systems back from `greedyEnd` and never emptying the page.
|
|
96
|
+
*
|
|
97
|
+
* Searched downward with a strict improvement test, so a tie keeps the fullest
|
|
98
|
+
* page — pulling back with nothing to show for it is pure loss.
|
|
99
|
+
*/
|
|
100
|
+
function bestTurn(plan, track, start, greedyEnd) {
|
|
101
|
+
let best = greedyEnd;
|
|
102
|
+
let bestScore = turnFreeBars(plan, track, greedyEnd - 1);
|
|
103
|
+
const earliest = Math.max(start + 1, greedyEnd - MAX_PULL_BACK);
|
|
104
|
+
for (let end = greedyEnd - 1; end >= earliest; end -= 1) {
|
|
105
|
+
const score = turnFreeBars(plan, track, end - 1);
|
|
106
|
+
if (score > bestScore) {
|
|
107
|
+
best = end;
|
|
108
|
+
bestScore = score;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return best;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Which systems go on which page.
|
|
115
|
+
*
|
|
116
|
+
* Greedy: take systems in order while they fit. A system taller than the page
|
|
117
|
+
* still gets its own page — it overflows, which is better than dropping it and
|
|
118
|
+
* better than never advancing.
|
|
119
|
+
*
|
|
120
|
+
* With `turnTrack`, each break is then pulled back up to `MAX_PULL_BACK`
|
|
121
|
+
* systems if that buys its player a better page turn.
|
|
122
|
+
*/
|
|
123
|
+
export function paginate(plan, pageHeight, turnTrack) {
|
|
124
|
+
const pages = [];
|
|
125
|
+
let start = 0;
|
|
126
|
+
while (start < plan.systems.length) {
|
|
127
|
+
let end = start;
|
|
128
|
+
let used = 0;
|
|
129
|
+
while (end < plan.systems.length) {
|
|
130
|
+
const height = systemHeight(plan.systems[end]);
|
|
131
|
+
// `end > start` is what guarantees progress: the first system on a page
|
|
132
|
+
// always goes on it, however tall it is.
|
|
133
|
+
if (end > start && used + height > pageHeight)
|
|
134
|
+
break;
|
|
135
|
+
used += height;
|
|
136
|
+
end += 1;
|
|
137
|
+
}
|
|
138
|
+
// Only worth doing when there *is* a turn: the last page ends the piece.
|
|
139
|
+
// A whole-score print passes no track — "the player rests" means nothing
|
|
140
|
+
// when a dozen staves share the page, and a conductor turns at will.
|
|
141
|
+
if (turnTrack && end < plan.systems.length) {
|
|
142
|
+
end = bestTurn(plan, turnTrack, start, end);
|
|
143
|
+
}
|
|
144
|
+
const systemIndices = [];
|
|
145
|
+
for (let i = start; i < end; i += 1)
|
|
146
|
+
systemIndices.push(i);
|
|
147
|
+
pages.push({ systemIndices });
|
|
148
|
+
start = end;
|
|
149
|
+
}
|
|
150
|
+
return pages;
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=pagination.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pagination.js","sourceRoot":"","sources":["../src/pagination.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a drum sits on the staff, and what its notehead looks like.
|
|
3
|
+
*
|
|
4
|
+
* A percussion track's "pitch" is not a pitch — it is which drum was struck.
|
|
5
|
+
* General MIDI puts the kick at 35/36 and the snare at 38, which as pitches
|
|
6
|
+
* are B1 and D2: drawn literally they land six or seven ledger lines below a
|
|
7
|
+
* treble staff, with stems long enough to reach into the next track's row.
|
|
8
|
+
* That is what a drum part looked like here.
|
|
9
|
+
*
|
|
10
|
+
* Percussion notation answers a different question — not "how high is it" but
|
|
11
|
+
* "which instrument is it" — so each drum gets a fixed line or space and, for
|
|
12
|
+
* anything struck with a stick or shaken rather than hit like a drum, a cross
|
|
13
|
+
* notehead. The positions below are the common drum-set convention: kick in
|
|
14
|
+
* the bottom space, snare in the third space, hi-hat above the top line, toms
|
|
15
|
+
* descending between them, cymbals crossed at the top.
|
|
16
|
+
*
|
|
17
|
+
* The keys are VexFlow's `note/octave/notehead` form, and the octaves in them
|
|
18
|
+
* are staff positions read as if in treble clef, not sounding pitches. Nothing
|
|
19
|
+
* outside this file should read them as pitch.
|
|
20
|
+
*/
|
|
21
|
+
/** Whether `midi` is struck with a foot rather than a hand. */
|
|
22
|
+
export declare function isFootDrum(midi: number): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* The stem base offsets that join a stem to `keys`, or `null` when they need
|
|
25
|
+
* none.
|
|
26
|
+
*
|
|
27
|
+
* Only when *every* head in the group is a cross: the stem attaches to one
|
|
28
|
+
* outer head, and in a mixed chord that may be an ordinary one, which already
|
|
29
|
+
* meets its stem correctly.
|
|
30
|
+
*/
|
|
31
|
+
export declare function crossHeadStemOffsets(keys: string[]): {
|
|
32
|
+
stem_up_y_base_offset: number;
|
|
33
|
+
stem_down_y_base_offset: number;
|
|
34
|
+
} | null;
|
|
35
|
+
/** The VexFlow key for General MIDI percussion note `midi`. */
|
|
36
|
+
export declare function percussionVexKey(midi: number): string;
|
|
37
|
+
/**
|
|
38
|
+
* Whether `midi` has a staff position of its own.
|
|
39
|
+
*
|
|
40
|
+
* Exposed because `UNMAPPED` is `b/4`, which is also a real position — Low Tom
|
|
41
|
+
* sits there — so the return of `percussionVexKey` cannot be used to tell a
|
|
42
|
+
* drum this table knows from one it does not. `gm-percussion.ts` pins its own
|
|
43
|
+
* coverage against this, since a drum in one and not the other is either drawn
|
|
44
|
+
* without a name or named without a place to draw it.
|
|
45
|
+
*/
|
|
46
|
+
export declare function hasPercussionMapping(midi: number): boolean;
|
|
47
|
+
//# sourceMappingURL=percussion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"percussion.d.ts","sourceRoot":"","sources":["../src/percussion.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAoGH,+DAA+D;AAC/D,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEhD;AAyBD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EAAE,GACb;IAAE,qBAAqB,EAAE,MAAM,CAAC;IAAC,uBAAuB,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAM3E;AAED,+DAA+D;AAC/D,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE1D"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a drum sits on the staff, and what its notehead looks like.
|
|
3
|
+
*
|
|
4
|
+
* A percussion track's "pitch" is not a pitch — it is which drum was struck.
|
|
5
|
+
* General MIDI puts the kick at 35/36 and the snare at 38, which as pitches
|
|
6
|
+
* are B1 and D2: drawn literally they land six or seven ledger lines below a
|
|
7
|
+
* treble staff, with stems long enough to reach into the next track's row.
|
|
8
|
+
* That is what a drum part looked like here.
|
|
9
|
+
*
|
|
10
|
+
* Percussion notation answers a different question — not "how high is it" but
|
|
11
|
+
* "which instrument is it" — so each drum gets a fixed line or space and, for
|
|
12
|
+
* anything struck with a stick or shaken rather than hit like a drum, a cross
|
|
13
|
+
* notehead. The positions below are the common drum-set convention: kick in
|
|
14
|
+
* the bottom space, snare in the third space, hi-hat above the top line, toms
|
|
15
|
+
* descending between them, cymbals crossed at the top.
|
|
16
|
+
*
|
|
17
|
+
* The keys are VexFlow's `note/octave/notehead` form, and the octaves in them
|
|
18
|
+
* are staff positions read as if in treble clef, not sounding pitches. Nothing
|
|
19
|
+
* outside this file should read them as pitch.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Cross (`noteheadXBlack`): sticks, cymbals, shakers — anything that is not a
|
|
23
|
+
* drumhead.
|
|
24
|
+
*/
|
|
25
|
+
const X = 'x';
|
|
26
|
+
/**
|
|
27
|
+
* Circled cross (`noteheadCircleX`): the standard "let it ring" mark, which is
|
|
28
|
+
* how an open hi-hat is told from a closed one.
|
|
29
|
+
*
|
|
30
|
+
* Verified against VexFlow's own codes rather than assumed: `x` and `x2` are
|
|
31
|
+
* both `noteheadXBlack`, `x3` is `noteheadCircleX`, and `ci` is
|
|
32
|
+
* `noteheadCircledBlack` — a filled head inside a circle, which means nothing
|
|
33
|
+
* here and is what open hi-hat was wrongly drawn with.
|
|
34
|
+
*/
|
|
35
|
+
const CIRCLE_X = 'x3';
|
|
36
|
+
/** Diamond: bells and cups. */
|
|
37
|
+
const DIAMOND = 'd';
|
|
38
|
+
/** Triangle: the cowbell's usual mark. */
|
|
39
|
+
const TRIANGLE = 'tu';
|
|
40
|
+
/**
|
|
41
|
+
* General MIDI percussion (35-81) to staff position.
|
|
42
|
+
*
|
|
43
|
+
* Numbers outside this range are not General MIDI percussion; `percussionVexKey`
|
|
44
|
+
* keeps them on the middle line rather than inventing a position, so an
|
|
45
|
+
* unexpected note is visible and obviously unplaced instead of flying off the
|
|
46
|
+
* staff.
|
|
47
|
+
*/
|
|
48
|
+
const DRUM_MAP = {
|
|
49
|
+
35: 'f/4', // Acoustic Bass Drum
|
|
50
|
+
36: 'f/4', // Bass Drum 1
|
|
51
|
+
37: `c/5/${X}`, // Side Stick
|
|
52
|
+
38: 'c/5', // Acoustic Snare
|
|
53
|
+
39: `c/5/${X}`, // Hand Clap
|
|
54
|
+
40: 'c/5', // Electric Snare
|
|
55
|
+
41: 'g/4', // Low Floor Tom
|
|
56
|
+
42: `g/5/${X}`, // Closed Hi-Hat
|
|
57
|
+
43: 'a/4', // High Floor Tom
|
|
58
|
+
44: `d/4/${X}`, // Pedal Hi-Hat
|
|
59
|
+
45: 'b/4', // Low Tom
|
|
60
|
+
46: `g/5/${CIRCLE_X}`, // Open Hi-Hat
|
|
61
|
+
47: 'd/5', // Low-Mid Tom
|
|
62
|
+
48: 'e/5', // Hi-Mid Tom
|
|
63
|
+
49: `a/5/${X}`, // Crash Cymbal 1
|
|
64
|
+
50: 'f/5', // High Tom
|
|
65
|
+
51: `f/5/${X}`, // Ride Cymbal 1
|
|
66
|
+
52: `b/5/${CIRCLE_X}`, // Chinese Cymbal
|
|
67
|
+
53: `f/5/${DIAMOND}`, // Ride Bell
|
|
68
|
+
54: `d/5/${X}`, // Tambourine
|
|
69
|
+
55: `b/5/${X}`, // Splash Cymbal
|
|
70
|
+
56: `e/5/${TRIANGLE}`, // Cowbell
|
|
71
|
+
57: `a/5/${X}`, // Crash Cymbal 2
|
|
72
|
+
58: `b/4/${X}`, // Vibraslap
|
|
73
|
+
59: `f/5/${X}`, // Ride Cymbal 2
|
|
74
|
+
// 60-81 are hand and Latin percussion rather than kit pieces, so there is no
|
|
75
|
+
// drum-set convention to follow. They are spread over the staff by family,
|
|
76
|
+
// pitched pairs keeping high above low, so a part stays readable.
|
|
77
|
+
60: 'e/5', // Hi Bongo
|
|
78
|
+
61: 'c/5', // Low Bongo
|
|
79
|
+
62: 'd/5', // Mute Hi Conga
|
|
80
|
+
63: 'd/5', // Open Hi Conga
|
|
81
|
+
64: 'b/4', // Low Conga
|
|
82
|
+
65: 'e/5', // High Timbale
|
|
83
|
+
66: 'c/5', // Low Timbale
|
|
84
|
+
67: `f/5/${X}`, // High Agogo
|
|
85
|
+
68: `d/5/${X}`, // Low Agogo
|
|
86
|
+
69: `g/5/${X}`, // Cabasa
|
|
87
|
+
70: `g/5/${X}`, // Maracas
|
|
88
|
+
71: `a/5/${X}`, // Short Whistle
|
|
89
|
+
72: `a/5/${X}`, // Long Whistle
|
|
90
|
+
73: `f/5/${X}`, // Short Guiro
|
|
91
|
+
74: `f/5/${X}`, // Long Guiro
|
|
92
|
+
75: `e/5/${X}`, // Claves
|
|
93
|
+
76: `e/5/${X}`, // Hi Wood Block
|
|
94
|
+
77: `c/5/${X}`, // Low Wood Block
|
|
95
|
+
78: `d/5/${X}`, // Mute Cuica
|
|
96
|
+
79: `d/5/${X}`, // Open Cuica
|
|
97
|
+
80: `a/5/${X}`, // Mute Triangle
|
|
98
|
+
81: `a/5/${CIRCLE_X}`, // Open Triangle
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* The middle line — where anything unrecognised goes, so it stays on the staff.
|
|
102
|
+
*
|
|
103
|
+
* The staff runs from line 1 (bottom, E4) to line 5 (top, F5) in VexFlow's
|
|
104
|
+
* numbering; every mapping above stays within one ledger of that, and the
|
|
105
|
+
* pedal hi-hat is deliberately just below it, as convention has it.
|
|
106
|
+
*/
|
|
107
|
+
const UNMAPPED = 'b/4';
|
|
108
|
+
/**
|
|
109
|
+
* The kit pieces played with the feet: both bass drums and the hi-hat pedal.
|
|
110
|
+
*
|
|
111
|
+
* They are stemmed downward against the hands, which is what lets a drum staff
|
|
112
|
+
* carry a whole kit on five lines and still be read at a glance.
|
|
113
|
+
*/
|
|
114
|
+
const FOOT_DRUMS = new Set([35, 36, 44]);
|
|
115
|
+
/** Whether `midi` is struck with a foot rather than a hand. */
|
|
116
|
+
export function isFootDrum(midi) {
|
|
117
|
+
return FOOT_DRUMS.has(midi);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* How far a cross notehead's stem must reach into the glyph to join it.
|
|
121
|
+
*
|
|
122
|
+
* A stem meets a notehead at its right (or left) edge, halfway up. On an oval
|
|
123
|
+
* that is solid ink; on a cross it is the hollow middle, and the only ink at
|
|
124
|
+
* that edge is the two arm tips at the corners. VexFlow's default stops the
|
|
125
|
+
* stem four units short of even the halfway point, so it grazed the upper tip
|
|
126
|
+
* at a single point and read as a separate mark sitting above the note.
|
|
127
|
+
*
|
|
128
|
+
* Half a notehead — five units at the default glyph scale — runs the stem the
|
|
129
|
+
* full height of the glyph, bridging both arm tips, which is what reads as
|
|
130
|
+
* joined. Measured from the rendered SVG in both directions: +5 lands an
|
|
131
|
+
* up-stem exactly on the head's bottom edge and -5 lands a down-stem exactly
|
|
132
|
+
* on its top edge, while -9 and beyond overshoot past the glyph.
|
|
133
|
+
*/
|
|
134
|
+
const CROSS_STEM_OVERLAP = 5;
|
|
135
|
+
/** Whether `key` (VexFlow `note/octave/head` form) draws as a cross. */
|
|
136
|
+
function isCrossHead(key) {
|
|
137
|
+
const head = key.split('/')[2];
|
|
138
|
+
return head === X || head === CIRCLE_X;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* The stem base offsets that join a stem to `keys`, or `null` when they need
|
|
142
|
+
* none.
|
|
143
|
+
*
|
|
144
|
+
* Only when *every* head in the group is a cross: the stem attaches to one
|
|
145
|
+
* outer head, and in a mixed chord that may be an ordinary one, which already
|
|
146
|
+
* meets its stem correctly.
|
|
147
|
+
*/
|
|
148
|
+
export function crossHeadStemOffsets(keys) {
|
|
149
|
+
if (keys.length === 0 || !keys.every(isCrossHead))
|
|
150
|
+
return null;
|
|
151
|
+
return {
|
|
152
|
+
stem_up_y_base_offset: CROSS_STEM_OVERLAP,
|
|
153
|
+
stem_down_y_base_offset: -CROSS_STEM_OVERLAP,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/** The VexFlow key for General MIDI percussion note `midi`. */
|
|
157
|
+
export function percussionVexKey(midi) {
|
|
158
|
+
return DRUM_MAP[midi] ?? UNMAPPED;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Whether `midi` has a staff position of its own.
|
|
162
|
+
*
|
|
163
|
+
* Exposed because `UNMAPPED` is `b/4`, which is also a real position — Low Tom
|
|
164
|
+
* sits there — so the return of `percussionVexKey` cannot be used to tell a
|
|
165
|
+
* drum this table knows from one it does not. `gm-percussion.ts` pins its own
|
|
166
|
+
* coverage against this, since a drum in one and not the other is either drawn
|
|
167
|
+
* without a name or named without a place to draw it.
|
|
168
|
+
*/
|
|
169
|
+
export function hasPercussionMapping(midi) {
|
|
170
|
+
return midi in DRUM_MAP;
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=percussion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"percussion.js","sourceRoot":"","sources":["../src/percussion.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Playhead caret geometry: maps a playback tick to a vertical-line position
|
|
3
|
+
* on the notation canvas, and a canvas point back to a tick (click-to-seek).
|
|
4
|
+
* Pure functions over `LayoutPlan` (logical/unscaled units — callers
|
|
5
|
+
* multiply by zoom). Moved here from music_app's score-editor feature and
|
|
6
|
+
* re-based on the binary-search lookups so every call is O(log n) — part of
|
|
7
|
+
* the canvas renderer's "no O(score) interaction work" rule.
|
|
8
|
+
*
|
|
9
|
+
* The tick <-> x mapping interpolates linearly across each measure's stave
|
|
10
|
+
* box. VexFlow doesn't space notes perfectly linearly (and first-in-system
|
|
11
|
+
* measures spend their left edge on clef/key/time), so the caret is a close
|
|
12
|
+
* approximation rather than glyph-exact — the same trade every DAW-style
|
|
13
|
+
* ruler makes.
|
|
14
|
+
*/
|
|
15
|
+
import type { Score } from '@sudobility/music_types';
|
|
16
|
+
import type { LayoutPlan } from './layout.js';
|
|
17
|
+
export type CaretPosition = {
|
|
18
|
+
/** Logical x of the caret line. */
|
|
19
|
+
x: number;
|
|
20
|
+
/** Logical top/bottom of the system the tick falls in (the caret spans the whole system, all tracks). */
|
|
21
|
+
yTop: number;
|
|
22
|
+
yBottom: number;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Where the caret for `tick` sits on the canvas, or `null` when there is
|
|
26
|
+
* nothing to draw against (empty score / no layout). Ticks past the end of
|
|
27
|
+
* the score clamp to the final measure's right edge.
|
|
28
|
+
*/
|
|
29
|
+
export declare function caretPositionForTick(plan: LayoutPlan, score: Score, tick: number): CaretPosition | null;
|
|
30
|
+
/**
|
|
31
|
+
* The tick a canvas click at logical `(x, y)` should seek to, or `null`
|
|
32
|
+
* when the point is in dead space between/outside systems. Horizontal
|
|
33
|
+
* positions left/right of a system's measures clamp to that system's
|
|
34
|
+
* first/last measure, so clicking the clef area seeks to the system start.
|
|
35
|
+
*/
|
|
36
|
+
export declare function tickForPoint(plan: LayoutPlan, score: Score, x: number, y: number): number | null;
|
|
37
|
+
//# sourceMappingURL=playhead.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playhead.d.ts","sourceRoot":"","sources":["../src/playhead.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAErD,OAAO,KAAK,EAAE,UAAU,EAAgB,MAAM,aAAa,CAAC;AAE5D,MAAM,MAAM,aAAa,GAAG;IAC1B,mCAAmC;IACnC,CAAC,EAAE,MAAM,CAAC;IACV,yGAAyG;IACzG,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AA2CF;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,MAAM,GACX,aAAa,GAAG,IAAI,CAsBtB;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,KAAK,EACZ,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,GACR,MAAM,GAAG,IAAI,CAiBf"}
|