@greenblacksky/honmoon 0.0.0-stage → 0.4.0

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 +81 -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,230 @@
1
+ <!-- Every note on the staff in order, what it is called, and where it lives on the neck. -->
2
+ <script lang="ts" module>
3
+ /** The selected note (written pitch, starts on A2) and clef; kept across visits. */
4
+ const ref = $state({ sel: 45, clef: 'bass' as string });
5
+ </script>
6
+
7
+ <script lang="ts">
8
+ import { honmoon } from '../../store/honmoon.svelte';
9
+ import { STRINGS, placesForMidi } from '../../music/bass';
10
+ import {
11
+ clefDef,
12
+ clefLines,
13
+ clefRange,
14
+ clefSpaces,
15
+ diatonicMidi,
16
+ enabledClefs,
17
+ } from '../../music/clefs';
18
+ import { bothNames, noteName, pcOf } from '../../music/notes';
19
+ import { piano, pluck } from '../../audio/synth';
20
+ import { RUN, clefW } from '../../notation/staff';
21
+ import { hscroll } from '../actions';
22
+ import Fretboard from '../Fretboard.svelte';
23
+ import StaffRun from '../StaffRun.svelte';
24
+ import RefBar from '../RefBar.svelte';
25
+
26
+ const { back }: { back?: { label: string; to: string } } = $props();
27
+
28
+ /** The natural notes from E2 to G4: open E string to the 12th fret of the G string. */
29
+ const BASS_NATURALS = [40, 41, 43, 45, 47, 48, 50, 52, 53, 55, 57, 59, 60, 62, 64, 65, 67];
30
+ /** The open strings, low to high: the four landmarks of the bass staff. */
31
+ const OPEN_STRINGS = [...STRINGS].reverse().map((string) => string.open);
32
+ const OCTAVE = 12;
33
+ /** Diatonic steps from the bottom line of a staff to its middle and top lines. */
34
+ const MIDDLE_LINE_STEPS = 4;
35
+ const TOP_LINE_STEPS = 8;
36
+ /** Room after the clef, and after the last note, in the staff run's units. */
37
+ const RUN_MARGIN = 14;
38
+ const refRun = (clef: string) => (clef === 'bass' ? BASS_NATURALS : clefRange(clef));
39
+ const pref = $derived(honmoon.settings.pref);
40
+ const clefsOn = $derived(enabledClefs());
41
+ $effect(() => {
42
+ if (!clefsOn.includes(ref.clef as never)) {
43
+ ref.clef = clefsOn[0];
44
+ }
45
+ const notes = refRun(ref.clef);
46
+ if (!notes.includes(ref.sel)) {
47
+ ref.sel = notes[Math.floor(notes.length / 2)];
48
+ }
49
+ });
50
+ const clef = $derived(clefDef(ref.clef));
51
+ const run = $derived(refRun(ref.clef));
52
+ const places = $derived(placesForMidi(ref.sel));
53
+ /* keep the selected note on screen, wherever it sits in the run */
54
+ const frac = $derived.by(() => {
55
+ const index = run.indexOf(ref.sel);
56
+ if (index < 0) {
57
+ return null;
58
+ }
59
+ const firstX = Math.max(RUN.x0, clefW(ref.clef) + RUN_MARGIN);
60
+ return (firstX + index * RUN.dx) / (firstX + run.length * RUN.dx + RUN_MARGIN);
61
+ });
62
+
63
+ /** Bass notes are plucked an octave down, as a bass sounds; other clefs on the piano. */
64
+ function sound(midi: number) {
65
+ if (ref.clef === 'bass') {
66
+ pluck(midi - OCTAVE);
67
+ } else {
68
+ piano(midi, null, 2.2, 0.8);
69
+ }
70
+ }
71
+ function pickNote(midi: number) {
72
+ ref.sel = midi;
73
+ sound(midi);
74
+ }
75
+ const names = (midis: number[], system: 'en' | 'solf') =>
76
+ midis.map((midi) => noteName(pcOf(midi), system, pref)).join(' ');
77
+ </script>
78
+
79
+ <RefBar title="Staff map" {back} />
80
+
81
+ {#if clefsOn.length > 1}
82
+ <div class="sec">
83
+ <div class="levels">
84
+ {#each clefsOn as id (id)}<button
85
+ class="chip sm"
86
+ data-refclef={id}
87
+ aria-pressed={id === ref.clef}
88
+ onclick={() => {
89
+ ref.clef = id;
90
+ }}>{clefDef(id).n}</button
91
+ >{/each}
92
+ </div>
93
+ </div>
94
+ {/if}
95
+
96
+ <div class="sec">
97
+ <h2>Every note, in order</h2>
98
+ <div class="card">
99
+ <div class="fbwrap" id="refw" use:hscroll={{ frac, key: ref.clef + ':' + ref.sel }}>
100
+ <StaffRun midis={run} {pref} sel={ref.sel} clef={ref.clef} onNote={pickNote} />
101
+ </div>
102
+ <p class="note" style="margin-top:8px">
103
+ Tap any note to hear it{ref.clef === 'bass' ? ' and see where it lives on the neck' : ''}. {ref.clef ===
104
+ 'bass'
105
+ ? 'This runs from the open E string up to the 12th fret of the G string — the whole range the drills use.'
106
+ : 'This runs from two ledger lines below the ' +
107
+ clef.n.toLowerCase() +
108
+ ' staff to two above, which is the range the drills use for it.'}
109
+ </p>
110
+ </div>
111
+ </div>
112
+
113
+ <div class="sec">
114
+ <h2>{bothNames(pcOf(ref.sel), pref)}</h2>
115
+ <div class="card">
116
+ <div class="fbwrap fit">
117
+ <Fretboard
118
+ lo={0}
119
+ hi={12}
120
+ cells={places.map((place) => ({ s: place.s, f: place.f, fill: 'var(--accent)', r: 10 }))}
121
+ scale={1.25}
122
+ />
123
+ </div>
124
+ <div class="bar" style="margin-top:10px">
125
+ <button class="ghost" id="hearref" onclick={() => sound(ref.sel)}>♪ Hear it</button>
126
+ <span class="tick"
127
+ >{places.length
128
+ ? places.map((place) => STRINGS[place.s].n + ' ' + place.f).join(' · ')
129
+ : 'not on the first twelve frets — the same letter is, an octave down'}</span
130
+ >
131
+ </div>
132
+ </div>
133
+ </div>
134
+
135
+ <div class="sec">
136
+ <h2>The five lines</h2>
137
+ <div class="card">
138
+ <div class="fbwrap">
139
+ <StaffRun midis={clefLines(ref.clef)} {pref} clef={ref.clef} onNote={pickNote} />
140
+ </div>
141
+ <p class="note" style="margin-top:8px">
142
+ Bottom to top: <b>{names(clefLines(ref.clef), 'en')}</b>. {clef.ml}. In solfège that is {names(
143
+ clefLines(ref.clef),
144
+ 'solf',
145
+ )}.
146
+ </p>
147
+ </div>
148
+ </div>
149
+
150
+ <div class="sec">
151
+ <h2>The four spaces</h2>
152
+ <div class="card">
153
+ <div class="fbwrap">
154
+ <StaffRun midis={clefSpaces(ref.clef)} {pref} clef={ref.clef} onNote={pickNote} />
155
+ </div>
156
+ <p class="note" style="margin-top:8px">
157
+ Bottom to top: <b>{names(clefSpaces(ref.clef), 'en')}</b> — <i>{clef.ms}</i>. {names(
158
+ clefSpaces(ref.clef),
159
+ 'solf',
160
+ )}.
161
+ </p>
162
+ </div>
163
+ </div>
164
+
165
+ {#if ref.clef === 'bass'}
166
+ <div class="sec">
167
+ <h2>Anchors worth memorising first</h2>
168
+ <div class="card stack">
169
+ <p class="note">
170
+ Rather than counting lines every time, learn four landmarks and read the rest as one step up
171
+ or down from them. All four are open strings, so you already half-know them:
172
+ </p>
173
+ <div class="fbwrap">
174
+ <StaffRun midis={OPEN_STRINGS} {pref} clef="bass" onNote={pickNote} />
175
+ </div>
176
+ <p class="note">
177
+ <b>E</b> hangs on its own ledger line below the staff. <b>A</b> is the first space. <b>D</b>
178
+ is the middle line — the easiest shape on the staff. <b>G</b> is the top space. Learn these four
179
+ and everything else is within two steps of one of them.
180
+ </p>
181
+ </div>
182
+ </div>
183
+ {:else}
184
+ <div class="sec">
185
+ <h2>Where to start</h2>
186
+ <div class="card stack">
187
+ <p class="note">
188
+ Learn three landmarks rather than counting lines. The bottom line, the middle line and the
189
+ top line are enough — everything else is within two steps of one of them.
190
+ </p>
191
+ <div class="fbwrap">
192
+ <StaffRun
193
+ midis={[0, MIDDLE_LINE_STEPS, TOP_LINE_STEPS].map((steps) =>
194
+ diatonicMidi(clef.bot + steps),
195
+ )}
196
+ {pref}
197
+ clef={ref.clef}
198
+ onNote={pickNote}
199
+ />
200
+ </div>
201
+ {#if ref.clef === 'treble'}
202
+ <p class="note">
203
+ Treble clef and bass clef are two frames onto the same keyboard, joined by middle C: the
204
+ note one ledger line <i>above</i> the bass staff is the note one ledger line <i>below</i> the
205
+ treble staff. If you know where middle C is in both, you can always find your way across.
206
+ </p>
207
+ {:else}
208
+ <p class="note">
209
+ A C clef points at middle C with the middle of its bracket, which is why it moves: on the
210
+ middle line it is alto clef, one line higher it is tenor. Find middle C first and read out
211
+ from it.
212
+ </p>
213
+ {/if}
214
+ </div>
215
+ </div>
216
+ {/if}
217
+
218
+ <div class="sec">
219
+ <h2>Why bass is written an octave high</h2>
220
+ <div class="card">
221
+ <p class="note">
222
+ Bass guitar is a transposing instrument: the notes are written one octave above where they
223
+ actually sound, so the part stays on the staff instead of drowning in ledger lines. The open E
224
+ string is written on the first ledger line below the bass staff but sounds at 41 Hz, an octave
225
+ lower. You never have to think about it while playing — read the note, play the fret, and the
226
+ octave takes care of itself. It only matters if you are reading a part written for another
227
+ instrument.
228
+ </p>
229
+ </div>
230
+ </div>