@sudobility/music_drawing 0.0.33 → 0.0.35
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 +79 -4
- package/dist/canvas-renderer.d.ts.map +1 -1
- package/dist/canvas-renderer.js +245 -53
- package/dist/canvas-renderer.js.map +1 -1
- package/dist/cursor.d.ts +60 -0
- package/dist/cursor.d.ts.map +1 -0
- package/dist/cursor.js +157 -0
- package/dist/cursor.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -1
- package/dist/keyboard-geometry.d.ts +50 -2
- package/dist/keyboard-geometry.d.ts.map +1 -1
- package/dist/keyboard-geometry.js +71 -2
- package/dist/keyboard-geometry.js.map +1 -1
- package/dist/keyboard-keys.d.ts +93 -0
- package/dist/keyboard-keys.d.ts.map +1 -0
- package/dist/keyboard-keys.js +60 -0
- package/dist/keyboard-keys.js.map +1 -0
- package/dist/layered-paint.d.ts +47 -0
- package/dist/layered-paint.d.ts.map +1 -0
- package/dist/layered-paint.js +22 -0
- package/dist/layered-paint.js.map +1 -0
- package/dist/pagination.d.ts +6 -2
- package/dist/pagination.d.ts.map +1 -1
- package/dist/pagination.js +4 -0
- package/dist/pagination.js.map +1 -1
- package/dist/playback-binding.d.ts +57 -0
- package/dist/playback-binding.d.ts.map +1 -0
- package/dist/playback-binding.js +110 -0
- package/dist/playback-binding.js.map +1 -0
- package/dist/playhead.d.ts.map +1 -1
- package/dist/playhead.js +22 -3
- package/dist/playhead.js.map +1 -1
- package/dist/press.d.ts +66 -0
- package/dist/press.d.ts.map +1 -0
- package/dist/press.js +64 -0
- package/dist/press.js.map +1 -0
- package/dist/print-plan.d.ts +64 -0
- package/dist/print-plan.d.ts.map +1 -0
- package/dist/print-plan.js +101 -0
- package/dist/print-plan.js.map +1 -0
- package/dist/render-theme.d.ts.map +1 -1
- package/dist/render-theme.js +11 -2
- package/dist/render-theme.js.map +1 -1
- package/dist/score-canvas.d.ts +280 -0
- package/dist/score-canvas.d.ts.map +1 -0
- package/dist/score-canvas.js +702 -0
- package/dist/score-canvas.js.map +1 -0
- package/dist/skia/index.d.ts +2 -0
- package/dist/skia/index.d.ts.map +1 -1
- package/dist/skia/index.js +1 -0
- package/dist/skia/index.js.map +1 -1
- package/dist/skia/skia-layers.d.ts +43 -0
- package/dist/skia/skia-layers.d.ts.map +1 -0
- package/dist/skia/skia-layers.js +24 -0
- package/dist/skia/skia-layers.js.map +1 -0
- package/dist/types.d.ts +13 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +3 -3
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* │C │D │E │F │G │
|
|
18
18
|
* └──┴──┴──┴──┴──┘
|
|
19
19
|
*/
|
|
20
|
-
import { FULL_KEYBOARD, gmPercussion, gmPercussionName, midiToPitch, pitchToString, } from '@sudobility/music_types';
|
|
20
|
+
import { FULL_KEYBOARD, gmPercussion, gmPercussionName, isNoteEvent, midiIsInRange, midiToPitch, pitchToMidi, pitchToString, trackKeyboardRange, trackRangeIsBinding, } from '@sudobility/music_types';
|
|
21
21
|
/**
|
|
22
22
|
* The 88-key piano — `FULL_KEYBOARD` **by identity**, not a second pair of
|
|
23
23
|
* numbers that agrees with it today.
|
|
@@ -76,6 +76,20 @@ export const MIN_WHITE_KEY_WIDTH = 14;
|
|
|
76
76
|
/** Black-key size, as a fraction of a white key. */
|
|
77
77
|
export const BLACK_KEY_WIDTH_RATIO = 0.6;
|
|
78
78
|
export const BLACK_KEY_HEIGHT_RATIO = 0.62;
|
|
79
|
+
/**
|
|
80
|
+
* A white key outside the instrument's compass: pale enough to read as a key,
|
|
81
|
+
* grey enough to tell apart from the playable ones beside it.
|
|
82
|
+
*/
|
|
83
|
+
export const KEYBOARD_OUT_OF_RANGE_WHITE = '#e4e4e7';
|
|
84
|
+
/**
|
|
85
|
+
* A black key outside the instrument's compass.
|
|
86
|
+
*
|
|
87
|
+
* Its own colour because the white one alone left half the dimmed span looking
|
|
88
|
+
* playable: an out-of-range octave read as pale whites with ordinary black keys
|
|
89
|
+
* between them, and the blacks are exactly as unplayable. Still dark enough to
|
|
90
|
+
* read as a black key, so the keyboard's pattern is not broken.
|
|
91
|
+
*/
|
|
92
|
+
export const KEYBOARD_OUT_OF_RANGE_BLACK = '#63636b';
|
|
79
93
|
/** Height of one row of key labels, and so of the gutter a keyboard needs per row. */
|
|
80
94
|
export const LABEL_ROW_HEIGHT = 12;
|
|
81
95
|
/** True for the five black keys per octave (pitch classes 1, 3, 6, 8, 10). */
|
|
@@ -98,7 +112,9 @@ export function keyboardWidth(whiteKeyWidth, range = FULL_RANGE) {
|
|
|
98
112
|
* and the blacks land on top of the whites they overlap, with no z-index
|
|
99
113
|
* bookkeeping.
|
|
100
114
|
*/
|
|
101
|
-
export function computeKeys(whiteKeyWidth, whiteKeyHeight, range = FULL_RANGE, naming = 'pitch'
|
|
115
|
+
export function computeKeys(whiteKeyWidth, whiteKeyHeight, range = FULL_RANGE, naming = 'pitch',
|
|
116
|
+
/** What the instrument can play; keys outside it are `outOfRange`. Null marks none. */
|
|
117
|
+
playable = null) {
|
|
102
118
|
const whites = [];
|
|
103
119
|
const blacks = [];
|
|
104
120
|
const blackWidth = whiteKeyWidth * BLACK_KEY_WIDTH_RATIO;
|
|
@@ -120,6 +136,7 @@ export function computeKeys(whiteKeyWidth, whiteKeyHeight, range = FULL_RANGE, n
|
|
|
120
136
|
? noteLabel(midi)
|
|
121
137
|
: null;
|
|
122
138
|
const name = drums ? gmPercussionName(midi) : noteLabel(midi);
|
|
139
|
+
const outOfRange = playable !== null && !midiIsInRange(midi, playable);
|
|
123
140
|
if (isBlackKey(midi)) {
|
|
124
141
|
blacks.push({
|
|
125
142
|
midi,
|
|
@@ -134,6 +151,7 @@ export function computeKeys(whiteKeyWidth, whiteKeyHeight, range = FULL_RANGE, n
|
|
|
134
151
|
name,
|
|
135
152
|
// Second row: clear of the white keys and of their labels.
|
|
136
153
|
labelTop: whiteKeyHeight + 1 + LABEL_ROW_HEIGHT,
|
|
154
|
+
outOfRange,
|
|
137
155
|
});
|
|
138
156
|
}
|
|
139
157
|
else {
|
|
@@ -146,10 +164,61 @@ export function computeKeys(whiteKeyWidth, whiteKeyHeight, range = FULL_RANGE, n
|
|
|
146
164
|
label,
|
|
147
165
|
name,
|
|
148
166
|
labelTop: whiteKeyHeight + 1,
|
|
167
|
+
outOfRange,
|
|
149
168
|
});
|
|
150
169
|
whiteIndex += 1;
|
|
151
170
|
}
|
|
152
171
|
}
|
|
153
172
|
return [...whites, ...blacks];
|
|
154
173
|
}
|
|
174
|
+
/** The whole MIDI note range, which an imported part may use past the piano's ends. */
|
|
175
|
+
const MIDI_MIN = 0;
|
|
176
|
+
const MIDI_MAX = 127;
|
|
177
|
+
/**
|
|
178
|
+
* The keyboard for a track: its instrument's compass, widened to reach every
|
|
179
|
+
* note the track actually holds.
|
|
180
|
+
*
|
|
181
|
+
* Widened because a part can hold notes its instrument cannot play — an
|
|
182
|
+
* imported sub-octave bass layer is the usual one — and a keyboard that stopped
|
|
183
|
+
* at the compass lit no key at all for them. The widened keys are marked
|
|
184
|
+
* `outOfRange` through `playable`, which does not widen: they show what is
|
|
185
|
+
* there, they are not an invitation to write more of it. Both apps call this,
|
|
186
|
+
* so they draw the same keys and refuse the same presses.
|
|
187
|
+
*/
|
|
188
|
+
export function trackKeyboardSpan(track) {
|
|
189
|
+
const compass = trackKeyboardRange(track);
|
|
190
|
+
let min = compass.min;
|
|
191
|
+
let max = compass.max;
|
|
192
|
+
for (const measure of track.measures) {
|
|
193
|
+
for (const voice of measure.voices) {
|
|
194
|
+
for (const event of voice.events) {
|
|
195
|
+
if (!isNoteEvent(event))
|
|
196
|
+
continue;
|
|
197
|
+
const midi = pitchToMidi(event.pitch);
|
|
198
|
+
if (midi < min)
|
|
199
|
+
min = midi;
|
|
200
|
+
if (midi > max)
|
|
201
|
+
max = midi;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// The compass itself keeps the 88-key snapping. Past it, the keyboard follows
|
|
206
|
+
// the notes as far as MIDI goes: an imported sub-bass reaches below A0, and
|
|
207
|
+
// stopping at the piano's end would light nothing for exactly those notes.
|
|
208
|
+
let { min: from, max: to } = snapToWhiteKeys(compass);
|
|
209
|
+
if (min < from) {
|
|
210
|
+
from = min;
|
|
211
|
+
while (from > MIDI_MIN && isBlackKey(from))
|
|
212
|
+
from -= 1;
|
|
213
|
+
}
|
|
214
|
+
if (max > to) {
|
|
215
|
+
to = max;
|
|
216
|
+
while (to < MIDI_MAX && isBlackKey(to))
|
|
217
|
+
to += 1;
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
range: { min: from, max: to },
|
|
221
|
+
playable: trackRangeIsBinding(track) ? compass : null,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
155
224
|
//# sourceMappingURL=keyboard-geometry.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keyboard-geometry.js","sourceRoot":"","sources":["../src/keyboard-geometry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EACL,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,aAAa,
|
|
1
|
+
{"version":3,"file":"keyboard-geometry.js","sourceRoot":"","sources":["../src/keyboard-geometry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The keyboard for a track, in one call: which keys, how big, what they are
|
|
3
|
+
* called, and where their labels go.
|
|
4
|
+
*
|
|
5
|
+
* Each of those was already a shared function — `trackKeyboardSpan`,
|
|
6
|
+
* `computeKeys`, `keyboardLabelSemitones`/`relabelKeys` — but the *composition*
|
|
7
|
+
* was written out in both apps, and the two compositions disagreed: the native
|
|
8
|
+
* app never relabelled a transposing part in written pitch, laid its labels
|
|
9
|
+
* inside the keys rather than in a gutter, recomputed black-key heights from
|
|
10
|
+
* the ratio, and fitted the width where the web scrolled. A keyboard is the
|
|
11
|
+
* sum of those decisions, so the sum lives here too.
|
|
12
|
+
*
|
|
13
|
+
* Web's rules are the reference: the height handed in is the whole panel, the
|
|
14
|
+
* label gutter comes out of it, and a drum kit's gutter is two rows because its
|
|
15
|
+
* black keys are named as well.
|
|
16
|
+
*/
|
|
17
|
+
import type { PitchDisplay, Track } from '@sudobility/music_types';
|
|
18
|
+
import { type KeyNaming, type KeyboardRange, type PianoKey } from './keyboard-geometry.js';
|
|
19
|
+
import type { MidiRange } from '@sudobility/music_types';
|
|
20
|
+
import type { RenderTheme } from './types.js';
|
|
21
|
+
/**
|
|
22
|
+
* Room below the keys for a pitched keyboard's one row of C/F labels.
|
|
23
|
+
*
|
|
24
|
+
* A little more than `LABEL_ROW_HEIGHT`, so the label clears the key's bottom
|
|
25
|
+
* edge instead of touching it.
|
|
26
|
+
*/
|
|
27
|
+
export declare const PITCH_LABEL_GUTTER = 14;
|
|
28
|
+
/** A playable white key. */
|
|
29
|
+
export declare const KEYBOARD_WHITE = "#fbfbfd";
|
|
30
|
+
/** A playable black key. */
|
|
31
|
+
export declare const KEYBOARD_BLACK = "#1f1f23";
|
|
32
|
+
/**
|
|
33
|
+
* How the keys meet the width they are given.
|
|
34
|
+
*
|
|
35
|
+
* - `'scroll'` (web, the default): keys fill the width but never shrink below
|
|
36
|
+
* `MIN_WHITE_KEY_WIDTH`; past that the keyboard is wider than its panel and
|
|
37
|
+
* the panel scrolls. On a desktop a narrow window should not turn 88 keys
|
|
38
|
+
* into slivers nobody can click.
|
|
39
|
+
* - `'width'` (native): the whole range always fits. On a phone a keyboard you
|
|
40
|
+
* have to scroll is one you cannot play a two-handed chord on, and a finger
|
|
41
|
+
* presses a sliver more reliably than a pointer does.
|
|
42
|
+
*/
|
|
43
|
+
export type KeyboardFit = 'scroll' | 'width';
|
|
44
|
+
export type KeyboardKeysOptions = {
|
|
45
|
+
/** Available width. */
|
|
46
|
+
width: number;
|
|
47
|
+
/** The whole panel's height, label gutter included. */
|
|
48
|
+
height: number;
|
|
49
|
+
/** The active track; null or absent draws the whole piano with nothing marked. */
|
|
50
|
+
track: Pick<Track, 'clef' | 'midiProgram' | 'measures'> | null | undefined;
|
|
51
|
+
/** What the staff shows; moves the lettering of a transposing part. Default concert. */
|
|
52
|
+
pitchDisplay?: PitchDisplay;
|
|
53
|
+
fit?: KeyboardFit;
|
|
54
|
+
};
|
|
55
|
+
export type KeyboardKeys = {
|
|
56
|
+
/** The keys drawn, snapped to whole white keys. */
|
|
57
|
+
range: KeyboardRange;
|
|
58
|
+
/** What the instrument can play, or null when nothing is marked. */
|
|
59
|
+
playable: MidiRange | null;
|
|
60
|
+
naming: KeyNaming;
|
|
61
|
+
/** Whites first, then blacks — render in order and the blacks land on top. Each carries its own height and label position. */
|
|
62
|
+
keys: PianoKey[];
|
|
63
|
+
/** Height reserved under the white keys for labels. */
|
|
64
|
+
labelGutter: number;
|
|
65
|
+
/** A white key's height: `height − labelGutter`. */
|
|
66
|
+
keyHeight: number;
|
|
67
|
+
whiteKeyWidth: number;
|
|
68
|
+
/** The keyboard's total width — of `range`, not of the 88-key piano. */
|
|
69
|
+
width: number;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Everything a keyboard view needs to draw `track`'s keys into a box.
|
|
73
|
+
*
|
|
74
|
+
* Cheap enough to call per render (it walks the track's notes once) but it
|
|
75
|
+
* returns fresh arrays, so memoize on its inputs where the keys are reconciled.
|
|
76
|
+
*/
|
|
77
|
+
export declare function keyboardKeys({ width, height, track, pitchDisplay, fit, }: KeyboardKeysOptions): KeyboardKeys;
|
|
78
|
+
/** What is happening to a key right now. */
|
|
79
|
+
export type KeyState = {
|
|
80
|
+
/** Sounding in playback, or held down. */
|
|
81
|
+
lit: boolean;
|
|
82
|
+
/** A pitch of the one selected chord. */
|
|
83
|
+
selected: boolean;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* The fill for `key`.
|
|
87
|
+
*
|
|
88
|
+
* Sounding wins over selected — it is the more transient signal, and a key
|
|
89
|
+
* that is both should show what is happening now — and either wins over the
|
|
90
|
+
* out-of-range dimming, which is a standing fact about the instrument.
|
|
91
|
+
*/
|
|
92
|
+
export declare function keyboardKeyFill(key: Pick<PianoKey, 'isBlack' | 'outOfRange'>, state: KeyState, theme: RenderTheme): string;
|
|
93
|
+
//# sourceMappingURL=keyboard-keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyboard-keys.d.ts","sourceRoot":"","sources":["../src/keyboard-keys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EAWL,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,QAAQ,EACd,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC,4BAA4B;AAC5B,eAAO,MAAM,cAAc,YAAY,CAAC;AACxC,4BAA4B;AAC5B,eAAO,MAAM,cAAc,YAAY,CAAC;AAExC;;;;;;;;;;GAUG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE7C,MAAM,MAAM,mBAAmB,GAAG;IAChC,uBAAuB;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,kFAAkF;IAClF,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,GAAG,UAAU,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC;IAC3E,wFAAwF;IACxF,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,GAAG,CAAC,EAAE,WAAW,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,mDAAmD;IACnD,KAAK,EAAE,aAAa,CAAC;IACrB,oEAAoE;IACpE,QAAQ,EAAE,SAAS,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,SAAS,CAAC;IAClB,8HAA8H;IAC9H,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjB,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,MAAM,EACN,KAAK,EACL,YAAwB,EACxB,GAAc,GACf,EAAE,mBAAmB,GAAG,YAAY,CAiCpC;AAED,4CAA4C;AAC5C,MAAM,MAAM,QAAQ,GAAG;IACrB,0CAA0C;IAC1C,GAAG,EAAE,OAAO,CAAC;IACb,yCAAyC;IACzC,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,IAAI,CAAC,QAAQ,EAAE,SAAS,GAAG,YAAY,CAAC,EAC7C,KAAK,EAAE,QAAQ,EACf,KAAK,EAAE,WAAW,GACjB,MAAM,CAMR"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { FULL_RANGE, KEYBOARD_OUT_OF_RANGE_BLACK, KEYBOARD_OUT_OF_RANGE_WHITE, LABEL_ROW_HEIGHT, MIN_WHITE_KEY_WIDTH, computeKeys, keyboardWidth, snapToWhiteKeys, trackKeyboardSpan, whiteKeyCount, } from './keyboard-geometry.js';
|
|
2
|
+
import { keyboardLabelSemitones, relabelKeys } from './keyboard-labels.js';
|
|
3
|
+
/**
|
|
4
|
+
* Room below the keys for a pitched keyboard's one row of C/F labels.
|
|
5
|
+
*
|
|
6
|
+
* A little more than `LABEL_ROW_HEIGHT`, so the label clears the key's bottom
|
|
7
|
+
* edge instead of touching it.
|
|
8
|
+
*/
|
|
9
|
+
export const PITCH_LABEL_GUTTER = 14;
|
|
10
|
+
/** A playable white key. */
|
|
11
|
+
export const KEYBOARD_WHITE = '#fbfbfd';
|
|
12
|
+
/** A playable black key. */
|
|
13
|
+
export const KEYBOARD_BLACK = '#1f1f23';
|
|
14
|
+
/**
|
|
15
|
+
* Everything a keyboard view needs to draw `track`'s keys into a box.
|
|
16
|
+
*
|
|
17
|
+
* Cheap enough to call per render (it walks the track's notes once) but it
|
|
18
|
+
* returns fresh arrays, so memoize on its inputs where the keys are reconciled.
|
|
19
|
+
*/
|
|
20
|
+
export function keyboardKeys({ width, height, track, pitchDisplay = 'concert', fit = 'scroll', }) {
|
|
21
|
+
const span = track
|
|
22
|
+
? trackKeyboardSpan(track)
|
|
23
|
+
: { range: snapToWhiteKeys(FULL_RANGE), playable: null };
|
|
24
|
+
const { range, playable } = span;
|
|
25
|
+
// On a percussion track `midiProgram` is a kit and every key is a drum.
|
|
26
|
+
const naming = track?.clef === 'percussion' ? 'percussion' : 'pitch';
|
|
27
|
+
// A kit names its black keys too, on a second row under the first.
|
|
28
|
+
const labelGutter = naming === 'percussion' ? LABEL_ROW_HEIGHT * 2 : PITCH_LABEL_GUTTER;
|
|
29
|
+
const keyHeight = Math.max(1, height - labelGutter);
|
|
30
|
+
const fitted = width / Math.max(1, whiteKeyCount(range));
|
|
31
|
+
const whiteKeyWidth = fit === 'width' ? fitted : Math.max(MIN_WHITE_KEY_WIDTH, fitted);
|
|
32
|
+
const keys = relabelKeys(computeKeys(whiteKeyWidth, keyHeight, range, naming, playable), keyboardLabelSemitones(track, pitchDisplay));
|
|
33
|
+
return {
|
|
34
|
+
range,
|
|
35
|
+
playable,
|
|
36
|
+
naming,
|
|
37
|
+
keys,
|
|
38
|
+
labelGutter,
|
|
39
|
+
keyHeight,
|
|
40
|
+
whiteKeyWidth,
|
|
41
|
+
width: keyboardWidth(whiteKeyWidth, range),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The fill for `key`.
|
|
46
|
+
*
|
|
47
|
+
* Sounding wins over selected — it is the more transient signal, and a key
|
|
48
|
+
* that is both should show what is happening now — and either wins over the
|
|
49
|
+
* out-of-range dimming, which is a standing fact about the instrument.
|
|
50
|
+
*/
|
|
51
|
+
export function keyboardKeyFill(key, state, theme) {
|
|
52
|
+
if (state.lit)
|
|
53
|
+
return theme.keyboardLit ?? theme.notePlaying;
|
|
54
|
+
if (state.selected)
|
|
55
|
+
return theme.keyboardSelected ?? theme.noteSelected;
|
|
56
|
+
if (key.isBlack)
|
|
57
|
+
return key.outOfRange ? KEYBOARD_OUT_OF_RANGE_BLACK : KEYBOARD_BLACK;
|
|
58
|
+
return key.outOfRange ? KEYBOARD_OUT_OF_RANGE_WHITE : KEYBOARD_WHITE;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=keyboard-keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyboard-keys.js","sourceRoot":"","sources":["../src/keyboard-keys.ts"],"names":[],"mappings":"AAiBA,OAAO,EACL,UAAU,EACV,2BAA2B,EAC3B,2BAA2B,EAC3B,gBAAgB,EAChB,mBAAmB,EACnB,WAAW,EACX,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,aAAa,GAId,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,sBAAsB,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAI3E;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAErC,4BAA4B;AAC5B,MAAM,CAAC,MAAM,cAAc,GAAG,SAAS,CAAC;AACxC,4BAA4B;AAC5B,MAAM,CAAC,MAAM,cAAc,GAAG,SAAS,CAAC;AA4CxC;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,EAC3B,KAAK,EACL,MAAM,EACN,KAAK,EACL,YAAY,GAAG,SAAS,EACxB,GAAG,GAAG,QAAQ,GACM;IACpB,MAAM,IAAI,GAAG,KAAK;QAChB,CAAC,CAAC,iBAAiB,CAAC,KAAK,CAAC;QAC1B,CAAC,CAAC,EAAE,KAAK,EAAE,eAAe,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3D,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;IAEjC,wEAAwE;IACxE,MAAM,MAAM,GACV,KAAK,EAAE,IAAI,KAAK,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC;IACxD,mEAAmE;IACnE,MAAM,WAAW,GACf,MAAM,KAAK,YAAY,CAAC,CAAC,CAAC,gBAAgB,GAAG,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC;IACtE,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC;IAEpD,MAAM,MAAM,GAAG,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC;IACzD,MAAM,aAAa,GACjB,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC;IAEnE,MAAM,IAAI,GAAG,WAAW,CACtB,WAAW,CAAC,aAAa,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAC9D,sBAAsB,CAAC,KAAK,EAAE,YAAY,CAAC,CAC5C,CAAC;IAEF,OAAO;QACL,KAAK;QACL,QAAQ;QACR,MAAM;QACN,IAAI;QACJ,WAAW;QACX,SAAS;QACT,aAAa;QACb,KAAK,EAAE,aAAa,CAAC,aAAa,EAAE,KAAK,CAAC;KAC3C,CAAC;AACJ,CAAC;AAUD;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAC7B,GAA6C,EAC7C,KAAe,EACf,KAAkB;IAElB,IAAI,KAAK,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,WAAW,CAAC;IAC7D,IAAI,KAAK,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAC,gBAAgB,IAAI,KAAK,CAAC,YAAY,CAAC;IACxE,IAAI,GAAG,CAAC,OAAO;QACb,OAAO,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,2BAA2B,CAAC,CAAC,CAAC,cAAc,CAAC;IACvE,OAAO,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,2BAA2B,CAAC,CAAC,CAAC,cAAc,CAAC;AACvE,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Painting a frame as a cached base with the active track's notes over it.
|
|
3
|
+
*
|
|
4
|
+
* During playback the only thing a repaint changes is which of the active
|
|
5
|
+
* track's notes are lit (`noteColorsFor`), so redrawing the whole window for
|
|
6
|
+
* it — every stave, clef, beam and notehead of every visible track — is almost
|
|
7
|
+
* entirely wasted. The renderer draws a frame as two layers (see
|
|
8
|
+
* `CanvasRenderOptions.layer`); this keeps the base from the last paint and
|
|
9
|
+
* redraws only the overlay while nothing else has changed.
|
|
10
|
+
*
|
|
11
|
+
* What counts as "nothing else" is the canvas's call, not this module's: the
|
|
12
|
+
* `ScoreCanvas` passes a `baseVersion` that moves whenever anything but the lit
|
|
13
|
+
* notes changes — the score, the view, the scroll, the active track, the
|
|
14
|
+
* selection. This compares versions and nothing more.
|
|
15
|
+
*
|
|
16
|
+
* Platform-free. A host supplies the two things only it can do: record into an
|
|
17
|
+
* offscreen layer (an offscreen canvas on the web, an `SkPicture` in Skia) and
|
|
18
|
+
* put a layer on screen with more drawing over it.
|
|
19
|
+
*/
|
|
20
|
+
import type { CanvasRenderOptions, CanvasRenderResult, CanvasScoreRenderer } from './canvas-renderer.js';
|
|
21
|
+
import type { DrawingContext2D } from './drawing-context.js';
|
|
22
|
+
import type { Score } from '@sudobility/music_types';
|
|
23
|
+
export type LayerCompositor<Layer> = {
|
|
24
|
+
/**
|
|
25
|
+
* Draw into an offscreen layer the size of the visible canvas and return it.
|
|
26
|
+
*
|
|
27
|
+
* May reuse and return the previous layer: it is never shown again once a
|
|
28
|
+
* new one is recorded.
|
|
29
|
+
*/
|
|
30
|
+
record(draw: (ctx: DrawingContext2D) => void, previous: Layer | null): Layer;
|
|
31
|
+
/**
|
|
32
|
+
* Show `base` on the visible surface with an identity transform, then run
|
|
33
|
+
* `drawOverlay` over it on the same context.
|
|
34
|
+
*/
|
|
35
|
+
present(base: Layer, drawOverlay: (ctx: DrawingContext2D) => void): void;
|
|
36
|
+
};
|
|
37
|
+
/** What `ScoreCanvas` tells a surface about the frame it is asking for. */
|
|
38
|
+
export type PaintFrame = {
|
|
39
|
+
/**
|
|
40
|
+
* Moves whenever anything a base depends on has changed; equal means only
|
|
41
|
+
* the lit notes differ from the last paint.
|
|
42
|
+
*/
|
|
43
|
+
baseVersion: number;
|
|
44
|
+
};
|
|
45
|
+
export type LayeredPaint = (score: Score, options: CanvasRenderOptions, frame?: PaintFrame) => CanvasRenderResult | null;
|
|
46
|
+
export declare function createLayeredPaint<Layer>(renderer: CanvasScoreRenderer, compositor: LayerCompositor<Layer>): LayeredPaint;
|
|
47
|
+
//# sourceMappingURL=layered-paint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layered-paint.d.ts","sourceRoot":"","sources":["../src/layered-paint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EACV,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAErD,MAAM,MAAM,eAAe,CAAC,KAAK,IAAI;IACnC;;;;;OAKG;IACH,MAAM,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,IAAI,EAAE,QAAQ,EAAE,KAAK,GAAG,IAAI,GAAG,KAAK,CAAC;IAC7E;;;OAGG;IACH,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,IAAI,GAAG,IAAI,CAAC;CAC1E,CAAC;AAEF,2EAA2E;AAC3E,MAAM,MAAM,UAAU,GAAG;IACvB;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,CACzB,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,mBAAmB,EAC5B,KAAK,CAAC,EAAE,UAAU,KACf,kBAAkB,GAAG,IAAI,CAAC;AAE/B,wBAAgB,kBAAkB,CAAC,KAAK,EACtC,QAAQ,EAAE,mBAAmB,EAC7B,UAAU,EAAE,eAAe,CAAC,KAAK,CAAC,GACjC,YAAY,CA0Bd"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function createLayeredPaint(renderer, compositor) {
|
|
2
|
+
let base = null;
|
|
3
|
+
let version = null;
|
|
4
|
+
return (score, options, frame) => {
|
|
5
|
+
// Without a version there is no telling what changed, so the base is
|
|
6
|
+
// always re-recorded: correct, and exactly the old cost plus a composite.
|
|
7
|
+
let shown = base;
|
|
8
|
+
if (shown === null ||
|
|
9
|
+
frame === undefined ||
|
|
10
|
+
frame.baseVersion !== version) {
|
|
11
|
+
shown = compositor.record(ctx => renderer.render(score, ctx, { ...options, layer: 'base' }), base);
|
|
12
|
+
base = shown;
|
|
13
|
+
version = frame?.baseVersion ?? null;
|
|
14
|
+
}
|
|
15
|
+
let result = null;
|
|
16
|
+
compositor.present(shown, ctx => {
|
|
17
|
+
result = renderer.render(score, ctx, { ...options, layer: 'overlay' });
|
|
18
|
+
});
|
|
19
|
+
return result;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=layered-paint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layered-paint.js","sourceRoot":"","sources":["../src/layered-paint.ts"],"names":[],"mappings":"AAyDA,MAAM,UAAU,kBAAkB,CAChC,QAA6B,EAC7B,UAAkC;IAElC,IAAI,IAAI,GAAiB,IAAI,CAAC;IAC9B,IAAI,OAAO,GAAkB,IAAI,CAAC;IAElC,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QAC/B,qEAAqE;QACrE,0EAA0E;QAC1E,IAAI,KAAK,GAAG,IAAI,CAAC;QACjB,IACE,KAAK,KAAK,IAAI;YACd,KAAK,KAAK,SAAS;YACnB,KAAK,CAAC,WAAW,KAAK,OAAO,EAC7B,CAAC;YACD,KAAK,GAAG,UAAU,CAAC,MAAM,CACvB,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EACjE,IAAI,CACL,CAAC;YACF,IAAI,GAAG,KAAK,CAAC;YACb,OAAO,GAAG,KAAK,EAAE,WAAW,IAAI,IAAI,CAAC;QACvC,CAAC;QACD,IAAI,MAAM,GAA8B,IAAI,CAAC;QAC7C,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE;YAC9B,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;QACzE,CAAC,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;AACJ,CAAC"}
|
package/dist/pagination.d.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type { Track } from '@sudobility/music_types';
|
|
2
2
|
import type { LayoutPlan } from './layout.js';
|
|
3
|
-
|
|
4
|
-
export
|
|
3
|
+
/** The papers a score prints on, in the order a picker offers them. */
|
|
4
|
+
export declare const PAPER_SIZES: readonly ["a4", "letter", "legal"];
|
|
5
|
+
export type PaperSize = (typeof PAPER_SIZES)[number];
|
|
6
|
+
/** Portrait first: it is the default and what almost every part is. */
|
|
7
|
+
export declare const PAPER_ORIENTATIONS: readonly ["portrait", "landscape"];
|
|
8
|
+
export type PaperOrientation = (typeof PAPER_ORIENTATIONS)[number];
|
|
5
9
|
/** Portrait dimensions in millimetres; landscape swaps them. */
|
|
6
10
|
export declare const PAPER_DIMENSIONS_MM: Record<PaperSize, {
|
|
7
11
|
width: number;
|
package/dist/pagination.d.ts.map
CHANGED
|
@@ -1 +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,
|
|
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,uEAAuE;AACvE,eAAO,MAAM,WAAW,oCAAqC,CAAC;AAC9D,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAErD,uEAAuE;AACvE,eAAO,MAAM,kBAAkB,oCAAqC,CAAC;AACrE,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnE,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"}
|
package/dist/pagination.js
CHANGED
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
* of a phrase costs a real player a hand.
|
|
11
11
|
*/
|
|
12
12
|
import { isSilentMeasure } from '@sudobility/music_types';
|
|
13
|
+
/** The papers a score prints on, in the order a picker offers them. */
|
|
14
|
+
export const PAPER_SIZES = ['a4', 'letter', 'legal'];
|
|
15
|
+
/** Portrait first: it is the default and what almost every part is. */
|
|
16
|
+
export const PAPER_ORIENTATIONS = ['portrait', 'landscape'];
|
|
13
17
|
/** Portrait dimensions in millimetres; landscape swaps them. */
|
|
14
18
|
export const PAPER_DIMENSIONS_MM = {
|
|
15
19
|
a4: { width: 210, height: 297 },
|
package/dist/pagination.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pagination.js","sourceRoot":"","sources":["../src/pagination.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"pagination.js","sourceRoot":"","sources":["../src/pagination.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Playback → the score canvas, in one place both apps share.
|
|
3
|
+
*
|
|
4
|
+
* Watches the shared playhead (`IMusicPosition`) and the player's sounding
|
|
5
|
+
* notes and drives a `ScoreCanvas` with them: the cursor, the lit notes, and
|
|
6
|
+
* following the music. The rules below used to live separately in the web
|
|
7
|
+
* app's caret and in the native app's, and disagreed.
|
|
8
|
+
*
|
|
9
|
+
* **The cursor is described, not written per frame.** While playing, the
|
|
10
|
+
* binding hands the canvas a motion (a tick, when it was true, and a rate) and
|
|
11
|
+
* re-describes it only when the reports disagree with it by more than
|
|
12
|
+
* `DRIFT_SECONDS`, or the tempo has measurably changed. Between those the
|
|
13
|
+
* surface moves the line on its own.
|
|
14
|
+
*/
|
|
15
|
+
import type { IMusicPosition, SoundingNote } from '@sudobility/music_types';
|
|
16
|
+
import type { CursorMotion } from './cursor.js';
|
|
17
|
+
/** Re-describe the motion when the line would be further than this from the reports. */
|
|
18
|
+
export declare const DRIFT_SECONDS = 0.04;
|
|
19
|
+
/** What the binding needs from the canvas — `ScoreCanvas` satisfies it. */
|
|
20
|
+
export type PlaybackCanvasTarget = {
|
|
21
|
+
setCursorTick(tick: number): void;
|
|
22
|
+
animateCursor(motion: CursorMotion): void;
|
|
23
|
+
setPlayingNotes(notes: readonly SoundingNote[]): void;
|
|
24
|
+
scrollToFollow(tick: number): void;
|
|
25
|
+
measureIndexAt(tick: number): number | null;
|
|
26
|
+
writtenTicksPerSecond(tick: number): number;
|
|
27
|
+
/** How long lit notes take to draw here; see `ScoreCanvas.highlightLatencySeconds`. */
|
|
28
|
+
subscribeHighlightLatency?(listener: (seconds: number) => void): () => void;
|
|
29
|
+
};
|
|
30
|
+
export type PlaybackBindingOptions = {
|
|
31
|
+
position: IMusicPosition;
|
|
32
|
+
/** The player's sounding-notes subscription; returns an unsubscribe. */
|
|
33
|
+
onSounding?: (listener: (notes: readonly SoundingNote[]) => void) => () => void;
|
|
34
|
+
/**
|
|
35
|
+
* Run `work` once the current burst of notifications is over — a microtask
|
|
36
|
+
* (`queueMicrotask`) on every host.
|
|
37
|
+
*
|
|
38
|
+
* A single engine call can notify several times, and only the end state means
|
|
39
|
+
* anything: stopping homes the position to 0 while it still reports playing,
|
|
40
|
+
* then reports stopped. Decided notification by notification, that homing
|
|
41
|
+
* reads as the music moving to bar 1 and the view scrolls there; decided once
|
|
42
|
+
* the call is over, it is a stop, and stopping is not a navigation.
|
|
43
|
+
*/
|
|
44
|
+
defer: (work: () => void) => void;
|
|
45
|
+
/**
|
|
46
|
+
* The player's `setSoundingRenderDelay`: told how long lit notes take to
|
|
47
|
+
* draw on this host, so it publishes them that far ahead and they land with
|
|
48
|
+
* the sound rather than behind it.
|
|
49
|
+
*/
|
|
50
|
+
setSoundingRenderDelay?: (seconds: number) => void;
|
|
51
|
+
/** Whether the view follows the playhead. Default true. */
|
|
52
|
+
follow?: boolean;
|
|
53
|
+
/** `performance.now()` — passed in, since this package names no host global. */
|
|
54
|
+
now: () => number;
|
|
55
|
+
};
|
|
56
|
+
export declare function bindPlaybackToCanvas(canvas: PlaybackCanvasTarget, { position, defer, onSounding, setSoundingRenderDelay, follow, now, }: PlaybackBindingOptions): () => void;
|
|
57
|
+
//# sourceMappingURL=playback-binding.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playback-binding.d.ts","sourceRoot":"","sources":["../src/playback-binding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,wFAAwF;AACxF,eAAO,MAAM,aAAa,OAAO,CAAC;AAQlC,2EAA2E;AAC3E,MAAM,MAAM,oBAAoB,GAAG;IACjC,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IAC1C,eAAe,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,GAAG,IAAI,CAAC;IACtD,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5C,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5C,uFAAuF;IACvF,yBAAyB,CAAC,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CAC7E,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,cAAc,CAAC;IACzB,wEAAwE;IACxE,UAAU,CAAC,EAAE,CACX,QAAQ,EAAE,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,KAAK,IAAI,KAC/C,MAAM,IAAI,CAAC;IAChB;;;;;;;;;OASG;IACH,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;IAClC;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,2DAA2D;IAC3D,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,gFAAgF;IAChF,GAAG,EAAE,MAAM,MAAM,CAAC;CACnB,CAAC;AAEF,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,oBAAoB,EAC5B,EACE,QAAQ,EACR,KAAK,EACL,UAAU,EACV,sBAAsB,EACtB,MAAa,EACb,GAAG,GACJ,EAAE,sBAAsB,GACxB,MAAM,IAAI,CA6GZ"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/** Re-describe the motion when the line would be further than this from the reports. */
|
|
2
|
+
export const DRIFT_SECONDS = 0.04;
|
|
3
|
+
/** The shortest span a rate measurement is trusted over. */
|
|
4
|
+
const RATE_WINDOW_SECONDS = 0.25;
|
|
5
|
+
/** A tempo change smaller than this is measurement noise… */
|
|
6
|
+
const RATE_CHANGE = 0.05;
|
|
7
|
+
/** …and it is judged only over a span long enough to tell. */
|
|
8
|
+
const RATE_CHANGE_WINDOW_SECONDS = 1;
|
|
9
|
+
export function bindPlaybackToCanvas(canvas, { position, defer, onSounding, setSoundingRenderDelay, follow = true, now, }) {
|
|
10
|
+
let motion = null;
|
|
11
|
+
let wasPlaying = position.isPlaying;
|
|
12
|
+
let rateWindow = null;
|
|
13
|
+
let rate = 0;
|
|
14
|
+
let followedMeasure = null;
|
|
15
|
+
const describe = (next) => {
|
|
16
|
+
motion = next;
|
|
17
|
+
canvas.animateCursor(next);
|
|
18
|
+
};
|
|
19
|
+
const followTo = (tick) => {
|
|
20
|
+
if (!follow)
|
|
21
|
+
return;
|
|
22
|
+
const measure = canvas.measureIndexAt(tick);
|
|
23
|
+
// 14. Once per bar.
|
|
24
|
+
if (measure === null || measure === followedMeasure)
|
|
25
|
+
return;
|
|
26
|
+
followedMeasure = measure;
|
|
27
|
+
canvas.scrollToFollow(tick);
|
|
28
|
+
};
|
|
29
|
+
const wake = () => {
|
|
30
|
+
const tick = position.tick;
|
|
31
|
+
const at = now();
|
|
32
|
+
const playing = position.isPlaying;
|
|
33
|
+
const transportChanged = playing !== wasPlaying;
|
|
34
|
+
wasPlaying = playing;
|
|
35
|
+
if (!playing) {
|
|
36
|
+
rateWindow = null;
|
|
37
|
+
if (!motion || motion.ticksPerSecond !== 0 || motion.tick !== tick) {
|
|
38
|
+
motion = { tick, atMs: at, ticksPerSecond: 0 };
|
|
39
|
+
canvas.setCursorTick(tick);
|
|
40
|
+
}
|
|
41
|
+
// 15. Stopping or pausing is not a navigation; a seek while stopped is.
|
|
42
|
+
if (transportChanged)
|
|
43
|
+
followedMeasure = null;
|
|
44
|
+
else
|
|
45
|
+
followTo(tick);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
followTo(tick);
|
|
49
|
+
if (transportChanged || !rateWindow || tick < rateWindow.tick) {
|
|
50
|
+
// Just started, or jumped back: nothing measured yet belongs to this run.
|
|
51
|
+
rate = canvas.writtenTicksPerSecond(tick);
|
|
52
|
+
rateWindow = { tick, at };
|
|
53
|
+
describe({ tick, atMs: at, ticksPerSecond: rate });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const span = (at - rateWindow.at) / 1000;
|
|
57
|
+
const measured = span >= RATE_WINDOW_SECONDS ? (tick - rateWindow.tick) / span : 0;
|
|
58
|
+
if (rate <= 0 || !motion) {
|
|
59
|
+
if (measured <= 0)
|
|
60
|
+
return;
|
|
61
|
+
rate = measured;
|
|
62
|
+
rateWindow = { tick, at };
|
|
63
|
+
describe({ tick, atMs: at, ticksPerSecond: rate });
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const expected = motion.tick + (motion.ticksPerSecond * (at - motion.atMs)) / 1000;
|
|
67
|
+
const drifted = Math.abs(expected - tick) > rate * DRIFT_SECONDS;
|
|
68
|
+
const retimed = span >= RATE_CHANGE_WINDOW_SECONDS &&
|
|
69
|
+
measured > 0 &&
|
|
70
|
+
Math.abs(measured - rate) > rate * RATE_CHANGE;
|
|
71
|
+
if (drifted || retimed) {
|
|
72
|
+
// The measurement window restarts only once it has produced a rate:
|
|
73
|
+
// restarting it on every correction would keep it too short to measure,
|
|
74
|
+
// and a transport running at another speed would be re-anchored at the
|
|
75
|
+
// wrong rate forever.
|
|
76
|
+
if (measured > 0) {
|
|
77
|
+
rate = measured;
|
|
78
|
+
rateWindow = { tick, at };
|
|
79
|
+
}
|
|
80
|
+
describe({ tick, atMs: at, ticksPerSecond: rate });
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
let queued = false;
|
|
84
|
+
let disposed = false;
|
|
85
|
+
const unsubscribePosition = position.subscribe(() => {
|
|
86
|
+
if (queued)
|
|
87
|
+
return;
|
|
88
|
+
queued = true;
|
|
89
|
+
defer(() => {
|
|
90
|
+
queued = false;
|
|
91
|
+
if (!disposed)
|
|
92
|
+
wake();
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
const unsubscribeLatency = setSoundingRenderDelay && canvas.subscribeHighlightLatency
|
|
96
|
+
? canvas.subscribeHighlightLatency(setSoundingRenderDelay)
|
|
97
|
+
: undefined;
|
|
98
|
+
const unsubscribeSounding = onSounding?.(notes => canvas.setPlayingNotes(notes));
|
|
99
|
+
// The cursor starts where the playhead is, without scrolling anywhere.
|
|
100
|
+
canvas.setCursorTick(position.tick);
|
|
101
|
+
motion = { tick: position.tick, atMs: now(), ticksPerSecond: 0 };
|
|
102
|
+
followedMeasure = canvas.measureIndexAt(position.tick);
|
|
103
|
+
return () => {
|
|
104
|
+
disposed = true;
|
|
105
|
+
unsubscribePosition();
|
|
106
|
+
unsubscribeSounding?.();
|
|
107
|
+
unsubscribeLatency?.();
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=playback-binding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playback-binding.js","sourceRoot":"","sources":["../src/playback-binding.ts"],"names":[],"mappings":"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"}
|
package/dist/playhead.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playhead.d.ts","sourceRoot":"","sources":["../src/playhead.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,KAAK,EACV,oBAAoB,EAGrB,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"playhead.d.ts","sourceRoot":"","sources":["../src/playhead.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,KAAK,EACV,oBAAoB,EAGrB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,KAAK,EAAE,UAAU,EAAgB,MAAM,aAAa,CAAC;AAE5D;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,WAAW,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;AAEtE,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;AA8NF;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,MAAM,EACZ,aAAa,CAAC,EAAE,aAAa,GAC5B,aAAa,GAAG,IAAI,CAkDtB;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,KAAK,EACZ,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,aAAa,CAAC,EAAE,aAAa,GAC5B,MAAM,GAAG,IAAI,CA6Cf"}
|