@sudobility/music_drawing 0.0.23 → 0.0.25
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 +53 -0
- package/dist/canvas-renderer.d.ts.map +1 -1
- package/dist/canvas-renderer.js +123 -2
- package/dist/canvas-renderer.js.map +1 -1
- package/dist/hit-test.d.ts +101 -0
- package/dist/hit-test.d.ts.map +1 -0
- package/dist/hit-test.js +213 -0
- package/dist/hit-test.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/layout.d.ts +15 -1
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js +36 -3
- package/dist/layout.js.map +1 -1
- package/dist/measure-content.d.ts +3 -1
- package/dist/measure-content.d.ts.map +1 -1
- package/dist/measure-content.js +2 -2
- package/dist/measure-content.js.map +1 -1
- package/dist/note-colors.d.ts +55 -0
- package/dist/note-colors.d.ts.map +1 -0
- package/dist/note-colors.js +31 -0
- package/dist/note-colors.js.map +1 -0
- package/dist/note-drag.d.ts +29 -0
- package/dist/note-drag.d.ts.map +1 -0
- package/dist/note-drag.js +27 -0
- package/dist/note-drag.js.map +1 -0
- package/dist/playback-scroll.d.ts +40 -0
- package/dist/playback-scroll.d.ts.map +1 -0
- package/dist/playback-scroll.js +128 -0
- package/dist/playback-scroll.js.map +1 -0
- package/dist/playhead.d.ts +41 -7
- package/dist/playhead.d.ts.map +1 -1
- package/dist/playhead.js +153 -2
- package/dist/playhead.js.map +1 -1
- package/dist/render-theme.d.ts +30 -0
- package/dist/render-theme.d.ts.map +1 -0
- package/dist/render-theme.js +23 -0
- package/dist/render-theme.js.map +1 -0
- package/dist/skia/index.d.ts +1 -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-api.d.ts +8 -0
- package/dist/skia/skia-api.d.ts.map +1 -1
- package/dist/skia/skia-context.d.ts +1 -0
- package/dist/skia/skia-context.d.ts.map +1 -1
- package/dist/skia/skia-context.js +104 -8
- package/dist/skia/skia-context.js.map +1 -1
- package/dist/stave-header.d.ts +29 -0
- package/dist/stave-header.d.ts.map +1 -0
- package/dist/stave-header.js +122 -0
- package/dist/stave-header.js.map +1 -0
- package/dist/test/fake-skia.d.ts +12 -1
- package/dist/test/fake-skia.d.ts.map +1 -1
- package/dist/test/fake-skia.js +8 -1
- package/dist/test/fake-skia.js.map +1 -1
- package/dist/track-gutter.d.ts +14 -0
- package/dist/track-gutter.d.ts.map +1 -0
- package/dist/track-gutter.js +37 -0
- package/dist/track-gutter.js.map +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export function buildNoteColors({ selectedIds, playingIds, regenerated, }) {
|
|
2
|
+
const colors = new Map();
|
|
3
|
+
const selectedRole = regenerated ? 'regenerated' : 'selected';
|
|
4
|
+
for (const id of selectedIds)
|
|
5
|
+
colors.set(id, selectedRole);
|
|
6
|
+
for (const id of playingIds)
|
|
7
|
+
colors.set(id, 'playing');
|
|
8
|
+
return colors;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The colours actually painted: the selection with the sounding notes over it.
|
|
12
|
+
*
|
|
13
|
+
* **Only the active track's sounding notes light up.** Every track's used to,
|
|
14
|
+
* on the reasoning that a state colour marks a note as needing attention
|
|
15
|
+
* wherever it is — which is right for selection, where a cross-track selection
|
|
16
|
+
* is something the reader made on purpose. It is wrong for playback: on a
|
|
17
|
+
* large score the lit notes then scatter across whichever parts happen to be
|
|
18
|
+
* sounding, which reads as random rather than as a playhead, and the one track
|
|
19
|
+
* being read goes dark whenever it rests. The piano keyboard already showed
|
|
20
|
+
* the active track alone, and this is what makes the notation agree with it.
|
|
21
|
+
*/
|
|
22
|
+
export function noteColorsFor({ selectedIds, sounding, activeTrackId, regenerated, }) {
|
|
23
|
+
return buildNoteColors({
|
|
24
|
+
selectedIds,
|
|
25
|
+
playingIds: sounding
|
|
26
|
+
.filter(note => note.trackId === activeTrackId)
|
|
27
|
+
.map(note => note.noteId),
|
|
28
|
+
regenerated,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=note-colors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"note-colors.js","sourceRoot":"","sources":["../src/note-colors.ts"],"names":[],"mappings":"AA4BA,MAAM,UAAU,eAAe,CAAC,EAC9B,WAAW,EACX,UAAU,EACV,WAAW,GACW;IACtB,MAAM,MAAM,GAAG,IAAI,GAAG,EAAyB,CAAC;IAChD,MAAM,YAAY,GAAkB,WAAW,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,UAAU,CAAC;IAC7E,KAAK,MAAM,EAAE,IAAI,WAAW;QAAE,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC;IAC3D,KAAK,MAAM,EAAE,IAAI,UAAU;QAAE,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;IACvD,OAAO,MAAM,CAAC;AAChB,CAAC;AAiBD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,aAAa,CAAC,EAC5B,WAAW,EACX,QAAQ,EACR,aAAa,EACb,WAAW,GACS;IACpB,OAAO,eAAe,CAAC;QACrB,WAAW;QACX,UAAU,EAAE,QAAQ;aACjB,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,KAAK,aAAa,CAAC;aAC9C,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC;QAC3B,WAAW;KACZ,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an Option+drag resolves to: which track, and how far in time.
|
|
3
|
+
*
|
|
4
|
+
* Pure over a layout plan and a point — no store, no React — in the same shape
|
|
5
|
+
* as `pitch-drag.ts` and `playback-scroll.ts`, so the rules are testable
|
|
6
|
+
* without rendering anything.
|
|
7
|
+
*/
|
|
8
|
+
import type { Score } from '@sudobility/music_types';
|
|
9
|
+
import type { LayoutPlan } from './layout.js';
|
|
10
|
+
import type { Point } from './hit-test.js';
|
|
11
|
+
/** The note the gesture is anchored to: the one under the pointer at press. */
|
|
12
|
+
export type NoteDrag = {
|
|
13
|
+
anchorId: string;
|
|
14
|
+
anchorTick: number;
|
|
15
|
+
};
|
|
16
|
+
/** Where a drop would put the selection. */
|
|
17
|
+
export type DropTarget = {
|
|
18
|
+
trackId: string;
|
|
19
|
+
deltaTicks: number;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* The track and tick delta a drop at `point` would produce, or null when the
|
|
23
|
+
* pointer is not over a stave.
|
|
24
|
+
*
|
|
25
|
+
* The delta is relative to the **anchor**, so every other note in the
|
|
26
|
+
* selection keeps its offset and a phrase keeps its shape.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolveDrop(plan: LayoutPlan, score: Score, drag: NoteDrag, point: Point, snapTicks: number): DropTarget | null;
|
|
29
|
+
//# sourceMappingURL=note-drag.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"note-drag.d.ts","sourceRoot":"","sources":["../src/note-drag.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAE3C,+EAA+E;AAC/E,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhE,4CAA4C;AAC5C,MAAM,MAAM,UAAU,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAQjE;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,KAAK,EACZ,SAAS,EAAE,MAAM,GAChB,UAAU,GAAG,IAAI,CAUnB"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { tickForPoint } from './playhead.js';
|
|
2
|
+
import { trackIdAtContentPoint } from './hit-test.js';
|
|
3
|
+
/** Rounds `tick` to the nearest multiple of `snapTicks`. */
|
|
4
|
+
function snap(tick, snapTicks) {
|
|
5
|
+
if (snapTicks <= 0)
|
|
6
|
+
return tick;
|
|
7
|
+
return Math.round(tick / snapTicks) * snapTicks;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The track and tick delta a drop at `point` would produce, or null when the
|
|
11
|
+
* pointer is not over a stave.
|
|
12
|
+
*
|
|
13
|
+
* The delta is relative to the **anchor**, so every other note in the
|
|
14
|
+
* selection keeps its offset and a phrase keeps its shape.
|
|
15
|
+
*/
|
|
16
|
+
export function resolveDrop(plan, score, drag, point, snapTicks) {
|
|
17
|
+
const trackId = trackIdAtContentPoint(plan, point);
|
|
18
|
+
if (!trackId)
|
|
19
|
+
return null;
|
|
20
|
+
// `tickForPoint` needs the score as well as the plan: it reads measure
|
|
21
|
+
// timings, not just geometry. Same call the caret already makes.
|
|
22
|
+
const tick = tickForPoint(plan, score, point.x, point.y);
|
|
23
|
+
if (tick === null)
|
|
24
|
+
return null;
|
|
25
|
+
return { trackId, deltaTicks: snap(tick, snapTicks) - drag.anchorTick };
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=note-drag.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"note-drag.js","sourceRoot":"","sources":["../src/note-drag.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAStD,4DAA4D;AAC5D,SAAS,IAAI,CAAC,IAAY,EAAE,SAAiB;IAC3C,IAAI,SAAS,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAChC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,SAAS,CAAC,GAAG,SAAS,CAAC;AAClD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CACzB,IAAgB,EAChB,KAAY,EACZ,IAAc,EACd,KAAY,EACZ,SAAiB;IAEjB,MAAM,OAAO,GAAG,qBAAqB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACnD,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAE1B,uEAAuE;IACvE,iEAAiE;IACjE,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IACzD,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAE/B,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;AAC1E,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { LayoutPlan } from './layout.js';
|
|
2
|
+
export type PlaybackScrollParams = {
|
|
3
|
+
plan: LayoutPlan;
|
|
4
|
+
layoutMode: 'page' | 'continuous';
|
|
5
|
+
zoom: number;
|
|
6
|
+
/** Index of the measure now playing. */
|
|
7
|
+
measureIndex: number;
|
|
8
|
+
/** The measure's box in logical (unzoomed) content coordinates. */
|
|
9
|
+
measureX: number;
|
|
10
|
+
/** The measure's width in logical (unzoomed) content coordinates. */
|
|
11
|
+
measureWidth: number;
|
|
12
|
+
/** Current horizontal scroll position, in on-screen px. */
|
|
13
|
+
scrollLeft: number;
|
|
14
|
+
/** Visible width of the scroll box, in on-screen px. */
|
|
15
|
+
viewportWidth: number;
|
|
16
|
+
/** Current vertical scroll position, in on-screen px. */
|
|
17
|
+
scrollTop: number;
|
|
18
|
+
/** Visible height of the scroll box, in on-screen px. */
|
|
19
|
+
viewportHeight: number;
|
|
20
|
+
/** Breathing room left before the target, in on-screen px. */
|
|
21
|
+
margin: number;
|
|
22
|
+
};
|
|
23
|
+
export type PlaybackScrollTarget = {
|
|
24
|
+
left: number;
|
|
25
|
+
top: number;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Where to scroll for the measure now playing, or `null` to leave the scroll
|
|
29
|
+
* box alone.
|
|
30
|
+
*
|
|
31
|
+
* Every "no move needed" path returns `null` rather than the current scroll
|
|
32
|
+
* position, and that distinction is load-bearing: the caller scrolls with
|
|
33
|
+
* `behavior: 'smooth'`, and issuing *any* `scrollTo` — even one naming the
|
|
34
|
+
* position the box is already heading to — cancels the animation in flight and
|
|
35
|
+
* strands it wherever it had reached. Returning the current position once per
|
|
36
|
+
* measure therefore killed each scroll a moment after it started, and the sheet
|
|
37
|
+
* appeared not to follow the music at all.
|
|
38
|
+
*/
|
|
39
|
+
export declare function playbackScrollTarget({ plan, layoutMode, zoom, measureIndex, measureX, measureWidth, scrollLeft, viewportWidth, scrollTop, viewportHeight, margin, }: PlaybackScrollParams): PlaybackScrollTarget | null;
|
|
40
|
+
//# sourceMappingURL=playback-scroll.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playback-scroll.d.ts","sourceRoot":"","sources":["../src/playback-scroll.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,UAAU,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,YAAY,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,YAAY,EAAE,MAAM,CAAC;IACrB,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,YAAY,EAAE,MAAM,CAAC;IACrB,2DAA2D;IAC3D,UAAU,EAAE,MAAM,CAAC;IACnB,wDAAwD;IACxD,aAAa,EAAE,MAAM,CAAC;IACtB,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,cAAc,EAAE,MAAM,CAAC;IACvB,8DAA8D;IAC9D,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AA4BjE;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,IAAI,EACJ,UAAU,EACV,IAAI,EACJ,YAAY,EACZ,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,aAAa,EACb,SAAS,EACT,cAAc,EACd,MAAM,GACP,EAAE,oBAAoB,GAAG,oBAAoB,GAAG,IAAI,CA4EpD"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where to scroll so the playing measure stays in view.
|
|
3
|
+
*
|
|
4
|
+
* Pure over the `LayoutPlan` — no DOM, no store — so the arithmetic is
|
|
5
|
+
* unit-testable, in the same shape as `autoscroll.ts` and `track-gutter.ts`.
|
|
6
|
+
*
|
|
7
|
+
* The rule that matters here is that following playback must not undo the
|
|
8
|
+
* reader's own vertical scroll. A multi-track score is taller than the viewport,
|
|
9
|
+
* so the reader picks a track to watch; scrolling to the top of each new system
|
|
10
|
+
* threw that away and snapped back to track 1 every time the music wrapped.
|
|
11
|
+
* Both modes therefore preserve vertical position, by different means:
|
|
12
|
+
*
|
|
13
|
+
* - **Page mode** scrolls only when the system being played is not already on
|
|
14
|
+
* screen, and then keeps the *offset within the system*: if track 3 sits at
|
|
15
|
+
* the top of the viewport, it still does after the wrap, because the new
|
|
16
|
+
* system's top replaces the old one's and the reader's offset into it is
|
|
17
|
+
* carried over.
|
|
18
|
+
* - **Continuous mode** is one very wide system, so following the music is
|
|
19
|
+
* purely horizontal and vertical scroll is left completely alone. It too
|
|
20
|
+
* moves only when the playing measure is reaching the edge of the viewport.
|
|
21
|
+
*/
|
|
22
|
+
import { TRACK_INFO_WIDTH } from './layout.js';
|
|
23
|
+
/** The system containing `measureIndex`, or null if it is outside the plan. */
|
|
24
|
+
function systemOf(plan, measureIndex) {
|
|
25
|
+
return (plan.systems.find(s => s.measureIndices.includes(measureIndex)) ?? null);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The system the reader is currently looking at: the one spanning the top of
|
|
29
|
+
* the viewport, else the last one above it (which is where a scroll position
|
|
30
|
+
* inside an inter-system gap lands), else the first.
|
|
31
|
+
*/
|
|
32
|
+
function systemAtViewportTop(plan, scrollTop, zoom) {
|
|
33
|
+
const y = scrollTop / zoom;
|
|
34
|
+
let best = plan.systems[0] ?? null;
|
|
35
|
+
for (const system of plan.systems) {
|
|
36
|
+
if (system.yTop <= y)
|
|
37
|
+
best = system;
|
|
38
|
+
if (system.yTop <= y && y < system.yBottom)
|
|
39
|
+
return system;
|
|
40
|
+
}
|
|
41
|
+
return best;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Where to scroll for the measure now playing, or `null` to leave the scroll
|
|
45
|
+
* box alone.
|
|
46
|
+
*
|
|
47
|
+
* Every "no move needed" path returns `null` rather than the current scroll
|
|
48
|
+
* position, and that distinction is load-bearing: the caller scrolls with
|
|
49
|
+
* `behavior: 'smooth'`, and issuing *any* `scrollTo` — even one naming the
|
|
50
|
+
* position the box is already heading to — cancels the animation in flight and
|
|
51
|
+
* strands it wherever it had reached. Returning the current position once per
|
|
52
|
+
* measure therefore killed each scroll a moment after it started, and the sheet
|
|
53
|
+
* appeared not to follow the music at all.
|
|
54
|
+
*/
|
|
55
|
+
export function playbackScrollTarget({ plan, layoutMode, zoom, measureIndex, measureX, measureWidth, scrollLeft, viewportWidth, scrollTop, viewportHeight, margin, }) {
|
|
56
|
+
const target = systemOf(plan, measureIndex);
|
|
57
|
+
if (!target)
|
|
58
|
+
return null;
|
|
59
|
+
if (layoutMode === 'continuous') {
|
|
60
|
+
// The track-info gutter is pinned to the viewport's left edge and painted
|
|
61
|
+
// over the sheet, so the target has to clear its width as well as the
|
|
62
|
+
// margin — scrolling the playing measure to `margin` parked it, and the
|
|
63
|
+
// caret travelling through it, *behind* the track info for most of every
|
|
64
|
+
// measure. Gutter width scales with zoom: it is cleared in the zoom-scaled
|
|
65
|
+
// space.
|
|
66
|
+
//
|
|
67
|
+
// Vertical is left completely alone: one long system means there is no
|
|
68
|
+
// "next line" to follow, so any vertical move would be the caller fighting
|
|
69
|
+
// the reader for the scrollbar.
|
|
70
|
+
const gutter = TRACK_INFO_WIDTH * zoom;
|
|
71
|
+
const measureLeft = measureX * zoom;
|
|
72
|
+
const measureRight = (measureX + measureWidth) * zoom;
|
|
73
|
+
// Only move once the music is actually reaching the edge of what the reader
|
|
74
|
+
// can see. This used to re-target every measure, which both fought the
|
|
75
|
+
// reader for the scrollbar and meant a fresh smooth-scroll animation was
|
|
76
|
+
// started before the previous one had travelled anywhere.
|
|
77
|
+
const firstVisible = scrollLeft + gutter + margin;
|
|
78
|
+
const lastVisible = scrollLeft + viewportWidth - margin;
|
|
79
|
+
if (measureLeft >= firstVisible && measureRight <= lastVisible)
|
|
80
|
+
return null;
|
|
81
|
+
return { left: Math.max(0, measureLeft - gutter - margin), top: scrollTop };
|
|
82
|
+
}
|
|
83
|
+
// Page mode wraps every system to the viewport and does not scroll
|
|
84
|
+
// horizontally at all, so there is no horizontal following to do — and
|
|
85
|
+
// asking for one would scroll the sheet under the gutter with no way for the
|
|
86
|
+
// reader to bring it back.
|
|
87
|
+
const left = 0;
|
|
88
|
+
// Already on screen in full: leave it alone. Wrapping to a new system is not
|
|
89
|
+
// by itself a reason to scroll — a viewport tall enough to show several
|
|
90
|
+
// systems was being yanked up a line every time the music crossed into the
|
|
91
|
+
// next one, long before the caret was anywhere near the bottom of what the
|
|
92
|
+
// reader could see. Only a target the reader cannot actually see justifies
|
|
93
|
+
// moving the page under them.
|
|
94
|
+
//
|
|
95
|
+
// A system taller than the viewport can never satisfy this (a multi-track
|
|
96
|
+
// score is the normal case), so that falls through to the offset-carrying
|
|
97
|
+
// path below, which is what it is there for.
|
|
98
|
+
const targetTop = target.yTop * zoom;
|
|
99
|
+
const targetBottom = target.yBottom * zoom;
|
|
100
|
+
if (targetTop >= scrollTop && targetBottom <= scrollTop + viewportHeight)
|
|
101
|
+
return null;
|
|
102
|
+
const current = systemAtViewportTop(plan, scrollTop, zoom);
|
|
103
|
+
// Same system: the music has not wrapped, so nothing vertical needs to move.
|
|
104
|
+
if (!current || current === target)
|
|
105
|
+
return null;
|
|
106
|
+
// Carry the reader's offset into the system across to the new one, but never
|
|
107
|
+
// so far that the music leaves the screen.
|
|
108
|
+
//
|
|
109
|
+
// The clamp is what makes this safe. `systemAtViewportTop` answers with the
|
|
110
|
+
// system *above* when the scroll position has landed in the gap between two
|
|
111
|
+
// systems — which is exactly where the previous wrap tends to leave it — so
|
|
112
|
+
// the raw offset came out close to a whole system tall. The next wrap then
|
|
113
|
+
// overshot by that much and the line being played ended up above the top of
|
|
114
|
+
// the viewport, with the music audible but nowhere on screen. Worse, each
|
|
115
|
+
// overshoot parked the reader in another gap, so the error repeated on every
|
|
116
|
+
// wrap and the page raced a line ahead of the music.
|
|
117
|
+
//
|
|
118
|
+
// Bounding the offset to what the target system can actually give up keeps
|
|
119
|
+
// the intent — a reader watching track 3 goes on watching track 3 — while
|
|
120
|
+
// guaranteeing the system stays in view. A system shorter than the viewport
|
|
121
|
+
// has nothing to give up, so it simply aligns to the top.
|
|
122
|
+
const targetHeight = (target.yBottom - target.yTop) * zoom;
|
|
123
|
+
const maxOffset = Math.max(0, targetHeight - viewportHeight);
|
|
124
|
+
const rawOffset = scrollTop - current.yTop * zoom;
|
|
125
|
+
const offsetIntoSystem = Math.min(Math.max(rawOffset, 0), maxOffset);
|
|
126
|
+
return { left, top: Math.max(0, target.yTop * zoom + offsetIntoSystem) };
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=playback-scroll.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playback-scroll.js","sourceRoot":"","sources":["../src/playback-scroll.ts"],"names":[],"mappings":"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"}
|
package/dist/playhead.d.ts
CHANGED
|
@@ -6,14 +6,48 @@
|
|
|
6
6
|
* re-based on the binary-search lookups so every call is O(log n) — part of
|
|
7
7
|
* the canvas renderer's "no O(score) interaction work" rule.
|
|
8
8
|
*
|
|
9
|
-
* The
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* **The caret reads the positions the notes were drawn at.** The renderer
|
|
10
|
+
* records, per measure, the x VexFlow placed each note at — the value from
|
|
11
|
+
* `Note.getAbsoluteX()`, which is the same number the notehead is painted from
|
|
12
|
+
* — and both directions here interpolate *between those notes*. At a note the
|
|
13
|
+
* answer is exactly where the notehead is; between two notes there is nothing
|
|
14
|
+
* left to disagree about.
|
|
15
|
+
*
|
|
16
|
+
* That is a different thing from what this used to do, and the difference is
|
|
17
|
+
* the whole accuracy of the file. It used to spread a measure's duration
|
|
18
|
+
* evenly across the measure's **stave box**, which runs barline to barline. A
|
|
19
|
+
* stave spends its left edge on whatever modifiers it carries — the clef and
|
|
20
|
+
* key signature every system restates, the time signature at the start of the
|
|
21
|
+
* piece — so the box is systematically wider than the span the notes occupy,
|
|
22
|
+
* and by more on the left than the right. The caret therefore sat *behind* the
|
|
23
|
+
* note it pointed at by a margin that grew with the furniture: measured on a
|
|
24
|
+
* simple score, 76px at the downbeat of bar 1, easing to 17px by bar 2 and
|
|
25
|
+
* 11px by bar 3, and sliding within every bar as it played. Which is exactly
|
|
26
|
+
* how it looked.
|
|
27
|
+
*
|
|
28
|
+
* Two consequences worth stating, because both look like bugs and are not:
|
|
29
|
+
*
|
|
30
|
+
* - **Before a measure's first note the caret is flat, sitting on that note.**
|
|
31
|
+
* Everything left of it is engraving furniture, and a caret drifting through
|
|
32
|
+
* the clef would be pointing at nothing. `tickForX` reads that region the
|
|
33
|
+
* same way, so clicking the clef seeks to the start of the bar.
|
|
34
|
+
* - **The positions cover the drawn window only.** That is the right coverage
|
|
35
|
+
* rather than a limitation — the caret is only visible where the music is
|
|
36
|
+
* drawn, and a click can only land there. Off it, and for the single frame
|
|
37
|
+
* before the first paint, both fall back to the stave box, which is what
|
|
38
|
+
* this always did.
|
|
14
39
|
*/
|
|
15
40
|
import type { Score } from '@sudobility/music_types';
|
|
41
|
+
import type { MeasureNotePositions } from './canvas-renderer.js';
|
|
16
42
|
import type { LayoutPlan } from './layout.js';
|
|
43
|
+
/**
|
|
44
|
+
* Measure id -> where its notes were drawn, from the last frame.
|
|
45
|
+
*
|
|
46
|
+
* Optional at every call site: without it these fall back to the stave box,
|
|
47
|
+
* so a caller that has not wired the renderer up gets the old approximation
|
|
48
|
+
* rather than nothing.
|
|
49
|
+
*/
|
|
50
|
+
export type NotePositions = ReadonlyMap<string, MeasureNotePositions>;
|
|
17
51
|
export type CaretPosition = {
|
|
18
52
|
/** Logical x of the caret line. */
|
|
19
53
|
x: number;
|
|
@@ -26,12 +60,12 @@ export type CaretPosition = {
|
|
|
26
60
|
* nothing to draw against (empty score / no layout). Ticks past the end of
|
|
27
61
|
* the score clamp to the final measure's right edge.
|
|
28
62
|
*/
|
|
29
|
-
export declare function caretPositionForTick(plan: LayoutPlan, score: Score, tick: number): CaretPosition | null;
|
|
63
|
+
export declare function caretPositionForTick(plan: LayoutPlan, score: Score, tick: number, notePositions?: NotePositions): CaretPosition | null;
|
|
30
64
|
/**
|
|
31
65
|
* The tick a canvas click at logical `(x, y)` should seek to, or `null`
|
|
32
66
|
* when the point is in dead space between/outside systems. Horizontal
|
|
33
67
|
* positions left/right of a system's measures clamp to that system's
|
|
34
68
|
* first/last measure, so clicking the clef area seeks to the system start.
|
|
35
69
|
*/
|
|
36
|
-
export declare function tickForPoint(plan: LayoutPlan, score: Score, x: number, y: number): number | null;
|
|
70
|
+
export declare function tickForPoint(plan: LayoutPlan, score: Score, x: number, y: number, notePositions?: NotePositions): number | null;
|
|
37
71
|
//# sourceMappingURL=playhead.d.ts.map
|
package/dist/playhead.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playhead.d.ts","sourceRoot":"","sources":["../src/playhead.ts"],"names":[],"mappings":"AAAA
|
|
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;AAE9B,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;AAqMF;;;;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,CAiDtB;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,CA2Cf"}
|
package/dist/playhead.js
CHANGED
|
@@ -49,12 +49,131 @@ function systemForMeasureIndex(plan, measureIndex) {
|
|
|
49
49
|
}
|
|
50
50
|
return null;
|
|
51
51
|
}
|
|
52
|
+
/** Straight-line interpolation between two known points. */
|
|
53
|
+
function lerp(tick, a, b) {
|
|
54
|
+
if (b.tick === a.tick)
|
|
55
|
+
return a.x;
|
|
56
|
+
return a.x + ((tick - a.tick) / (b.tick - a.tick)) * (b.x - a.x);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The x for `tick` inside a measure whose drawn notes are known.
|
|
60
|
+
*
|
|
61
|
+
* Interpolates **between the notes themselves**, which is what makes this
|
|
62
|
+
* agree with the page rather than approximate it: VexFlow does not space a bar
|
|
63
|
+
* proportionally to ticks — a bar of `q q h` is engraved, not measured — so any
|
|
64
|
+
* mapping that spread a bar's duration evenly across some span disagreed with
|
|
65
|
+
* the page between the notes by however much engraving differs from
|
|
66
|
+
* arithmetic. Between two adjacent notes there is nothing to disagree about,
|
|
67
|
+
* and at a note the answer is exactly where the notehead is.
|
|
68
|
+
*
|
|
69
|
+
* The two ends are where the notes stop being able to say anything, and each
|
|
70
|
+
* is deliberate:
|
|
71
|
+
*
|
|
72
|
+
* - **Before the first note the caret sits at it**, flat, rather than sliding
|
|
73
|
+
* in from the barline. Everything to the left of the first note is clef, key
|
|
74
|
+
* and time signature, and a caret drifting through those is pointing at
|
|
75
|
+
* engraving furniture rather than at music. This is also what makes a click
|
|
76
|
+
* to the left of the clef land on the first note, which is the only thing
|
|
77
|
+
* such a click can sensibly mean.
|
|
78
|
+
* - **After the last note it runs on to `endX`**, so the tail of a bar whose
|
|
79
|
+
* last note is short still carries the caret to the barline rather than
|
|
80
|
+
* parking it on that note.
|
|
81
|
+
*/
|
|
82
|
+
function xForTick(tick, positions, area, measureStartTick, endTick, endX) {
|
|
83
|
+
const first = positions[0];
|
|
84
|
+
/*
|
|
85
|
+
Nothing drawn: a measure standing in for a run of silent ones, or one whose
|
|
86
|
+
events were all dropped. There is no note to point at, so the caret crosses
|
|
87
|
+
the note area evenly — which for a multi-measure rest is exactly right, and
|
|
88
|
+
is what `tickForX` reads back.
|
|
89
|
+
*/
|
|
90
|
+
if (!first) {
|
|
91
|
+
const span = endTick - measureStartTick;
|
|
92
|
+
if (span <= 0)
|
|
93
|
+
return area.startX;
|
|
94
|
+
const fraction = Math.min(1, Math.max(0, (tick - measureStartTick) / span));
|
|
95
|
+
return area.startX + fraction * (area.endX - area.startX);
|
|
96
|
+
}
|
|
97
|
+
if (tick <= first.tick)
|
|
98
|
+
return first.x;
|
|
99
|
+
for (let index = 1; index < positions.length; index += 1) {
|
|
100
|
+
const next = positions[index];
|
|
101
|
+
if (tick <= next.tick)
|
|
102
|
+
return lerp(tick, positions[index - 1], next);
|
|
103
|
+
}
|
|
104
|
+
const last = positions[positions.length - 1];
|
|
105
|
+
if (endTick <= last.tick)
|
|
106
|
+
return last.x;
|
|
107
|
+
return lerp(tick, last, { tick: endTick, x: endX });
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The tick at `x`, inside a measure whose drawn notes are known.
|
|
111
|
+
*
|
|
112
|
+
* The exact inverse of `xForTick`, and written as one rather than derived from
|
|
113
|
+
* it, because the two are used in opposite directions and a mismatch between
|
|
114
|
+
* them is invisible until somebody clicks: the caret would land somewhere
|
|
115
|
+
* other than the pointer. The tests assert the round trip rather than either
|
|
116
|
+
* one alone.
|
|
117
|
+
*
|
|
118
|
+
* Left of the first note answers that note's tick — the flat region
|
|
119
|
+
* `xForTick` draws there, read backwards. That is what makes a click on the
|
|
120
|
+
* clef seek to the start of the bar rather than to nothing.
|
|
121
|
+
*/
|
|
122
|
+
function tickForX(x, positions, area, measureStartTick, endTick, endX) {
|
|
123
|
+
const first = positions[0];
|
|
124
|
+
if (!first) {
|
|
125
|
+
const width = area.endX - area.startX;
|
|
126
|
+
if (width <= 0)
|
|
127
|
+
return measureStartTick;
|
|
128
|
+
const fraction = Math.min(1, Math.max(0, (x - area.startX) / width));
|
|
129
|
+
return measureStartTick + fraction * (endTick - measureStartTick);
|
|
130
|
+
}
|
|
131
|
+
if (x <= first.x)
|
|
132
|
+
return first.tick;
|
|
133
|
+
for (let index = 1; index < positions.length; index += 1) {
|
|
134
|
+
const next = positions[index];
|
|
135
|
+
if (x <= next.x) {
|
|
136
|
+
const previous = positions[index - 1];
|
|
137
|
+
if (next.x === previous.x)
|
|
138
|
+
return previous.tick;
|
|
139
|
+
return (previous.tick +
|
|
140
|
+
((x - previous.x) / (next.x - previous.x)) * (next.tick - previous.tick));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const last = positions[positions.length - 1];
|
|
144
|
+
if (endX <= last.x || endTick <= last.tick)
|
|
145
|
+
return last.tick;
|
|
146
|
+
const ratio = Math.min(1, (x - last.x) / (endX - last.x));
|
|
147
|
+
return last.tick + ratio * (endTick - last.tick);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Where this measure's music ends on screen.
|
|
151
|
+
*
|
|
152
|
+
* The next measure's first note when there is one on the same system: a bar's
|
|
153
|
+
* ticks run from its own first note to the next bar's, so using the next
|
|
154
|
+
* origin is what keeps the mapping continuous across a barline instead of
|
|
155
|
+
* kinking at every one. Not across a system break, where the next origin is
|
|
156
|
+
* back at the left of the page and the distance is a wrap rather than a bar.
|
|
157
|
+
* Otherwise the end of this measure's own note area.
|
|
158
|
+
*/
|
|
159
|
+
function endAnchorFor(plan, system, measureIndex, area, notePositions) {
|
|
160
|
+
const lastInSystem = system.measureIndices[system.measureIndices.length - 1];
|
|
161
|
+
if (notePositions &&
|
|
162
|
+
lastInSystem !== undefined &&
|
|
163
|
+
measureIndex < lastInSystem) {
|
|
164
|
+
const nextId = plan.trackLayouts[0]?.track.measures[measureIndex + 1]?.id;
|
|
165
|
+
const next = nextId ? notePositions.get(nextId) : undefined;
|
|
166
|
+
if (next)
|
|
167
|
+
return next.positions[0]?.x ?? next.area.startX;
|
|
168
|
+
}
|
|
169
|
+
return area.endX;
|
|
170
|
+
}
|
|
52
171
|
/**
|
|
53
172
|
* Where the caret for `tick` sits on the canvas, or `null` when there is
|
|
54
173
|
* nothing to draw against (empty score / no layout). Ticks past the end of
|
|
55
174
|
* the score clamp to the final measure's right edge.
|
|
56
175
|
*/
|
|
57
|
-
export function caretPositionForTick(plan, score, tick) {
|
|
176
|
+
export function caretPositionForTick(plan, score, tick, notePositions) {
|
|
58
177
|
if (Number.isNaN(tick))
|
|
59
178
|
return null;
|
|
60
179
|
const timings = measureTimings(score);
|
|
@@ -66,6 +185,23 @@ export function caretPositionForTick(plan, score, tick) {
|
|
|
66
185
|
const system = systemForMeasureIndex(plan, measureIndex);
|
|
67
186
|
if (!layout || !system)
|
|
68
187
|
return null;
|
|
188
|
+
const measureId = plan.trackLayouts[0]?.track.measures[measureIndex]?.id;
|
|
189
|
+
const drawn = measureId ? notePositions?.get(measureId) : undefined;
|
|
190
|
+
if (drawn) {
|
|
191
|
+
return {
|
|
192
|
+
x: xForTick(tick, drawn.positions, drawn.area, timing.startTick, timing.startTick + timing.durationTicks, endAnchorFor(plan, system, measureIndex, drawn.area, notePositions)),
|
|
193
|
+
yTop: system.yTop,
|
|
194
|
+
yBottom: system.yBottom,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
/*
|
|
198
|
+
Not drawn: the stave box, spread evenly.
|
|
199
|
+
|
|
200
|
+
A coarse answer, and the one this always gave — the box runs barline to
|
|
201
|
+
barline, so it puts the caret left of the note by whatever the clef and key
|
|
202
|
+
signature occupy. It only applies off the drawn window, where there is no
|
|
203
|
+
caret to see, and for the frame before the first paint.
|
|
204
|
+
*/
|
|
69
205
|
const fraction = timing.durationTicks > 0
|
|
70
206
|
? Math.min(1, Math.max(0, (tick - timing.startTick) / timing.durationTicks))
|
|
71
207
|
: 0;
|
|
@@ -81,7 +217,7 @@ export function caretPositionForTick(plan, score, tick) {
|
|
|
81
217
|
* positions left/right of a system's measures clamp to that system's
|
|
82
218
|
* first/last measure, so clicking the clef area seeks to the system start.
|
|
83
219
|
*/
|
|
84
|
-
export function tickForPoint(plan, score, x, y) {
|
|
220
|
+
export function tickForPoint(plan, score, x, y, notePositions) {
|
|
85
221
|
if (!Number.isFinite(x) || !Number.isFinite(y))
|
|
86
222
|
return null;
|
|
87
223
|
const timings = measureTimings(score);
|
|
@@ -96,6 +232,21 @@ export function tickForPoint(plan, score, x, y) {
|
|
|
96
232
|
const timing = timings[hit.measureIndex];
|
|
97
233
|
if (!timing)
|
|
98
234
|
return null;
|
|
235
|
+
/*
|
|
236
|
+
The same positions the caret uses, so the two are inverses of each other.
|
|
237
|
+
|
|
238
|
+
They have to be: clicking the sheet seeks, and the caret then draws where
|
|
239
|
+
the click landed. Two different mappings would move the caret away from the
|
|
240
|
+
pointer on every click — by the width of the clef, in the first bar of a
|
|
241
|
+
system.
|
|
242
|
+
*/
|
|
243
|
+
const measureId = plan.trackLayouts[0]?.track.measures[hit.measureIndex]?.id;
|
|
244
|
+
const drawn = measureId ? notePositions?.get(measureId) : undefined;
|
|
245
|
+
if (drawn) {
|
|
246
|
+
const endTick = timing.startTick + timing.durationTicks;
|
|
247
|
+
const endX = endAnchorFor(plan, system, hit.measureIndex, drawn.area, notePositions);
|
|
248
|
+
return Math.round(tickForX(x, drawn.positions, drawn.area, timing.startTick, endTick, endX));
|
|
249
|
+
}
|
|
99
250
|
const fraction = hit.box.width > 0
|
|
100
251
|
? Math.min(1, Math.max(0, (x - hit.box.x) / hit.box.width))
|
|
101
252
|
: 0;
|
package/dist/playhead.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"playhead.js","sourceRoot":"","sources":["../src/playhead.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"playhead.js","sourceRoot":"","sources":["../src/playhead.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VexFlow render colors, one set per resolved light/dark color scheme.
|
|
3
|
+
*
|
|
4
|
+
* Literal color strings, not CSS custom properties: VexFlow draws straight to
|
|
5
|
+
* the canvas context, which never resolves `var(--...)` (and jsdom doesn't
|
|
6
|
+
* process CSS for tests anyway).
|
|
7
|
+
*
|
|
8
|
+
* Note *state* is carried by these colors — the highlight overlay and its
|
|
9
|
+
* second canvas are gone, so `noteNormal`/`noteSelected`/`noteRegenerated`/
|
|
10
|
+
* `notePlaying` are what a reader actually reads state from. Each value
|
|
11
|
+
* clears 4.5:1 against its mode's stave background. (Color is not the *only*
|
|
12
|
+
* cue: `noteEmphasisFor` thickens and haloes non-normal notes, and the piano
|
|
13
|
+
* roll thickens their borders — spec §27.)
|
|
14
|
+
*
|
|
15
|
+
* `noteInactive` is the one value that does NOT clear 4.5:1 — it sits at ~3:1,
|
|
16
|
+
* deliberately. It marks the tracks you are *not* editing, and its whole job is
|
|
17
|
+
* to recede; at 4.5:1 against the same background it was indistinguishable from
|
|
18
|
+
* `noteNormal` and the active track stopped standing out at all. The music it
|
|
19
|
+
* draws stays legible (large glyphs, not body text), and no state is carried by
|
|
20
|
+
* it alone: a note you need to act on is selected, regenerated or playing, and
|
|
21
|
+
* those keep their full-contrast colours on every track.
|
|
22
|
+
*
|
|
23
|
+
* Its own module rather than a `ScoreEditorView` export: both editors draw
|
|
24
|
+
* from this one palette, so a note means the same thing in either, and a
|
|
25
|
+
* component file exporting shared constants breaks Fast Refresh.
|
|
26
|
+
*/
|
|
27
|
+
import type { RenderTheme } from './types.js';
|
|
28
|
+
export declare const LIGHT_RENDER_THEME: RenderTheme;
|
|
29
|
+
export declare const DARK_RENDER_THEME: RenderTheme;
|
|
30
|
+
//# sourceMappingURL=render-theme.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-theme.d.ts","sourceRoot":"","sources":["../src/render-theme.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,eAAO,MAAM,kBAAkB,EAAE,WAUhC,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,WAU/B,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const LIGHT_RENDER_THEME = {
|
|
2
|
+
foreground: '#3f3f46',
|
|
3
|
+
noteNormal: '#3f3f46',
|
|
4
|
+
noteInactive: '#8e8e97',
|
|
5
|
+
noteSelected: '#6d28d9',
|
|
6
|
+
noteRegenerated: '#8b5a2b',
|
|
7
|
+
notePlaying: '#1565c0',
|
|
8
|
+
staveActive: '#000000',
|
|
9
|
+
staveInactive: '#71717a',
|
|
10
|
+
caret: '#d32f2f',
|
|
11
|
+
};
|
|
12
|
+
export const DARK_RENDER_THEME = {
|
|
13
|
+
foreground: '#d4d4d8',
|
|
14
|
+
noteNormal: '#d4d4d8',
|
|
15
|
+
noteInactive: '#82828c',
|
|
16
|
+
noteSelected: '#a78bfa',
|
|
17
|
+
noteRegenerated: '#d9a066',
|
|
18
|
+
notePlaying: '#64b5f6',
|
|
19
|
+
staveActive: '#ffffff',
|
|
20
|
+
staveInactive: '#8a8a93',
|
|
21
|
+
caret: '#ef5350',
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=render-theme.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-theme.js","sourceRoot":"","sources":["../src/render-theme.ts"],"names":[],"mappings":"AA4BA,MAAM,CAAC,MAAM,kBAAkB,GAAgB;IAC7C,UAAU,EAAE,SAAS;IACrB,UAAU,EAAE,SAAS;IACrB,YAAY,EAAE,SAAS;IACvB,YAAY,EAAE,SAAS;IACvB,eAAe,EAAE,SAAS;IAC1B,WAAW,EAAE,SAAS;IACtB,WAAW,EAAE,SAAS;IACtB,aAAa,EAAE,SAAS;IACxB,KAAK,EAAE,SAAS;CACjB,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAgB;IAC5C,UAAU,EAAE,SAAS;IACrB,UAAU,EAAE,SAAS;IACrB,YAAY,EAAE,SAAS;IACvB,YAAY,EAAE,SAAS;IACvB,eAAe,EAAE,SAAS;IAC1B,WAAW,EAAE,SAAS;IACtB,WAAW,EAAE,SAAS;IACtB,aAAa,EAAE,SAAS;IACxB,KAAK,EAAE,SAAS;CACjB,CAAC"}
|