@greenblacksky/honmoon 0.0.0-stage → 0.4.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.
Files changed (82) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +85 -2
  3. package/package.json +58 -4
  4. package/src/lib/audio/context.ts +29 -0
  5. package/src/lib/audio/metronome.svelte.ts +146 -0
  6. package/src/lib/audio/mic.ts +254 -0
  7. package/src/lib/audio/pitch.ts +133 -0
  8. package/src/lib/audio/rhythmplay.ts +152 -0
  9. package/src/lib/audio/synth.ts +409 -0
  10. package/src/lib/audio/voiceplay.ts +60 -0
  11. package/src/lib/drills/defs.ts +459 -0
  12. package/src/lib/drills/questions.ts +920 -0
  13. package/src/lib/drills/registry.svelte.ts +167 -0
  14. package/src/lib/drills/runtime.svelte.ts +629 -0
  15. package/src/lib/drills/verdict.ts +241 -0
  16. package/src/lib/index.ts +117 -0
  17. package/src/lib/learn/describe.ts +72 -0
  18. package/src/lib/learn/pools.ts +44 -0
  19. package/src/lib/learn/progress.ts +100 -0
  20. package/src/lib/learn/session.ts +304 -0
  21. package/src/lib/music/bass.ts +139 -0
  22. package/src/lib/music/clefs.ts +218 -0
  23. package/src/lib/music/intervals.ts +94 -0
  24. package/src/lib/music/notes.ts +133 -0
  25. package/src/lib/music/rhythm.ts +624 -0
  26. package/src/lib/music/scales.ts +560 -0
  27. package/src/lib/music/voice.ts +227 -0
  28. package/src/lib/music/warmups.ts +318 -0
  29. package/src/lib/nav.svelte.ts +53 -0
  30. package/src/lib/notation/fretboard.ts +187 -0
  31. package/src/lib/notation/glyphdata.ts +101 -0
  32. package/src/lib/notation/glyphs.ts +71 -0
  33. package/src/lib/notation/icons.ts +12 -0
  34. package/src/lib/notation/keyboard.ts +166 -0
  35. package/src/lib/notation/rhythmsvg.ts +341 -0
  36. package/src/lib/notation/staff.ts +288 -0
  37. package/src/lib/notation/svg.ts +37 -0
  38. package/src/lib/notation/tab.ts +80 -0
  39. package/src/lib/store/honmoon.svelte.ts +329 -0
  40. package/src/lib/store/normalize.ts +23 -0
  41. package/src/lib/store/sync.ts +43 -0
  42. package/src/lib/store/types.ts +101 -0
  43. package/src/lib/styles/honmoon.css +2064 -0
  44. package/src/lib/ui/AccRow.svelte +23 -0
  45. package/src/lib/ui/BackBar.svelte +23 -0
  46. package/src/lib/ui/BassRow.svelte +30 -0
  47. package/src/lib/ui/Chips.svelte +23 -0
  48. package/src/lib/ui/DataCard.svelte +66 -0
  49. package/src/lib/ui/Fretboard.svelte +71 -0
  50. package/src/lib/ui/Keyboard.svelte +54 -0
  51. package/src/lib/ui/MetronomePanel.svelte +101 -0
  52. package/src/lib/ui/RefBar.svelte +28 -0
  53. package/src/lib/ui/RhythmBar.svelte +9 -0
  54. package/src/lib/ui/Shell.svelte +96 -0
  55. package/src/lib/ui/StaffRun.svelte +46 -0
  56. package/src/lib/ui/Stat.svelte +13 -0
  57. package/src/lib/ui/actions.ts +36 -0
  58. package/src/lib/ui/keyfollow.svelte.ts +48 -0
  59. package/src/lib/ui/overlay.ts +15 -0
  60. package/src/lib/ui/refdata.ts +355 -0
  61. package/src/lib/ui/scaleboard.ts +420 -0
  62. package/src/lib/ui/screens/DegreesRef.svelte +405 -0
  63. package/src/lib/ui/screens/DrillScreen.svelte +1155 -0
  64. package/src/lib/ui/screens/Explorer.svelte +245 -0
  65. package/src/lib/ui/screens/FamilyScreen.svelte +280 -0
  66. package/src/lib/ui/screens/Glossary.svelte +65 -0
  67. package/src/lib/ui/screens/IntervalRef.svelte +37 -0
  68. package/src/lib/ui/screens/LearnIndex.svelte +44 -0
  69. package/src/lib/ui/screens/MetronomeScreen.svelte +17 -0
  70. package/src/lib/ui/screens/PracticeHome.svelte +159 -0
  71. package/src/lib/ui/screens/RhythmRef.svelte +239 -0
  72. package/src/lib/ui/screens/ScalesScreen.svelte +210 -0
  73. package/src/lib/ui/screens/SessionDone.svelte +119 -0
  74. package/src/lib/ui/screens/SetupScreen.svelte +548 -0
  75. package/src/lib/ui/screens/StaffMap.svelte +230 -0
  76. package/src/lib/ui/screens/StatsScreen.svelte +478 -0
  77. package/src/lib/ui/screens/TapCalibration.svelte +186 -0
  78. package/src/lib/ui/tapfix.ts +93 -0
  79. package/src/lib/ui/types.ts +40 -0
  80. package/src/lib/util/list.ts +4 -0
  81. package/src/lib/util/random.ts +23 -0
  82. package/src/lib/util/text.ts +50 -0
