diff options
| author | s-ol <s+removethis@s-ol.nu> | 2022-03-21 23:45:16 +0000 |
|---|---|---|
| committer | s-ol <s+removethis@s-ol.nu> | 2022-03-21 23:55:10 +0000 |
| commit | 9f4a54afa2fa3ec60d2b0946dabfcc198b43b44f (patch) | |
| tree | 701f5725f89f35623045cad3e654b5d1ef8332d5 /app/app.js | |
| parent | simpler key styling (diff) | |
| download | isomorphic-kb-explorer-9f4a54afa2fa3ec60d2b0946dabfcc198b43b44f.tar.gz isomorphic-kb-explorer-9f4a54afa2fa3ec60d2b0946dabfcc198b43b44f.zip | |
move MIDI settings in toolbar, reorganize
Diffstat (limited to 'app/app.js')
| -rw-r--r-- | app/app.js | 286 |
1 files changed, 98 insertions, 188 deletions
@@ -1,6 +1,7 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { css, pos, width, height } from './style'; import * as notes from './notes'; +import { useAppState, useMIDI, Toolbar } from './config'; const statbyte = (stat, chan) => (stat << 4) | chan; const CHANNEL = 0b1011; @@ -36,9 +37,8 @@ const Hexagon = ({ x, y, children, state, note, major, scale, noteon, noteoff }) style={pos([x, y], major)} > <div - className="inner" onMouseDown={() => noteon(note)} - onMouseUp ={() => noteoff(note)} + onMouseUp={() => noteoff(note)} > {children} </div> @@ -60,7 +60,7 @@ const layouts = { // B = 7 // C = 2 = x // A + B = 12 = y - let note = 41 + 2 * x + 6 * y; + let note = 5 + 2 * x + 6 * y; if (y % 2 == 0) note += 1; return note; }, @@ -128,7 +128,7 @@ const tallyup = (steps, offset=0) => { }; }; -const Keyboard = ({ w, h, layout: { major, map }, state, scale, labels, noteon, noteoff }) => ( +const Keyboard = ({ w, h, layout: { major, map }, state, scale, transpose, labels, noteon, noteoff }) => ( <div className={`keyboard ${major}-major`} style={{ @@ -138,7 +138,7 @@ const Keyboard = ({ w, h, layout: { major, map }, state, scale, labels, noteon, > {range(w).map((_, x) => ( range(h).map((_, y) => { - const note = map([x, y], [w, h]); + const note = map([x, y], [w, h]) + transpose; const onCore = scale && scale.notes.indexOf(note) >= 0; const onScale = scale && scale.modulo.indexOf(note % 12) >= 0; return ( @@ -182,30 +182,7 @@ const ChordView = ({ chord }) => { </div> ); }; - -css(` -nav { - display: flex; - flex-wrap: wrap; - justfiy-content: space-between; -} - -nav > .group, nav > .spacer { - display: flex; - align-items: baseline; - padding: 0.25em 1em; - gap: 1em; - - border: 0 solid #363636; - border-width: 1px 0 1px 0; -} - -nav > .group:nth-child(2n) { - background: #363636; -} - -nav > .spacer { flex: 1; } -`); +; css(` main { @@ -240,190 +217,123 @@ main:focus-within .focus-msg { } `); -export default class App extends React.Component { - state = { +export default () => { + const [settings, setSettings] = useAppState({ layout: 'wicki_hayden', scale: 'major', labels: 'english', offset: 60, - state: {}, + transpose: 3*12, w: 12, h: 4, - }; - - ref = React.createRef(); - - componentDidMount() { - this.ref.current.addEventListener("keydown", this.onkeydown); - this.ref.current.addEventListener("keyup", this.onkeyup); - } - - componentDidUpdate(prevProps) { - this.send(CHANNEL, ALL_SOUND_OFF, 0); - this.send(CHANNEL, ALL_NOTES_OFF, 0); - - if (prevProps.midiin) - prevProps.midiin.onmidimessage = null; - - if (this.props.midiin) - this.props.midiin.onmidimessage = this.onmessage; - } + }); + const [state, setState] = useState({}); + const midi = useMIDI(); - blink = (note) => { - this.onmessage({ cmd: NOTE_ON, note }); - setTimeout(() => this.onmessage({ cmd: NOTE_OFF, note }), 250); - } + const ref = React.createRef(); - send(command, note, vel=127) { - if (command === NOTE_ON && this.state.offset === null) - this.setState({ offset: note }); + const midiin = midi.inputs.get(settings.midiin); + const midiout = midi.outputs.get(settings.midiout); - if (!this.props.midiout) - return; + const send = (command, note, vel=127) => { + if (!midiout) return; const msg = [statbyte(command, 0), note, vel]; - this.props.midiout.send(msg); - } + midiout.send(msg); + }; - noteon(note) { - if (this.state.offset === null) - this.setState({ offset: note }); + const noteon = (note) => { + if (settings.offset === null) { + setSettings((s) => ({ ...s, offset: note })); + } - this.setState(({ state }) => ({ state: { ...state, [note]: true } })); - this.send(NOTE_ON, note); + setState((s) => ({ ...s, [note]: true })); + send(NOTE_ON, note); } - noteoff(note) { - this.setState(({ state }) => ({ state: { ...state, [note]: false } })); - this.send(NOTE_OFF, note); + const noteoff = (note) => { + setState((s) => ({ ...s, [note]: false })); + send(NOTE_OFF, note); } - set = (e) => { - const key = e.target.name; - let value = e.target.value; - if (key === 'w' || key === 'h') value = +value; - this.setState({ [key]: value }); - }; + useEffect(() => { + ref.current.onkeydown = (e) => { + if (e.repeat) return; - onoffset = (offset) => (e) => this.setState({ offset }); + const note = notes.key2midi[e.code]; + if (!note) return; + e.preventDefault(); + noteon(note); + } - onmessage = (e) => { - const message = parse(e); + ref.current.onkeyup = (e) => { + if (e.repeat) return; - switch (message.cmd) { - case NOTE_ON: - console.info(message.note); - break; + const note = notes.key2midi[e.code]; + if (!note) return; + e.preventDefault(); + noteoff(note); } + }); - switch (message.cmd) { - case NOTE_ON: - this.setState(({ state }) => ({ state: { ...state, [message.note]: true } })); - break; + useEffect(() => { + send(CHANNEL, ALL_SOUND_OFF, 0); + send(CHANNEL, ALL_NOTES_OFF, 0); + }, [midiout]); - case NOTE_OFF: - this.setState(({ state }) => ({ state: { ...state, [message.note]: false } })); - break; - } - } + useEffect(() => { + if (!midiin) return; - onkeydown = (e) => { - if (e.repeat) return; + midiin.onmidimessage = (e) => { + const message = parse(e); - const note = notes.key2midi[e.code]; - if (!note) return; - e.preventDefault(); - this.noteon(note); - } + switch (message.cmd) { + case NOTE_ON: + if (settings.offset === null) { + setSettings((s) => ({ ...s, offset: note })); + } - onkeyup = (e) => { - if (e.repeat) return; + setState((s) => ({ ...s, [message.note]: true })); + break; - const note = notes.key2midi[e.code]; - if (!note) return; - e.preventDefault(); - this.noteoff(note); - } + case NOTE_OFF: + setState((s) => ({ ...s, [message.note]: false })); + break; + } + } - render() { - const { configure } = this.props; - const { w, h, scale, layout, labels, offset, state } = this.state; - - const chord = Object.entries(state).filter(([note, on]) => on).map(([k, _]) => k); - chord.sort(); - - return ( - <div className="app"> - <nav> - <div className="group"> - <button onClick={configure}>midi settings</button> - </div> - <div className="group"> - <label>layout:</label> - <select name="layout" value={layout} onChange={this.set}> - <option value="wicki_hayden">Wicki-Hayden</option> - <option value="harmonic">Harmonic Table</option> - <option value="gerhard">Gerhard</option> - </select> - </div> - <div className="group"> - <label>note format:</label> - <select name="labels" value={labels} onChange={this.set}> - <option value="english">English</option> - <option value="german">German</option> - <option value="sol">Solfège</option> - <option value="midi">MIDI no</option> - </select> - </div> - <div className="group"> - <label>scale:</label> - <button onClick={this.onoffset(null)}> - {labels ? notes.labels[labels][offset % 12] : offset} - </button> - <select name="scale" value={scale} onChange={this.set}> - <option value="none">None</option> - <option value="major">Major</option> - <option value="minor_nat">Natural Minor</option> - <option value="minor_harm">Harmonic Minor</option> - <option value="minor_mel">Melodic Minor</option> - <option value="minor_hung">Hungarian Minor</option> - <option value="whole">Whole-Tone</option> - <option value="penta">Pentatonic</option> - </select> - </div> - <div className="group"> - <label>octave:</label> - <button onClick={this.onoffset(offset - 12)}>-</button> - {Math.floor(offset / 12)} - <button onClick={this.onoffset(offset + 12)}>+</button> - </div> - <div className="group"> - <label>size:</label> - <input className="small" type="number" min="1" name="w" value={w} onChange={this.set} /> - {'x'} - <input className="small" type="number" min="1" name="h" value={h} onChange={this.set} /> - </div> - <div className="spacer" /> - </nav> - - <main ref={this.ref} tabIndex="0"> - <Keyboard - w={w} - h={h} - noteon={this.noteon.bind(this)} - noteoff={this.noteoff.bind(this)} - layout={layouts[layout]} - scale={scale !== "none" && offset !== null && tallyup(scales[scale], offset)} - labels={notes.labels[this.state.labels]} - offset={offset} - state={state} - /> - <ChordView chord={chord} /> - <div className="focus-msg"> - <span>click here to activate<br/>keyboard input</span> - </div> - </main> - </div> - ); - } + return () => { + midiin.onmidimessage = null; + }; + }, [midiin]); + + const { scale, layout, labels, offset } = settings; + const chord = Object.entries(state).filter(([note, on]) => on).map(([k, _]) => k); + chord.sort(); + + return ( + <div className="app"> + <Toolbar + state={settings} + setState={setSettings} + midi={midi} + /> + + <main ref={ref} tabIndex="0"> + <Keyboard + {...settings} + noteon={noteon} + noteoff={noteoff} + layout={layouts[layout]} + scale={scale !== "none" && offset !== null && tallyup(scales[scale], offset)} + labels={notes.labels[labels]} + state={state} + /> + <ChordView chord={chord} /> + <div className="focus-msg"> + <span>click here to activate<br/>keyboard input</span> + </div> + </main> + </div> + ); }; |
