45 | 45 |
);
|
46 | 46 |
};
|
47 | 47 |
|
|
48 |
// B A
|
|
49 |
// A B A C B
|
|
50 |
// o C o o
|
|
51 |
// C
|
|
52 |
|
|
53 |
|
48 | 54 |
const layouts = {
|
49 | 55 |
wicki_hayden: {
|
50 | 56 |
major: 'row',
|
51 | 57 |
map: ([x, y], [w, h]) => {
|
52 | |
let note = 48 + 2 * x + 6 * y;
|
|
58 |
// A = 5
|
|
59 |
// B = 7
|
|
60 |
// C = 2 = x
|
|
61 |
// A + B = 12 = y
|
|
62 |
let note = 41 + 2 * x + 6 * y;
|
53 | 63 |
if (y % 2 == 0) note += 1;
|
54 | 64 |
return note;
|
55 | 65 |
},
|
56 | 66 |
},
|
57 | 67 |
harmonic: {
|
|
68 |
// A = 3
|
|
69 |
// B = 7 = y
|
|
70 |
// C = 4
|
|
71 |
// C - A = 1 = x
|
|
72 |
|
|
73 |
// A = 7 = y
|
|
74 |
// B = 4
|
|
75 |
// C = -3
|
|
76 |
// B + C = 1 = x
|
58 | 77 |
major: 'col',
|
59 | 78 |
map: ([x, y], [w, h]) => {
|
60 | 79 |
let note = 48 + x * 0.5 + y * 7;
|
61 | |
if (x % 2 === 1) note += 3.5;
|
|
80 |
if (x % 2 === 1) note -= 3.5;
|
62 | 81 |
return note;
|
63 | 82 |
},
|
64 | 83 |
},
|
65 | 84 |
gerhard: {
|
|
85 |
// A = -3
|
|
86 |
// B = 1
|
|
87 |
// C = 4
|
|
88 |
//
|
|
89 |
// A = 1 = y
|
|
90 |
// B = 4
|
|
91 |
// C = 3
|
|
92 |
// B + C = 7 = x
|
66 | 93 |
major: 'col',
|
67 | 94 |
map: ([x, y], [w, h]) => {
|
68 | 95 |
let note = 48 + x * 3.5 + y;
|
|
100 | 127 |
};
|
101 | 128 |
};
|
102 | 129 |
|
103 | |
const Keyboard = ({ w, h, layout: { major, map }, showMidi, state, scale, noteon, noteoff }) => (
|
|
130 |
const Keyboard = ({ w, h, layout: { major, map }, state, scale, labels, noteon, noteoff }) => (
|
104 | 131 |
<div
|
105 | 132 |
className={`keyboard ${major}-major`}
|
106 | 133 |
style={{
|
|
123 | 150 |
noteoff={noteoff}
|
124 | 151 |
major={major}
|
125 | 152 |
>
|
126 | |
{showMidi ? note : notes.music[note % 12]}
|
|
153 |
{labels ? labels[note % 12] : note}
|
127 | 154 |
</Hexagon>
|
128 | 155 |
);
|
129 | 156 |
})
|
|
216 | 243 |
state = {
|
217 | 244 |
layout: 'wicki_hayden',
|
218 | 245 |
scale: 'major',
|
|
246 |
labels: 'english',
|
219 | 247 |
offset: 60,
|
220 | 248 |
state: {},
|
221 | 249 |
w: 12,
|
|
269 | 297 |
this.send(NOTE_OFF, note);
|
270 | 298 |
}
|
271 | 299 |
|
272 | |
set(key) {
|
273 | |
return (e) => {
|
274 | |
let value = e.target.value;
|
275 | |
if (key === 'w' || key === 'h') value = +value;
|
276 | |
this.setState({ [key]: value });
|
277 | |
};
|
278 | |
}
|
|
300 |
set = (e) => {
|
|
301 |
const key = e.target.name;
|
|
302 |
let value = e.target.value;
|
|
303 |
if (key === 'w' || key === 'h') value = +value;
|
|
304 |
this.setState({ [key]: value });
|
|
305 |
};
|
279 | 306 |
|
280 | 307 |
onoffset = (offset) => (e) => this.setState({ offset });
|
281 | 308 |
|
|
318 | 345 |
}
|
319 | 346 |
|
320 | 347 |
render() {
|
321 | |
const { configure, showMidi } = this.props;
|
322 | |
const { w, h, scale, offset, layout, state } = this.state;
|
|
348 |
const { configure } = this.props;
|
|
349 |
const { w, h, scale, layout, labels, offset, state } = this.state;
|
323 | 350 |
|
324 | 351 |
const chord = Object.entries(state).filter(([note, on]) => on).map(([k, _]) => k);
|
325 | 352 |
chord.sort();
|
|
332 | 359 |
</div>
|
333 | 360 |
<div className="group">
|
334 | 361 |
<label>layout:</label>
|
335 | |
<select name="layout" value={layout} onChange={this.set('layout')}>
|
|
362 |
<select name="layout" value={layout} onChange={this.set}>
|
336 | 363 |
<option value="wicki_hayden">Wicki-Hayden</option>
|
337 | 364 |
<option value="harmonic">Harmonic Table</option>
|
338 | 365 |
<option value="gerhard">Gerhard</option>
|
339 | 366 |
</select>
|
340 | 367 |
</div>
|
341 | 368 |
<div className="group">
|
|
369 |
<label>note format:</label>
|
|
370 |
<select name="labels" value={labels} onChange={this.set}>
|
|
371 |
<option value="english">English</option>
|
|
372 |
<option value="german">German</option>
|
|
373 |
<option value="sol">Solfège</option>
|
|
374 |
<option value="midi">MIDI no</option>
|
|
375 |
</select>
|
|
376 |
</div>
|
|
377 |
<div className="group">
|
342 | 378 |
<label>scale:</label>
|
343 | 379 |
<button onClick={this.onoffset(null)}>
|
344 | |
{notes.music[offset % 12]}
|
|
380 |
{labels ? notes.labels[labels][offset % 12] : offset}
|
345 | 381 |
</button>
|
346 | |
<select name="layout" value={scale} onChange={this.set('scale')}>
|
|
382 |
<select name="scale" value={scale} onChange={this.set}>
|
347 | 383 |
<option value="none">None</option>
|
348 | 384 |
<option value="major">Major</option>
|
349 | 385 |
<option value="minor_nat">Natural Minor</option>
|
|
362 | 398 |
</div>
|
363 | 399 |
<div className="group">
|
364 | 400 |
<label>size:</label>
|
365 | |
<input className="small" type="number" min="1" value={w} onChange={this.set('w')} />
|
|
401 |
<input className="small" type="number" min="1" name="w" value={w} onChange={this.set} />
|
366 | 402 |
{'x'}
|
367 | |
<input className="small" type="number" min="1" value={h} onChange={this.set('h')} />
|
|
403 |
<input className="small" type="number" min="1" name="h" value={h} onChange={this.set} />
|
368 | 404 |
</div>
|
369 | 405 |
<div className="spacer" />
|
370 | 406 |
</nav>
|
|
375 | 411 |
h={h}
|
376 | 412 |
noteon={this.noteon.bind(this)}
|
377 | 413 |
noteoff={this.noteoff.bind(this)}
|
378 | |
showMidi={showMidi}
|
379 | 414 |
layout={layouts[layout]}
|
380 | 415 |
scale={scale !== "none" && offset !== null && tallyup(scales[scale], offset)}
|
|
416 |
labels={notes.labels[this.state.labels]}
|
381 | 417 |
offset={offset}
|
382 | 418 |
state={state}
|
383 | 419 |
/>
|