@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,94 @@
1
+ import { honmoon } from '../store/honmoon.svelte';
2
+
3
+ export interface IntervalDef {
4
+ /** size in semitones */
5
+ s: number;
6
+ /** name, e.g. "Perfect 5th" */
7
+ n: string;
8
+ /** abbreviation, e.g. "P5" */
9
+ ab: string;
10
+ /** what it looks and sounds like */
11
+ c: string;
12
+ }
13
+ export const IVS: IntervalDef[] = [
14
+ { s: 0, n: 'Unison', ab: 'P1', c: 'The same note twice.' },
15
+ {
16
+ s: 1,
17
+ n: 'Minor 2nd',
18
+ ab: 'm2',
19
+ c: 'One fret. The most uncomfortable gap there is — horror films live here.',
20
+ },
21
+ { s: 2, n: 'Major 2nd', ab: 'M2', c: 'Two frets. One step of a scale.' },
22
+ { s: 3, n: 'Minor 3rd', ab: 'm3', c: 'Three frets. The interval that makes a chord minor.' },
23
+ { s: 4, n: 'Major 3rd', ab: 'M3', c: 'Four frets. The interval that makes a chord major.' },
24
+ {
25
+ s: 5,
26
+ n: 'Perfect 4th',
27
+ ab: 'P4',
28
+ c: 'Five frets — the same note on the next string up. Opening of the wedding march.',
29
+ },
30
+ {
31
+ s: 6,
32
+ n: 'Tritone',
33
+ ab: 'TT',
34
+ c: 'Six frets, dead centre of the octave. Restless, wants to resolve.',
35
+ },
36
+ {
37
+ s: 7,
38
+ n: 'Perfect 5th',
39
+ ab: 'P5',
40
+ c: 'Seven frets. Opening of Twinkle Twinkle. The most stable interval after the octave.',
41
+ },
42
+ { s: 8, n: 'Minor 6th', ab: 'm6', c: 'Eight frets. A minor 3rd turned upside down.' },
43
+ { s: 9, n: 'Major 6th', ab: 'M6', c: 'Nine frets. Bright and open.' },
44
+ { s: 10, n: 'Minor 7th', ab: 'm7', c: 'Ten frets. The top note of a dominant chord.' },
45
+ {
46
+ s: 11,
47
+ n: 'Major 7th',
48
+ ab: 'M7',
49
+ c: 'Eleven frets. One fret short of the octave and it aches for it.',
50
+ },
51
+ { s: 12, n: 'Octave', ab: 'P8', c: 'Twelve frets. The same note, higher.' },
52
+ ];
53
+ export function ivDef(semitones: number): IntervalDef {
54
+ return IVS.find((interval) => interval.s === semitones) ?? IVS[0];
55
+ }
56
+ /** Every interval from a minor second to the octave, in semitones. */
57
+ const ALL_INTERVALS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
58
+ /** The starter set: 2nd, minor and major 3rd, 4th, 5th, octave. */
59
+ const BASIC_INTERVALS = [2, 3, 4, 5, 7, 12];
60
+ export function ivPool(): number[] {
61
+ return honmoon.settings.ivset === 'all' ? [...ALL_INTERVALS] : [...BASIC_INTERVALS];
62
+ }
63
+
64
+ export interface ChordDef {
65
+ id: string;
66
+ /** name, e.g. "Minor" */
67
+ n: string;
68
+ /** semitones above the root, e.g. [0, 3, 7] */
69
+ iv: number[];
70
+ /** 1 when part of the starter set */
71
+ basic: 0 | 1;
72
+ }
73
+ export const CHORDS: ChordDef[] = [
74
+ { id: 'maj', n: 'Major', iv: [0, 4, 7], basic: 1 },
75
+ { id: 'min', n: 'Minor', iv: [0, 3, 7], basic: 1 },
76
+ { id: 'dom7', n: 'Dominant 7', iv: [0, 4, 7, 10], basic: 0 },
77
+ { id: 'min7', n: 'Minor 7', iv: [0, 3, 7, 10], basic: 0 },
78
+ { id: 'maj7', n: 'Major 7', iv: [0, 4, 7, 11], basic: 0 },
79
+ { id: 'dim', n: 'Diminished', iv: [0, 3, 6], basic: 0 },
80
+ { id: 'aug', n: 'Augmented', iv: [0, 4, 8], basic: 0 },
81
+ ];
82
+ export function chPool(): ChordDef[] {
83
+ return CHORDS.filter((chord) => honmoon.settings.ivset === 'all' || chord.basic);
84
+ }
85
+ export function chDef(id: string): ChordDef {
86
+ return CHORDS.find((chord) => chord.id === id) ?? CHORDS[0];
87
+ }
88
+ /** Interval and chord cards: "iv:7" is a fifth, "ch:min" a minor chord. */
89
+ export function earCards(): string[] {
90
+ return [
91
+ ...ivPool().map((semitones) => 'iv:' + semitones),
92
+ ...chPool().map((chord) => 'ch:' + chord.id),
93
+ ];
94
+ }
@@ -0,0 +1,133 @@
1
+ import type { Pref, Sys } from '../store/types';
2
+ import { honmoon } from '../store/honmoon.svelte';
3
+
4
+ /* ---------------- pitch arithmetic ---------------- */
5
+
6
+ export const SEMITONES_PER_OCTAVE = 12;
7
+ /** Concert A: MIDI note 69 sounds at 440 Hz. */
8
+ const A4_MIDI = 69;
9
+ const A4_HZ = 440;
10
+
11
+ /** The pitch class (0 = C … 11 = B) of a MIDI note; works for negatives too. */
12
+ export function pcOf(midi: number): number {
13
+ return ((midi % SEMITONES_PER_OCTAVE) + SEMITONES_PER_OCTAVE) % SEMITONES_PER_OCTAVE;
14
+ }
15
+ export function freqOfSounding(midi: number): number {
16
+ return A4_HZ * Math.pow(2, (midi - A4_MIDI) / SEMITONES_PER_OCTAVE);
17
+ }
18
+
19
+ /* ---------------- note vocabulary ---------------- */
20
+
21
+ /** Letter and alteration for each pitch class, spelled with sharps … */
22
+ const SHARP_LETTERS = ['C', 'C', 'D', 'D', 'E', 'F', 'F', 'G', 'G', 'A', 'A', 'B'];
23
+ const SHARP_ALTERATIONS = [0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0];
24
+ /** … and with flats. */
25
+ const FLAT_LETTERS = ['C', 'D', 'D', 'E', 'E', 'F', 'G', 'G', 'A', 'A', 'B', 'B'];
26
+ const FLAT_ALTERATIONS = [0, -1, 0, -1, 0, 0, -1, 0, -1, 0, -1, 0];
27
+
28
+ export const SOLF: Record<string, string> = {
29
+ C: 'Do',
30
+ D: 'Re',
31
+ E: 'Mi',
32
+ F: 'Fa',
33
+ G: 'Sol',
34
+ A: 'La',
35
+ B: 'Si',
36
+ };
37
+ export const SYSNAME: Record<Sys, string> = { en: 'letters', solf: 'solfège' };
38
+ /** Position of each letter in the C-major scale: C = 0 … B = 6. */
39
+ export const STEP: Record<string, number> = { C: 0, D: 1, E: 2, F: 3, G: 4, A: 5, B: 6 };
40
+ /** Pitch classes of the seven natural notes. */
41
+ export const NAT = [0, 2, 4, 5, 7, 9, 11];
42
+ export const LETTERS = ['C', 'D', 'E', 'F', 'G', 'A', 'B'];
43
+ export const NATPC = [0, 2, 4, 5, 7, 9, 11];
44
+
45
+ /** How a pitch class is written: a letter (`l`) and an alteration (`a`):
46
+ -1 flat, 0 natural, 1 sharp. */
47
+ export interface Spelling {
48
+ l: string;
49
+ a: number;
50
+ }
51
+
52
+ export function spell(pc: number, pref: Pref): Spelling {
53
+ return pref === 'flat'
54
+ ? { l: FLAT_LETTERS[pc], a: FLAT_ALTERATIONS[pc] }
55
+ : { l: SHARP_LETTERS[pc], a: SHARP_ALTERATIONS[pc] };
56
+ }
57
+ export function accGlyph(alteration: number): string {
58
+ if (alteration === 1) {
59
+ return '♯';
60
+ }
61
+ if (alteration === -1) {
62
+ return '♭';
63
+ }
64
+ return '';
65
+ }
66
+ export function noteName(pc: number, sys: Sys, pref: Pref): string {
67
+ const spelling = spell(pc, pref);
68
+ const letterOrSyllable = sys === 'solf' ? SOLF[spelling.l] : spelling.l;
69
+ return letterOrSyllable + accGlyph(spelling.a);
70
+ }
71
+ /** Guarded, because every caller reaches it by slicing a number out of a card
72
+ id, and one id that does not carry a number should not print "undefined". */
73
+ export function bothNames(pc: number, pref: Pref): string {
74
+ if (!isFinite(pc)) {
75
+ return '?';
76
+ }
77
+ return noteName(pc, 'en', pref) + ' · ' + noteName(pc, 'solf', pref);
78
+ }
79
+ export function otherSys(sys: Sys): Sys {
80
+ return sys === 'en' ? 'solf' : 'en';
81
+ }
82
+ /** A written pitch with its octave, in the user's spelling: "E2", "F♯3". */
83
+ export function midiName(midi: number, pref: Pref = honmoon.settings.pref): string {
84
+ const octave = Math.floor(midi / SEMITONES_PER_OCTAVE) - 1;
85
+ return noteName(pcOf(midi), 'en', pref) + octave;
86
+ }
87
+
88
+ /* ---- movable do and scale degrees ----
89
+ Fixed do above ties a syllable to a letter for ever: Do is always C.
90
+ Movable do ties it to the key instead: Do is whatever the tonic is, and
91
+ the same tune keeps the same syllables in every key. Movable do says Ti,
92
+ not Si, because Si is taken by the raised fifth. */
93
+ const MOVABLE_DO_SHARP = ['Do', 'Di', 'Re', 'Ri', 'Mi', 'Fa', 'Fi', 'Sol', 'Si', 'La', 'Li', 'Ti'];
94
+ const MOVABLE_DO_FLAT = ['Do', 'Ra', 'Re', 'Me', 'Mi', 'Fa', 'Se', 'Sol', 'Le', 'La', 'Te', 'Ti'];
95
+ const DEGREES_SHARP = ['1', '♯1', '2', '♯2', '3', '4', '♯4', '5', '♯5', '6', '♯6', '7'];
96
+ const DEGREES_FLAT = ['1', '♭2', '2', '♭3', '3', '4', '♭5', '5', '♭6', '6', '♭7', '7'];
97
+
98
+ /** Semitones from the key's tonic up to this pitch class (0–11). */
99
+ export function relOf(pc: number, keyPc: number): number {
100
+ return pcOf(pc - keyPc);
101
+ }
102
+ export function mdoName(pc: number, keyPc: number, pref: Pref): string {
103
+ return mdoFromRel(relOf(pc, keyPc), pref);
104
+ }
105
+ export function degName(pc: number, keyPc: number, pref: Pref): string {
106
+ return degFromRel(relOf(pc, keyPc), pref);
107
+ }
108
+ export function mdoFromRel(semitonesFromTonic: number, pref: Pref): string {
109
+ return (pref === 'flat' ? MOVABLE_DO_FLAT : MOVABLE_DO_SHARP)[pcOf(semitonesFromTonic)];
110
+ }
111
+ export function degFromRel(semitonesFromTonic: number, pref: Pref): string {
112
+ return (pref === 'flat' ? DEGREES_FLAT : DEGREES_SHARP)[pcOf(semitonesFromTonic)];
113
+ }
114
+ /** Keys used for movable-do work: majors out to four accidentals, spelled the
115
+ way a key signature spells them. */
116
+ export const MKEYS = [
117
+ { pc: 0, n: 'C' },
118
+ { pc: 7, n: 'G' },
119
+ { pc: 2, n: 'D' },
120
+ { pc: 9, n: 'A' },
121
+ { pc: 4, n: 'E' },
122
+ { pc: 5, n: 'F' },
123
+ { pc: 10, n: 'B♭' },
124
+ { pc: 3, n: 'E♭' },
125
+ { pc: 8, n: 'A♭' },
126
+ ];
127
+ export function keyName(pc: number): string {
128
+ const key = MKEYS.find((k) => k.pc === pc);
129
+ return key ? key.n : noteName(pc, 'en', 'sharp');
130
+ }
131
+ export function mdoOn(): boolean {
132
+ return !!honmoon.settings.mdo;
133
+ }