@@ -0,0 +1,187 @@
1
+ import { honmoon, mastery } from '../store/honmoon.svelte';
2
+ import { STRINGS, allowedPc, lvl, vhi } from '../music/bass';
3
+ import { pcOf } from '../music/notes';
4
+ import { masteryColour } from './svg';
5
+
6
+ /** A dot (or ring) drawn on the neck. */
7
+ export interface FretCell {
8
+ /** string index, 0 = G (top row) */
9
+ s: number;
10
+ /** fret, 0 = open */
11
+ f: number;
12
+ fill?: string;
13
+ stroke?: string;
14
+ /** stroke width */
15
+ sw?: number;
16
+ /** radius */
17
+ r?: number;
18
+ label?: string;
19
+ /** label font size */
20
+ fs?: number;
21
+ /** label text colour */
22
+ tc?: string;
23
+ }
24
+ export interface FretboardOpts {
25
+ lo?: number;
26
+ hi?: number;
27
+ cells?: FretCell[];
28
+ /** highlight one string as the one to tap */
29
+ tap?: boolean;
30
+ onlyString?: number | null;
31
+ noLabels?: boolean;
32
+ scale?: number;
33
+ }
34
+
35
+ /** Layout of the drawn neck, in viewBox units: fret width, where fret 1
36
+ starts, string spacing, top margin, right padding, room below for fret
37
+ numbers, and the width of the open-string column left of the nut. */
38
+ export const FB = { fw: 44, x0: 42, rowH: 30, top: 18, pad: 22, foot: 46, openW: 28 };
39
+ const STRING_COUNT = STRINGS.length;
40
+ /** Height of the strings area, top string to bottom string. */
41
+ const STRINGS_SPAN = (STRING_COUNT - 1) * FB.rowH;
42
+ /** Inlay dots, and the double dot at 12 and 24. */
43
+ const SINGLE_DOT_FRETS = [3, 5, 7, 9, 15, 17, 19, 21];
44
+ const DOUBLE_DOT_FRETS = [12, 24];
45
+ const MONO = 'IBM Plex Mono,monospace';
46
+
47
+ export function fbDims(lo: number, hi: number) {
48
+ const fretCount = hi - lo;
49
+ return {
50
+ n: fretCount,
51
+ W: FB.x0 + fretCount * FB.fw + FB.pad,
52
+ H: FB.top + STRINGS_SPAN + FB.foot,
53
+ };
54
+ }
55
+ /** The tappable box for one string and fret, in viewBox units. */
56
+ export function fbCell(lo: number, string: number, fret: number) {
57
+ const x = fret === 0 ? FB.x0 - FB.openW : FB.x0 + (fret - lo - 1) * FB.fw;
58
+ const width = fret === 0 ? FB.openW : FB.fw;
59
+ return { x, y: FB.top + string * FB.rowH - FB.rowH / 2, w: width, h: FB.rowH };
60
+ }
61
+ /** Horizontal centre of a fret (the open column sits left of the nut). */
62
+ function fretCentreX(fret: number, lo: number): number {
63
+ return fret === 0 ? FB.x0 - 12 : FB.x0 + (fret - lo - 0.5) * FB.fw;
64
+ }
65
+ const stringY = (string: number) => FB.top + string * FB.rowH;
66
+
67
+ function boardBackground(fretCount: number): string {
68
+ return `<rect x="${FB.x0 - 2}" y="${FB.top - 13}" width="${fretCount * FB.fw + 4}" height="${STRINGS_SPAN + 26}" fill="var(--board)" rx="3"/>`;
69
+ }
70
+ /** The band behind the one string a question asks you to tap on. */
71
+ function stringHighlight(string: number, fretCount: number, showsOpen: boolean): string {
72
+ const x = FB.x0 - (showsOpen ? 28 : 2);
73
+ const width = fretCount * FB.fw + 4 + (showsOpen ? 26 : 0);
74
+ return `<rect x="${x}" y="${stringY(string) - FB.rowH / 2}" width="${width}" height="${FB.rowH}" fill="var(--accent)" opacity="0.16" rx="3"/>`;
75
+ }
76
+ function fretWires(lo: number, fretCount: number): string {
77
+ let out = '';
78
+ for (let i = 0; i <= fretCount; i++) {
79
+ const x = FB.x0 + i * FB.fw;
80
+ const isNut = lo + i === 0;
81
+ out += `<line x1="${x}" y1="${FB.top - 13}" x2="${x}" y2="${FB.top + STRINGS_SPAN + 13}" stroke="${isNut ? 'var(--ink)' : 'var(--wire)'}" stroke-width="${isNut ? 4.5 : 2}" stroke-linecap="round"/>`;
82
+ }
83
+ return out;
84
+ }
85
+ function inlayDots(lo: number, hi: number): string {
86
+ const dot = (cx: number, cy: number) =>
87
+ `<circle cx="${cx}" cy="${cy}" r="5" fill="var(--board-2)"/>`;
88
+ const middleY = FB.top + 1.5 * FB.rowH;
89
+ let out = '';
90
+ for (let fret = lo + 1; fret <= hi; fret++) {
91
+ const cx = fretCentreX(fret, lo);
92
+ if (SINGLE_DOT_FRETS.includes(fret)) {
93
+ out += dot(cx, middleY);
94
+ }
95
+ if (DOUBLE_DOT_FRETS.includes(fret)) {
96
+ out += dot(cx, middleY - FB.rowH * 0.6) + dot(cx, middleY + FB.rowH * 0.6);
97
+ }
98
+ }
99
+ return out;
100
+ }
101
+ /** The strings, thicker towards the low E, each named at the left. */
102
+ function stringsAndNames(fretCount: number, showsOpen: boolean, hideNames: boolean): string {
103
+ let out = '';
104
+ for (let string = 0; string < STRING_COUNT; string++) {
105
+ const y = stringY(string);
106
+ const thickness = 1.1 + string * 0.65;
107
+ out += `<line x1="${FB.x0 - (showsOpen ? 24 : 2)}" y1="${y}" x2="${FB.x0 + fretCount * FB.fw}" y2="${y}" stroke="var(--string)" stroke-width="${thickness}"/>`;
108
+ out += `<text x="${FB.x0 - (showsOpen ? 32 : 12)}" y="${y + 4.5}" text-anchor="end" font-size="13" font-family="${MONO}" font-weight="600" fill="var(--ink-3)">${hideNames ? '·' : STRINGS[string].n}</text>`;
109
+ }
110
+ return out;
111
+ }
112
+ function fretNumbers(lo: number, hi: number): string {
113
+ let out = '';
114
+ for (let fret = lo; fret <= hi; fret++) {
115
+ if (fret === 0 && lo !== 0) {
116
+ continue;
117
+ }
118
+ out += `<text x="${fretCentreX(fret, lo)}" y="${FB.top + STRINGS_SPAN + 34}" text-anchor="middle" font-size="12" font-family="${MONO}" fill="var(--ink-3)">${fret}</text>`;
119
+ }
120
+ return out;
121
+ }
122
+ function cellMarks(cells: FretCell[], lo: number, hi: number): string {
123
+ let out = '';
124
+ for (const cell of cells) {
125
+ const cx = fretCentreX(cell.f, lo);
126
+ const cy = stringY(cell.s);
127
+ if (cell.f < lo || cell.f > hi) {
128
+ continue;
129
+ }
130
+ if (cell.fill || cell.stroke) {
131
+ out += `<circle cx="${cx}" cy="${cy}" r="${cell.r || 11}" fill="${cell.fill || 'none'}" stroke="${cell.stroke || 'none'}" stroke-width="${cell.sw || 2}"/>`;
132
+ }
133
+ if (cell.label) {
134
+ out += `<text x="${cx}" y="${cy + 4.6}" text-anchor="middle" font-size="${cell.fs || 12}" font-family="${MONO}" font-weight="600" fill="${cell.tc || 'var(--accent-ink)'}">${cell.label}</text>`;
135
+ }
136
+ }
137
+ return out;
138
+ }
139
+
140
+ export function fretboardSVG(options: FretboardOpts = {}): string {
141
+ const lo = options.lo || 0;
142
+ const hi = options.hi ?? 12;
143
+ const { n: fretCount, W: width, H: height } = fbDims(lo, hi);
144
+ const showsOpen = lo === 0;
145
+ const parts = [boardBackground(fretCount)];
146
+ if (options.tap && options.onlyString != null) {
147
+ parts.push(stringHighlight(options.onlyString, fretCount, showsOpen));
148
+ }
149
+ parts.push(fretWires(lo, fretCount));
150
+ parts.push(inlayDots(lo, hi));
151
+ parts.push(stringsAndNames(fretCount, showsOpen, !!options.noLabels));
152
+ parts.push(fretNumbers(lo, hi));
153
+ parts.push(cellMarks(options.cells || [], lo, hi));
154
+ const scale = options.scale || 1;
155
+ return (
156
+ `<svg viewBox="0 0 ${width} ${height}" width="${Math.round(width * scale)}" height="${Math.round(height * scale)}" role="img" aria-label="bass fretboard">` +
157
+ parts.join('') +
158
+ '</svg>'
159
+ );
160
+ }
161
+
162
+ /** The neck, shaded by how solid each position is — the map on Stats. */
163
+ export function heatFretboard(): string {
164
+ const level = lvl();
165
+ const cells: FretCell[] = [];
166
+ for (let string = 0; string < STRING_COUNT; string++) {
167
+ for (let fret = level.lo; fret <= vhi(); fret++) {
168
+ const midi = STRINGS[string].open + fret;
169
+ if (fret > level.hi || !allowedPc(pcOf(midi))) {
170
+ continue;
171
+ }
172
+ const id = 'fb:' + string + ':' + fret;
173
+ const card = honmoon.cards[id];
174
+ if (!card || !card.s) {
175
+ cells.push({ s: string, f: fret, stroke: 'var(--ink-3)', sw: 1.6, r: 8 });
176
+ } else {
177
+ cells.push({
178
+ s: string,
179
+ f: fret,
180
+ fill: masteryColour(mastery(id), 'var(--board)'),
181
+ r: 8.5,
182
+ });
183
+ }
184
+ }
185
+ }
186
+ return fretboardSVG({ lo: level.lo, hi: vhi(), cells, scale: 1.45 });
187
+ }
@@ -0,0 +1,101 @@
1
+ /* Generated from the original app: the real Bravura outlines (SIL Open Font
2
+ Licence), converted once into staff coordinates, where one staff space is ten
3
+ units and y grows downward. Every glyph is drawn from its own origin — the
4
+ staff position a clef names, the centre of a notehead, the tip of the stem for
5
+ a flag — so placing one is a translate and nothing else.
6
+
7
+ The scale is set by the black notehead, which is exactly one staff space tall
8
+ in any music font: its outline spans 360 units, so 360 outline units are one
9
+ space. (The metrics that ship beside the outlines are quoted in a different
10
+ em, 250 to the space — trusting those made every glyph 44 per cent too big,
11
+ which reads as vaguely wrong long before you can say why.) */
12
+
13
+ export type GlyphName =
14
+ | 'gClef'
15
+ | 'fClef'
16
+ | 'cClef'
17
+ | 'noteheadBlack'
18
+ | 'noteheadHalf'
19
+ | 'noteheadWhole'
20
+ | 'restWhole'
21
+ | 'restHalf'
22
+ | 'restQuarter'
23
+ | 'rest8th'
24
+ | 'rest16th'
25
+ | 'flag8thUp'
26
+ | 'flag16thUp'
27
+ | 'augmentationDot'
28
+ | 'accidentalSharp'
29
+ | 'accidentalFlat'
30
+ | 'accidentalNatural'
31
+ | 'timeSig0'
32
+ | 'timeSig1'
33
+ | 'timeSig2'
34
+ | 'timeSig3'
35
+ | 'timeSig4'
36
+ | 'timeSig5'
37
+ | 'timeSig6'
38
+ | 'timeSig7'
39
+ | 'timeSig8'
40
+ | 'timeSig9';
41
+
42
+ export const GP: Record<GlyphName, string> ={
43
+ gClef:'M15.03 -16.61 C14.97 -17.08 15.03 -17.11 15.28 -17.36 C19.61 -21.39 22.89 -26.47 22.89 -32.61 C22.89 -36.08 21.92 -39.53 20.28 -41.92 C19.67 -42.81 18.64 -43.92 18.19 -43.92 C17.64 -43.92 16.39 -42.89 15.61 -42 C12.64 -38.72 11.67 -33.72 11.67 -29.56 C11.67 -27.25 11.97 -24.64 12.25 -23 C12.33 -22.53 12.36 -22.44 11.89 -22.03 C6.11 -17.28 0 -11.56 0 -3.47 C0 3.47 4.75 10.08 14.56 10.08 C15.47 10.08 16.53 10 17.33 9.83 C17.75 9.75 17.83 9.72 17.92 10.19 C18.39 12.89 19 16.36 19 18.25 C19 24.17 15 24.89 12.64 24.89 C10.47 24.89 9.44 24.25 9.44 23.72 C9.44 23.44 9.81 23.33 10.72 23.03 C11.97 22.67 13.39 21.61 13.39 19.28 C13.39 17.08 12 15.19 9.56 15.19 C6.89 15.19 5.28 17.33 5.28 19.81 C5.28 22.39 6.83 26.33 12.89 26.33 C15.56 26.33 20.75 25.11 20.75 18.33 C20.75 16.03 20.03 12.25 19.61 9.75 C19.53 9.28 19.56 9.33 20.11 9.08 C24.17 7.47 26.83 4.08 26.83 -0.44 C26.83 -5.56 23.08 -10.08 17.19 -10.08 C16.17 -10.08 16.17 -10.08 16.03 -10.81 Z M18.81 -37.72 C20.11 -37.72 21.19 -36.64 21.19 -34.44 C21.19 -30 17.39 -26.39 14.25 -23.64 C13.97 -23.39 13.81 -23.44 13.72 -23.97 C13.56 -25 13.47 -26.36 13.47 -27.64 C13.47 -33.89 16.36 -37.72 18.81 -37.72 Z M14.44 -10.47 C14.56 -9.72 14.56 -9.75 13.83 -9.53 C10.33 -8.33 8.03 -5.17 8.03 -1.75 C8.03 1.83 9.92 4.39 12.64 5.33 C12.97 5.44 13.44 5.56 13.72 5.56 C14.03 5.56 14.19 5.36 14.19 5.11 C14.19 4.83 13.89 4.72 13.61 4.61 C11.92 3.89 10.72 2.17 10.72 0.33 C10.72 -1.97 12.28 -3.67 14.72 -4.36 C15.36 -4.53 15.44 -4.47 15.53 -4.03 L17.53 7.89 C17.61 8.33 17.56 8.33 16.97 8.44 C16.33 8.56 15.53 8.64 14.72 8.64 C7.72 8.64 3.19 4.75 3.19 -0.81 C3.19 -3.17 3.61 -6.33 6.92 -10.08 C9.33 -12.75 11.17 -14.25 13.03 -15.75 C13.44 -16.08 13.53 -16.03 13.61 -15.61 Z M17.19 -4.11 C17.11 -4.61 17.17 -4.72 17.64 -4.67 C20.89 -4.39 23.56 -1.67 23.56 1.83 C23.56 4.36 22.03 6.39 19.81 7.53 C19.33 7.75 19.25 7.75 19.17 7.28 Z',
44
+ fClef:'M10.08 -10.47 C3.11 -10.47 0 -5.39 0 -1.56 C0 1.64 1.67 4.39 4.92 4.39 C7.44 4.39 9.17 2.64 9.17 0.17 C9.17 -2.39 7.28 -4 5.33 -4 C4.25 -4 3.83 -3.72 3.33 -3.72 C2.81 -3.72 2.67 -4.03 2.67 -4.44 C2.67 -6.03 5.08 -8.97 9.17 -8.97 C13.39 -8.97 15.25 -4.81 15.25 1.47 C15.25 12.64 9.72 18.89 0.39 24.19 C0.03 24.39 -0.19 24.61 -0.19 24.92 C-0.19 25.17 -0.03 25.39 0.33 25.39 C0.53 25.39 0.75 25.33 1 25.19 C10.83 20.39 21.25 13.28 21.25 1.11 C21.25 -5.83 17 -10.47 10.08 -10.47 Z M25.17 -7.19 C23.92 -7.19 22.97 -6.25 22.97 -5 C22.97 -3.75 23.92 -2.81 25.17 -2.81 C26.39 -2.81 27.36 -3.75 27.36 -5 C27.36 -6.25 26.39 -7.19 25.17 -7.19 Z M25.19 2.83 C23.97 2.83 23.03 3.75 23.03 5 C23.03 6.25 23.97 7.17 25.19 7.17 C26.44 7.17 27.36 6.25 27.36 5 C27.36 3.75 26.44 2.83 25.19 2.83 Z',
45
+ cClef:'M9.19 -19.28 C9.19 -19.83 8.92 -20.11 8.36 -20.11 L8.33 -20.11 C7.75 -20.11 7.47 -19.83 7.47 -19.28 L7.47 19.28 C7.47 19.83 7.75 20.11 8.33 20.11 L8.36 20.11 C8.92 20.11 9.19 19.83 9.19 19.28 L9.19 1.75 C9.19 1.44 9.39 1.47 9.56 1.53 C10.61 1.81 12.28 2.83 13.11 7.36 C13.25 8 13.47 8.36 13.89 8.36 C14.33 8.36 14.53 7.97 14.72 7.28 C15.25 5.53 16.17 3.56 19 3.56 C21.61 3.56 22.33 6.11 22.33 11.36 C22.33 16.61 21.39 18.97 18.08 18.97 C17.53 18.97 14.67 18.72 14.67 17.89 C14.67 17.67 15.33 17.44 15.75 17.28 C16.56 17 17.36 16.19 17.36 14.67 C17.36 12.92 16.19 11.92 14.64 11.92 C12.92 11.92 11.56 13.08 11.56 15.19 C11.56 17.72 13.75 20.25 18.53 20.25 C25.08 20.25 27.97 15.64 27.97 11.47 C27.97 5.97 24.92 2.11 19.61 2.11 C18.44 2.11 17.67 2.33 17.17 2.47 C16.75 2.61 16.36 2.67 16 2.44 C15.44 2.08 14.56 0.81 14.56 -0 C14.56 -0.81 15.44 -2.08 16 -2.44 C16.36 -2.67 16.75 -2.61 17.17 -2.47 C17.67 -2.33 18.44 -2.11 19.61 -2.11 C24.92 -2.11 27.97 -5.97 27.97 -11.47 C27.97 -15.64 25.08 -20.25 18.53 -20.25 C13.75 -20.25 11.56 -17.72 11.56 -15.19 C11.56 -13.08 12.92 -11.92 14.64 -11.92 C16.19 -11.92 17.36 -12.92 17.36 -14.67 C17.36 -16.19 16.56 -17 15.75 -17.28 C15.33 -17.44 14.67 -17.67 14.67 -17.89 C14.67 -18.72 17.53 -18.97 18.08 -18.97 C21.39 -18.97 22.33 -16.61 22.33 -11.36 C22.33 -6.11 21.61 -3.56 19 -3.56 C16.17 -3.56 15.25 -5.53 14.72 -7.28 C14.53 -7.97 14.33 -8.36 13.89 -8.36 C13.47 -8.36 13.25 -8 13.11 -7.36 C12.28 -2.83 10.61 -1.81 9.56 -1.53 C9.39 -1.47 9.19 -1.44 9.19 -1.75 Z M0.83 -20.11 C0.28 -20.11 0 -19.83 0 -19.28 L0 19.28 C0 19.83 0.28 20.11 0.83 20.11 L4.28 20.11 C4.83 20.11 5.11 19.83 5.11 19.28 L5.11 -19.28 C5.11 -19.83 4.83 -20.11 4.28 -20.11 Z',
46
+ noteheadBlack:'M3.89 5 C7.44 5 11.81 1.72 11.81 -1.67 C11.81 -3.72 10.19 -5 7.92 -5 C3.53 -5 0 -1.75 0 1.67 C0 3.75 1.72 5 3.89 5 Z',
47
+ noteheadHalf:'M3.89 5 C10.47 5 11.81 -0.36 11.81 -1.67 C11.81 -3.72 10.17 -5 7.83 -5 C1.89 -5 0 -0.39 0 1.67 C0 3.81 1.67 5 3.89 5 Z M3 3.47 C2.17 3.47 1.67 3.03 1.39 2.56 C1.28 2.33 1.17 2.03 1.17 1.75 C1.17 -0.19 6.97 -3.36 8.83 -3.36 C9.61 -3.36 10.03 -3 10.33 -2.53 C10.44 -2.28 10.56 -2.03 10.56 -1.75 C10.56 -0.03 4.92 3.47 3 3.47 Z',
48
+ noteheadWhole:'M8.64 -5 C3.33 -5 0 -2.81 0 -0.08 C0 2.61 2.28 5 8.25 5 C14.81 5 16.89 2.72 16.89 -0.08 C16.89 -2.92 12.36 -5 8.64 -5 Z M4.44 -2.53 C4.89 -3.92 6.36 -4.11 7.61 -4.11 C10.36 -4.11 12.56 -1.17 12.56 1.25 C12.56 2.47 12.03 3.61 10.72 3.92 C10.33 4.03 9.89 4.08 9.47 4.08 C8.03 4.08 6.56 3.11 5.72 2 C4.92 1.08 4.33 -0.28 4.33 -1.56 C4.33 -1.89 4.36 -2.19 4.44 -2.53 Z',
49
+ restWhole:'M11.28 4.36 L11.28 0.67 C11.28 0.08 10.81 -0.36 10.25 -0.36 L1.03 -0.36 C0.44 -0.36 0 0.08 0 0.67 L0 4.36 C0 4.92 0.44 5.39 1.03 5.39 L10.25 5.39 C10.81 5.39 11.28 4.92 11.28 4.36 Z',
50
+ restHalf:'M11.28 -0.97 L11.28 -4.64 C11.28 -5.25 10.81 -5.67 10.25 -5.67 L1.03 -5.67 C0.44 -5.67 0 -5.25 0 -4.64 L0 -0.97 C0 -0.39 0.44 0.08 1.03 0.08 L10.25 0.08 C10.81 0.08 11.28 -0.39 11.28 -0.97 Z',
51
+ restQuarter:'M3.11 1.53 C3.75 2.33 4.33 3.08 4.83 3.92 C4.92 4.08 5.08 4.39 5.08 4.47 C5.08 4.53 5.08 4.61 5.03 4.64 C4.97 4.81 4.81 4.83 4.61 4.83 C4.44 4.83 4.11 4.75 3.97 4.72 C3.92 4.72 3.83 4.72 3.81 4.72 L3.47 4.61 C1.39 4.61 0.03 6.47 0.03 8.44 C0.03 10.44 1.75 12.39 4.67 14.64 C5 14.89 5.39 15 5.72 15 C6 15 6.28 14.92 6.33 14.75 C6.36 14.64 6.39 14.56 6.39 14.47 C6.39 14.11 6.08 13.81 5.75 13.53 C5.25 13.53 4.81 12.44 4.72 12.08 C4.61 11.75 4.56 11.39 4.56 11.03 C4.56 9.39 5.39 8.11 7.08 8.11 C8.25 8.11 9.56 8.56 10.25 8.81 L10.28 8.83 C10.44 8.89 10.53 8.89 10.61 8.89 C10.72 8.89 10.81 8.83 10.81 8.72 C10.81 8.25 9.75 6.92 9.33 6.44 C7.81 4.61 6.56 3.11 6.56 0.89 C6.56 0.83 6.56 0.81 6.56 0.75 L6.61 0.47 C6.61 0.44 6.61 0.39 6.61 0.36 C6.75 -1.97 8.19 -3.89 9.25 -5.53 C9.36 -5.72 9.39 -5.92 9.39 -6.11 C9.39 -6.53 9.25 -6.89 9.25 -6.89 C9.25 -6.89 3.33 -13.92 2.64 -14.61 C2.44 -14.81 2.17 -14.92 1.92 -14.92 C1.53 -14.92 1.11 -14.64 1.11 -14.08 C1.11 -13.89 1.17 -13.67 1.28 -13.44 C1.44 -13 3.72 -10.97 3.72 -8.08 C3.72 -6.61 3.11 -4.89 1.33 -3 C0.92 -2.61 0.75 -2.17 0.75 -1.83 C0.75 -1.28 1.17 -0.89 1.17 -0.89 Z',
52
+ rest8th:'M5.36 -4.28 C5.36 -5.75 4.17 -6.97 2.67 -6.97 C1.19 -6.97 0 -5.75 0 -4.28 C0 -3.44 0.47 -2.72 1.08 -2.25 C1.72 -1.81 2.47 -1.56 3.25 -1.56 C3.81 -1.56 4.36 -1.67 4.81 -1.83 C5.36 -2 5.72 -2.17 6.25 -2.44 C6.33 -2.47 6.39 -2.47 6.44 -2.47 C6.61 -2.47 6.64 -2.33 6.64 -2.11 C6.64 -2 6.64 -1.83 6.61 -1.67 C6.47 -1.08 3.61 6.89 2.89 9.53 C2.89 10 3.81 10.03 4.03 10.03 C4.47 10.03 5.03 9.97 5.44 9.64 C5.56 9.56 9.47 -4.47 9.47 -4.47 C9.64 -5.19 9.83 -5.83 9.89 -6.03 C9.89 -6.44 9.47 -6.64 9.39 -6.67 C9.33 -6.67 9.19 -6.67 8.97 -6.53 C8.67 -6.28 6.67 -3.89 5.36 -3.89 Z',
53
+ rest16th:'M8.33 -4.44 C8.33 -5.97 7.11 -7.17 5.61 -7.17 C4.11 -7.17 2.89 -5.97 2.89 -4.44 C2.89 -2.81 4.56 -1.72 6.08 -1.72 C7.17 -1.72 8.28 -2.08 9.19 -2.61 C9.33 -2.64 9.39 -2.67 9.47 -2.67 C9.61 -2.67 9.67 -2.61 9.67 -2.39 C9.67 -1.75 7.64 4.19 7.36 4.81 C7.03 5.56 5.97 6.03 5.39 6.03 C5.44 5.89 5.44 5.75 5.44 5.64 C5.44 4.11 4.19 2.92 2.72 2.92 C1.19 2.92 0 4.11 0 5.64 C0 7.28 1.67 8.36 3.19 8.36 C4.25 8.36 5.28 8 6.19 7.53 C6.28 7.53 6.36 7.61 6.36 7.75 L6.33 7.81 C6.33 7.83 6.33 7.83 6.33 7.83 L2.53 19.17 C2.53 19.17 2.53 19.19 2.53 19.19 L2.47 19.25 C2.47 19.61 2.83 20 3.72 20 C4.89 20 5.08 19.53 5.25 19.08 L9.89 3.83 C10.92 0.44 11.67 -2.25 11.67 -2.25 C11.67 -2.25 12.67 -5.75 12.75 -6.28 C12.75 -6.36 12.81 -6.39 12.81 -6.44 C12.81 -6.67 12.47 -6.83 12.39 -6.89 C12.19 -6.89 12.08 -6.81 11.97 -6.72 C11.67 -6.47 9.67 -4.08 8.33 -4.03 Z',
54
+ flag8thUp:'M9.53 31.61 C9.53 31.61 10.56 27.81 10.56 24.67 C10.56 19.67 8.47 14.97 5.97 10.97 C3.92 7.81 2.25 4.36 1.61 0.53 C1.47 -0.11 1.17 -0.36 0.75 -0.36 C0.33 -0.36 0 -0.25 0 0.25 L0 9.81 C2.64 10.28 6.44 15.72 7.89 19.11 C8.47 20.47 8.83 22.75 8.83 25.11 C8.83 26.92 8.56 28.81 7.89 30.61 C7.81 30.83 7.75 31.03 7.75 31.19 C7.75 31.83 8.17 32.19 8.39 32.36 C8.64 32.53 9.33 32.28 9.53 31.61 Z',
55
+ flag16thUp:'M10.89 31.83 C11.03 31.64 11.17 29.36 11.17 27.44 L11.17 26.56 C11.17 24.89 10.72 23.25 10 21.75 C10 21.64 9.97 21.56 9.97 21.39 C9.97 21.33 9.97 21.25 10 21.11 C10.11 20.89 11 18.47 11 16.03 C11 15.53 10.97 15.08 10.89 14.61 C10.47 11.89 9.44 10.75 6.56 7.64 C4.39 5.33 2.17 4.67 1.47 0.44 C1.39 -0 0.92 -0.08 0.67 -0.08 C0.44 -0.08 0 0.03 0 0.33 L0 15.83 L0.19 15.83 C2.67 15.92 5.53 16 8.28 21.61 C9.19 23.53 9.56 25.47 9.56 27.56 C9.56 28.72 9.44 29.92 9.25 31.11 C9.19 31.28 9.19 31.36 9.19 31.47 C9.19 32 9.53 32.53 10.08 32.53 C10.36 32.53 10.64 32.36 10.89 31.83 Z M8.36 18.36 C7.72 17.36 7.03 16.56 6.19 15.61 C4.33 13.44 2.47 12.47 1.64 9.19 C1.61 9.17 1.61 9.11 1.61 9.08 C1.61 8.92 1.83 8.67 2.17 8.67 L2.47 8.67 C4.92 8.67 7.08 10.92 8.39 12.89 C9.11 13.92 9.47 15.17 9.47 16.44 C9.47 16.72 9.47 16.97 9.44 17.25 C9.36 17.56 9.36 17.97 9.17 18.28 C9.11 18.39 8.83 18.53 8.64 18.53 C8.53 18.53 8.44 18.47 8.36 18.36 Z',
56
+ augmentationDot:'M4 -0 C4 -1.11 3.11 -2 2 -2 C0.89 -2 0 -1.11 0 -0 C0 1.11 0.89 2 2 2 C3.11 2 4 1.11 4 -0 Z',
57
+ accidentalSharp:'M9.47 -4.72 C9.75 -4.83 9.97 -5.17 9.97 -5.39 L9.97 -8.25 C9.97 -8.44 9.83 -8.56 9.67 -8.56 C9.61 -8.56 9.56 -8.56 9.47 -8.53 C9.47 -8.53 8.67 -8.19 8.47 -8.17 C8.19 -8.17 7.92 -8.36 7.92 -8.67 L7.92 -13.56 C7.92 -13.81 7.67 -14 7.36 -14 C6.97 -14 6.72 -13.81 6.72 -13.56 L6.72 -8.36 C6.67 -7.97 6.56 -7.44 6.19 -7.19 C5.72 -6.92 4.36 -6.36 3.67 -6.19 C3.33 -6.19 3.19 -6.67 3.19 -7 L3.19 -11.81 C3.19 -12.03 2.92 -12.25 2.64 -12.25 C2.25 -12.25 2 -12.03 2 -11.81 L2 -6.39 C2 -5.83 1.75 -5.44 1.53 -5.33 C1.28 -5.19 0.47 -4.89 0.47 -4.89 C0.19 -4.81 0 -4.47 0 -4.25 L0 -1.39 C0 -1.17 0.11 -1.03 0.36 -1.03 L0.44 -1.08 C0.47 -1.08 1.08 -1.33 1.39 -1.47 L1.44 -1.53 C1.75 -1.53 2 -1.11 2 -0.81 L2 3.17 C2 3.61 1.81 3.97 1.56 4.08 C1.33 4.17 0.47 4.53 0.47 4.53 C0.19 4.61 0 4.92 0 5.17 L0 8 C0 8.25 0.11 8.36 0.36 8.36 L0.44 8.33 C0.47 8.33 1.03 8.08 1.39 7.97 C1.44 7.92 1.47 7.92 1.53 7.92 C1.81 7.92 2 8.36 2 8.56 L2 13.47 C2 13.72 2.25 13.92 2.53 13.92 C2.92 13.92 3.19 13.72 3.19 13.47 L3.19 7.92 C3.19 7.39 3.39 7.11 3.61 7.03 L6.03 6.03 C6.03 6.03 6.08 6.03 6.08 6.03 L6.17 6 C6.53 6 6.72 6.47 6.72 6.72 L6.72 11.72 C6.72 11.97 6.97 12.17 7.25 12.17 C7.67 12.17 7.92 11.97 7.92 11.72 L7.92 6.03 C7.92 5.72 8.08 5.25 8.36 5.11 C8.64 5 9.47 4.67 9.47 4.67 C9.75 4.56 9.97 4.25 9.97 4 L9.97 1.17 C9.97 0.97 9.83 0.83 9.67 0.83 C9.61 0.83 9.56 0.83 9.47 0.89 L8.44 1.28 C8.19 1.28 7.92 1.03 7.92 0.56 L7.92 -3.17 C7.92 -3.44 8.11 -4.19 8.44 -4.33 Z M6.72 1.81 C6.47 2.61 4.61 3.39 3.67 3.39 C3.44 3.39 3.25 3.33 3.19 3.19 C3.11 3.03 3.08 2.17 3.08 1.19 C3.08 -0.03 3.11 -1.44 3.19 -1.75 C3.28 -2.44 5.11 -3.28 6.11 -3.28 C6.39 -3.28 6.64 -3.19 6.72 -3.03 C6.81 -2.83 6.89 -1.83 6.89 -0.75 C6.89 0.33 6.81 1.44 6.72 1.81 Z',
58
+ accidentalFlat:'M0.47 6.81 C0.61 6.97 0.72 7 0.83 7 C0.97 7 1.08 6.92 1.08 6.92 C2.28 6.25 3.25 5.17 4.25 4.47 C7.81 2 9.03 -0.44 9.03 -2.28 C9.03 -4.56 7.28 -6 5.44 -6.11 C4.75 -6.11 3.81 -5.81 3.25 -5.44 C3 -5.25 2.56 -4.89 2.36 -4.89 C2.28 -4.89 2.25 -4.89 2.17 -4.92 C1.89 -5.03 1.72 -5.33 1.72 -5.61 C1.75 -6.47 2 -16.08 2 -16.89 C2 -17.33 1.64 -17.56 1.25 -17.56 C0.67 -17.56 0.03 -17.17 0 -16.44 C0 -16.44 0.17 6.39 0.47 6.81 Z M1.89 3.25 C1.89 3.25 1.75 0.83 1.75 -0.75 C1.75 -1.39 1.81 -1.89 1.83 -2.03 C2.11 -2.83 3.72 -4 4.64 -4 C5.81 -4 6.28 -2.67 6.28 -1.67 C6.28 0.47 4.44 2.64 2.72 3.72 C2.56 3.81 2.44 3.83 2.33 3.83 C1.97 3.83 1.89 3.44 1.89 3.25 Z',
59
+ accidentalNatural:'M5.64 -7.25 C5.56 -7.25 5.53 -7.19 5.47 -7.19 C5.47 -7.19 2.92 -6.28 1.89 -6.28 C1.64 -6.28 1.47 -6.33 1.47 -6.47 L1.47 -13.17 C1.47 -13.44 1.25 -13.64 1 -13.64 L0.47 -13.64 C0.19 -13.64 0 -13.44 0 -13.17 L0 7.44 C0 7.67 0.11 7.81 0.36 7.81 L0.44 7.75 C0.47 7.75 0.56 7.75 0.61 7.72 C1.17 7.47 3.39 6.53 4.56 6.53 C4.97 6.53 5.25 6.64 5.25 6.97 L5.25 12.92 C5.25 13.19 5.44 13.39 5.72 13.39 L6.25 13.39 C6.47 13.39 6.72 13.19 6.72 12.92 L6.72 -7.17 C6.72 -7.36 6.56 -7.47 6.39 -7.47 C6.36 -7.47 6.28 -7.47 6.25 -7.44 Z M1.47 -1.56 C1.47 -2.11 3.92 -3.17 4.89 -3.17 C5.11 -3.17 5.25 -3.11 5.25 -2.97 L5.25 1.17 C5.25 1.89 2.97 2.81 1.97 2.81 C1.67 2.81 1.47 2.72 1.47 2.56 Z',
60
+ timeSig0:'M18 -0 C18 -5.56 14.17 -10.03 9.39 -10.03 C4.64 -10.03 0.81 -5.56 0.81 -0 C0.81 5.53 4.64 10 9.39 10 C14.17 10 18 5.53 18 -0 Z M9.39 -8.81 C11.03 -8.81 12.39 -5 12.39 -0.28 C12.39 4.39 11.03 8.19 9.39 8.19 C7.72 8.19 6.39 4.39 6.39 -0.28 C6.39 -5 7.72 -8.81 9.39 -8.81 Z',
61
+ timeSig1:'M0.97 -0.53 C0.97 -0.53 0.81 -0.28 0.81 -0 C0.81 0.19 0.92 0.44 1.25 0.56 C1.39 0.61 1.56 0.64 1.61 0.64 C2 0.64 2.17 0.28 2.17 0.28 C2.17 0.28 3.89 -2.47 4.33 -3.25 C4.47 -3.53 4.64 -3.64 4.72 -3.64 C4.89 -3.64 4.97 -3.33 4.97 -3.08 L4.97 7.25 C4.97 8.17 4.03 8.75 3.19 8.75 C2.92 8.75 2.53 8.89 2.53 9.36 C2.53 9.81 2.89 10 3.39 10 L11.92 10 C12.56 10 12.56 9.36 12.56 9.36 C12.56 9.36 12.56 8.75 11.97 8.75 C11.39 8.75 10.67 8.03 10.67 7.36 L10.67 -9.11 C10.67 -9.75 10.44 -10 9.89 -10.03 C9.33 -10.03 8.33 -9.89 7.81 -9.89 C7.03 -9.89 6.33 -9.92 5.72 -10 C5.64 -10 5.56 -10.03 5.53 -10.03 C5.11 -10.03 4.97 -9.64 4.81 -9.28 Z',
62
+ timeSig2:'M16.83 3.64 C16.83 3.17 16.64 3.08 16.36 3.08 C16.03 3.08 15.92 3.25 15.83 3.53 L15.81 3.56 C15.39 4.56 15.08 5.33 14.25 5.33 C14.03 5.33 13.83 5.28 13.56 5.19 C13.03 5 12.75 4.97 12.36 4.75 C11.56 4.44 9.67 3.81 8.03 3.81 C7.53 3.81 7 3.89 6.56 4.03 C7.44 2.61 10.83 1.39 11.72 1.17 C14.53 0.39 17.03 -0.75 17.03 -4.08 C17.03 -8.33 12.89 -10.17 9.17 -10.17 C6.36 -10.17 3.89 -9.92 1.92 -7.64 C1.25 -6.81 0.81 -5.81 0.81 -4.72 C0.81 -4.17 0.92 -3.61 1.17 -3 C1.75 -1.75 3 -0.81 4.44 -0.81 C6.89 -0.81 7.25 -3.33 7.25 -4.33 C7.25 -6.72 4.47 -6.83 4.47 -7.64 C4.56 -8.19 5.28 -9.17 7.64 -9.17 C11.19 -9.17 11.25 -6.47 11.25 -5.33 C11.25 -1.67 8.25 1.08 5.36 2.83 C3.17 4.25 1.61 6.19 0.92 8.81 L0.89 8.92 C0.89 9.44 1.33 10.28 1.92 10.28 C2.81 10.28 3.28 7.83 5.64 7.83 C7.25 7.83 7.83 10 11.39 10 C13.11 10 16.19 9.83 16.83 3.64 Z',
63
+ timeSig3:'M8.53 -9.92 C8.47 -9.92 8.44 -9.92 8.36 -9.92 L8.08 -9.97 C8.03 -9.97 7.97 -9.97 7.92 -9.97 C4.47 -9.97 1.03 -7.67 1.03 -5.56 C1.03 -4.25 1.81 -2.47 4.28 -2.33 L4.47 -2.33 C6.25 -2.33 7.11 -3.61 7.11 -4.92 L7.11 -5.25 C7 -6.72 6 -6.81 5.81 -6.89 C5.61 -6.97 5 -6.89 5 -7.44 L5 -7.61 C5.08 -8.28 6.33 -8.61 6.67 -8.61 C10.08 -8.61 10.39 -6.47 10.39 -5.53 L10.39 -5.25 C10.39 -2.28 8.03 -1.11 5.53 -1 C5.11 -0.97 4.56 -0.75 4.56 -0.33 C4.56 0.17 5.25 0.17 5.56 0.17 C10.17 0.17 10.53 3.28 10.53 3.81 C10.53 8.03 8.36 8.53 7.47 8.53 C7.33 8.53 7.17 8.47 7.11 8.47 C6.81 8.44 6.03 8.44 6 7.83 L6 7.64 C6 6.75 6.89 6.17 6.92 5 C6.92 3.33 5.75 2.11 4.03 2.11 C3.89 2.11 3.75 2.11 3.61 2.17 C2.92 2.28 2.17 2.67 1.67 3.19 C1 3.81 0.81 4.75 0.81 5.64 C0.89 8.75 3.72 9.97 7.64 10.03 L8 10.03 C11.97 10.03 16.03 8 16.03 4.47 L16.03 4.19 C15.97 3.03 15.67 2.28 14.92 1.39 C14.67 1.08 14.36 0.81 13.97 0.56 L13.11 0.08 L11.81 -0.28 C11.61 -0.33 11.47 -0.33 11.39 -0.47 C11.36 -0.56 11.36 -0.61 11.36 -0.67 C11.36 -0.83 11.39 -1 11.53 -1.03 C11.97 -1.17 12.39 -1.19 12.75 -1.39 C14.36 -2.17 15.19 -3.19 15.19 -5.03 C15.19 -8.72 10.19 -9.81 8.53 -9.92 Z',
64
+ timeSig4:'M14.47 2.97 L14.47 -5.61 C14.47 -5.92 14.44 -6.28 14 -6.28 C13.64 -6.28 13.44 -6.19 13.19 -5.92 L9.39 -1.33 C9.25 -1.11 9.03 -0.89 9.03 -0.39 L9.03 2.97 L3.64 2.97 C6.83 0.25 13.25 -8.83 13.36 -9.33 L13.39 -9.44 C13.39 -9.81 13.11 -10.03 12.81 -10.03 C12.44 -10.03 10.81 -9.97 10.08 -9.97 C9.36 -9.97 7.56 -10.03 7.25 -10.03 C6.89 -10.03 6.33 -9.92 6.33 -9.28 C6.33 -4.33 2.39 1.25 1.19 2.92 L0.97 3.25 C0.97 3.25 0.97 3.28 0.97 3.28 L0.92 3.33 C0.83 3.53 0.81 3.67 0.81 3.81 C0.81 4.19 1.11 4.47 1.61 4.47 L9.03 4.47 L9.03 7 C9.03 8.08 8.17 8.39 7.44 8.39 C6.81 8.39 6.53 8.75 6.53 9.17 C6.53 9.56 6.67 10 7.28 10 L15.81 10 C16.19 10 16.61 9.72 16.61 9.17 C16.61 8.61 16.11 8.36 15.72 8.36 C15.33 8.36 14.47 8.11 14.47 6.83 L14.47 4.47 L17.39 4.47 C17.81 4.47 18 4.19 18 3.72 C18 3.25 17.83 2.97 17.39 2.97 Z',
65
+ timeSig5:'M3.03 -2.36 C3.03 -2.36 3.19 -4.61 3.25 -4.97 C3.28 -5.28 3.47 -5.47 3.83 -5.47 L4 -5.47 C4.39 -5.39 6.28 -5.11 7.92 -5.11 C13.47 -5.11 13.67 -8.33 13.67 -8.97 C13.67 -9.47 13.56 -9.81 13.11 -9.81 C12.61 -9.81 9.47 -9.44 8.19 -9.44 C6.92 -9.44 3.47 -9.75 2.81 -9.83 C2.08 -9.83 1.89 -9.47 1.83 -9.17 L1.39 -0.28 L1.39 -0.19 C1.39 0.33 1.81 0.39 2.19 0.39 C2.61 0.39 2.64 0.03 3.08 -0.39 C3.47 -0.81 4.44 -1.72 5.81 -1.72 C7.17 -1.72 9.92 -0.97 9.92 3.47 C9.92 7.89 7.56 8.44 6.53 8.44 C6.19 8.44 5.89 8.44 5.61 8.33 C5.39 8.19 5.17 8.03 5.11 7.75 C5.11 7.47 5.39 7.33 5.61 7.19 C6.53 6.64 7.11 5.64 7.11 4.53 C7.11 2.75 5.72 1.39 4 1.39 C1.83 1.39 0.97 2.97 0.83 4.36 C0.81 4.61 0.81 4.83 0.81 5.08 C0.81 8.39 2.97 10.03 7.89 10.03 C12.67 10.03 15.33 7.08 15.33 3.47 C15.33 -0.17 12.36 -3.11 8.72 -3.11 C6.39 -3.11 4.67 -2.72 3.39 -1.97 C3.33 -1.92 3.25 -1.92 3.19 -1.92 C3.03 -1.92 3.03 -2.08 3.03 -2.19 Z',
66
+ timeSig6:'M12.19 -3.33 C13.89 -3.33 15.39 -4.64 15.39 -6.36 C15.39 -6.53 15.39 -6.64 15.36 -6.81 C14.97 -9.36 11.83 -10.03 9.67 -10.03 C6.64 -10 3.81 -8.53 2.36 -5.81 C1.47 -4.11 0.81 -2 0.81 -0.11 L0.81 0.03 C0.83 1.89 1.17 3.97 2.03 5.56 C3.67 8.53 5.64 9.97 9 9.97 C10.81 9.97 12.81 9.67 14.25 8.53 C15.64 7.39 16.56 5.61 16.56 3.81 C16.56 0.67 13.61 -2 10.53 -2 C9.25 -2 7.92 -1.53 6.89 -0.61 C6.81 -0.53 6.72 -0.53 6.64 -0.53 C6.39 -0.53 6.28 -0.83 6.28 -1.47 C6.39 -8.89 8.75 -9.08 9.61 -9.08 C10.39 -9.08 10.92 -8.89 10.92 -8.47 C10.92 -7.92 10.17 -7.44 9.92 -6.97 C9.72 -6.61 9.64 -6.19 9.64 -5.81 C9.64 -5.19 9.83 -4.61 10.25 -4.17 C10.53 -3.64 11.67 -3.33 12.19 -3.33 Z M8.89 -0.08 C10.17 -0.08 11.25 1.92 11.25 4.39 C11.25 6.89 10.17 8.89 8.89 8.89 C7.61 8.89 6.56 6.89 6.56 4.39 C6.56 1.92 7.61 -0.08 8.89 -0.08 Z',
67
+ timeSig7:'M16.83 -8.17 C16.83 -9.25 16.83 -9.75 16.17 -9.75 C16.11 -9.75 15.47 -9.61 15.33 -9.33 C15.03 -8.83 14.47 -6.56 13.47 -6.56 C12.47 -6.56 10.61 -9.97 7.28 -9.97 C4.97 -9.97 4.36 -9.03 3.81 -8.53 C3.25 -8 3 -7.83 2.72 -7.81 C2.39 -7.81 1.89 -8.36 1.67 -8.75 C1.61 -8.92 1.39 -9.03 1.19 -9.03 C1 -9.03 0.81 -8.92 0.81 -8.56 L0.81 -1.97 C0.81 -1.97 0.83 -1.33 1.25 -1.33 C1.56 -1.33 1.67 -1.67 1.83 -2.11 C2.25 -3.11 2.75 -5.44 4.56 -5.44 C6.17 -5.44 8.08 -2.44 10.39 -2.44 C11.53 -2.44 12.08 -3.03 12.39 -3.28 C12.53 -3.36 12.67 -3.44 12.75 -3.44 C12.92 -3.44 13 -3.33 13.03 -3.08 C13.03 -1.97 9.97 0.28 7.56 3.11 C6.03 4.89 4.81 7.19 4.81 8.75 C4.81 9.61 4.81 10 5.56 10 C6.28 10 7.17 9.64 8.17 9.64 C9.17 9.64 11.03 10 11.44 10 C11.83 10 12.08 9.67 12.08 8.53 C12.08 1.83 16.83 -3.89 16.83 -8 Z',
68
+ timeSig8:'M13.36 -1.44 C14.81 -2.36 15.75 -3.67 15.75 -5.67 C15.75 -9.75 9.89 -10.36 8.81 -10.36 C4.17 -10.36 1 -8.25 1 -4.89 C1 -2.11 2.56 -0.64 4.47 0.44 C2.39 1.44 0.81 2.75 0.81 5.28 C0.81 8.75 4.39 10.36 8.36 10.36 C12.36 10.36 16.64 8.64 16.64 3.25 C16.64 0.83 15.25 -0.47 13.36 -1.44 Z M11.28 -2.36 C8.08 -3.47 4.67 -4.17 4.67 -6.67 C4.67 -8.36 6.97 -9.19 8.72 -9.19 C10 -9.19 13.39 -8.56 13.39 -5.75 C13.39 -4.17 12.61 -3.11 11.28 -2.36 Z M8.19 9.03 C5.53 9.03 3.08 7.67 3.08 5.08 C3.08 3.47 4.47 2 6.25 1.33 C9.17 2.61 12.11 3.44 12.11 6.08 C12.11 7.67 10.89 9.03 8.19 9.03 Z',
69
+ timeSig9:'M5.17 3.25 C3.47 3.25 1.97 4.56 1.97 6.28 C1.97 6.44 1.97 6.56 2 6.72 C2.39 9.28 5.53 9.97 7.67 9.97 C10.72 9.92 13.56 8.44 15 5.72 C15.89 4.03 16.56 1.92 16.56 0.03 L16.56 -0.11 C16.53 -1.97 16.19 -4.03 15.33 -5.64 C13.67 -8.61 11.72 -10.03 8.36 -10.03 C6.56 -10.03 4.56 -9.75 3.11 -8.61 C1.72 -7.47 0.81 -5.67 0.81 -3.89 C0.81 -0.75 3.75 1.92 6.83 1.92 C8.11 1.92 9.44 1.44 10.47 0.53 C10.56 0.44 10.64 0.44 10.72 0.44 C10.97 0.44 11.08 0.75 11.08 1.39 C10.97 8.81 8.61 9 7.75 9 C6.97 9 6.44 8.81 6.44 8.39 C6.44 7.83 7.19 7.36 7.44 6.89 C7.64 6.53 7.72 6.11 7.72 5.72 C7.72 5.11 7.53 4.53 7.11 4.08 C6.83 3.56 5.67 3.25 5.17 3.25 Z M8.47 -0 C7.19 -0 6.11 -2 6.11 -4.47 C6.11 -6.97 7.19 -8.97 8.47 -8.97 C9.75 -8.97 10.81 -6.97 10.81 -4.47 C10.81 -2 9.75 -0 8.47 -0 Z'
70
+ };
71
+
72
+ /** [left, right, top, bottom] in staff units, y downward. */
73
+ export const GB: Record<GlyphName, [number, number, number, number]> ={
74
+ gClef:[0,26.84,-43.92,26.32],
75
+ fClef:[-0.2,27.36,-10.48,25.4],
76
+ cClef:[0,27.96,-20.24,20.24],
77
+ noteheadBlack:[0,11.8,-5,5],
78
+ noteheadHalf:[0,11.8,-5,5],
79
+ noteheadWhole:[0,16.88,-5,5],
80
+ restWhole:[0,11.28,-0.36,5.4],
81
+ restHalf:[0,11.28,-5.68,0.08],
82
+ restQuarter:[0.04,10.8,-14.92,15],
83
+ rest8th:[0,9.88,-6.96,10.04],
84
+ rest16th:[0,12.8,-7.16,20],
85
+ flag8thUp:[0,10.56,-0.36,32.4],
86
+ flag16thUp:[0,11.16,-0.08,32.52],
87
+ augmentationDot:[0,4,-2,2],
88
+ accidentalSharp:[0,9.96,-14,13.92],
89
+ accidentalFlat:[0,9.04,-17.56,7],
90
+ accidentalNatural:[0,6.72,-13.64,13.4],
91
+ timeSig0:[0.8,18,-10.04,10],
92
+ timeSig1:[0.8,12.56,-10.04,10],
93
+ timeSig2:[0.8,17.04,-10.16,10.28],
94
+ timeSig3:[0.8,16.04,-9.96,10.04],
95
+ timeSig4:[0.8,18,-10.04,10],
96
+ timeSig5:[0.8,15.32,-9.84,10.04],
97
+ timeSig6:[0.8,16.56,-10.04,9.96],
98
+ timeSig7:[0.8,16.84,-9.96,10],
99
+ timeSig8:[0.8,16.64,-10.36,10.36],
100
+ timeSig9:[0.8,16.56,-10.04,9.96]
101
+ };
@@ -0,0 +1,71 @@
1
+ import { GB, GP, type GlyphName } from './glyphdata';
2
+
3
+ export { GB, GP, type GlyphName };
4
+
5
+ /* Music glyphs (SMuFL outlines from glyphdata.ts) placed as SVG paths.
6
+ GB[name] is a glyph's horizontal extent [left, right]; GP[name] its path. */
7
+
8
+ const DEFAULT_INK = 'var(--staff)';
9
+ /** Coordinates in the SVG are rounded to two decimals to keep it small. */
10
+ const round2 = (value: number) => Math.round(value * 100) / 100;
11
+
12
+ /** A glyph's width. */
13
+ export function gW(name: GlyphName): number {
14
+ return GB[name][1] - GB[name][0];
15
+ }
16
+ /** An SVG path with no fill is black, which is invisible on a dark background
17
+ — every glyph names its colour. */
18
+ export function gPath(name: GlyphName, x: number, y: number, fill?: string | null): string {
19
+ return `<path d="${GP[name]}" transform="translate(${round2(x)} ${round2(y)})" fill="${fill || DEFAULT_INK}"/>`;
20
+ }
21
+ /** centred horizontally on x */
22
+ export function gMid(name: GlyphName, x: number, y: number, fill?: string | null): string {
23
+ return gPath(name, x - GB[name][0] - gW(name) / 2, y, fill);
24
+ }
25
+ function accGlyphName(alteration: number): GlyphName {
26
+ if (alteration === 1) {
27
+ return 'accidentalSharp';
28
+ }
29
+ if (alteration === -1) {
30
+ return 'accidentalFlat';
31
+ }
32
+ return 'accidentalNatural';
33
+ }
34
+ /** Gap between an accidental's right edge and its notehead's centre. */
35
+ const ACCIDENTAL_GAP = 8.4;
36
+ /** An accidental hangs to the left of its notehead, right-aligned against it. */
37
+ export function accPath(
38
+ alteration: number,
39
+ noteX: number,
40
+ y: number,
41
+ fill?: string | null,
42
+ ): string {
43
+ if (!alteration) {
44
+ return '';
45
+ }
46
+ const name = accGlyphName(alteration);
47
+ return gPath(name, noteX - ACCIDENTAL_GAP - GB[name][1], y, fill);
48
+ }
49
+ /** 5.9: half a notehead */
50
+ export const HEADW = gW('noteheadBlack') / 2;
51
+ export const STEMX = HEADW - 0.6;
52
+ export const STEMW = 1.2;
53
+ export const STEMLEN = 35;
54
+ /** Whole notes (16 sixteenths and up) and half notes have hollow heads. */
55
+ export function headName(sixteenths: number): GlyphName {
56
+ if (sixteenths >= 16) {
57
+ return 'noteheadWhole';
58
+ }
59
+ return sixteenths >= 8 ? 'noteheadHalf' : 'noteheadBlack';
60
+ }
61
+ /** A stem, the way an engraver puts one on: up on the right of the head for
62
+ notes below the middle line, down on the left for notes above it, and long
63
+ enough to reach the middle line when the note is far outside the staff. */
64
+ export function stemSVG(noteX: number, y: number, middleY: number, fill?: string | null): string {
65
+ const up = y > middleY;
66
+ const x = up ? noteX + STEMX - STEMW / 2 : noteX - HEADW;
67
+ const end = up ? Math.min(y - STEMLEN, middleY) : Math.max(y + STEMLEN, middleY);
68
+ const top = Math.min(y, end).toFixed(2);
69
+ const height = Math.abs(end - y).toFixed(2);
70
+ return `<rect x="${x.toFixed(2)}" y="${top}" width="${STEMW}" height="${height}" fill="${fill || DEFAULT_INK}"/>`;
71
+ }
@@ -0,0 +1,12 @@
1
+ /* The small line icons used on drill and family cards. */
2
+ export const IC ={
3
+ dot:'<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.4"><path d="M1 5h18M1 10h18M1 15h18"/><path d="M6.5 2v16" stroke-width="2.2"/><circle cx="13" cy="10" r="3.1" fill="currentColor" stroke="none"/></svg>',
4
+ target:'<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.4"><circle cx="10" cy="10" r="7.4"/><circle cx="10" cy="10" r="2.6" fill="currentColor" stroke="none"/><path d="M10 0v3.2M10 16.8V20M0 10h3.2M16.8 10H20"/></svg>',
5
+ staff:'<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.2"><path d="M0 4h20M0 8h20M0 12h20M0 16h20"/><ellipse cx="7" cy="14" rx="3.4" ry="2.5" fill="currentColor" stroke="none" transform="rotate(-18 7 14)"/><path d="M10.2 13.4V3.6" stroke-width="1.6"/></svg>',
6
+ wave:'<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"><path d="M2 8.5v3M6 4.5v11M10 1.5v17M14 5.5v9M18 8.5v3"/></svg>',
7
+ swap:'<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2 6.5h14M12.5 3l3.5 3.5-3.5 3.5M18 13.5H4M7.5 10 4 13.5 7.5 17"/></svg>',
8
+ voice:'<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M4.5 8v4M8 4.5v11"/><path d="M12 6.6a4.6 4.6 0 0 1 0 6.8M15.4 4a9 9 0 0 1 0 12"/></svg>',
9
+ book:'<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"><path d="M10 4.6C8.4 3.2 5.9 2.8 3 3.2v12.2c2.9-.4 5.4 0 7 1.4 1.6-1.4 4.1-1.8 7-1.4V3.2c-2.9-.4-5.4 0-7 1.4Z"/><path d="M10 4.6v12.2"/></svg>'
10
+ } as const;
11
+
12
+ export type IconName = keyof typeof IC;
@@ -0,0 +1,166 @@
1
+ import type { Pref } from '../store/types';
2
+ import { noteName, pcOf } from '../music/notes';
3
+ import { esc } from '../util/text';
4
+
5
+ /* ---------------- piano keyboard ----------------
6
+ Built to the shape the engine wants: geometry and drawing are pure, nothing
7
+ here reads the app's honmoon, and every use passes what it needs in. Four
8
+ modes — display (lit keys), input (taps answer), play (taps sound), follow
9
+ (lit from outside while audio runs).
10
+
11
+ The black keys are not evenly spaced, and spacing them evenly reads as
12
+ wrong even to people who cannot say why. Real geometry: inside the C–E
13
+ group the three white fronts are equal, and so are the four in F–B, which
14
+ puts the black keys where a piano puts them. */
15
+ /** A white key's width and height; a black key's width and height as
16
+ fractions of a white key's. */
17
+ export const KB = { w: 42, h: 150, bw: 0.583, bh: 0.62 };
18
+ /** Pitch classes of the white keys. */
19
+ const KBW = [0, 2, 4, 5, 7, 9, 11];
20
+ /** Each black key: its pitch class and its left edge, in white-key widths
21
+ from the C. */
22
+ const KBB: [number, number][] = [
23
+ [1, 0.611],
24
+ [3, 1.806],
25
+ [6, 3.5625],
26
+ [8, 4.708],
27
+ [10, 5.854],
28
+ ];
29
+ const WHITE_KEYS_PER_OCTAVE = 7;
30
+ /** Without a lowest note asked for, the board starts on C3. */
31
+ const DEFAULT_LOW = 48;
32
+
33
+ /** A lit key: `midi` matches one key, `pc` the same note in every octave —
34
+ which is what a question about a note rather than a pitch wants. */
35
+ export interface KeyCell {
36
+ midi?: number;
37
+ pc?: number;
38
+ fill?: string;
39
+ label?: string;
40
+ tc?: string;
41
+ }
42
+ export interface KeyboardOpts {
43
+ /** lowest note wanted; the board starts on the C at or below it */
44
+ lo?: number;
45
+ oct?: number;
46
+ w?: number;
47
+ h?: number;
48
+ cells?: KeyCell[];
49
+ labels?: 'letters' | 'all' | null;
50
+ pref?: Pref;
51
+ alt?: string;
52
+ }
53
+ export interface KeyBox {
54
+ midi: number;
55
+ black: boolean;
56
+ x: number;
57
+ w: number;
58
+ h: number;
59
+ }
60
+
61
+ function kbWhites(octaves: number): number {
62
+ return WHITE_KEYS_PER_OCTAVE * octaves + 1; /* the top C closes the octave */
63
+ }
64
+ export function kbDims(options: KeyboardOpts) {
65
+ const whiteCount = kbWhites(options.oct || 1);
66
+ const keyWidth = options.w || KB.w;
67
+ return { n: whiteCount, w: keyWidth, W: whiteCount * keyWidth, H: options.h || KB.h };
68
+ }
69
+ /** A sensible octave to draw for a given note: the C at or below it. */
70
+ export function kbBase(midi: number): number {
71
+ return Math.floor(midi / 12) * 12;
72
+ }
73
+ /** A keyboard always starts on a C: the pattern of black keys only makes sense
74
+ measured from one. Ask for a board around A and you get the C below it with
75
+ the A lit, not a board whose white keys are labelled A B C♯. */
76
+ function kbLo(options: KeyboardOpts): number {
77
+ return kbBase(options.lo ?? DEFAULT_LOW);
78
+ }
79
+ /** Every key in the drawn range, with its own box, in one list. White first so
80
+ the black ones paint over them; the tap layer reuses the same order. */
81
+ export function kbKeys(options: KeyboardOpts): KeyBox[] {
82
+ const dims = kbDims(options);
83
+ const lowC = kbLo(options);
84
+ const octaves = options.oct || 1;
85
+ const out: KeyBox[] = [];
86
+ const white = (midi: number, whiteIndex: number) =>
87
+ out.push({ midi, black: false, x: whiteIndex * dims.w, w: dims.w, h: dims.H });
88
+ for (let octave = 0; octave < octaves; octave++) {
89
+ KBW.forEach((pc, i) => white(lowC + 12 * octave + pc, WHITE_KEYS_PER_OCTAVE * octave + i));
90
+ }
91
+ white(lowC + 12 * octaves, WHITE_KEYS_PER_OCTAVE * octaves);
92
+ for (let octave = 0; octave < octaves; octave++) {
93
+ for (const [pc, leftEdge] of KBB) {
94
+ out.push({
95
+ midi: lowC + 12 * octave + pc,
96
+ black: true,
97
+ x: (WHITE_KEYS_PER_OCTAVE * octave + leftEdge) * dims.w,
98
+ w: KB.bw * dims.w,
99
+ h: dims.H * KB.bh,
100
+ });
101
+ }
102
+ }
103
+ return out;
104
+ }
105
+ /** The lit-key entry that applies to a key: by exact pitch, or by pitch class. */
106
+ export function kbLook(cells: KeyCell[] | undefined, key: KeyBox): KeyCell | null {
107
+ const matches = (cell: KeyCell) =>
108
+ cell.midi != null ? cell.midi === key.midi : pcOf(key.midi) === pcOf(cell.pc as number);
109
+ return (cells || []).find(matches) ?? null;
110
+ }
111
+ /** The text written on a key: the lit entry's own label, or the note name
112
+ when labels are on (black keys too, unless only the letters are wanted). */
113
+ function keyLabel(cell: KeyCell | null, key: KeyBox, options: KeyboardOpts): string {
114
+ if (cell && cell.label != null) {
115
+ return cell.label;
116
+ }
117
+ const name = noteName(pcOf(key.midi), 'en', options.pref || 'sharp');
118
+ if (options.labels === 'letters') {
119
+ return name;
120
+ }
121
+ if (options.labels === 'all' && !key.black) {
122
+ return name;
123
+ }
124
+ return '';
125
+ }
126
+ /** Label colour is fixed, not themed: the keys are light and dark in both
127
+ themes, so an --ink that flips would vanish on one of them. */
128
+ function labelColour(cell: KeyCell | null, key: KeyBox): string {
129
+ if (cell && cell.tc) {
130
+ return cell.tc;
131
+ }
132
+ if (cell) {
133
+ return 'var(--pk-lit-ink)';
134
+ }
135
+ return key.black ? 'var(--pk-b-ink)' : 'var(--pk-w-ink)';
136
+ }
137
+ function keyRect(key: KeyBox, cell: KeyCell | null): string {
138
+ const plainFill = key.black ? 'var(--pk-b)' : 'var(--pk-w)';
139
+ const fill = cell ? cell.fill || 'var(--accent)' : plainFill;
140
+ const radius = key.black ? 4 : 5;
141
+ const border = key.black ? 0.8 : 1;
142
+ return `<rect x="${(key.x + 0.6).toFixed(2)}" y="0" width="${(key.w - 1.2).toFixed(2)}" height="${key.h.toFixed(1)}" rx="${radius}" ry="${radius}" fill="${fill}" stroke="var(--pk-line)" stroke-width="${border}"/>`;
143
+ }
144
+ function keyText(key: KeyBox, label: string, colour: string): string {
145
+ const x = (key.x + key.w / 2).toFixed(2);
146
+ const y = (key.h - (key.black ? 8 : 10)).toFixed(1);
147
+ const size = key.black ? 10 : 12;
148
+ return `<text x="${x}" y="${y}" text-anchor="middle" font-size="${size}" font-weight="600" font-family="IBM Plex Mono,monospace" fill="${colour}">${esc(String(label))}</text>`;
149
+ }
150
+ export function kbSVG(options: KeyboardOpts = {}): string {
151
+ const dims = kbDims(options);
152
+ const parts: string[] = [];
153
+ for (const key of kbKeys(options)) {
154
+ const cell = kbLook(options.cells, key);
155
+ parts.push(keyRect(key, cell));
156
+ const label = keyLabel(cell, key, options);
157
+ if (label !== '' && label != null) {
158
+ parts.push(keyText(key, label, labelColour(cell, key)));
159
+ }
160
+ }
161
+ return (
162
+ `<svg viewBox="0 0 ${dims.W} ${dims.H}" width="${dims.W}" height="${dims.H}" role="img" aria-label="${esc(options.alt || 'piano keyboard')}">` +
163
+ parts.join('') +
164
+ '</svg>'
165
+ );
166
+ }