0 | 0 |
import React from 'react';
|
1 | |
import { pos, width, height } from './style';
|
|
1 |
import { css, pos, width, height } from './style';
|
2 | 2 |
import * as notes from './notes';
|
3 | 3 |
|
4 | 4 |
const statbyte = (stat, chan) => (stat << 4) | chan;
|
|
155 | 155 |
);
|
156 | 156 |
};
|
157 | 157 |
|
|
158 |
css(`
|
|
159 |
nav {
|
|
160 |
display: flex;
|
|
161 |
flex-wrap: wrap;
|
|
162 |
justfiy-content: space-between;
|
|
163 |
}
|
|
164 |
|
|
165 |
nav > .group, nav > .spacer {
|
|
166 |
display: flex;
|
|
167 |
align-items: baseline;
|
|
168 |
padding: 0.25em 1em;
|
|
169 |
gap: 1em;
|
|
170 |
|
|
171 |
border: 0 solid #363636;
|
|
172 |
border-width: 1px 0 1px 0;
|
|
173 |
}
|
|
174 |
|
|
175 |
nav > .group:nth-child(2n) {
|
|
176 |
background: #363636;
|
|
177 |
}
|
|
178 |
|
|
179 |
nav > .spacer { flex: 1; }
|
|
180 |
`);
|
|
181 |
|
|
182 |
css(`
|
|
183 |
main {
|
|
184 |
position: relative;
|
|
185 |
padding: 0.5em;
|
|
186 |
margin: 1em 0;
|
|
187 |
|
|
188 |
display: flex;
|
|
189 |
justify-content: space-evenly;
|
|
190 |
}
|
|
191 |
|
|
192 |
main .focus-msg {
|
|
193 |
position: absolute;
|
|
194 |
inset: 0;
|
|
195 |
|
|
196 |
display: flex;
|
|
197 |
font-size: 5em;
|
|
198 |
justify-content: space-around;
|
|
199 |
align-items: center;
|
|
200 |
text-align: center;
|
|
201 |
|
|
202 |
border: 4px solid white;
|
|
203 |
background: #212121;
|
|
204 |
pointer-events: none;
|
|
205 |
|
|
206 |
opacity: 0.8;
|
|
207 |
transition: opacity 0.4s;
|
|
208 |
}
|
|
209 |
|
|
210 |
main:focus-within .focus-msg {
|
|
211 |
opacity: 0;
|
|
212 |
}
|
|
213 |
`);
|
|
214 |
|
158 | 215 |
export default class App extends React.Component {
|
159 | 216 |
state = {
|
160 | 217 |
layout: 'wicki_hayden',
|
161 | 218 |
scale: 'major',
|
162 | 219 |
offset: 60,
|
163 | 220 |
state: {},
|
|
221 |
w: 12,
|
|
222 |
h: 4,
|
164 | 223 |
};
|
165 | 224 |
|
|
225 |
ref = React.createRef();
|
|
226 |
|
166 | 227 |
componentDidMount() {
|
167 | |
document.addEventListener("keydown", this.onkeydown);
|
168 | |
document.addEventListener("keyup", this.onkeyup);
|
169 | |
}
|
170 | |
|
171 | |
componentWillUnmount() {
|
172 | |
document.removeEventListener("keydown", this.onkeydown);
|
173 | |
document.removeEventListener("keyup", this.onkeyup);
|
|
228 |
this.ref.current.addEventListener("keydown", this.onkeydown);
|
|
229 |
this.ref.current.addEventListener("keyup", this.onkeyup);
|
174 | 230 |
}
|
175 | 231 |
|
176 | 232 |
componentDidUpdate(prevProps) {
|
|
213 | 269 |
this.send(NOTE_OFF, note);
|
214 | 270 |
}
|
215 | 271 |
|
216 | |
onlayout = (e) => this.setState({ layout: e.target.value })
|
217 | |
onscale = (e) => this.setState({ scale: e.target.value })
|
218 | |
|
219 | |
onoffset = (e) => this.setState({ offset: null });
|
|
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 |
}
|
|
279 |
|
|
280 |
onoffset = (offset) => (e) => this.setState({ offset });
|
220 | 281 |
|
221 | 282 |
onmessage = (e) => {
|
222 | 283 |
const message = parse(e);
|
|
258 | 319 |
|
259 | 320 |
render() {
|
260 | 321 |
const { configure, showMidi } = this.props;
|
261 | |
const { scale, offset, layout, state } = this.state;
|
|
322 |
const { w, h, scale, offset, layout, state } = this.state;
|
262 | 323 |
|
263 | 324 |
const chord = Object.entries(state).filter(([note, on]) => on).map(([k, _]) => k);
|
264 | 325 |
chord.sort();
|
265 | 326 |
|
266 | 327 |
return (
|
267 | 328 |
<div className="app">
|
268 | |
<button onClick={configure}>midi settings</button>
|
269 | |
<div className="option">
|
270 | |
<label>layout:</label>
|
271 | |
<select name="layout" onChange={this.onlayout} value={layout}>
|
272 | |
<option value="wicki_hayden">Wicki-Hayden</option>
|
273 | |
<option value="harmonic">Harmonic Table</option>
|
274 | |
<option value="gerhard">Gerhard</option>
|
275 | |
</select>
|
276 | |
</div>
|
277 | |
<div className="option">
|
278 | |
<label>scale:</label>
|
279 | |
<select name="layout" onChange={this.onscale} value={scale}>
|
280 | |
<option value="none">None</option>
|
281 | |
<option value="major">Major</option>
|
282 | |
<option value="minor_nat">Natural Minor</option>
|
283 | |
<option value="minor_harm">Harmonic Minor</option>
|
284 | |
<option value="minor_mel">Melodic Minor</option>
|
285 | |
<option value="minor_hung">Hungarian Minor</option>
|
286 | |
<option value="whole">Whole-Tone</option>
|
287 | |
<option value="penta">Pentatonic</option>
|
288 | |
</select>
|
289 | |
<button onClick={this.onoffset}>set offset</button>
|
290 | |
</div>
|
291 | |
|
292 | |
<div className="main">
|
|
329 |
<nav>
|
|
330 |
<div className="group">
|
|
331 |
<button onClick={configure}>midi settings</button>
|
|
332 |
</div>
|
|
333 |
<div className="group">
|
|
334 |
<label>layout:</label>
|
|
335 |
<select name="layout" value={layout} onChange={this.set('layout')}>
|
|
336 |
<option value="wicki_hayden">Wicki-Hayden</option>
|
|
337 |
<option value="harmonic">Harmonic Table</option>
|
|
338 |
<option value="gerhard">Gerhard</option>
|
|
339 |
</select>
|
|
340 |
</div>
|
|
341 |
<div className="group">
|
|
342 |
<label>scale:</label>
|
|
343 |
<button onClick={this.onoffset(null)}>
|
|
344 |
{notes.music[offset % 12]}
|
|
345 |
</button>
|
|
346 |
<select name="layout" value={scale} onChange={this.set('scale')}>
|
|
347 |
<option value="none">None</option>
|
|
348 |
<option value="major">Major</option>
|
|
349 |
<option value="minor_nat">Natural Minor</option>
|
|
350 |
<option value="minor_harm">Harmonic Minor</option>
|
|
351 |
<option value="minor_mel">Melodic Minor</option>
|
|
352 |
<option value="minor_hung">Hungarian Minor</option>
|
|
353 |
<option value="whole">Whole-Tone</option>
|
|
354 |
<option value="penta">Pentatonic</option>
|
|
355 |
</select>
|
|
356 |
</div>
|
|
357 |
<div className="group">
|
|
358 |
<label>octave:</label>
|
|
359 |
<button onClick={this.onoffset(offset - 12)}>-</button>
|
|
360 |
{Math.floor(offset / 12)}
|
|
361 |
<button onClick={this.onoffset(offset + 12)}>+</button>
|
|
362 |
</div>
|
|
363 |
<div className="group">
|
|
364 |
<label>size:</label>
|
|
365 |
<input className="small" type="number" min="1" value={w} onChange={this.set('w')} />
|
|
366 |
{'x'}
|
|
367 |
<input className="small" type="number" min="1" value={h} onChange={this.set('h')} />
|
|
368 |
</div>
|
|
369 |
<div className="spacer" />
|
|
370 |
</nav>
|
|
371 |
|
|
372 |
<main ref={this.ref} tabIndex="0">
|
293 | 373 |
<Keyboard
|
294 | |
w={12}
|
295 | |
h={4}
|
|
374 |
w={w}
|
|
375 |
h={h}
|
296 | 376 |
noteon={this.noteon}
|
297 | 377 |
noteoff={this.noteoff}
|
298 | 378 |
showMidi={showMidi}
|
|
301 | 381 |
offset={offset}
|
302 | 382 |
state={state}
|
303 | 383 |
/>
|
304 | |
<ChordView
|
305 | |
chord={chord}
|
306 | |
/>
|
307 | |
</div>
|
|
384 |
<ChordView chord={chord} />
|
|
385 |
<div className="focus-msg">
|
|
386 |
<span>click here to activate<br/>keyboard input</span>
|
|
387 |
</div>
|
|
388 |
</main>
|
308 | 389 |
</div>
|
309 | 390 |
);
|
310 | 391 |
}
|