-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
208 lines (207 loc) · 11.4 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><style>
body { background-color: #abcdef; height: 100vh;
background: radial-gradient(ellipse at 160% 110%, #468ace 0%, #abcdef 100%);
font-family: sans-serif; font-weight: bold;
}
@keyframes red { from { text-shadow: 0 0 4em #000000; color: #ff0000; } }
@keyframes green { from { text-shadow: 0 0 4em #000000; color: #00ff00; } }
.midc { fill: rgb(224,224,224); stroke-width: 2; stroke: black }
.midc:hover { fill: blue }
.white { fill: white; stroke-width: 2; stroke: black }
.white:hover { fill: blue }
.black { fill: black }
.black:hover { fill: blue }
.red { text-shadow: 0 0 0.3em #ff0000; color: #ffc0c0; animation: red 1s; }
.green { text-shadow: 0 0 0.3em #00ff00; color: #c0ffc0; animation: green 1s; }
.blue { text-shadow: 0 0 0.3em #0000ff; color: #c0c0ff; }
h2 span { margin: 1em }
</style>
<script>
const nrec = {}; // global but is populated during makeKbd
var ans, lowest, highest, accs, total = 0, correct = 0, wrong = 0;
function clearStave() {
const e = Array( "trebleclef", "bassclef", "semibreve", "above2", "above1",
"below1", "below2", "sharp", "flat", "doublesharp", "doubleflat");
for (var i of e) {
document.getElementById(i).setAttributeNS(null,
"style", "visibility: hidden");
}
}
function drawNote(clef, y, a) {
clearStave();
document.getElementById(clef+"clef").setAttributeNS(null,
"style", "visibility: visible");
document.getElementById("semibreve").setAttributeNS(null, "transform",
"translate(165, " + y + ") scale(0.04, -0.04)");
document.getElementById("semibreve").setAttributeNS(null,
"style", "visibility: visible");
if (y < 95) {
document.getElementById("above1").setAttributeNS(null,
"style", "visibility: visible");
if (y < 85) {
document.getElementById("above2").setAttributeNS(null,
"style", "visibility: visible");
}
} else if (y > 145) {
document.getElementById("below1").setAttributeNS(null,
"style", "visibility: visible");
if (y > 155) {
document.getElementById("below2").setAttributeNS(null,
"style", "visibility: visible");
}
}
if (a) { // print an accidental
document.getElementById(a).setAttributeNS(null,
"style", "visibility: visible");
document.getElementById(a).setAttributeNS(null, "transform",
"translate(145, " + y + ") scale(0.04, -0.04)");
}
}
function showNote(n, o, a) {
const yoff = { "C": 0, "D": 5, "E": 10, "F": 15, "G": 20, "A": 25, "B": 30 };
if (o < 4) { // low note
drawNote("bass", (2-o) * (7*5) + 160 - yoff[n], a);
} else { // high note
drawNote("treble", (4-o) * (7*5) + 150 - yoff[n], a);
}
if (a) {
const acc_note = { // neighbouring bb, b, #, ## with octave offset
"C": [["A#", -1], ["B", -1], ["C#", 0], ["D", 0]],
"D": [["C", 0], ["C#", 0], ["D#", 0], ["E", 0]],
"E": [["D", 0], ["D#", 0], ["F", 0], ["F#", 0]],
"F": [["D#", 0], ["E", 0], ["F#",0], ["G", 0]],
"G": [["F", 0], ["F#", 0], ["G#", 0], ["A", 0]],
"A": [["G", 0], ["G#", 0], ["A#", 0], ["B", 0]],
"B": [["A", 0], ["A#", 0], ["C", 1], ["C#", 1]]
};
const acc_map = { doubleflat: 0, flat: 1, sharp: 2, doublesharp: 3 };
const sarr = acc_note[n][acc_map[a]];
ans = sarr[0] + (o+sarr[1]);
} else {
ans = n + o;
}
}
function guessNote(g) {
if (lowest) {
document.getElementById("total").innerHTML = "Guesses: " + ++total;
if (g==ans) {
var c1 = document.getElementById("correct");
var c2 = c1.cloneNode(true);
c2.innerHTML = "Correct: " + ++correct;
c1.parentNode.replaceChild(c2, c1); // to force animation replay
pickNote(lowest, highest, accs);
} else {
var w1 = document.getElementById("wrong");
var w2 = w1.cloneNode(true);
w2.innerHTML = "Wrong: " + ++wrong;
w1.parentNode.replaceChild(w2, w1); // to force animation replay
}
} else {
window.alert("Please choose a skill level first.");
}
}
function makeKbd(o, s) { // o is octaves, s is scale factor
var k = document.getElementById("autokbd");
var ns = "http://www.w3.org/2000/svg";
k.setAttributeNS(null, "width", (o*7+1) * s*40);
k.setAttributeNS(null, "height", s*200);
// first do white keys
const wn = [ "C", "D", "E", "F", "G", "A", "B" ];
for (var w = 0; w <= o*7; w++) {
var noteName = wn[w%7], noteOct = Math.floor(w/7)+2;
nrec[noteName + noteOct.toString()] = [w, noteName, noteOct];
wk = document.createElementNS(ns, "rect");
wk.setAttributeNS(null, "x", w*s*40);
wk.setAttributeNS(null, "y", 0);
wk.setAttributeNS(null, "width", s*40);
wk.setAttributeNS(null, "height", s*200);
wk.setAttributeNS(null, "class", w==(o/2)*7 ? "midc" : "white");
wk.setAttributeNS(null, "onclick", "guessNote('" + noteName + noteOct + "')");
k.appendChild(wk);
}
// then blacks (as sharps for now)
const bn = { "C#": 24, "D#": 68, "F#": 142, "G#": 186, "A#": 230 };
for (var i = 0; i < o; i++) {
for (const [note, xoff] of Object.entries(bn)) {
bk = document.createElementNS(ns, "rect");
bk.setAttributeNS(null, "x", s*(i*7*40 + xoff));
bk.setAttributeNS(null, "y", 0);
bk.setAttributeNS(null, "width", s*28);
bk.setAttributeNS(null, "height", s*120);
bk.setAttributeNS(null, "class", "black");
bk.setAttributeNS(null, "onclick", "guessNote('" + note + (i+2) + "')");
k.appendChild(bk);
}
}
}
function pickNote(l, h, a) { // pick a playable note within range
var accidentals = a[Math.floor(Math.random()*a.length)];
var n;
while (true) {
n = Object.keys(nrec)[Math.floor(Math.random()*Object.keys(nrec).length)];
if (nrec[n][0] >= nrec[l][0] && nrec[n][0] <= nrec[h][0]) {
if ((nrec[n][0] == 0 && accidentals.indexOf("flat") != -1) ||
(nrec[n][0] == Object.keys(nrec).length - 1 &&
accidentals.indexOf("sharp") != -1) ||
(nrec[n][0] == Object.keys(nrec).length - 2 &&
(nrec[n][1] == "B" || nrec[n][1] == "E") &&
accidentals.indexOf("doublesharp") != -1)) { // note is unplayable
//console.log("out of bounds note "+n+" "+accidentals);
} else {
break;
}
}
}
lowest = l;
highest = h;
accs = a;
showNote(nrec[n][1], nrec[n][2], accidentals);
}
</script></head><body>
<div align="center">
<div>
<button id="easy" onclick="pickNote('F3', 'G4', [])">Easy</button>
<button id="normal" onclick="pickNote('F2', 'G5',
['sharp', 'flat', '', '', ''])">Normal</button>
<button id="hard" onclick="pickNote('C2', 'C6',
['sharp', 'flat', 'doublesharp', 'doubleflat', ''])">Hard</button>
</div>
<svg xmlns="http://www.w3.org/2000/svg" width="300" height="240">
<!-- stave -->
<line stroke="black" x1="100" y1="100" x2="200" y2="100" />
<line stroke="black" x1="100" y1="110" x2="200" y2="110" />
<line stroke="black" x1="100" y1="120" x2="200" y2="120" />
<line stroke="black" x1="100" y1="130" x2="200" y2="130" />
<line stroke="black" x1="100" y1="140" x2="200" y2="140" />
<!-- leger lines -->
<line stroke="black" x1="160" y1="80" x2="190" y2="80" id="above2" />
<line stroke="black" x1="160" y1="90" x2="190" y2="90" id="above1" />
<line stroke="black" x1="160" y1="150" x2="190" y2="150" id="below1" />
<line stroke="black" x1="160" y1="160" x2="190" y2="160" id="below2" />
<!-- these glyphs were shamelessly stolen from SVG output from lilypond -->
<path id="trebleclef" transform="translate(110, 130) scale(0.04, -0.04)" d="M376 262c4 0 9 1 13 1c155 0 256 -128 256 -261c0 -76 -33 -154 -107 -210c-22 -17 -47 -28 -73 -36c3 -35 5 -70 5 -105c0 -19 -1 -39 -2 -58c-7 -120 -90 -228 -208 -228c-108 0 -195 88 -195 197c0 58 53 103 112 103c54 0 95 -47 95 -103c0 -52 -43 -95 -95 -95
c-11 0 -21 2 -31 6c26 -39 68 -65 117 -65c96 0 157 92 163 191c1 18 2 37 2 55c0 31 -1 61 -4 92c-29 -5 -58 -8 -89 -8c-188 0 -333 172 -333 374c0 177 131 306 248 441c-19 62 -37 125 -45 190c-6 52 -7 104 -7 156c0 115 55 224 149 292c3 2 7 3 10 3c4 0 7 0 10 -3
c71 -84 133 -245 133 -358c0 -143 -86 -255 -180 -364c21 -68 39 -138 56 -207zM461 -203c68 24 113 95 113 164c0 90 -66 179 -173 190c24 -116 46 -231 60 -354zM74 28c0 -135 129 -247 264 -247c28 0 55 2 82 6c-14 127 -37 245 -63 364c-79 -8 -124 -61 -124 -119
c0 -44 25 -91 81 -123c5 -5 7 -10 7 -15c0 -11 -10 -22 -22 -22c-3 0 -6 1 -9 2c-80 43 -117 115 -117 185c0 88 58 174 160 197c-14 58 -29 117 -46 175c-107 -121 -213 -243 -213 -403zM408 1045c-99 -48 -162 -149 -162 -259c0 -74 18 -133 36 -194 c80 97 146 198 146 324c0 55 -4 79 -20 129z" fill="currentColor" />
<path id="bassclef" transform="translate(110,110) scale(0.04, -0.04)" d="M557 -125c0 28 23 51 51 51s51 -23 51 -51s-23 -51 -51 -51s-51 23 -51 51zM557 125c0 28 23 51 51 51s51 -23 51 -51s-23 -51 -51 -51s-51 23 -51 51zM232 263c172 0 293 -88 293 -251c0 -263 -263 -414 -516 -521c-3 -3 -6 -4 -9 -4c-7 0 -13 6 -13 13c0 3 1 6 4 9
c202 118 412 265 412 493c0 120 -63 235 -171 235c-74 0 -129 -54 -154 -126c11 5 22 8 34 8c55 0 100 -45 100 -100c0 -58 -44 -106 -100 -106c-60 0 -112 47 -112 106c0 133 102 244 232 244z" fill="currentColor"/>
<path id="semibreve" transform="translate(165, 150) scale(0.04, -0.04)" d="M213 112c-50 0 -69 -43 -69 -88c0 -77 57 -136 134 -136c50 0 69 43 69 88c0 77 -57 136 -134 136zM491 0c0 -43 -34 -75 -72 -96c-53 -29 -114 -40 -174 -40s-120 11 -173 40c-38 21 -72 53 -72 96s34 75 72 96c53 29 113 40 173 40s121 -11 174 -40
c38 -21 72 -53 72 -96z" fill="currentColor"/>
<path id="sharp" transform="translate(145, 150) scale(0.04, -0.04)" d="M216 -312c0 -10 -8 -19 -18 -19s-19 9 -19 19v145l-83 -31v-158c0 -10 -9 -19 -19 -19s-18 9 -18 19v145l-32 -12c-2 -1 -5 -1 -7 -1c-11 0 -20 9 -20 20v60c0 8 5 16 13 19l46 16v160l-32 -11c-2 -1 -5 -1 -7 -1c-11 0 -20 9 -20 20v60c0 8 5 15 13 18l46 17v158
c0 10 8 19 18 19s19 -9 19 -19v-145l83 31v158c0 10 9 19 19 19s18 -9 18 -19v-145l32 12c2 1 5 1 7 1c11 0 20 -9 20 -20v-60c0 -8 -5 -16 -13 -19l-46 -16v-160l32 11c2 1 5 1 7 1c11 0 20 -9 20 -20v-60c0 -8 -5 -15 -13 -18l-46 -17v-158zM96 65v-160l83 30v160z" fill="currentColor"/>
<path id="flat" transform="translate(145, 150) scale(0.04, -0.04)" d="M27 41l-1 -66v-11c0 -22 1 -44 4 -66c45 38 93 80 93 139c0 33 -14 67 -43 67c-31 0 -52 -30 -53 -63zM-15 -138l-12 595c8 5 18 8 27 8s19 -3 27 -8l-7 -345c25 21 58 34 91 34c52 0 89 -48 89 -102c0 -80 -86 -117 -147 -169c-15 -13 -24 -38 -45 -38
c-13 0 -23 11 -23 25z" fill="currentColor"/>
<path id="doublesharp" transform="translate(145, 150) scale(0.04, -0.04)" d="M152 0c23 -23 56 -28 89 -28c6 0 10 -4 10 -10l9 -87c1 -5 -4 -10 -9 -10h-1l-87 9c-5 1 -10 5 -10 10c0 33 -5 66 -28 89c-23 -23 -28 -56 -28 -89c0 -5 -4 -9 -9 -10l-88 -9h-1c-5 0 -10 5 -9 10l9 87c0 6 4 10 10 10c33 0 66 5 89 28c-23 23 -56 28 -89 28
c-6 0 -10 4 -10 10l-9 87c-1 5 4 10 9 10h1l88 -9c5 -1 9 -5 9 -10c0 -33 5 -66 28 -89c23 23 28 56 28 89c0 5 5 9 10 10l87 9h1c5 0 10 -5 9 -10l-9 -87c0 -6 -4 -10 -10 -10c-33 0 -66 -5 -89 -28z" fill="currentColor"/>
<path id="doubleflat" transform="translate(145, 150) scale(0.04, -0.04)" d="M190 41l-1 -66c0 -4 -1 -7 -1 -11c0 -22 1 -44 4 -66c44 37 87 82 87 139c0 33 -11 67 -40 67c-30 0 -48 -30 -49 -63zM147 -138l-2 106c-27 -34 -66 -62 -98 -93c-13 -13 -19 -38 -39 -38c-13 0 -23 11 -23 25l-12 595c8 5 18 8 27 8s19 -3 27 -8l-7 -345
c17 21 43 34 70 34c20 0 38 -8 52 -21l-7 332c8 5 18 8 27 8s19 -3 27 -8l-6 -345c24 21 55 34 87 34c53 0 92 -47 92 -101c0 -80 -86 -118 -147 -170c-15 -13 -23 -38 -44 -38c-13 0 -24 11 -24 25zM17 -26v-10c0 -23 1 -45 4 -68c39 38 71 86 71 141c0 32 -6 67 -33 67
c-28 0 -39 -32 -40 -63z" fill="currentColor"/>
</svg><br/><svg xmlns="http://www.w3.org/2000/svg" id="autokbd" />
<script>makeKbd(4,0.7);clearStave();</script>
</div>
<h2 align="center" id="score">
<span id="total" class="blue"></span>
<span id="correct" class="green"></span>
<span id="wrong" class="red"></span>
</h2></body></html>